# 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。 目标路径统一为: ```text 激活 dingtalk-chat → 从有限 Golden Route 中选择最小充分的意图入口 → CLI 内部完成自然目标解析、参数校验、分页、富化和执行 → 返回稳定、可证明完整性的任务级 DTO ``` 优化顺序冻结为: ```text 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 或本轮合并前置条件。 本方案同时冻结以下约束: 1. 不通过删减安全事实、确认门禁或结果完整性换取 Token。 2. 不为已有能力重复新增同义 Shortcut。 3. 不做未版本化的公开输出 DTO 破坏性变更。 4. 不机械修改 `schema_command_registry.json`;只有稳定 identity、主路径或 alias 真正变化时才评审修改。 5. 不手改生成的 Schema Catalog 或 Agent metadata。 6. Token 预算是防膨胀护栏,不是要求删除所有上下文;能显著降低误路由、误写或 false success 的内容应保留。 7. 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 当前实现仍有的真实缺口 1. `+dm` 与 `+send-to-group` 已完成名称解析,但仍各自直接调用发送接口,没有完全复用 `+messages-send` 的统一内容、幂等、AI 标记和输出逻辑。 2. 当前 person/group resolver 能拒绝零命中和多命中,但错误主要是人类可读字符串,尚未形成稳定结构化 candidates 契约。 3. `+chat-messages`、`+search-msg`、`+chat-create` 和事件监听仍要求调用者先提供 ID。 4. `+chat-create` 与 `+messages-reply` 已存在;后续任务是扩展闭环,而不是再次创建同义命令。 5. 不同消息读取入口的核心字段已较接近,但尚未冻结兼容的公共 Message DTO 和完整性字段。 6. `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 照搬: 1. Lark 的 `open_id`、`chat_id`、资源 key、消息字段和事件名;DWS 必须保留自己的稳定 identity 和下层接口事实。 2. 群 description、初始 Bot、visibility、owner/manager、Thread writer 等尚未确认的能力。 3. Bot/Webhook 与 user 完全等价的富媒体能力假设。 4. 把嵌套 sender/resources 或 `items` 顶层结构无版本替换到当前公开 DTO。 5. lark-im 的完整根文件体量、全量命令表或平台特有 SOP。 6. 依赖 Agent 手工调用 contact/chat search 再搬运 ID 的多步流程;应吸收结果语义,但把确定性步骤下沉到 CLI。 最终对齐目标不是命令名相同,而是同一类高频意图在两套 CLI 中都具备:短路由、唯一解析、完整结果、可恢复错误和可证明的安全边界。 --- ## 2. 目标、指标与非目标 ### 2.1 北极星目标 高频 IM 请求应满足: ```text 一个明确用户意图 → 一个最小充分的 Golden Shortcut → 最多一个用户可见业务 CLI 命令 → 无 Agent 手工搬运 ID、分页或拼接资源结果 ``` 优化优先级固定为: ```text 任务正确完成 → 安全且无错误副作用 → 结果完整、可继续使用 → 首次选路正确、少返工 → 在满足以上条件后减少无效控制 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 非目标 本轮不做: 1. 删除旧原子命令或已发布 Shortcut。 2. 为了命名统一机械重写 CommandRegistry identity 或主路径。 3. 把完整 Shortcut Catalog、原子 API 清单或 Scope 表重新放入根 Skill。 4. 在没有下层能力的情况下承诺 Bot 富媒体、Thread 写入、群 description、初始机器人或断点续传。 5. 无版本地把现有 `sender` 字段从字符串改成对象,或把 `messages` 顶层数组改名为 `items`。 6. 同时重构所有 DWS Multi Skill;本方案先验证 IM 路径。 --- ## 3. 目标架构 ### 3.1 四层职责 | 层 | 负责 | 不负责 | |---|---|---| | 产品根 Skill | 触发边界、Golden Route、最小不可推导语义 | 全量命令、完整参数表、旧 SOP | | 最小全局契约 | CLI 使用、安全底线、ID 不猜测、写后验证 | 产品路由大全、认证手册、错误码大全 | | Task reference | 低频复杂流程、卡片、复杂转发、Bot 管理、事件运维 | 高频命令选择 | | Runtime + leaf Schema/Help | 参数、约束、身份矩阵、确认、接口与执行 | 自然语言产品路由教程 | ### 3.2 目标加载路径 清晰单产品任务: ```text dingtalk-chat 根 Skill(含最小全局契约) → 一个 Golden Shortcut → 完成 ``` 低频复杂任务: ```text dingtalk-chat 根 Skill → 精确 task reference → 已知 leaf → 仅在参数/安全不确定时查询 leaf Schema → 仅在 Cobra flag 不确定时查询 leaf Help ``` 异常路径: ```text 认证/权限/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 降级顺序 ```text 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 和内部统一解决的是不同层次的问题: ```text 用户意图层:按任务分流,入口少而明确 +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` 目标结构 建议根文件固定为: ```text Frontmatter / 触发边界 → 最小 DWS 执行契约 → Golden Route → 三条关键结果语义 → 低频 reference 导航 → 最短错误分流 ``` 只保留三类无法完全由 leaf Schema 表达的结果语义: 1. `openTaskId` 是发送任务 ID,不是回复/撤回使用的消息 ID。 2. 消息读取默认保留 reaction、`updateTime`、引用、转发和 `resourceRefs`;`--no-reactions` 可关闭 reaction 输出。 3. 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` 而丢失全局安全契约。 冻结的交付方式为: 1. 新建一份单一来源的最小公共契约,例如: `skills/multi/dws-shared/references/runtime-contract.md`。 2. 由生成器把该区块同步到 `dingtalk-chat/SKILL.md` 的受控 marker 中。 3. `dws-shared/SKILL.md` 继续作为泛称 DWS、跨产品、认证/profile/错误路由入口,但不再是清晰 IM 任务的强制文本依赖。 4. 策略门禁校验源文件与内嵌区块一致,禁止人工漂移。 最小契约只包括: - 只通过 `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 目标 所有自然目标解析必须复用同一套内部组件: ```text ResolveUser(profile, query, requiredIdentity) ResolveChat(profile, query) ResolveBot(profile, query) ResolveUsers(profile, queries, requiredIdentity) ``` Resolver 只做确定性解析,不执行最终写操作。 ### 5.2 类型化结果 建议内部结构: ```json { "status": "resolved", "entityType": "user", "query": "张三", "matchType": "exact", "selected": { "id": "userId-or-openDingTalkId", "userId": "...", "openDingTalkId": "...", "name": "张三", "organization": "..." }, "candidates": [], "profile": "corpId:userId" } ``` 状态固定为: - `resolved` - `ambiguous` - `not_found` - `forbidden` - `upstream_failure` ### 5.3 匹配规则 1. 去除首尾空白,按产品允许的大小写规则比较。 2. 按稳定 ID 去重,禁止同一对象的重复行制造假歧义。 3. 用户解析按稳定 ID 去重后必须只有一个可用候选;即使其中一个显示名 exact,也不能隐藏同名或外部联系人候选。 4. 群聊解析仅有一个 exact match 时优先 exact;多个 exact match 仍为 `ambiguous`。 5. 群聊没有 exact 时,只有一个可用候选才允许 resolved。 6. 多候选禁止默认第一项、最近使用项或第一组织项。 7. 下游需要 `openDingTalkId` 时,不得选择只有 userId 且无法转换的候选;反之亦然。 8. 已提供稳定 ID 时直接使用,不触发名称搜索。 ### 5.4 结构化错误 零命中和多命中应返回非零退出码和机器可读细节: ```json { "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` 采用全量预检: ```text 解析所有查询 → 收集 resolved / ambiguous / not_found → 任意一项未 resolved,则整体停止 → 全部 resolved 后才进入写操作 ``` 创建群等不可接受半完成的操作禁止“成功一个先写一个”。 ### 5.7 Dry-run 一致性 `--dry-run` 必须: 1. 运行与真实执行相同的只读 Resolver; 2. 运行相同的 target/content/identity 校验; 3. 输出最终 resolved target 和将调用的下层动作; 4. 在第一个写调用前停止; 5. 不因 dry-run 使用另一套模糊匹配逻辑。 现有 `CallMCPData` 的 dry-run 只读通道继续作为基础;新增 resolver 所使用的下层工具必须进入明确的只读分类,不能用名称猜测绕过写保护。 --- ## 6. 分层发送路由与统一执行内核 ### 6.1 自然目标参数 扩展 `chat +messages-send`: ```bash --user-query --chat-query ``` 示例: ```bash dws chat +messages-send \ --as user \ --user-query "张三" \ --text "请查收" dws chat +messages-send \ --as user \ --chat-query "项目群" \ --file ./report.pdf \ --idempotency-key ``` 目标严格且只能选择一个: ```text --chat-id / --group --chat-query --user --user-query --open-dingtalk-id ``` Bot 和 Webhook 不自动继承 user 的自然目标或文件能力;是否支持由现有身份矩阵和真实下层能力决定。 这些自然目标参数服务于“自然目标 + 文件/复杂 @/幂等等高级发送”场景,不把 `+messages-send` 提升为所有发送意图的唯一公开入口。简单姓名文本继续走 `+dm`,简单群名文本继续走 `+send-to-group`。 ### 6.2 统一内部发送器 将当前发送路径收敛为一个内部执行器: ```text 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,但不再各自维护发送实现: ```text +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 输出 ```json { "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` 新增: ```bash --chat-query --user-query ``` 示例: ```bash dws chat +chat-messages \ --chat-query "项目群" \ --download-resources ``` 内部路径: ```text 解析 chat/user → 获取消息 → 投影 sender/text/time/identity → 保留 reaction/引用/转发/resourceRefs → 计算分页完整性 → 可选安全下载 → 输出稳定 DTO ``` 省略 `--time` 继续默认以当前时间为边界向前读取;Skill 不再强制 Agent 构造时间。 ### 7.2 扩展 `+search-msg` 新增: ```bash --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` 构造器,但不得无版本破坏现有公开输出。 第一阶段保留已有字段类型和顶层名称: ```json { "contractVersion": "im.message-list.v1", "messages": [ { "messageId": "...", "conversationId": "...", "threadId": "...", "sender": "张三", "senderId": "...", "senderType": "user", "messageType": "text", "text": "...", "createTime": "...", "updateTime": "...", "reactions": {}, "quotedMessage": {}, "forwarded": [], "resourceRefs": [] } ] } ``` 兼容规则: 1. `sender` 暂时保持现有标量类型,不直接替换为对象。 2. 新的 `senderId`、`senderType` 采用增量字段。 3. `resourceRefs` 保留现有名称;如需新 `resources` 结构,必须增量发布或引入显式 DTO 版本。 4. 顶层 `messages` 不无版本改名为 `items`。 5. 原字段删除或类型变化必须走独立兼容性评审。 如果未来确需嵌套 sender/resources 对象,使用显式 `v2` projection 或新主版本,而不是静默切换。 ### 7.4 完整性字段 所有分页读取和搜索逐步统一为: ```json { "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` 已存在,本阶段增加自然成员解析,而不是创建同义命令。 目标示例: ```bash 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` 当前入口已能根据原消息补全发送者;本阶段目标是补齐后续可执行上下文: ```bash dws chat +messages-reply \ --message-id \ --conversation-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: ```bash 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 建议参数 ```text --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 行为、公开参数或业务接口。 #### 工作项 1. 将 `dingtalk-chat` 重写为 Golden Route。 2. 删除旧原子 SOP 和旧文件上传路线。 3. 修正 `--time`、Webhook、资源下载说明。 4. 移除正常 IM 对完整 `dws-shared` 的强制读取。 5. 建立最小公共契约单一来源和受控注入区块。 6. 将卡片、复杂转发、Bot 管理、事件运维迁入精确 reference。 7. 增加禁止旧路线回流的静态策略。 #### 验收 - 已知 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 固定回归场景 1. 已知 ID 发文本; 2. 唯一姓名发文本; 3. 多人同名发消息; 4. 群名发文件; 5. 跨群搜索某人最近消息; 6. 群名读取历史并下载资源; 7. 使用姓名创建群; 8. 监听某群消息和 reaction。 ### 12.2 每场景记录 ```text 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` 或增加专用检查: 1. `dingtalk-chat` 字节与 Token 预算; 2. Golden Route 精确命令集合; 3. 同一意图不得出现两个“优先/必须”入口; 4. 禁止旧模式文本回流,例如: - 发消息必须先 `aisearch person`; - 文件必须先 `dt_media_upload`; - `+chat-messages` 必须传 `--time`; - Webhook 默认走原子命令; 5. 禁止 Chat 完整 Shortcut 表重新展开; 6. 最小公共契约生成区块必须与源一致; 7. 所有示例通过真实 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 必须: ```bash 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 ./... ``` 并重点运行: ```bash 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 已冻结 1. PR 顺序为 Skill → Resolver/Send → Read/Search → Create/Reply → Event。 2. 正常 IM 不再完整读取 shared。 3. 最小公共契约采用单一来源生成注入,不人工复制。 4. `+dm`、`+send-to-group` 保留兼容,但内部复用统一发送器。 5. 自然目标 canonical flag 使用 `--user-query`、`--chat-query`、`--sender-query`、`--member-query`。 6. 多候选永不默认第一项。 7. dry-run 运行真实只读解析。 8. `+chat-create`、`+messages-reply` 是扩展项,不重复新增。 9. 消息 DTO 只做兼容性增量;嵌套对象需要显式新版本。 10. `event +listen-im` 复用现有事件控制面,不另建订阅系统。 11. Token 预算是防膨胀护栏,北极星是任务价值、正确性、安全和完整性。 12. Golden Route 保留多个最小充分意图入口;发送和读取只在内部执行内核与数据契约层统一。 13. 简单姓名文本走 `+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 完成后,必须同时达到: 1. 八个固定高频场景的任务成功率、首次路由正确率和完整结果率不低于基线。 2. 多候选写操作副作用和 false success 为 0。 3. 每个根 Skill 区块都能映射到路由、安全、结果判断或错误恢复证据。 4. 八个固定高频场景平均模型轮次 ≤3.7。 5. 平均累计输入 ≤90K。 6. 平均常驻上下文 23~24.5K。 7. 高频已知意图 Schema/Help 调用为 0。 8. 自然姓名/群名场景只执行一个用户可见业务命令。 9. 群名读取并下载可一命令完成。 10. 姓名建群可一命令完成,任一歧义时不创建。 11. 多事件监听使用一个 facade 和一个消费进程。 12. 读取/搜索 partial failure 不产生 false success。 13. 消息 DTO 保持兼容并共享同一内部投影实现。 14. Safety、confirmation、Schema、Help 和真实 Runtime 无漂移。 如果效率指标达到但任务成功率、安全或完整性退化,则该阶段不算通过。 --- ## 附录 A:目标根 Skill 骨架 ```markdown # 钉钉群聊 / 消息 Skill ## 最小执行契约 ## 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:评测记录模板 ```markdown | 场景 | 成功 | 主入口 | 模型轮次 | Skill | Ref | Schema | Help | 业务 CLI | 常驻 Tokens | 累计输入 | 副作用 | complete | false success | |---|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---|---| | 已知 ID 发文本 | | | | | | | | | | | | | | | 唯一姓名发文本 | | | | | | | | | | | | | | | 多人同名发送 | | | | | | | | | | | | | | | 群名发文件 | | | | | | | | | | | | | | | 跨群搜某人 | | | | | | | | | | | | | | | 群名读并下载 | | | | | | | | | | | | | | | 姓名创建群 | | | | | | | | | | | | | | | 群消息+reaction 监听 | | | | | | | | | | | | | | ``` ## 附录 C:实施总路线 ```text 先修 Skill,使 Agent 使用现有最小充分路由 → 再统一 Resolver,消除名称到 ID 的 Agent 手工步骤 → 再统一发送内核,以及读取/搜索共享的投影和完整性契约 → 扩展现有建群与回复入口 → 最后封装长连接事件入口 → 下层能力成熟后再补 Thread、Bot 富媒体和大文件能力 ``` 最终判断标准不是 Shortcut 数量,而是:用户的高频 IM 意图是否能由一个稳定入口完成,Agent 是否不再承担本可由 CLI 确定性完成的工作。