任务状态管理
⚠️ 数据平面已脚本化:本 Skill 的组装执行入口已切换为
scripts/build_ui_payload.py(白名单裁剪 + 挂载增强字段 + 产出open_comment_reply_ui完整入参 + 直连 MCP 调set_common_data_cache写后台缓存),Agent 不再手工组装、也不再透传大载荷——只需把脚本返回的data_cache_id传给open_comment_reply_ui。本文档的11 字段白名单、ai_suggestion/process_name/refer_process_num挂载规则、submit.next_step契约文案、open_comment_reply_uischema 约束仍为权威定义,脚本按其实现。
概述
本 Skill 负责组装 Phase 1-4 的输出为前端需要的结构化数据,并管理留言回复任务的状态。
核心功能:
- 组装结构化数据(
total/risk_total/list),由 Agent 调用 MCP 工具open_comment_reply_ui(gongyi-open-mcp)传入该 JSON 展示到留言回复页面 list元素为OrgCommentItem按最小白名单裁剪后的字段(仅保留 UI 展示 + 提交必需的 12 个协议字段:comment_id/subject_id/content/project_id/project_name/created_at/nick_name/object_type/object_id/risk_audit_status/risk_audit_reason/head_img,其余 21 个字段全部剔除)+ai_suggestion增强字段 +process_name增强字段(仅 process 类型,取自进展标题,供前端展示信息来源)+refer_process_num增强字段(int,参考的进展条数,供前端展示信息来源规模)- 列表顺序保持 MCP 返回的原始数组顺序(后台返回的 tool 数据本身已排好序),Agent 不做任何二次排序
- 管理任务状态(选中状态、编辑内容)
关键约束:
- 不含机构鉴权(MCP Token 自动完成)
- 只负责数据组装,不负责 UI 渲染
list元素必须按最小白名单裁剪OrgCommentItem:只保留 12 个协议字段(comment_id/subject_id/content/project_id/project_name/created_at/nick_name/object_type/object_id/risk_audit_status/risk_audit_reason/head_img),其余字段(status/gy_uid/like_num/comment_type/can_reply/is_recommended/reply_list/audit_type/risk_audit_at/opt_user/parent_comment_content/is_sticky/sticky_expire/can_sticky/can_cancel_sticky/can_delete/org_comment_id/org_reply_content/org_reply_time/parent_comment/audit_reject_reason共 21 个)一律剔除(UI 不使用);不得新增业务字段(ai_suggestion、process_name、refer_process_num三个 Agent 增强字段除外)- 输出顶层只允许
total/risk_total/list/submit(open_comment_reply_uischema 不接受code/msg,additionalProperties: false);调用工具时另传 schema 唯一必填字段caller_expert_id(详见参考文档);Agent 运行状态(processed、ui_push_status、ui_push_error、note 等)禁止写入输出 JSON,由 Agent 文本报告承载 - reply-generator 的
sources(事实来源记录)属内部审计数据,不得挂载到 list 元素,不进入 UI 协议 - 字段类型对齐协议:
comment_id(uint64)全程必须是 JSON number——fetch_payload.py落盘comments_brief.json时已将上游字符串形态统一转为 int(Python int 无精度问题),本地文件与set_common_data_cache调用均不得出现字符串形态;build_ui_payload.py保留防御性转换兜底;org_comment_id等其他字段保持原始类型原样透传 - 输出结构必须与
openspec/changes/comment-assistant/fe-view-protocol.md协议一定义完全一致
触发场景
Agent 在 Phase 3(组装数据并调用 open_comment_reply_ui 展示留言回复页面)时加载本 Skill。本 Skill 只负责组装输出 JSON;Agent 拿到输出后必须调用 open_comment_reply_ui 将其展示到留言回复页面,不得仅以文本形式输出列表。
输入格式
{
"comments": [
{
"comment_id": 123456789,
"subject_id": "subj_001",
"content": "钱都去哪了?...",
"object_type": "project",
"object_id": "224328",
"project_id": "224328",
"project_name": "春蕾计划她们想上学",
"nick_name": "爱心网友A",
"created_at": 1756000000,
"status": 1,
"risk_audit_status": 4,
"risk_audit_reason": "质疑资金去向"
}
],
"ai_suggestions": {
"123456789": "您好,感谢您的关注和监督!..."
},
"total": 36,
"risk_total": 2
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
comments |
array | 是 | 留言列表(Phase 1 输出的完整 OrgCommentItem,本 Skill 负责按白名单裁剪后透出) |
ai_suggestions |
map | 是 | AI 建议回复映射(comment_id → ai_suggestion 字符串,Phase 4 的 reply-generator 输出) |
contexts |
map | 是 | 上下文映射(object_type:object_id → 上下文,Phase 3 的 comment-context-fetcher 输出);process 类型条目的 process_detail.content_title 用于填充 process_name;project 类型条目的 process_list 长度、process 类型条目的 process_detail 有无用于填充 refer_process_num |
total |
uint32 | 是 | 待回复总数(MCP 返回的 total) |
risk_total |
uint32 | 是 | 高风险数(MCP 返回的 risk_total) |
工作流程
采用零转录(no re-transcription)原地增强方式组装,杜绝手工抄写字段导致的错值/丢字段/类型变化:
- 保留原始对象:Phase 1 MCP 返回的响应对象原样保留在内存中(或原样字节),不重新拼写、不逐字段抄录、不写中间转换脚本/中间 JSON 文件
- 原地挂载(仅有的三个赋值操作):
list[i].ai_suggestion = ai_suggestions[list[i].comment_id];未处理到的条目置空串list[i].process_name:仅当list[i].object_type === 'process'时赋值,取contexts['process:' + list[i].object_id]?.process_detail?.content_title;取不到(进展拉取失败/无标题)或 project 类型置空串list[i].refer_process_num(int,参考的进展条数):object_type === 'project'时取contexts['project:' + list[i].object_id]?.process_list?.length ?? 0(0~5,无进展/拉取失败为 0);object_type === 'process'时取 1(参考该条进展;process_detail拉取失败则取 0) 除ai_suggestion、process_name与refer_process_num外不允许对任何字段重新赋值
- 最小白名单裁剪(仅有的删除操作):每个
list[i]只保留 12 个协议字段(comment_id/subject_id/content/project_id/project_name/created_at/nick_name/object_type/object_id/risk_audit_status/risk_audit_reason/head_img),删除其余全部字段(status/gy_uid/like_num/comment_type/can_reply/is_recommended/reply_list/audit_type/risk_audit_at/opt_user/parent_comment_content/is_sticky/sticky_expire/can_sticky/can_cancel_sticky/can_delete/org_comment_id/org_reply_content/org_reply_time/parent_comment/audit_reject_reason共 21 个,UI 不使用);保留字段的值一律原样,不增不改 - 保持原始顺序:
list不做任何排序/重排——后台返回的 tool 数据本身已排好序,按数组原始顺序直接展示 - 缓存写入 + key 展示:脚本把组装好的
{total, risk_total, list, submit}直连 MCP 调set_common_data_cache写入后台缓存,返回data_cache_id;Agent 调open_comment_reply_ui只传caller_expert_id+data_cache_id,大载荷不经过 LLM 输出。submit固定为{"next_step": "执行comment-assistant专家的刷新留言列表步骤"},告知 APP 应发起的专家步骤调用(回复由 APP 直连后台提交并自动删除已提交留言,不经 Agent;仅剩余待回复为 0 时 APP 才通知 Host 发起该步骤)。缓存写入失败时也走相同方式(caller_expert_id+data_cache_id)
⛔ 禁止:
- 把留言字段逐个手写进新 JSON(历史事故:head_img 重复、project_id 乱码、误加 code/msg、list 多余嵌套,均源于手工转录/组装)
- 由 Agent 在上下文内手工执行本 Skill 的组装逻辑——组装的唯一执行入口是
scripts/build_ui_payload.py(在本地完成裁剪与挂载、写出ui_payload.json,并直连 MCP 把数据写入后台缓存;Agent 只把返回的data_cache_id传给open_comment_reply_ui,无需 Read 载荷文件);手工组装在大载荷场景已实测发生结构错误与超长输出
输出格式
{
"total": 36,
"risk_total": 2,
"submit": {
"next_step": "执行comment-assistant专家的刷新留言列表步骤"
},
"list": [
{
"comment_id": 123456789,
"subject_id": "subj_001",
"content": "钱都去哪了?一直没看到进展更新,是不是骗人的?",
"project_id": "224328",
"project_name": "春蕾计划她们想上学",
"created_at": 1756000000,
"nick_name": "爱心网友A",
"object_type": "project",
"object_id": "224328",
"risk_audit_status": 4,
"risk_audit_reason": "质疑资金去向",
"ai_suggestion": "您好,感谢您的关注和监督!项目善款已于7月15日完成拨付...",
"process_name": "",
"refer_process_num": 5
},
{
"comment_id": 123456790,
"subject_id": "subj_002",
"content": "加油!希望孩子们都能好好读书",
"project_id": "224328",
"project_name": "春蕾计划她们想上学",
"created_at": 1756000100,
"nick_name": "匿名捐赠人",
"object_type": "project",
"object_id": "224328",
"risk_audit_status": 3,
"ai_suggestion": "感谢支持,我们会持续努力!",
"process_name": "",
"refer_process_num": 5
}
]
}
| 字段 | 类型 | 说明 |
|---|---|---|
total |
uint32 | 待回复总数(与 MCP 返回的 total 口径一致) |
risk_total |
uint32 | 高风险数(与 MCP 返回的 risk_total 口径一致:risk_audit_status=4 且 status=1) |
list |
array | 留言列表,保持 MCP 返回的原始数组顺序(后台已排序,不二次排序);元素为 OrgCommentItem 按最小白名单裁剪(仅保留 12 个协议字段,剔除其余 21 个字段)+ ai_suggestion / process_name / refer_process_num 三个增强字段 |
submit |
object | 提交动作契约:APP 完成提交后据此通知专家刷新。固定为 {"next_step": "执行comment-assistant专家的刷新留言列表步骤"} |
顶层只含
total/risk_total/list/submit:open_comment_reply_ui的 schema 为additionalProperties: false,传入code/msg会被参数校验直接拒绝。无论主路径还是降级路径,Agent 都只传caller_expert_id+data_cache_id(data_cache_id =set_common_data_cache返回的缓存 key,由脚本写入后台缓存后获得)。submit.next_step固定为「执行comment-assistant专家的刷新留言列表步骤」,一字不差(格式为可路由句式"执行X专家的Y步骤",Host 据此解析专家与步骤名并重新调度)。错误信息由 Agent 文本报告承载,不写入输出 JSON。
关键规则:
list[]元素必须按最小白名单裁剪:只保留comment_id/subject_id/content/project_id/project_name/created_at/nick_name/object_type/object_id/risk_audit_status/risk_audit_reason/head_img共 12 个协议字段 + 3 个增强字段,其余 21 个字段一律剔除(UI 不使用),不得额外裁剪或新增process_name仅 process 类型挂载(取process_detail.content_title),供前端展示「信息来源:项目名称(项目id);进展名称(进展id)」;project 类型为空串refer_process_num为 int:project 类型 = 该项目上下文process_list条数(0~5,无进展/拉取失败为 0);process 类型 = 1(参考该条进展,process_detail拉取失败为 0),供前端展示「参考进展 N 条」ai_suggestion取自输入ai_suggestions映射,原样写入;未生成建议的条目置空串(前端做空态展示)- 不拆分高风险/无风险列表,也不对 list 做二次排序;高风险优先由后台返回的原始数组顺序体现
异常处理
| 场景 | 处理方式 |
|---|---|
| 某条无 AI 建议 | 该条 ai_suggestion 置空串 |
| 无待回复留言 | 返回 total: 0, risk_total: 0, list: [] |
| 上游任一环节失败 | list 置空数组,错误原因由 Agent 文本报告承载,不写入输出 JSON |
依赖
- 无外部依赖(纯数据组装)
参考文档
- references/tools/open_comment_reply_ui.md:
open_comment_reply_ui工具调用参数定义(仅caller_expert_id+data_cache_id) - references/tools/set_common_data_cache.md:
set_common_data_cache写入数据的 JSON 结构定义(total/risk_total/list/submit字段表、CommentReplyItem/CommentReplyChildItem元素白名单、调用示例与注意事项)