feat: overhaul task board and cross-conversation workflows
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
{"file":".trellis/spec/backend/index.md","reason":"复核后端改动与项目质量入口"}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"复核所有跨层边界和回传数据完整性"}
|
||||
{"file":".trellis/spec/guides/code-reuse-thinking-guide.md","reason":"复核正式 MCP 与备用工具定义没有再次复制漂移"}
|
||||
{"file":".trellis/tasks/08-11-unify-ccweb-message-reply/research/current-behavior.md","reason":"复核兼容行为和真实失败场景均有覆盖"}
|
||||
@@ -0,0 +1,4 @@
|
||||
{"file":".trellis/spec/backend/index.md","reason":"后端规范入口与质量检查索引"}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"跨工具 Schema、dispatcher、pending 与运行提示的数据流契约"}
|
||||
{"file":".trellis/spec/guides/code-reuse-thinking-guide.md","reason":"正式 MCP 与备用定义必须复用单一来源"}
|
||||
{"file":".trellis/tasks/08-11-unify-ccweb-message-reply/research/current-behavior.md","reason":"当前行为、真实误用和兼容边界"}
|
||||
48
.trellis/tasks/08-11-unify-ccweb-message-reply/info.md
Normal file
48
.trellis/tasks/08-11-unify-ccweb-message-reply/info.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# 技术设计:统一 ccweb 消息回传接口
|
||||
|
||||
## 数据流
|
||||
|
||||
```text
|
||||
MCP 工具 Schema
|
||||
↓ replyMode
|
||||
内部 dispatcher
|
||||
↓ one_way / expectReply
|
||||
sendCrossConversationMessage
|
||||
├─ one_way → 目标运行,结果留在目标
|
||||
└─ return_and_continue
|
||||
↓ pending(requestId + originalRequest)
|
||||
目标完成 → ready → 来源空闲
|
||||
↓
|
||||
写回展示消息 + 带关联信息的隐藏续跑消息
|
||||
```
|
||||
|
||||
## 建议模块边界
|
||||
|
||||
- `lib/ccweb-mcp-server.js`
|
||||
- 继续作为正式工具定义的单一来源。
|
||||
- 导出 reply mode 常量;send schema 复用这些常量。
|
||||
- 不再公开 request-reply definition。
|
||||
- `server.js`
|
||||
- dynamic tools 从已导入的 `CCWEB_MCP_TOOLS` 按白名单筛选并添加 namespace。
|
||||
- dispatcher 保留旧别名。
|
||||
- 发送、pending、目标提示和来源续跑接入 reply mode。
|
||||
|
||||
## 兼容边界
|
||||
|
||||
- 新 Schema:`replyMode` 必填。
|
||||
- 旧 `send_message`:内部缺省归一为 `one_way`。
|
||||
- 旧 `request_reply`:内部固定归一为 `return_and_continue`。
|
||||
- 旧 MCP/dynamic 客户端:隐藏 allowlist 接受 `ccweb_request_reply`,但工具列表不公开。
|
||||
- 旧 pending 数据:`originalRequest` 缺失时来源提示显示明确占位,不失败。
|
||||
|
||||
## 测试策略
|
||||
|
||||
先补失败断言,再实现:
|
||||
|
||||
1. 共享定义:send schema 必填 replyMode、enum 正确、旧工具不公开。
|
||||
2. `one_way`:目标提示标注不自动回传,不创建 requestId。
|
||||
3. `return_and_continue`:创建 requestId,目标提示禁止手工回传,来源收到结果并自动续跑。
|
||||
4. 来源续跑:包含 requestId、目标信息、原始请求、完整性判断指令。
|
||||
5. 兼容别名:内部 `ccweb_request_reply` 仍建立回传。
|
||||
6. 隐藏入口:正式 MCP tools/call 与旧 dynamic call 接受旧名,tools/list 不包含旧名。
|
||||
7. 双路径一致:正式 MCP 和 dynamic tools 的 send definition 深度一致(namespace 除外)。
|
||||
98
.trellis/tasks/08-11-unify-ccweb-message-reply/prd.md
Normal file
98
.trellis/tasks/08-11-unify-ccweb-message-reply/prd.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# PRD:统一 ccweb 消息回传接口
|
||||
|
||||
## 背景
|
||||
|
||||
cc-web 当前同时公开 `ccweb_send_message` 与 `ccweb_request_reply`。两者输入完全
|
||||
相同,后者在内部只是为发送函数开启 `expectReply`。真实会话已出现:任务文字明确
|
||||
要求“完成后汇报/最终验收”,模型仍选择 `ccweb_send_message`,导致来源对话拿不到
|
||||
自动回传。工具描述、Codex App 备用定义、目标运行提示和来源续跑提示也存在语义缺口。
|
||||
|
||||
## 目标
|
||||
|
||||
向新模型只公开一个 `ccweb_send_message`,用必填 `replyMode` 明确选择:
|
||||
|
||||
- `one_way`:单向投递,目标本轮输出只留在目标对话。
|
||||
- `return_and_continue`:建立异步回传请求;目标完成后把结果写回来源,并触发来源续跑。
|
||||
|
||||
调用始终立即返回,任何模式都不表达同步阻塞等待。
|
||||
|
||||
## 功能要求
|
||||
|
||||
### 1. 公开工具契约
|
||||
|
||||
- `ccweb_send_message` 的输入必须包含:
|
||||
- `targetConversationId: string`
|
||||
- `content: string`
|
||||
- `replyMode: "one_way" | "return_and_continue"`
|
||||
- JSON Schema 必须把 `replyMode` 列入 `required`,且不得为新调用提供默认值。
|
||||
- 工具描述必须包含强选择规则:
|
||||
- 仅当来源不需要目标结果时使用 `one_way`。
|
||||
- 分析、实现、测试、验收、完成后汇报或来源后续依赖结果时,必须使用
|
||||
`return_and_continue`。
|
||||
- `ccweb_request_reply` 不再出现在正式 MCP 工具列表或 Codex App 备用工具列表中。
|
||||
|
||||
### 2. 兼容策略
|
||||
|
||||
- 内部 dispatcher 继续接受旧工具名 `ccweb_request_reply`,语义固定映射为
|
||||
`return_and_continue`,保护已经加载旧工具定义的存量线程。
|
||||
- 正式 MCP 的 `tools/call` 与 Codex App 旧 dynamic call 必须继续接受该隐藏旧名;
|
||||
它只从 `tools/list`/新 dynamic tools 中移除,不能在 dispatcher 前被 allowlist 拒绝。
|
||||
- 内部 dispatcher 可继续把缺少 `replyMode` 的旧 `ccweb_send_message` 调用按
|
||||
`one_way` 处理;新公开 Schema 仍必须强制该字段,以避免破坏存量线程。
|
||||
- 现有返回字段保持兼容;自动回传模式继续返回 `requestId`、`status: waiting`、
|
||||
`replyDelivery` 与 `sourceAutoRun`。
|
||||
|
||||
### 3. 单一定义来源
|
||||
|
||||
- 以 `lib/ccweb-mcp-server.js` 导出的正式工具定义作为单一来源:
|
||||
- 集中定义并导出 reply mode 常量;
|
||||
- 在正式 `TOOLS` 中定义公开 `ccweb_send_message` 的 description 与 inputSchema。
|
||||
- `server.js` 已导入 `CCWEB_MCP_TOOLS`;Codex App 备用工具列表必须从该数组按通信
|
||||
工具白名单筛选并添加 namespace,不能继续复制文案/Schema。
|
||||
- 筛选不得把 task-board、图片显示或用户表单等非备用通信工具意外加入 dynamic tools。
|
||||
|
||||
### 4. 目标对话运行提示
|
||||
|
||||
- `one_way` 模式必须明确:本轮输出不会自动回传来源。
|
||||
- `return_and_continue` 模式必须明确:
|
||||
- 系统会自动回传本轮最终输出;
|
||||
- 目标应在真正完成或明确阻塞后给出完整交付;
|
||||
- 不要为回复本请求而手工调用跨对话发送工具,避免重复。
|
||||
- 目标提示继续包含来源标题、来源 ID 和原消息内容。
|
||||
|
||||
### 5. 来源续跑提示
|
||||
|
||||
- pending reply 状态需要保留原始请求文本,兼容旧状态缺少该字段。
|
||||
- 自动续跑运行文本至少包含:
|
||||
- `requestId`
|
||||
- 目标对话标题/ID
|
||||
- 原始请求
|
||||
- 返回正文
|
||||
- 提示必须要求来源先判断返回是否完整满足原始请求;不能把“已返回”直接当作“已完成”。
|
||||
|
||||
### 6. 文档
|
||||
|
||||
- README 只把 `ccweb_send_message` 列为公开发送工具,并说明两个 `replyMode`。
|
||||
- README 可说明旧 `ccweb_request_reply` 仅为内部兼容别名,但不得继续推荐新调用使用。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不实现同步阻塞等待。
|
||||
- 不新增第三种回传模式。
|
||||
- 不重构跨对话 pending reply 的整体持久化机制。
|
||||
- 不在本任务中解决所有“turn 完成是否等于任务完成”的协议问题;只通过提示与来源完整性检查降低误判。
|
||||
- 不修改任务看板、失败插入卡片等并发任务的功能。
|
||||
- 不重启生产 `ccweb` 服务。
|
||||
|
||||
## 验收标准
|
||||
|
||||
1. 新工具清单中存在一个带必填 `replyMode` 的 `ccweb_send_message`,不存在公开
|
||||
`ccweb_request_reply`。
|
||||
2. 正式 MCP 与 Codex App 备用定义引用同一个共享契约。
|
||||
3. 两种新模式均有端到端回归;自动回传行为与现状兼容。
|
||||
4. 旧 `ccweb_request_reply` 通过隐藏兼容入口(MCP tools/call、旧 dynamic call、
|
||||
内部 dispatcher)仍成功,但不出现在公开列表。
|
||||
5. 目标提示和来源续跑提示具有上述模式与关联信息。
|
||||
6. README 与实现一致。
|
||||
7. 语法检查、相关专项测试、完整回归和 `git diff --check` 通过。
|
||||
8. 实施不得覆盖任务开始前已有的并发改动。
|
||||
@@ -0,0 +1,25 @@
|
||||
# 并发改动基线
|
||||
|
||||
## 当前并发任务
|
||||
|
||||
- 任务看板:占用全局 `.planning/.active_plan` 与 `.trellis/.current-task`,仍在运行。
|
||||
- 失败插入卡片:有 `server.js`/前端/回归改动,当前对话显示运行中或刚完成。
|
||||
- `ccweb_list_conversations` children scope:已完成但改动仍未提交。
|
||||
- 本任务:只允许精确修改跨对话 MCP、提示、README 与相关回归,不覆盖上述改动。
|
||||
|
||||
## 高冲突文件与安全范围
|
||||
|
||||
| 文件 | 本任务安全范围 | 必须保留 |
|
||||
|---|---|---|
|
||||
| `server.js` | 5733-5777 提示、5796-6103 跨对话发送/回传、10221-10410 dynamic tools、dispatcher 对应 case | task-board lifecycle、children scope、failed-insert rollback |
|
||||
| `lib/ccweb-mcp-server.js` | send/request 工具定义与导出常量 | children scope schema、task-board spread、其他 MCP 工具 |
|
||||
| `scripts/regression.js` | 6392-6524、7349 附近跨对话断言及工具列表断言 | task-board、失败插入和 children scope 回归 |
|
||||
| `README.md` | MCP 工具表与跨对话说明 | children scope 文档与其他功能说明 |
|
||||
|
||||
## 实施纪律
|
||||
|
||||
1. 修改前重新查看目标函数当前文本,避免使用旧行号/旧上下文套补丁。
|
||||
2. 不做全文件格式化或 import 重排。
|
||||
3. 每个阶段检查 `git diff`,只确认本任务新增的 hunk。
|
||||
4. 不重启生产服务;验证使用隔离测试进程。
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# 当前行为与缺口
|
||||
|
||||
## 当前实现
|
||||
|
||||
- `requestCrossConversationReply()` 是 `sendCrossConversationMessage()` 的 `expectReply`
|
||||
包装,因此公开工具合并不需要新增通信机制。
|
||||
- 正式 MCP 与 Codex App 备用 dynamic tools 各自复制 description 和 inputSchema。
|
||||
- `server.js` 已经导入正式 `CCWEB_MCP_TOOLS`,可以直接按白名单派生备用定义,
|
||||
无需增加新的契约模块。
|
||||
- pending reply 已持久化 requestId、来源/目标、hopCount 和 replyText,可向后兼容地新增
|
||||
`originalRequest`。
|
||||
- 来源忙碌时 ready reply 会保留,空闲后由 flush 逻辑投递;该行为必须保持。
|
||||
|
||||
## 已观察失败
|
||||
|
||||
- 模型在明确要求“完成后汇报/最终验收”的任务上调用了单向发送工具。
|
||||
- 同一模型此前使用过自动回传工具,说明仅靠两个工具名称仍不能稳定表达选择规则。
|
||||
- 某次自动回传正文只是“完成定位、准备修改”,目标后来继续执行;来源续跑需要明确检查完整性。
|
||||
|
||||
## 设计结论
|
||||
|
||||
- 工具选择必须从“选两个近似工具”改为“调用一个工具时填写必填枚举”。
|
||||
- 新公开契约不提供默认值;内部兼容层可宽容旧调用。
|
||||
- 目标与来源的运行提示都必须携带通信模式和关联上下文。
|
||||
- 提示优化无法完全替代任务完成协议;本任务不扩大到生命周期重构。
|
||||
@@ -0,0 +1,361 @@
|
||||
# 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`。
|
||||
@@ -0,0 +1,40 @@
|
||||
# 测试影响图
|
||||
|
||||
## 最小覆盖层次
|
||||
|
||||
1. **公开契约**:正式 `TOOLS/tools/list` 与 Codex App 备用列表只公开一个
|
||||
`ccweb_send_message`;`replyMode` 必填且 enum 精确。
|
||||
2. **统一发送 E2E**:`one_way` 不建 request;`return_and_continue` 建 request、
|
||||
自动回传、来源忙碌时排队。
|
||||
3. **隐藏兼容**:旧 `ccweb_request_reply` 不在 tools/list,但正式 MCP `tools/call`、
|
||||
Codex App 旧 dynamic call 和内部 dispatcher 仍接受。
|
||||
4. **提示语义**:目标提示区分模式;来源续跑包含 requestId、目标、原请求和完整性检查。
|
||||
|
||||
## 现有可复用入口
|
||||
|
||||
- `scripts/regression.js` 已有 `TOOLS` 静态断言、server 源码片段断言和完整跨对话 E2E。
|
||||
- `callInternalMcp`、`nextMessage`、`waitForJsonCondition` 可复用。
|
||||
- 现有单向发送、request-reply、busy source 三段应改为:
|
||||
- 公开单向:`ccweb_send_message(replyMode='one_way')`;
|
||||
- 公开回传:`ccweb_send_message(replyMode='return_and_continue')`;
|
||||
- 旧别名:单独保留一条隐藏兼容用例。
|
||||
|
||||
## 必须先失败的断言
|
||||
|
||||
- `TOOLS` 不包含 `ccweb_request_reply`。
|
||||
- send schema 的 required 包含 `replyMode`,enum 为
|
||||
`['one_way', 'return_and_continue']`。
|
||||
- send description 含“仅当来源不需要结果才 one_way / 依赖结果必须 return”。
|
||||
- Codex App 备用列表与正式通信定义一致,且无旧公开工具。
|
||||
- `return_and_continue` 返回 waiting requestId。
|
||||
- 自动回传目标提示禁止手工重复回传。
|
||||
- 来源 auto-run 文本包含 requestId、目标 ID、原始请求和完整性判断规则。
|
||||
- hidden legacy allowlist 仍允许 `ccweb_request_reply`。
|
||||
|
||||
## 关键风险
|
||||
|
||||
- `lib/ccweb-mcp-server.js` 的 JSON-RPC `tools/call` 当前使用公开 `TOOLS` allowlist。
|
||||
直接删除定义会让已缓存旧工具 schema 的客户端在 dispatcher 前失败;必须增加隐藏兼容 allowlist。
|
||||
- 只测 Schema 不能证明 dispatcher 使用 replyMode,必须保留 E2E。
|
||||
- 完整 regression 覆盖面很大,TDD 循环应优先使用可聚焦的静态/跨对话目标,最终再跑完整回归。
|
||||
|
||||
26
.trellis/tasks/08-11-unify-ccweb-message-reply/task.json
Normal file
26
.trellis/tasks/08-11-unify-ccweb-message-reply/task.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "unify-ccweb-message-reply",
|
||||
"name": "unify-ccweb-message-reply",
|
||||
"title": "统一 ccweb 消息回传接口",
|
||||
"description": "",
|
||||
"status": "planning",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shiyue",
|
||||
"assignee": "shiyue",
|
||||
"createdAt": "2026-08-11",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
Reference in New Issue
Block a user