Unify natural target resolution and message contracts, add deterministic IM event listening, streamline cold-start skills, and cover the flows with schema gates and end-to-end tests.
60 KiB
Multi IM 优化完整执行方案
状态:方案已实施并完成端到端验证
日期:2026-08-03
适用范围:DWS Multi Skill 的 IM 发送、读取、搜索、群创建、回复、资源下载与个人 IM 事件监听
实现分支:
codex/multi-im-optimization(基于最新origin/main)Token 口径:
tiktoken 0.12.0、o200k_base
0. 执行摘要
本方案的核心不是继续增加 Shortcut,也不是盲目追求 Skill 更短,而是提高每个控制 Token 对正确选路、正确执行、安全和结果判断的贡献,并把仍需 Agent 手工完成的名称解析、分页、富化、资源处理和事件映射下沉到 CLI。
目标路径统一为:
激活 dingtalk-chat
→ 从有限 Golden Route 中选择最小充分的意图入口
→ CLI 内部完成自然目标解析、参数校验、分页、富化和执行
→ 返回稳定、可证明完整性的任务级 DTO
优化顺序冻结为:
PR 1:Skill 路由收敛与 shared 冷启动解耦
→ PR 2:统一 Resolver 与发送执行内核
→ PR 3:读取、搜索、消息 DTO 与资源闭环
→ PR 4:扩展建群与回复闭环
→ PR 5:IM 事件高阶入口
→ 后续:依赖下层的新能力
其中 PR 1 收益最大、风险最低:不需要新增后端能力,就能消除 Skill 仍在推荐的旧多步链路。PR 2 是功能层 Token 价值密度和模型轮次收益最大的改造。
0.1 实施结果
截至 2026-08-03,本方案的 PR 1~PR 5 已在同一实现分支完成:
| 阶段 | 已实现结果 |
|---|---|
| PR 1 | Chat 根 Skill 改为 Golden Route;最小运行契约由单一 authored source 注入 Chat/Shared;正常单产品任务不再要求完整读取 Shared;卡片、Bot、复杂转发与运维细节按需加载 |
| PR 2 | 新增共享 typed target resolver、结构化 candidates 错误和批量全量预检;+dm、+send-to-group 与 +messages-send 复用发送执行内核;高级 user 发送支持 --user-query/--chat-query |
| PR 3 | +chat-messages 支持 --chat-query/--user-query,+search-msg 支持批量 --chat-query/--sender-query;list/search/mget/thread 共享 MessageViewV1 投影和 im.message-list.v1 完整性契约;资源失败并入任务级 ledger |
| PR 4 | +chat-create --member-query 在读取当前用户和创建群前完成全部成员解析;+messages-reply 返回 im.message-reply.v1 后续上下文并保证 dry-run 不触达写 transport |
| PR 5 | 新增 event +listen-im,把 kind/events/自然目标确定性编译为 EventKey 集合,并复用既有订阅、bus、ready、NDJSON、回滚和清理生命周期;Event 根 Skill 收敛为 Golden Route |
Schema 暴露同步完成:+chat-create、+messages-reply 和 event +listen-im 已进入 reviewed CommandRegistry、selection 与 metadata;生成 Catalog 由源输入重新生成,没有手改输出快照。
0.2 验证结果
截至 2026-08-03,最终工作树通过以下验证:
| 验证层 | 命令 / 结果 |
|---|---|
| 构建 | go build -o /tmp/dws-multi-im-codex-build ./cmd 通过 |
| 全仓测试 | DWS_PACKAGE_VERSION=0.0.0-test go test ./... 通过;90 个包、13,545 个测试/子测试通过事件,命令退出码为 0 |
| 真实 CLI 子进程 E2E | go test ./test/mock_mcp -run '^TestMultiIME2E_NaturalTargetsCompletenessAndWriteBoundaries$' -count=1 -v 通过 9 个场景 |
| E2E 覆盖 | 自然用户解析后恰好一次写;文件真实 PUT→commit→发送;用户歧义零写;资源通过受信任 HTTPS 实际原子落盘;第二页失败保留结果并 complete=false;Event 自然群 dry-run 零订阅;自然群完整读取;建群 dry-run 零创建;回复 continuation context |
| Event lifecycle E2E | facade 直通真实 lifecycle,覆盖多订阅 ready、正常退出全部清理、第二项创建失败回滚第一项且不启动 consumer |
| 真实钉钉组织 smoke | 当前 OAuth 身份创建仅含自己的临时测试群,真实发送后 delivery=SUCCESS,再由 +chat-messages 读回同一消息且 complete=true;event +listen-im 创建两个真实订阅并输出 ready,bounded 退出后两类订阅均为空;最后成功解散临时群 |
| Schema 生成漂移 | go generate ./internal/cli、check-generated-drift.sh 通过;两次独立生成一致 |
| Schema 合同 | check-schema-catalog.sh 通过:26 个产品、848 个工具;Runtime confirmation truth 为 137 个 gated 工具 |
| Agent 示例 | make test-schema-agent-examples 通过:1,045 个示例,其中 24 个走真实 dry-run |
| Skill 冷启动门禁 | check-skill-context-budget.sh 通过:Chat 根 Skill 8,346 bytes,最小 Runtime 契约 1,531 bytes;check-skill-commands.sh 校验 1,003 条可执行路径 |
| 精确 Token | 使用冻结的 tiktoken 0.12.0 / o200k_base:Chat 根 Skill 2,217;最小 Runtime 契约 380;Event 根 Skill 2,041;完整 Shared 1,493(正常单产品任务不冷加载) |
| 变更卫生 | git diff --check 通过 |
这里的 E2E 是隔离认证目录下启动真实 app.Execute() 子进程,经过真实 Cobra 解析和 HTTP JSON-RPC transport 访问本地 mock MCP;它验证 CLI 进程边界、调用顺序、输出 DTO 和写副作用,但不会向真实钉钉组织发送消息或创建群。
真实组织 smoke 与本地 E2E 分开执行:只创建带时间戳且仅含当前用户的临时群,不邀请其他用户;消息、读取、事件 ready/清理验证完成后立即解散。文档不记录真实 conversation/message/subscription ID 或凭证。
0.3 固定场景执行证据
| 场景 | 业务 CLI | 内部关键调用 | 结果护栏 |
|---|---|---|---|
| 已知/自然用户发送 | 1 | resolve → send×1 | 成功只写一次 |
| 多人同名发送 | 1 | resolve | 写副作用 0,返回 candidates |
| 群名文件发送 | 1 | resolve → init → HTTP PUT → commit → send | 上传字节一致,最终只发送一次 |
| 跨会话分页搜索 | 1 | search page 1 → page 2 failure | 保留首屏,complete=false、failure ledger |
| 群名读取并下载 | 1 | resolve → list → resource URL → HTTPS download | 受信任域、相对路径、原子落盘、complete=true |
| 姓名建群 dry-run | 1 | resolve all → current profile | create 副作用 0 |
| 群事件监听 dry-run | 1 | resolve → compile EventKeys | subscription 副作用 0 |
| 群事件真实 lifecycle | 1 | create×2 → ready → bounded exit → delete×2 | 正常清理;第二项失败时回滚第一项 |
| 引用回复 | 1 | send×1 | 返回 continuation context |
上述场景均不需要泛化 schema/help 查询,证明 Golden Route 已把用户可见业务命令收敛为 1。模型轮次属于外部 Agent host 指标;仓库内以 reviewed selection fixture 和命令级 E2E 作为可复现门禁,Ark live-model 评测仍按仓库约定保持显式 opt-in,不作为普通 CI 或本轮合并前置条件。
本方案同时冻结以下约束:
- 不通过删减安全事实、确认门禁或结果完整性换取 Token。
- 不为已有能力重复新增同义 Shortcut。
- 不做未版本化的公开输出 DTO 破坏性变更。
- 不机械修改
schema_command_registry.json;只有稳定 identity、主路径或 alias 真正变化时才评审修改。 - 不手改生成的 Schema Catalog 或 Agent metadata。
- Token 预算是防膨胀护栏,不是要求删除所有上下文;能显著降低误路由、误写或 false success 的内容应保留。
- Golden Route 负责公开意图分流;“统一”只发生在内部 Resolver、执行内核、DTO 和错误契约层,不强行收敛成一个万能公开命令。
1. 背景与当前事实
本章 1.1~1.4 冻结的是实施前基线;其中“当前”均指从
origin/main切出实现分支时的状态,不代表完成后的工作树。完成态和验证证据以 0.1、0.2 为准。
1.1 当前冷启动成本
三套 IM Skill 根加载体量如下:
| 体系 | 根加载文件 | 行数 | Tokens |
|---|---|---|---|
| Mono | mono/SKILL.md |
319 | 8,914 |
| Multi | dingtalk-chat + dws-shared |
229 | 4,922 |
| lark-cli | lark-im + lark-shared |
460 | 8,449 |
Multi 的总量已经低于 Mono 和 lark-cli,但这不代表当前加载结构合理。Multi 的 4,922 Tokens 中仍有重复路由、旧 SOP 和无条件 shared 串行读取;lark-cli 的 8,449 Tokens 也不应成为 Multi 的目标预算。应吸收的是 lark-im 已验证的任务闭环、结果契约和渐进披露方式,而不是复制其根文件篇幅。
当前 Multi Chat 根加载由两个文件组成:
| 文件 | 行数 | Tokens | 说明 |
|---|---|---|---|
skills/multi/dingtalk-chat/SKILL.md |
139 | 3,402 | IM 路由、意图表和旧 SOP |
skills/multi/dws-shared/SKILL.md |
90 | 1,520 | 全局执行、安全、路由和错误契约 |
| 合计 | 229 | 4,922 | 当前正常 IM 强制冷启动 |
其中 dingtalk-chat 的“标准 SOP”约 1,551 Tokens,是当前产品根 Skill 最大单块;它与前面的 Shortcut 优先路由、意图表存在重复甚至冲突。
1.2 当前 Runtime 已具备的高频能力
| 能力 | 当前入口 | 已有行为 |
|---|---|---|
| 按姓名发单聊 | chat +dm |
姓名搜索、唯一性判断、使用 openDingTalkId 发送 |
| 按群名发消息 | chat +send-to-group |
群名搜索、精确匹配优先、歧义拒绝 |
| 统一发送 | chat +messages-send |
user/bot/webhook、文本/Markdown、user 文件上传、幂等、@约束 |
| 指定会话读消息 | chat +chat-messages |
群/单聊统一浏览语义、默认最近时间、稳定消息投影、可选资源下载 |
| 跨会话搜索 | chat +search-msg |
多维过滤、默认近 7 天、翻页、mget 富化、失败 ledger |
| 批量取消息 | chat +messages-mget |
批量详情、reaction、资源引用和可选下载 |
| 单资源下载 | chat +messages-resource-download |
安全相对路径、默认不覆盖、原子落盘 |
| 引用回复 | chat +messages-reply |
自动读取原发送者、可选幂等键 |
| 置顶会话 | chat +conversation-list-top |
group/direct 规范化、保留 unresolved 项 |
| 创建群聊 | chat +chat-create |
当前用户入群并设为群主、基础群类型和话题模式 |
| 事件监听 | event consume |
多 EventKey、NDJSON、ready marker、状态与清理 |
因此,当前首要问题不是 CLI 缺少高频入口,而是 Skill 仍指导 Agent 绕开这些入口。
1.3 当前 Skill 与 Runtime 的主要冲突
| 用户意图 | Skill 旧路线 | 已有最短路线 | 直接代价 |
|---|---|---|---|
| 给姓名发消息 | aisearch person → 原子 message send |
chat +dm |
多一次业务命令和模型继续轮次 |
| 给群名发消息 | chat search → +messages-send |
chat +send-to-group |
多一次业务命令和 ID 搬运 |
| 已知目标发送文件 | 独立上传 → 提取 mediaId → 发送 | chat +messages-send --file ... |
多次调用、旧链路漂移 |
| 读最近聊天记录 | 旧 SOP 强制 --time |
chat +chat-messages 可自动取当前时间边界 |
Agent 需要额外构造参数 |
| Webhook 发送 | 原子 send-by-webhook |
chat +messages-send --as webhook |
身份入口分叉 |
| 群消息下载 | 读消息 → 手工识别资源 → 单独下载 | +chat-messages --download-resources |
Agent 手工 join,失败语义不统一 |
1.4 当前实现仍有的真实缺口
+dm与+send-to-group已完成名称解析,但仍各自直接调用发送接口,没有完全复用+messages-send的统一内容、幂等、AI 标记和输出逻辑。- 当前 person/group resolver 能拒绝零命中和多命中,但错误主要是人类可读字符串,尚未形成稳定结构化 candidates 契约。
+chat-messages、+search-msg、+chat-create和事件监听仍要求调用者先提供 ID。+chat-create与+messages-reply已存在;后续任务是扩展闭环,而不是再次创建同义命令。- 不同消息读取入口的核心字段已较接近,但尚未冻结兼容的公共 Message DTO 和完整性字段。
dws-shared既是共享入口又被产品 Skill 当作每次必须完整读取的文本依赖,导致清晰单产品任务仍多一个串行读取阶段。
1.5 从 lark-im 吸收什么
吸收原则是“迁移设计能力,不迁移平台假设”。
| lark-im 中值得吸收的设计 | DWS 当前基础 | Multi 的落点 | 阶段 |
|---|---|---|---|
| 根 Skill 以用户意图选择任务级入口 | 已有多个高阶 Shortcut,但旧 SOP 仍优先 | Golden Route 成为唯一默认路由 | PR 1 |
| 已知 leaf 直接执行,参数和安全由 leaf/Runtime 提供 | Schema/Runtime 已能交付参数和 confirmation | 根 Skill 删除整张参数、身份和 Scope 表 | PR 1 |
| 认证、权限和错误恢复按失败类型加载 | shared 当前无条件读取 | 最小公共契约内嵌,完整 shared 按需 | PR 1 |
| 自然姓名/群名解析进入 CLI | +dm、+send-to-group 已有局部实现 |
统一 typed Resolver,扩展到 send/read/search/create/event | PR 2~5 |
| 歧义返回 candidates,禁止默认第一个 | 当前能拒绝歧义,但主要是错误字符串 | 稳定 resolution envelope 和退出语义 | PR 2 |
| 发送入口统一承载身份、文件和幂等 | +messages-send 已具备主要能力 |
让 +dm、+send-to-group 复用同一执行器 |
PR 2 |
| 读取命令直接保留 reaction、引用和资源语义 | Multi 已有投影、mget 和下载基础 | 统一 MessageView、完整性和 failure ledger | PR 3 |
| 搜索、富化、分页是一个任务闭环 | +search-msg 已具备大部分链路 |
增加自然 sender/chat 目标和稳定 DTO | PR 3 |
| 建群先完成全部成员解析再写入 | +chat-create 目前只接受 IDs |
--member-query 全量预检 |
PR 4 |
| 普通引用回复与 Thread 回复语义分离 | +messages-reply 已存在 |
先补引用回复闭环,Thread 等真实 writer | PR 4/后续 |
| 事件入口按用户语义映射底层 EventKey | 已有 event consume 控制面 |
+listen-im 作为确定性 facade |
PR 5 |
以下内容不得直接从 lark-im 照搬:
- Lark 的
open_id、chat_id、资源 key、消息字段和事件名;DWS 必须保留自己的稳定 identity 和下层接口事实。 - 群 description、初始 Bot、visibility、owner/manager、Thread writer 等尚未确认的能力。
- Bot/Webhook 与 user 完全等价的富媒体能力假设。
- 把嵌套 sender/resources 或
items顶层结构无版本替换到当前公开 DTO。 - lark-im 的完整根文件体量、全量命令表或平台特有 SOP。
- 依赖 Agent 手工调用 contact/chat search 再搬运 ID 的多步流程;应吸收结果语义,但把确定性步骤下沉到 CLI。
最终对齐目标不是命令名相同,而是同一类高频意图在两套 CLI 中都具备:短路由、唯一解析、完整结果、可恢复错误和可证明的安全边界。
2. 目标、指标与非目标
2.1 北极星目标
高频 IM 请求应满足:
一个明确用户意图
→ 一个最小充分的 Golden Shortcut
→ 最多一个用户可见业务 CLI 命令
→ 无 Agent 手工搬运 ID、分页或拼接资源结果
优化优先级固定为:
任务正确完成
→ 安全且无错误副作用
→ 结果完整、可继续使用
→ 首次选路正确、少返工
→ 在满足以上条件后减少无效控制 Token 和模型轮次
不能因为根 Skill 变短,却导致更多 Schema/Help 查询、更多纠错轮次或更高 false success;这种变化属于 Token 搬家,不属于优化。
2.2 价值与效率目标
主指标优先于体量指标:
| 类型 | 指标 | 目标 |
|---|---|---|
| 任务价值 | 首次路由正确率 | 不低于基线,并逐 PR 提升 |
| 任务价值 | 任务成功率、完整结果率 | 不低于基线 |
| 安全价值 | 多候选误写、false success | 0 |
| Token 价值 | 已加载根 Skill 内容均能映射到路由、安全、结果判断或错误恢复场景 | 100% 可解释 |
| Token 价值 | 因 Skill 缺失而追加的 Schema/Help/纠错 Token | 不高于基线 |
| 执行效率 | 用户可见业务 CLI 和模型继续轮次 | 在质量护栏内下降 |
体量和轮次作为次级量化目标:
按每轮固定 20K 基础输入计算:
| 指标 | 当前 | 最终目标 |
|---|---|---|
| 核心场景平均模型轮次 | 4.67 | ≤3.7 |
| 平均累计输入 | 111.7K | ≤90K |
| 平均常驻上下文 | 25.7K | 23~24.5K |
| 已知目标发送 | 4 轮 | 3 轮 |
| 唯一姓名发送 | 6 轮 | 3~4 轮 |
| 群名读取并下载 | 5 轮 | 3~4 轮 |
| IM 事件监听 | 5 轮 | 3~4 轮 |
业务消息正文、文件内容和最终用户回答不计入控制 Token。
2.3 Skill 预算
| 内容 | 目标预算 |
|---|---|
dingtalk-chat/SKILL.md |
≤2.5K Tokens |
| 正常单产品 IM 强制 shared | 0 |
| 内嵌最小全局契约 | 300~500 Tokens |
| 单个任务 reference | ≤1.5K Tokens |
| 高频任务 reference | 默认不读取 |
Token 使用 o200k_base 计量;CI 同时保留稳定字节预算,防止 tokenizer 环境差异让硬门禁失效。这些预算是评审触发线:超过预算必须解释新增内容防止了什么失败;低于预算也不能作为内容充分的证明。
根 Skill 中每个保留区块至少要承担一种明确作用:
- 选择正确任务入口;
- 阻止高代价误操作;
- 解释 Runtime 输出中无法从 Schema 推导的关键语义;
- 把已知失败精确路由到按需 reference。
不承担上述作用的内容迁出;承担作用但表达重复的内容合并;能够由 leaf Schema/Runtime 稳定交付的内容不再复制。
2.4 质量与安全护栏
效率优化必须同时满足:
- 高频场景任务成功率不低于优化前基线;
- 多候选写操作副作用为 0;
- 所有写操作继续遵循最终 Runtime/Schema 的 confirmation truth;
- 读取、搜索和下载不得把部分结果标记为完整成功;
--dry-run与真实执行使用同一目标解析链;- 不泄露 token、appSecret、webhook token 或内部原始错误载荷;
- 不发生跨 profile、跨组织 ID 串用;
- 不通过隐藏命令、未公开 Shortcut 或猜测接口绕过能力边界。
2.5 非目标
本轮不做:
- 删除旧原子命令或已发布 Shortcut。
- 为了命名统一机械重写 CommandRegistry identity 或主路径。
- 把完整 Shortcut Catalog、原子 API 清单或 Scope 表重新放入根 Skill。
- 在没有下层能力的情况下承诺 Bot 富媒体、Thread 写入、群 description、初始机器人或断点续传。
- 无版本地把现有
sender字段从字符串改成对象,或把messages顶层数组改名为items。 - 同时重构所有 DWS Multi Skill;本方案先验证 IM 路径。
3. 目标架构
3.1 四层职责
| 层 | 负责 | 不负责 |
|---|---|---|
| 产品根 Skill | 触发边界、Golden Route、最小不可推导语义 | 全量命令、完整参数表、旧 SOP |
| 最小全局契约 | CLI 使用、安全底线、ID 不猜测、写后验证 | 产品路由大全、认证手册、错误码大全 |
| Task reference | 低频复杂流程、卡片、复杂转发、Bot 管理、事件运维 | 高频命令选择 |
| Runtime + leaf Schema/Help | 参数、约束、身份矩阵、确认、接口与执行 | 自然语言产品路由教程 |
3.2 目标加载路径
清晰单产品任务:
dingtalk-chat 根 Skill(含最小全局契约)
→ 一个 Golden Shortcut
→ 完成
低频复杂任务:
dingtalk-chat 根 Skill
→ 精确 task reference
→ 已知 leaf
→ 仅在参数/安全不确定时查询 leaf Schema
→ 仅在 Cobra flag 不确定时查询 leaf Help
异常路径:
认证/权限/profile/未知错误
→ 按错误类型读取 dws-shared 对应 reference
→ 修复或停止
3.3 Golden Route
根 Skill 的核心只保留以下高频路线:
| 意图 | 唯一推荐入口 | 说明 |
|---|---|---|
| 按姓名发文本/Markdown | chat +dm |
PR 2 后内部委托统一发送器 |
| 按群名发文本/Markdown | chat +send-to-group |
PR 2 后内部委托统一发送器 |
| 已知目标、文件、Bot、Webhook | chat +messages-send |
身份和内容矩阵由 Runtime 校验 |
| 指定会话读取 | chat +chat-messages |
最近消息无需强制 --time |
| 跨会话搜索 | chat +search-msg |
支持多维过滤、翻页、富化 |
| 读取并下载附件 | 查询命令加 --download-resources |
不另起手工下载循环 |
| 查看置顶会话 | chat +conversation-list-top |
group/direct/unresolved 契约 |
| 监听 IM 事件 | 切换 dingtalk-event |
PR 5 后优先 event +listen-im |
以下能力作为次级直接入口保留,但不扩大成完整命令表:
| 意图 | 入口 |
|---|---|
| 已知消息 ID 批量读取 | chat +messages-mget |
| 已知资源引用单独下载 | chat +messages-resource-download |
| 引用回复 | chat +messages-reply |
| 创建基础群 | chat +chat-create |
3.4 降级顺序
Golden Shortcut
→ 精确 task reference 中的专用 Shortcut
→ 已知原子 leaf
→ leaf Schema / leaf Help
→ 最后才使用低频发现接口
已知意图禁止调用:
- 产品级 Schema;
schema --all;- 完整
shortcut list --service chat; - 父级 Help 代替 leaf Help;
- 旧原子 SOP 作为默认路线。
3.5 Golden Route 与内部统一的边界
Golden Route 和内部统一解决的是不同层次的问题:
用户意图层:按任务分流,入口少而明确
+dm / +send-to-group / +messages-send
+chat-messages / +search-msg / +messages-mget
↓
Runtime 内部层:复用同一 Resolver、执行内核、投影和错误契约
公开层不建设一个同时承担发送、读取、搜索、下载和事件的万能命令。内部层也不允许每个 Shortcut 各自复制名称解析、分页、消息投影或错误处理。
选择公开入口时采用“最小充分能力”原则,而不是“argv 最短”或“功能最多”原则:
| 意图 | 公开入口 | 选择理由 |
|---|---|---|
| 给姓名发简单文本/Markdown | +dm |
语义窄,参数空间小,误选身份概率低 |
| 给群名发简单文本/Markdown | +send-to-group |
群目标明确,不暴露无关身份矩阵 |
| 文件、Bot、Webhook、复杂 @、已知 ID 或高级发送控制 | +messages-send |
需要统一发送的完整能力矩阵 |
| 浏览一个指定会话的消息 | +chat-messages |
以会话和时间窗口为中心 |
| 跨会话或按多维条件检索 | +search-msg |
以查询条件、分页和富化为中心 |
| 已知一组消息 ID 获取详情 | +messages-mget |
精确读取,不应伪装成搜索 |
因此,+messages-send --user-query/--chat-query 可以支持“自然目标 + 高级发送能力”,但不会取代简单文本场景的 +dm 和 +send-to-group���三个入口共享内部发送内核,不共享同一份公开参数复杂度。
4. Skill 与 shared 设计
4.1 dingtalk-chat 目标结构
建议根文件固定为:
Frontmatter / 触发边界
→ 最小 DWS 执行契约
→ Golden Route
→ 三条关键结果语义
→ 低频 reference 导航
→ 最短错误分流
只保留三类无法完全由 leaf Schema 表达的结果语义:
openTaskId是发送任务 ID,不是回复/撤回使用的消息 ID。- 消息读取默认保留 reaction、
updateTime、引用、转发和resourceRefs;--no-reactions可关闭 reaction 输出。 - Favorite、消息 Pin、消息 Top、会话 Top 是不同对象层级,不能互换。
不在根 Skill 重复完整身份矩阵;只说明如何选择 +messages-send。具体 target/content/credential 互斥关系以 leaf Schema 与 Runtime 为唯一事实源。
4.2 必须删除或迁移的内容
- 七条旧原子 SOP 的命令细节;
- 人名先查 ID 的手工流程;
- 群名先查会话 ID 的手工流程;
- 文件先上传再提取 mediaId 的旧默认流程;
- 聊天记录强制
--time; - Webhook 默认原子发送路线;
- 完整 Shortcut 表;
- 完整 flags、互斥和 required 说明;
- 卡片 payload、复杂转发、Bot 管理和事件运维细节。
4.3 shared 冷启动解耦
正常 IM 请求不再强制完整读取 dws-shared/SKILL.md。但不能简单删除 MUST read 而丢失全局安全契约。
冻结的交付方式为:
- 新建一份单一来源的最小公共契约,例如:
skills/multi/dws-shared/references/runtime-contract.md。 - 由生成器把该区块同步到
dingtalk-chat/SKILL.md的受控 marker 中。 dws-shared/SKILL.md继续作为泛称 DWS、跨产品、认证/profile/错误路由入口,但不再是清晰 IM 任务的强制文本依赖。- 策略门禁校验源文件与内嵌区块一致,禁止人工漂移。
最小契约只包括:
- 只通过
dws操作; - 结构化读取使用 JSON;
- 不猜命令、flag、ID 或字段;
- 后续 ID 来自真实返回;
- 不输出凭据;
- 高影响操作遵循 Runtime confirmation;
- 写后按任务契约验证;
- 未知命令只查精确 leaf Help。
4.4 shared 按需入口
仅在以下条件加载 shared 或其 reference:
| 条件 | 内容 |
|---|---|
| 登录态或权限失败 | global-reference.md / 对应错误章节 |
| profile 或组织不明确 | dingtalk-profile 与 shared profile 规则 |
| confirmation 错误 | Runtime confirmation 处理协议 |
| 未知 CLI/业务错误 | error-codes.md 对应章节 |
| 跨产品流程 | workflow-routing.md |
| 泛称 DWS、产品不明 | routing.md |
4.5 Task reference 策略
根 Skill 不加载下列低频内容:
- 流式卡片生命周期;
- 合并转发、话题转发和复杂引用;
- Bot 搜索、入群、管理员和撤回;
- Favorite/Pin/Top 的完整原子命令;
- 事件 ready、cooldown、terminal hold、status/stop 运维;
- 兼容媒体链路和异常恢复。
只有现有 reference 超过单任务预算且无法精确定位时才继续拆文件,避免为了“细粒度”制造大量无路由价值的小文档。
5. 统一 Resolver 契约
5.1 目标
所有自然目标解析必须复用同一套内部组件:
ResolveUser(profile, query, requiredIdentity)
ResolveChat(profile, query)
ResolveBot(profile, query)
ResolveUsers(profile, queries, requiredIdentity)
Resolver 只做确定性解析,不执行最终写操作。
5.2 类型化结果
建议内部结构:
{
"status": "resolved",
"entityType": "user",
"query": "张三",
"matchType": "exact",
"selected": {
"id": "userId-or-openDingTalkId",
"userId": "...",
"openDingTalkId": "...",
"name": "张三",
"organization": "..."
},
"candidates": [],
"profile": "corpId:userId"
}
状态固定为:
resolvedambiguousnot_foundforbiddenupstream_failure
5.3 匹配规则
- 去除首尾空白,按产品允许的大小写规则比较。
- 按稳定 ID 去重,禁止同一对象的重复行制造假歧义。
- 用户解析按稳定 ID 去重后必须只有一个可用候选;即使其中一个显示名 exact,也不能隐藏同名或外部联系人候选。
- 群聊解析仅有一个 exact match 时优先 exact;多个 exact match 仍为
ambiguous。 - 群聊没有 exact 时,只有一个可用候选才允许 resolved。
- 多候选禁止默认第一项、最近使用项或第一组织项。
- 下游需要
openDingTalkId时,不得选择只有 userId 且无法转换的候选;反之亦然。 - 已提供稳定 ID 时直接使用,不触发名称搜索。
5.4 结构化错误
零命中和多命中应返回非零退出码和机器可读细节:
{
"ok": false,
"error": {
"type": "resolution",
"subtype": "ambiguous",
"entityType": "chat",
"query": "项目群",
"candidates": [
{
"name": "项目群",
"openConversationId": "..."
}
]
}
}
禁止只把候选拼进不可解析的错误字符串。面向人的 message 可以保留,但不是唯一数据面。
5.5 Profile 与组织一致性
- 解析和最终执行必须使用同一个
--profile。 - Resolver 输出记录 profile identity,执行器在写入前校验一致。
- 不缓存或跨调用复用其它 profile 下的 userId/openDingTalkId/openConversationId。
- 多组织名称搜索遵循现有 profile 选择规则;没有明确默认账号时先停止并要求用户选择。
5.6 批量解析
ResolveUsers 采用全量预检:
解析所有查询
→ 收集 resolved / ambiguous / not_found
→ 任意一项未 resolved,则整体停止
→ 全部 resolved 后才进入写操作
创建群等不可接受半完成的操作禁止“成功一个先写一个”。
5.7 Dry-run 一致性
--dry-run 必须:
- 运行与真实执行相同的只读 Resolver;
- 运行相同的 target/content/identity 校验;
- 输出最终 resolved target 和将调用的下层动作;
- 在第一个写调用前停止;
- 不因 dry-run 使用另一套模糊匹配逻辑。
现有 CallMCPData 的 dry-run 只读通道继续作为基础;新增 resolver 所使用的下层工具必须进入明确的只读分类,不能用名称猜测绕过写保护。
6. 分层发送路由与统一执行内核
6.1 自然目标参数
扩展 chat +messages-send:
--user-query
--chat-query
示例:
dws chat +messages-send \
--as user \
--user-query "张三" \
--text "请查收"
dws chat +messages-send \
--as user \
--chat-query "项目群" \
--file ./report.pdf \
--idempotency-key <key>
目标严格且只能选择一个:
--chat-id / --group
--chat-query
--user
--user-query
--open-dingtalk-id
Bot 和 Webhook 不自动继承 user 的自然目标或文件能力;是否支持由现有身份矩阵和真实下层能力决定。
这些自然目标参数服务于“自然目标 + 文件/复杂 @/幂等等高级发送”场景,不把 +messages-send 提升为所有发送意图的唯一公开入口。简单姓名文本继续走 +dm,简单群名文本继续走 +send-to-group。
6.2 统一内部发送器
将当前发送路径收敛为一个内部执行器:
resolve target(如需要)
→ validate identity/content/target
→ normalize @ placeholders
→ resolve/upload file(如需要)
→ attach idempotency / AI tag
→ dispatch lower transport
→ normalize delivery result
+dm 和 +send-to-group 保留稳定 CLI path,但不再各自维护发送实现:
+dm
→ ResolveUser
→ unified send executor
+send-to-group
→ ResolveChat
→ unified send executor
这里的“统一”指 transport 前的校验、内容构造、幂等、AI tag、执行和结果归一化,不要求三个公开命令接受完全相同的 flags。
6.3 内容与身份边界
- user:文本、Markdown、已有 mediaId 图片、安全相对路径文件/音频/视频;
- bot:仅发布真实下层支持的文本/Markdown;
- webhook:目标由 token 所属群决定,只发布真实支持的文本/Markdown;
- 不把 user 文件能力伪装为 bot/webhook 等价能力;
- 不在 Skill 复制完整矩阵,由 leaf Schema/Runtime 交付。
6.4 幂等与重试
- 已提供 idempotency key 时原样透传;
- Agent 或 facade 重试必须复用原 key;
- 未提供时不得宣称具备跨进程幂等;
- 解析成功但发送结果未知时,返回明确 delivery/unknown 状态,不自动换目标重发;
- 不因错误 envelope 误判成功而重复发送。
6.5 Dry-run 输出
{
"identity": "user",
"resolvedTarget": {
"type": "chat",
"name": "项目群",
"openConversationId": "..."
},
"contentType": "file",
"file": "./report.pdf",
"idempotencyKey": "...",
"willSend": false
}
6.6 兼容策略
- 保留
+dm、+send-to-group、旧 ID flags 和原子命令; - 不为统一发送新建第二个 canonical Shortcut;
- 新增 flags 属于现有 Cobra leaf 的兼容扩展,不修改稳定 command identity;
- 只有主路径或 alias 真正调整时才评审 CommandRegistry;
- Skill 始终按最小充分意图选路;PR 2 完成后也不把简单文本意图统一改为
+messages-send。
7. 读取、搜索与资源闭环
读取侧不新增一个万能 +messages-read。+chat-messages、+search-msg 和 +messages-mget 的任务语义不同,继续作为三个公开入口;它们只在内部共享自然目标 Resolver、分页基元、MessageViewV1 投影、资源处理和完整性契约。
7.1 扩展 +chat-messages
新增:
--chat-query
--user-query
示例:
dws chat +chat-messages \
--chat-query "项目群" \
--download-resources
内部路径:
解析 chat/user
→ 获取消息
→ 投影 sender/text/time/identity
→ 保留 reaction/引用/转发/resourceRefs
→ 计算分页完整性
→ 可选安全下载
→ 输出稳定 DTO
省略 --time 继续默认以当前时间为边界向前读取;Skill 不再强制 Agent 构造时间。
7.2 扩展 +search-msg
新增:
--sender-query
--chat-query
其中 --sender-query 应支持重复或列表输入,先解析全部发送者,再执行一次搜索;任何名称歧义都在搜索前停止。
继续保留:
- 时间范围和默认近 7 天;
- 多会话、多发送人;
- @我/@对象;
- 消息类型和机器人来源;
--page-all与页数上限;- mget 批量富化;
--no-enrich、--no-reactions;- partial failure ledger;
--download-resources。
7.3 Message DTO 兼容策略
目标是让 list/search/mget/thread reply 共享一个内部 MessageViewV1 构造器,但不得无版本破坏现有公开输出。
第一阶段保留已有字段类型和顶层名称:
{
"contractVersion": "im.message-list.v1",
"messages": [
{
"messageId": "...",
"conversationId": "...",
"threadId": "...",
"sender": "张三",
"senderId": "...",
"senderType": "user",
"messageType": "text",
"text": "...",
"createTime": "...",
"updateTime": "...",
"reactions": {},
"quotedMessage": {},
"forwarded": [],
"resourceRefs": []
}
]
}
兼容规则:
sender暂时保持现有标量类型,不直接替换为对象。- 新的
senderId、senderType采用增量字段。 resourceRefs保留现有名称;如需新resources结构,必须增量发布或引入显式 DTO 版本。- 顶层
messages不无版本改名为items。 - 原字段删除或类型变化必须走独立兼容性评审。
如果未来确需嵌套 sender/resources 对象,使用显式 v2 projection 或新主版本,而不是静默切换。
7.4 完整性字段
所有分页读取和搜索逐步统一为:
{
"messages": [],
"complete": true,
"hasMore": false,
"nextCursor": "",
"pagesFetched": 1,
"enrichedCount": 0,
"failedCount": 0,
"failures": []
}
规则:
hasMore=true且未继续翻页时,complete=false;- 下层缺失可靠分页字段时,不能证明完整,
complete=false; - 达到 page limit 且仍有后续页,
complete=false; - mget 富化部分失败,保留已有消息并记录 failures;
- 单资源下载失败不丢弃消息;
- 不捕获错误后动态降级为虚假的完整成功。
7.5 资源下载契约
- 路径必须是工作目录内安全相对路径;
- 默认不覆盖;
- 显式
--overwrite才允许覆盖; - 临时文件完成后原子 rename;
- 按稳定资源 key 去重;
- 单资源失败隔离;
- 返回每项
messageId、resource key/type、local path、size/error; - 下载保持
read/not_required,不得制造无意义的--yes; - 资源引用中的子消息 ID 优先使用子消息自己的
messageId。
8. 建群与回复闭环
8.1 扩展现有 +chat-create
+chat-create 已存在,本阶段增加自然成员解析,而不是创建同义命令。
目标示例:
dws chat +chat-create \
--name "项目群" \
--member-query "张三,李四" \
--type INTERNAL
保留现有:
--users;--type INTERNAL|EXTERNAL|NORMAL;--thread;- 当前用户自动加入并设为群主;
openCid规范化为openConversationId。
新增:
--member-query;- 批量解析预检;
- dry-run 成员清单与最终下层参数;
- 重复 ID 去重;
- 结构化 ambiguity/not-found 错误。
以下能力只有下层真实支持并完成 Schema/Runtime 评审后才能加入:
- 群 description;
- 初始机器人;
- 指定其他 owner;
- public/private 或其它 Lark 专属语义;
- 服务端幂等键。
不得通过群名预搜索把“禁止同名群”伪装成幂等,因为业务上可能合法存在同名群。
8.2 扩展现有 +messages-reply
当前入口已能根据原消息补全发送者;本阶段目标是补齐后续可执行上下文:
dws chat +messages-reply \
--message-id <id> \
--conversation-id <id> \
--text "收到"
期望结果包含:
- 新消息 ID(下层可提供时);
- conversation ID;
- thread ID(适用时);
- delivery 状态;
- idempotency key;
- 原消息解析结果或来源。
--reply-in-thread 只有在确认真实 Thread writer 下层能力后才发布。普通引用回复和 Thread 回复必须是两个明确模式,不能用同一个 flag 猜测下层行为。
8.3 写操作安全
- 目标解析、原消息读取和参数校验全部先于写调用;
- 多候选或缺失原消息时不产生副作用;
- confirmation 由最终 Runtime gate 与 Schema 决定,不根据
risk文案机械推导; - dry-run 必须在写调用前停止;
- 部分成功时返回已创建对象和恢复上下文,禁止统一报成“完全失败”。
9. IM 事件高阶入口
9.1 定位
新增一个高层 facade:
dws event +listen-im --kind at-me
dws event +listen-im --kind sender --user-query "张三"
dws event +listen-im --kind group --chat-query "项目群"
dws event +listen-im \
--events message,reaction,recall \
--chat-query "项目群"
它不是新事件系统,而是现有 event consume 的确定性编译层。
9.2 建议参数
--kind at-me|sender|group|all-direct|all-group
--events message,reaction,read,recall
--user / --open-dingtalk-id / --user-query
--chat-id / --chat-query
--max-events
--timeout
--profile
kind、target 和 events 的组合由 Runtime 校验;不兼容组合在创建订阅前失败。
9.3 确定性映射
示例:
| 用户语义 | EventKey |
|---|---|
| @我消息 | user_im_message_receive_at |
| 指定人的单聊消息 | user_im_message_receive_o2o |
| 指定人发给我的所有消息 | user_im_message_receive_user |
| 指定群消息 | user_im_message_receive_group |
| 指定群 reaction | user_im_message_reaction_group |
| 指定群撤回 | user_im_message_recall_group |
| 指定单聊已读 | user_im_message_read_o2o |
映射表由代码和测试维护,根 Skill 只保留高频用户语义,不展开全部 EventKey。
9.4 执行与生命周期
内部必须复用现有 event 控制面:
- 多个兼容事件合并为一次命令、一个消费进程;
- 自动启用
--flatten -f ndjson; - stdout 只输出事件 NDJSON;
- stderr 输出 subscription/ready/status/error;
- 等待真实 ready marker,不用
sleep猜测; - 部分订阅创建失败时回滚已创建订阅;
- stdin、SIGINT、SIGTERM 和 timeout 触发有序清理;
- 尊重
in_flight、cooldown、terminal_hold; --profile在目标解析、订阅、status 和 stop 全程一致;- bounded 结束不泄漏订阅。
由于这是持续输出命令,不应简单套用只在结束时 rt.Output 一次的普通 Shortcut 模板。优先设计为复用 event consume 内部 runner 的 composite facade,避免通过 shell 启动第二个 dws。
9.5 dingtalk-event Skill 精简
根 Skill只保留:
- 监听对象选择;
+listen-im高频示例;- 有界/无界监听;
- 何时进入运维 reference;
- chat 写操作与 event 监听的边界。
全部 EventKey 表、ready 细节、重试状态和 status/stop 运维迁入按需 reference。
10. 分阶段实施计划
PR 1:Skill 路由收敛与 shared 解耦
范围
只修改 Skill、reference、生成器和策略门禁;不改变 CLI 行为、公开参数或业务接口。
工作项
- 将
dingtalk-chat重写为 Golden Route。 - 删除旧原子 SOP 和旧文件上传路线。
- 修正
--time、Webhook、资源下载说明。 - 移除正常 IM 对完整
dws-shared的强制读取。 - 建立最小公共契约单一来源和受控注入区块。
- 将卡片、复杂转发、Bot 管理、事件运维迁入精确 reference。
- 增加禁止旧路线回流的静态策略。
验收
- 已知 ID 发送:读产品 Skill → 执行一个业务命令;
- 姓名发送直接选择
+dm; - 群名发送直接选择
+send-to-group; - 最近聊天记录不强制
--time; - Webhook 默认选择
+messages-send --as webhook; - 文件不再推荐独立 mediaId 上传链;
- 同一意图只有一个主入口;
- 根 Skill ≤2.5K Tokens;
- 正常 IM 强制 shared 为 0;
- 所有示例通过真实 Cobra 路径和 flag 校验。
回滚边界
PR 仅改变 Agent 路由文本,可独立回滚,不影响 CLI 兼容性。
PR 2:统一 Resolver 与发送执行内核
范围
- 内部 typed resolver;
- 结构化 resolution error;
+messages-send --user-query/--chat-query;+dm、+send-to-group委托统一发送内核;- dry-run parity;
- Schema/Help/selection metadata 同步。
验收
- 唯一姓名发送只执行一个用户可见业务命令;
- 群名发送只执行一个用户可见业务命令;
- 群名发送文件可由
+messages-send --chat-query --file一步完成; - 多候选副作用为 0,并返回结构化 candidates;
- 已知 ID 不触发 resolver;
- dry-run 与真实执行选择同一目标;
+dm、+send-to-group经统一发送内核产生等价的下层发送结果;- AI tag、幂等和内容验证不因入口不同而漂移。
- 简单姓名/群名文本仍分别选择
+dm、+send-to-group,不会因为内部统一而暴露完整身份参数矩阵。
兼容性
只增加可选 flags 和内部复用;保留已有命令与参数。
PR 3:读取、搜索、DTO 与资源闭环
范围
+chat-messages --chat-query/--user-query;+search-msg --sender-query/--chat-query;- 公共
MessageViewV1投影; - 完整性字段;
- 资源下载 ledger 统一;
- 兼容性 fixture。
验收
- 群名读取并下载资源只需要一个业务命令;
- 跨群搜索某人消息只需要一个业务命令;
- 单资源失败不影响其它消息;
- 翻页和富化结束后能证明
complete=true; - list/search/mget/thread 的核心字段一致;
- Agent 不再手工 join sender/chat/resource;
- 现有公开 DTO 字段类型和顶层名称保持兼容。
PR 4:扩展建群与回复闭环
范围
- 扩展现有
+chat-create --member-query; - 批量解析全量预检;
- dry-run 成员计划;
- 扩展
+messages-reply输出上下文; - Thread writer 若下层仍缺失则只保留需求,不虚假发布。
验收
- 使用姓名创建群不需 Agent 手工查 ID;
- 任一成员歧义时不创建群;
- 当前用户仍在成员列表并作为群主;
- 回复结果可继续用于查询、撤回和资源操作;
- 普通引用回复与 Thread 回复不混淆;
- 不承诺下层不存在的 description/bot/visibility 能力。
PR 5:IM 事件高阶入口
范围
event +listen-im;- kind/events 到 EventKey 的映射;
- user/chat natural resolution;
- 多事件合并;
- ready、bounded、取消、回滚和清理;
dingtalk-event根 Skill 精简。
验收
- 已知监听意图不调用
event list; - 不需要字段推断时不调用
event schema; - 多兼容事件使用一个命令和一个进程;
- 等待 ready marker,不使用 sleep;
- 正常、超时、取消和部分失败均不泄漏订阅;
- 控制模型轮次 ≤4;
- stdout/stderr 契约保持稳定。
11. 后续依赖下层的能力(明确非本轮遗留)
以下项目依赖尚未存在或未确认的下层能力,不阻塞前五个 PR,也不属于本轮完成定义:
| 优先级 | 能力 | 当前处理 |
|---|---|---|
| P1 | Thread 回复 writer | 未确认前不发布 --reply-in-thread |
| P1 | 异步 delivery status / --wait |
先保留 openTaskId 和明确未知状态 |
| P1 | 图片、文件、音视频统一资源元数据 | PR 3 先规范当前可识别资源 |
| P2 | Bot 富媒体发送 | 不把 user 能力映射给 bot |
| P2 | 大文件分片、Range、断点续传 | 保持当前安全下载边界 |
| P2 | 卡片按钮回调与完整生命周期 | 独立 card/event reference |
| P3 | Moderation、owner/manager/visibility | 仅发布真实 DWS 下层子集 |
| P3 | 事件触发发送幂等关联 | 待 delivery/event correlation 下层支持 |
12. 评测设计
12.1 固定回归场景
- 已知 ID 发文本;
- 唯一姓名发文本;
- 多人同名发消息;
- 群名发文件;
- 跨群搜索某人最近消息;
- 群名读取历史并下载资源;
- 使用姓名创建群;
- 监听某群消息和 reaction。
12.2 每场景记录
task_success
routing_correct
first_pass_success
model_calls
skill_files_loaded
reference_files_loaded
schema_calls
help_calls
business_cli_calls
internal_read_calls
internal_write_calls
resident_control_tokens
cumulative_input_tokens
ambiguous_target_behavior
side_effect_count
complete
false_success
correction_rounds
12.3 指标定义
model_calls:初始决策及每次工具结果后的模型继续调用;business_cli_calls:Agent 显式执行的用户可见dws业务命令;Shortcut 内部 MCP 调用不另算模型轮次;resident_control_tokens:本任务进入上下文的 Skill、shared、reference、Schema 和 Help 去重总量;cumulative_input_tokens:每轮模型输入中固定基础上下文与已驻留控制文本的累计暴露;first_pass_success:首次选定入口和参数后,无需改走其它命令或补查泛化 Schema/Help 即完成任务;correction_rounds:因路由、参数、身份或结果解释不足产生的额外模型继续轮次;false_success:结果不完整、写入未知或部分失败却被报告为完整成功;side_effect_count:测试期望之外的创建、发送、更新、订阅或删除次数。
Token 价值通过场景证据评审,而不是仅计算“越少越好”:根 Skill 每个区块必须标注它支持的评测场景或阻止的失败;删除区块后若 Schema/Help fallback、纠错轮次或失败率上升,即使冷启动 Token 下降也不能合入。
12.4 分阶段预期路径
| 场景 | PR 1 后 | 最终目标 |
|---|---|---|
| 已知 ID 发文本 | +messages-send |
1 个业务命令,3 轮 |
| 唯一姓名发文本 | +dm |
1 个业务命令,3~4 轮 |
| 多人同名发送 | +dm 无写入失败 |
结构化 candidates,副作用 0 |
| 群名发文件 | 仍可能需 ID | +messages-send --chat-query --file 一步 |
| 跨群搜某人 | 仍可能需先解析 | +search-msg --sender-query 一步 |
| 群名读并下载 | 仍可能需先解析 | +chat-messages --chat-query --download-resources 一步 |
| 姓名建群 | 仍需 IDs | +chat-create --member-query 一步 |
| 群事件监听 | 切 dingtalk-event |
event +listen-im --chat-query 一步 |
12.5 硬门禁
- 任务成功率、首次路由正确率和完整结果率不得低于基线;
- 冷启动 Token 下降但 Schema/Help fallback 或纠错轮次上升,不算通过;
- 高频已知意图
schema_calls=0; - 高频已知意图
help_calls=0; - 唯一姓名/群名最终只执行一个业务 CLI 命令;
- 多候选写操作
side_effect_count=0; - 不允许完整 Shortcut Catalog 进入上下文;
- 不允许根 Skill 恢复旧原子 SOP;
- 不允许正常 Chat 强制读取完整 shared;
- 不允许 partial result 标记
complete=true; - 不允许生成文件被手工编辑。
13. 测试与 CI 门禁
13.1 Skill 静态门禁
扩展 check-skill-context-budget.sh 或增加专用检查:
dingtalk-chat字节与 Token 预算;- Golden Route 精确命令集合;
- 同一意图不得出现两个“优先/必须”入口;
- 禁止旧模式文本回流,例如:
- 发消息必须先
aisearch person; - 文件必须先
dt_media_upload; +chat-messages必须传--time;- Webhook 默认走原子命令;
- 发消息必须先
- 禁止 Chat 完整 Shortcut 表重新展开;
- 最小公共契约生成区块必须与源一致;
- 所有示例通过真实 Cobra path/flag/constraint 校验。
13.2 Resolver 单元测试
至少覆盖:
- 零命中;
- 单一模糊候选;
- 单一 exact;
- 多个 exact;
- 重复 ID 去重;
- 外部联系人只有 openDingTalkId;
- 下游要求 userId/openDingTalkId 的差异;
- 群 exact 优先和同名群歧义;
- profile 不一致;
- upstream error;
- dry-run 与真实解析相同;
- 已知 ID 不调用搜索。
13.3 Shortcut 行为测试
+dm、+send-to-group、+messages-send使用同一发送器;- user/bot/webhook 目标矩阵;
- 文件路径与内容类型;
- idempotency 和 AI tag;
- 解析失败前无写调用;
- send/read/search/create 的 dry-run 计划;
- Message DTO fixture 和向后兼容;
- 分页、富化、资源 partial failure;
- 群创建全量预检;
- 回复结果的后续上下文。
13.4 Event 测试
- kind/events/target 映射;
- 多事件去重和顺序;
- ready marker;
- 部分订阅失败回滚;
- timeout/SIGINT/SIGTERM 清理;
- dry-run 不创建订阅;
- profile 一致;
- stdout 只包含 NDJSON;
- bounded 结束无泄漏。
13.5 Schema 与生成门禁
每个改变 Cobra flags、Runtime Schema 或 Agent selection 的 PR 必须:
go generate ./internal/cli
./scripts/policy/check-generated-drift.sh
./scripts/policy/check-schema-catalog.sh
./scripts/policy/check-runtime-confirmation-truth.sh
make skill-command-integrity skill-context-budget
DWS_PACKAGE_VERSION=0.0.0-test go test ./...
并重点运行:
go test ./internal/shortcut/... -count=1
go test ./internal/app -run 'Test(Chat|Event|Schema|ManualAgentExample)' -count=1
make test-schema-agent-examples
示例中不得存储 --yes;所有公开示例必须通过真实 BoundCommand 的路径、flag、required 和跨参数约束。
13.6 Live 验证
在具备隔离测试账号和明确授权时运行:
- 唯一/歧义姓名;
- 同名群;
- user 文本与文件;
- bot/webhook 文本;
- 消息分页和下载;
- 事件多订阅和清理。
Live 报告必须脱敏,不保存 token、webhook token、原始私人消息或下载内容。
14. 生成与单一来源
14.1 Command identity
- 已有
+dm、+send-to-group、+messages-send、+chat-messages、+search-msg、+chat-create、+messages-reply保持 identity 和主路径; - 新增可选 flag 不机械改写 CommandRegistry;
event +listen-im是新公开 leaf,需要评审 registry identity、主路径、selection 和 metadata;- alias 只有存在真实兼容需求时才加入。
14.2 参数与安全来源
- Cobra tree:命令存在性与接受的 flags;
- Runtime constraints:互斥、至少一个、条件组合;
- metadata:接口、安全、runtime gate 和必要参数 overlay;
- selection:Agent 选择语义;
- Skill:Golden Route,不重复参数真相。
14.3 输出 DTO 来源
消息投影应有单一内部 builder 和共享 fixture。list/search/mget/thread 不各自维护字段别名、reaction、quoted、forwarded 和 resource 解析逻辑。
14.4 生成文件
只能修改源输入和生成器,再运行生成:
- 不手改
schema_catalog/; - 不手改
schema_agent_metadata/; - 不以旧 Catalog 为生成输入;
- 生成前后必须通过 byte guard 和 drift check。
15. 风险与缓解
| 风险 | 表现 | 缓解 |
|---|---|---|
| Skill 过度精简 | 低频能力找不到 | 精确 reference + 最后回退发现,不恢复全量 Catalog |
| 移除 shared 后安全丢失 | 产品 Skill 缺全局底线 | 单一来源最小契约生成注入 + hash/marker 门禁 |
| Resolver 误选人/群 | 发错对象 | exact/unique 规则、结构化 candidates、写前停止 |
| 批量解析半完成 | 建出成员不完整的群 | 全量预检,全成功后单次写入 |
| dry-run 与真实执行漂移 | 预览目标不同 | 同一 resolver、同一校验、只在写调用前分叉 |
| DTO 统一导致兼容破坏 | 脚本解析失败 | 共享内部模型、增量字段、显式版本、禁止改字段类型 |
| facade 继续膨胀 | 又出现大量同义入口 | 只扩展五个主入口,保留兼容 wrapper,不再横向新增 |
| 虚假对齐 Lark | 广告下层不支持能力 | availability/interface_reason 如实发布,能力进入后续清单 |
| 事件订阅泄漏 | 进程结束后仍订阅 | ready/rollback/cancel/timeout/cleanup 全路径测试 |
| 跨 profile ID 串用 | 错组织操作 | Resolver 结果携带 profile,执行前一致性校验 |
| 输出富化过度 | 延迟和业务数据 Token 增长 | 保留 --no-enrich、--no-reactions、下载 opt-in |
| 安全从 Skill 下沉后漂移 | confirmation 不一致 | Runtime gate truth + final embedded Schema 语义测试 |
16. 冻结决策与待确认项
16.1 已冻结
- PR 顺序为 Skill → Resolver/Send → Read/Search → Create/Reply → Event。
- 正常 IM 不再完整读取 shared。
- 最小公共契约采用单一来源生成注入,不人工复制。
+dm、+send-to-group保留兼容,但内部复用统一发送器。- 自然目标 canonical flag 使用
--user-query、--chat-query、--sender-query、--member-query。 - 多候选永不默认第一项。
- dry-run 运行真实只读解析。
+chat-create、+messages-reply是扩展项,不重复新增。- 消息 DTO 只做兼容性增量;嵌套对象需要显式新版本。
event +listen-im复用现有事件控制面,不另建订阅系统。- Token 预算是防膨胀护栏,北极星是任务价值、正确性、安全和完整性。
- Golden Route 保留多个最小充分意图入口;发送和读取只在内部执行内核与数据契约层统一。
- 简单姓名文本走
+dm,简单群名文本走+send-to-group;高级发送才选择+messages-send。
16.2 实施前仍需确认
| 项目 | 需要确认的事实 |
|---|---|
| Thread writer | 下层是否能稳定返回新消息 ID、thread ID 和 delivery 状态 |
| 群创建幂等 | 下层是否支持服务端幂等键;否则不宣称幂等 |
| 群 description/初始 Bot | DWS 下层是否真实支持,不照搬 Lark 参数 |
| sender DTO v2 | 是否有消费者需要嵌套对象,是否值得显式版本 |
| event bounded 模式 | --max-events、--timeout 的退出码和 cleanup 语义 |
| Resolution envelope | 是否扩展通用 apperrors,供其它产品复用 |
| Token CI | 使用固定 tokenizer 依赖还是字节硬门禁 + Token 报告双轨 |
未确认项不得阻塞 PR 1,也不得在 Skill 中提前广告。
17. PR 评审清单
路由
- 高频意图只有一个主入口;
- 已知意图不需要产品 Schema、Catalog 或 Help;
- 原子命令只作为明确降级;
- Skill 没有广告当前下层不可用能力。
Resolver
- 已知 ID 跳过解析;
- exact、unique、ambiguous、not_found 行为有测试;
- candidates 结构化且不泄露敏感信息;
- profile 全链路一致;
- dry-run 与真实执行同解析结果。
安全
- 多候选写操作副作用为 0;
- confirmation 与最终 Runtime gate/Schema 一致;
- 文件路径、覆盖和原子落盘规则未放宽;
- partial success 不被报告为完整成功;
- 事件异常退出完成清理。
兼容性
- 未删除旧 CLI path/flag;
- 未机械改写 CommandRegistry;
- 未改变现有 DTO 字段类型;
- 新字段为增量或显式版本;
- alias 有真实兼容理由。
生成与验证
- 修改了正确的 authored source;
- 未手改 Catalog/Agent metadata;
- generation、drift、Schema policy 通过;
- Skill 命令完整性和 context budget 通过;
- 单元、dry-run、fixture、事件清理测试通过;
- 评测表记录了模型轮次和控制 Token 变化。
18. 最终验收标准
全部五个 PR 完成后,必须同时达到:
- 八个固定高频场景的任务成功率、首次路由正确率和完整结果率不低于基线。
- 多候选写操作副作用和 false success 为 0。
- 每个根 Skill 区块都能映射到路由、安全、结果判断或错误恢复证据。
- 八个固定高频场景平均模型轮次 ≤3.7。
- 平均累计输入 ≤90K。
- 平均常驻上下文 23~24.5K。
- 高频已知意图 Schema/Help 调用为 0。
- 自然姓名/群名场景只执行一个用户可见业务命令。
- 群名读取并下载可一命令完成。
- 姓名建群可一命令完成,任一歧义时不创建。
- 多事件监听使用一个 facade 和一个消费进程。
- 读取/搜索 partial failure 不产生 false success。
- 消息 DTO 保持兼容并共享同一内部投影实现。
- Safety、confirmation、Schema、Help 和真实 Runtime 无漂移。
如果效率指标达到但任务成功率、安全或完整性退化,则该阶段不算通过。
附录 A:目标根 Skill 骨架
# 钉钉群聊 / 消息 Skill
## 最小执行契约
<!-- 由共享源生成;约 300~500 Tokens -->
## Golden Route
| 用户意图 | 唯一入口 |
|---|---|
| 按姓名发文本 | `dws chat +dm ...` |
| 按群名发文本 | `dws chat +send-to-group ...` |
| 已知目标/文件/Bot/Webhook | `dws chat +messages-send ...` |
| 读指定会话 | `dws chat +chat-messages ...` |
| 跨会话搜索 | `dws chat +search-msg ...` |
| 读并下载资源 | 查询命令加 `--download-resources` |
| 查看置顶会话 | `dws chat +conversation-list-top` |
| 监听事件 | 切 `dingtalk-event` |
## 关键结果语义
- openTaskId 不是消息 ID。
- reaction/updateTime/resourceRefs 默认随稳定投影返回。
- Favorite/Pin/Message Top/Conversation Top 不可混用。
## 低频能力
- 卡片、复杂转发、Bot 管理、事件运维按需读取精确 reference。
- 已知 leaf 直接执行;仅在参数/安全不确定时读取 leaf Schema。
## 错误分流
- resolution candidates:交给用户消歧,不猜。
- auth/profile/confirmation:读取对应 shared reference。
- unknown command/flag:精确 leaf Help,最多修正一次。
附录 B:评测记录模板
| 场景 | 成功 | 主入口 | 模型轮次 | Skill | Ref | Schema | Help | 业务 CLI | 常驻 Tokens | 累计输入 | 副作用 | complete | false success |
|---|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---|---|
| 已知 ID 发文本 | | | | | | | | | | | | | |
| 唯一姓名发文本 | | | | | | | | | | | | | |
| 多人同名发送 | | | | | | | | | | | | | |
| 群名发文件 | | | | | | | | | | | | | |
| 跨群搜某人 | | | | | | | | | | | | | |
| 群名读并下载 | | | | | | | | | | | | | |
| 姓名创建群 | | | | | | | | | | | | | |
| 群消息+reaction 监听 | | | | | | | | | | | | | |
附录 C:实施总路线
先修 Skill,使 Agent 使用现有最小充分路由
→ 再统一 Resolver,消除名称到 ID 的 Agent 手工步骤
→ 再统一发送内核,以及读取/搜索共享的投影和完整性契约
→ 扩展现有建群与回复入口
→ 最后封装长连接事件入口
→ 下层能力成熟后再补 Thread、Bot 富媒体和大文件能力
最终判断标准不是 Shortcut 数量,而是:用户的高频 IM 意图是否能由一个稳定入口完成,Agent 是否不再承担本可由 CLI 确定性完成的工作。