# Hub 钉钉项目进度汇报

仅当当前会话是 Hub 且发生本文件定义的汇报事件时读取。钉钉是面向项目群组的单向辅助可见性与异常提醒，不是任务派发、跨角色沟通、审批、门禁、人类确认或项目记录的事实源。

## 权限与唯一入口

- 只有 Hub／主 Agent 可以发送；成员 Agent 只把成果、状态或阻塞交给 Hub。
- 项目配置为启用时视为持续发送授权，不需每条消息再次询问。
- 只能调用项目封装入口：

  `./scripts/dingtalk-progress <start|milestone|blocked|complete|failed> "<中文 Markdown 摘要>"`

- Agent 不得直接调用底层 OpenAPI、`dws api` 或其他消息入口绕过封装脚本。
- Agent 不读取、修改、输出或请求 Client Secret、DING_SEC、App Token、Client ID、robotCode、openConversationId 等凭据和内部标识。
- Skill 与消息正文不得硬编码项目 Client ID、robotCode、openConversationId、Client Secret 或群名；项目差异只由 `.dingtalk/config.env` 和封装脚本处理。

## 汇报单位与顺序

汇报以整个项目生命周期为单位。Hub 合并同一阶段或任务波次的相关结果，不为每条队列消息、普通回复、单个小步骤或短小只读问答发送通知。

Hub 必须先确认进展真实有效，并完成成果文件、成果索引和项目进度留档，再发送钉钉消息。钉钉发送失败不回滚留档，也不阻塞 Etunel 主任务。

## 事件判定

### start

每个项目最多一次。在第一个真实可执行任务或任务波次已经通过 Etunel 实际派发后发送。仅建立计划、等待输入或讨论想法时不发送。

### milestone

在可验证、会改变下一步的实质进展完成并已留档时发送，例如：

- 关键成果或版本成为当前有效版本；
- 一个有意义的任务波次完成并触发角色或阶段交接；
- 门禁、受控跳转、回退或豁免完成真实记录和必要确认；
- 正式阻塞解除，项目恢复到明确下一动作；
- 统一固件、版本矩阵、测试结论、验收或发布组合形成。

同一处理轮或同一波次合并成一条，不逐个小任务刷屏。没有固定时间间隔；依靠事件语义和去重控制频率。

### blocked

出现下列实际阻塞时立即发送：

- 角色已经询问负责人并完成必要线下协调，仍需用户、Hub 负责人或外部条件才能继续；
- 关键路径等待有权决定；
- Etunel 的任务投递、成员返回、会话或消息链路实际中断。

普通排队、正常等待、角色仍可自行推进或尚未完成产出不算阻塞。阻塞解除后用 `milestone` 汇报恢复，不修改历史消息。

### complete

整个项目或用户明确指定的整体目标最终完成时发送一次。模拟项目必须明确写“模拟完成”，不得表达真实发布、客户验收或生产就绪。

### failed

整个项目或整体目标已经最终终止、无法恢复或明确失败时发送。可返工的测试失败、单个任务失败或临时脚本异常不使用 `failed`。

## 消息写法

使用中文 Markdown 自然排版，可以使用标题、短段落和列表，不要求固定模板，也不要把全部内容挤成一行。像项目负责人向团队说明进展，不像日志转储。

根据事件选择必要内容：项目名称、当前阶段、已验证进展、关键成果、下一步和责任角色；阻塞时再说明问题、影响以及需要谁采取什么动作。通常保持一屏内可读。

不在正文中写运行时 ID、英文成果名、机器状态码或内部字段。只写已验证结论，不得包含密钥、Token、个人数据、客户敏感信息、大段日志、完整内部对话或未经验证的根因推断。

## 调用与结果

1. Hub 判断事件并形成中文 Markdown 摘要。
2. 调用封装脚本一次；脚本自行处理配置检查、发送和有限重试。
3. 只有真实发送响应包含非空 `processQueryKey`，才表示钉钉服务端已经接受；不证明群成员已读。
4. 缺少或未启用配置时安全跳过；预览模式只检查请求，不视为真实发送。
5. 真实发送失败或没有有效 key 时，脚本最多总计尝试 3 次，间隔 10 秒；首次成功立即停止。
6. 三次仍失败只终止本次钉钉发送，Etunel 主任务继续。Hub 在本地结果中向负责人说明一次，不递归发送新的阻塞或失败通知，也不自动补发历史事件。

脚本输出和退出码只用于判断通知调用，不得把钉钉结果升级成项目门禁。Agent 不在脚本外追加重试循环。
