Sync third-party and MCP marketplace plugins

Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
This commit is contained in:
KeyInfo Bot committed 2026-09-17 00:02:25 +08:00
1 parent 1fbb2a40a8
commit ec10f9dc0c
185 files changed
+8735 -3185

No files matched your search

+13 -13
View File
@@ -4,10 +4,10 @@
"id": "dingtalk-workspace", "id": "dingtalk-workspace",
"pluginName": "dingtalk-workspace", "pluginName": "dingtalk-workspace",
"repo": "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git", "repo": "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git",
"ref": "v1.0.61", "ref": "v1.0.62",
"adapter": "skill-collection", "adapter": "skill-collection",
"commit": "50eb73a0906c5911c5c25f9c7106b6ead4f14f62", "commit": "70323e1486e64b1ca823fa2bd9c48b9b07f88519",
"syncedAt": "2026-09-09T04:01:45Z" "syncedAt": "2026-09-16T15:59:59Z"
}, },
{ {
"id": "superpowers", "id": "superpowers",
@@ -51,8 +51,8 @@
"repo": "https://github.com/JuliusBrussee/caveman.git", "repo": "https://github.com/JuliusBrussee/caveman.git",
"ref": "main", "ref": "main",
"adapter": "codex-plugin", "adapter": "codex-plugin",
"commit": "4df4b03ba538751131d24415c59a1fad0d8c2c18", "commit": "cc067b2b3813e3187ba656b1d908761ad3e5b904",
"syncedAt": "2026-09-15T06:47:43Z" "syncedAt": "2026-09-16T15:59:59Z"
}, },
{ {
"id": "taste-skill", "id": "taste-skill",
@@ -60,8 +60,8 @@
"repo": "https://github.com/Leonxlnx/taste-skill.git", "repo": "https://github.com/Leonxlnx/taste-skill.git",
"ref": "main", "ref": "main",
"adapter": "skill-collection", "adapter": "skill-collection",
"commit": "ccbc15639c97057cbfcf32ecebc38ef716e4bb37", "commit": "e79ca9ec7e071eb3a3b623c4fb752e853fc3ed58",
"syncedAt": "2026-08-24T16:00:00Z" "syncedAt": "2026-09-16T15:59:59Z"
}, },
{ {
"id": "shadcn", "id": "shadcn",
@@ -69,8 +69,8 @@
"repo": "https://github.com/shadcn-ui/ui.git", "repo": "https://github.com/shadcn-ui/ui.git",
"ref": "main", "ref": "main",
"adapter": "claude-skill", "adapter": "claude-skill",
"commit": "2b3e6d4f8d9161fe5c19340dc383aade392012dd", "commit": "f5bb039c749afd2d111ad970dfaa9ab30bd90b3b",
"syncedAt": "2026-09-14T15:59:59Z" "syncedAt": "2026-09-16T15:59:59Z"
}, },
{ {
"id": "frontend-slides", "id": "frontend-slides",
@@ -105,8 +105,8 @@
"repo": "https://github.com/hugohe3/ppt-master.git", "repo": "https://github.com/hugohe3/ppt-master.git",
"ref": "main", "ref": "main",
"adapter": "claude-skill", "adapter": "claude-skill",
"commit": "be8089296767d3e8cf012e07ea31c8716591bad8", "commit": "3b45af2c3dab15626b1d499ea60ce689b7f97a5a",
"syncedAt": "2026-09-15T16:00:00Z" "syncedAt": "2026-09-16T15:59:59Z"
}, },
{ {
"id": "grill-me", "id": "grill-me",
@@ -177,8 +177,8 @@
"repo": "https://github.com/vercel/next.js.git", "repo": "https://github.com/vercel/next.js.git",
"ref": "canary", "ref": "canary",
"adapter": "skill-collection", "adapter": "skill-collection",
"commit": "bfcf687f5ab6f1e0577573eb209476cef802f5d7", "commit": "6130a493168e69c3c72bc1a3d161314ffa77b124",
"syncedAt": "2026-09-15T16:00:00Z" "syncedAt": "2026-09-16T15:59:59Z"
}, },
{ {
"id": "shuorenhua", "id": "shuorenhua",
@@ -2,8 +2,8 @@
"sourceId": "caveman", "sourceId": "caveman",
"repo": "https://github.com/JuliusBrussee/caveman.git", "repo": "https://github.com/JuliusBrussee/caveman.git",
"ref": "main", "ref": "main",
"commit": "4df4b03ba538751131d24415c59a1fad0d8c2c18", "commit": "cc067b2b3813e3187ba656b1d908761ad3e5b904",
"adapter": "codex-plugin", "adapter": "codex-plugin",
"sourcePath": "plugins/caveman", "sourcePath": "plugins/caveman",
"syncedAt": "2026-09-15T06:47:43Z" "syncedAt": "2026-09-16T15:59:59Z"
} }
@@ -1,6 +1,6 @@
{ {
"name": "dingtalk-workspace", "name": "dingtalk-workspace",
"version": "1.0.61", "version": "1.0.62",
"description": "通过 dws CLI 操作钉钉消息、文档、表格、日历、待办和知识库的官方 Skill 集合。需要另行安装匹配版本的 dws、登录并取得企业授权;辅助脚本需要 Python 3。", "description": "通过 dws CLI 操作钉钉消息、文档、表格、日历、待办和知识库的官方 Skill 集合。需要另行安装匹配版本的 dws、登录并取得企业授权;辅助脚本需要 Python 3。",
"author": { "author": {
"name": "DingTalk", "name": "DingTalk",
@@ -1,9 +1,9 @@
{ {
"sourceId": "dingtalk-workspace", "sourceId": "dingtalk-workspace",
"repo": "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git", "repo": "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git",
"ref": "v1.0.61", "ref": "v1.0.62",
"commit": "50eb73a0906c5911c5c25f9c7106b6ead4f14f62", "commit": "70323e1486e64b1ca823fa2bd9c48b9b07f88519",
"adapter": "skill-collection", "adapter": "skill-collection",
"sourcePath": "skills/multi", "sourcePath": "skills/multi",
"syncedAt": "2026-09-09T04:01:45Z" "syncedAt": "2026-09-16T15:59:59Z"
} }
@@ -1,6 +1,6 @@
--- ---
name: dingtalk-aisearch name: dingtalk-aisearch
description: AI搜问:人员语义搜索与跨源定位。Use when 按姓名/工号/部门/职责/上下级或手机号线索找人,跨文档/消息/邮件/听记检索,或回溯“我发过/收到过”。完整手机号反查走 dingtalk-contact;找到 userId 后由 contact 补详情。命令前缀:dws aisearch。 description: AI搜问:人员语义搜索、跨源内容定位与行为回溯。Use when 按姓名/工号/部门/职责/上下级找人,或在目标对象未知时按主题、语义、来源或行为发现相关内容。搜索结果用于候选定位;命中后需要读取、修改或验证原对象时切换到对象所属产品。完整手机号精确反查走 dingtalk-contact。命令前缀:dws aisearch。
metadata: metadata:
cli_version: ">=0.2.14" cli_version: ">=0.2.14"
category: product category: product
@@ -11,73 +11,102 @@ metadata:
# 钉钉 AI 搜问 Skill # 钉钉 AI 搜问 Skill
## 前置条件 — 执行操作前必读 <!-- DWS_RUNTIME_CONTRACT_START -->
## 最小 DWS 执行契约
> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dingtalk-shared`](../dingtalk-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。 - 只通过 `dws` CLI 操作钉钉;每条命令带 `--format json`,只按真实结构化返回下结论。
- 常用 `person/enterprise/behavior` 按本页直接执行,不预读 shared、Reference、Schema、Help 或下游 Skill。
- 不猜命令、字段、ID、profile 或事实;可选时间缺失则省略并说明范围;多候选不取首项,ID 不混域。
- 空结果结束当前搜索,同条件核验见第 5 节;失败或不完整不能说“没有”,候选不等于全量。
<!-- DWS_RUNTIME_CONTRACT_END -->
> 命令参考:[aisearch.md](references/aisearch.md)。 ## Golden Route
## 意图表 | 意图 | 唯一首选入口 | 关键槽位 |
|---|---|---|
| 姓名/工号/部门/职位/职责/上下级/手机号线索找人 | `dws aisearch person` | `--query` + `--dimension` |
| 按主题找文档、消息、邮件、待办、听记等内容 | `dws aisearch enterprise` | `--queries` + `--types` + 可选 `--time-range` |
| 以我为关系端点的发送/接收,或我创建、编辑、分享过什么 | `dws aisearch behavior` | 上述内容槽位 + `--behavior-type` + 可选 `--direction/--chat-scope` |
| <!-- dws-intent: chat.search.filtered -->资源只限 IM,答案是逐条消息并带结构化消息谓词 | `dws chat +search-msg` | 发送者、会话、关键词、@、类型、reaction、时间和完整分页由 Chat 负责 |
| 枚举部门成员、完整人员名单 | `dingtalk-contact` | 部门定位 → 成员列表 → 按需详情,不把人员搜索候选当全量 |
| 完整手机号精确反查 | `dws contact user search-mobile --mobile "<完整手机号>" --format json` | `--mobile` |
| 已知稳定 ID 后读取/修改原对象 | 对应产品 Skill | 不再用 AISearch 重搜 |
| 用户说 | 命令 | 选路顺序:资源范围 → 答案形态 → 原生谓词。
|--------|------|
| "找张三 / 张三是谁" | `dws aisearch person --query "张三" --dimension name` |
| "谁负责 XX / XX 负责人是谁" | `dws aisearch person --query "<XX>" --dimension duty` |
| "张三的上级 / 下级" | `dws aisearch person --query "张三" --dimension supervisor`(或 `subordinate`) |
| "X 部门有哪些人" | `dws aisearch person --query "<部门>" --dimension department` |
| "工号 12345 是谁" | `dws aisearch person --query "<工号>" --dimension jobNumber` |
| "按手机号线索语义搜人" | `dws aisearch person --query "<手机号线索>" --dimension phone` |
| "完整手机号精确反查" | `dws contact user search-mobile --mobile "<完整手机号>"` |
| "最近 OKR 相关邮件 / 项目相关文档" | `dws aisearch enterprise --queries "<主题>" --types mail/document --time-range "<时间>"` |
| "我发过/创建过/分享过/收到过什么" | `dws aisearch behavior --queries "<主题>" --behavior-type <动作> --direction <方向>` |
## 标准 SOP(必遵流程) ## 1. 人员搜索
> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 flag/ID。每条命令必须带 `--format json`,执行后必须按"解析"步取真实字段,不得凭返回结构猜测。 维度映射:姓名→`name`,部门→`department`,职位/岗位→`position`,职责/技能/负责人→`duty`,上级→`supervisor`,下属→`subordinate`,工号→`jobNumber`,手机号线索→`phone`;确实无法判断维度时才用 `all`。
### SOP-1 搜人 → 拿 userId(search-person) ```bash
dws aisearch person --query "<用户原始目标>" --dimension <维度> --format json
```
**触发**:姓名模糊找人/谁负责/查上下级/部门成员/工号反查/手机号线索语义搜人。 - 独立条件分别查询、汇报;保留完整目标,不截名、改昵称或扩同音词。
- 正确维度返回空结果就结束该组,不换 `all` 或缩词扩搜;同条件核验见第 5 节。仅维度选错时改正一次,空结果不等于路由错误。
- 保留全部候选的姓名、真实 ID 和人员链接。用户要详情才切 Contact: `dws contact user get --ids <userId> --format json`。
- 同一人的多条件须核对交集;姓名、部门、职位不互相替代。
- 无分页完成证据时,只称“本次返回 N 个候选”。
1. **定维度(必须)**:姓名→`name`、"谁负责 XX"→`duty`、部门成员→`department`、上级/下级→`supervisor`/`subordinate`、工号→`jobNumber`、手机号语义线索→`phone`;完整手机号精确反查切到 `dingtalk-contact` 的 `user search-mobile`;不确定→`all`。`--query` 必须按用户原文**完整保真**,切勿截断、改昵称、扩同音字。 ## 2. 跨源内容搜索
2. **执行(必须)**:`dws aisearch person --query "<完整值>" --dimension <维度> --format json`。
3. **解析(必须)**:从 JSON 取 `userId` / `openDingTalkId`;**多候选必须输出让用户选,禁止默认取第一个、禁止编造**未返回的人员字段。
4. **衔接(必须)**:要邮箱/部门/职位/主管等详情 → 切 `dingtalk-contact` 执行 `dws contact user get --ids <userId> --format json`;发消息 → `dingtalk-chat`;发 DING → `dingtalk-misc`(`references/ding.md`)。
5. **失败(必须)**:未命中最多换 1 个维度重试一次(如 `name`→`department`/`jobNumber`/`phone`),仍保留完整目标值;仍无果**必须如实告知**。
**禁止**:用半截姓名扩大搜索、跳过 `--format json`、取首个候选、凭空补全人员信息。 先从原句拆槽:时间词只进 `--time-range`,类型词只进 `--types`,剩余主题只进 `--queries`。类型枚举:`document,im,mail,calendar,todo,minute,report,image,link,notable,baike`。
### SOP-2 跨源搜内容(search-content) ```bash
dws aisearch enterprise --queries "<主题>" --types <类型CSV> [--time-range "<用户原始时间词>"] --format json
```
**触发**:跨文档/邮件/消息按主题找内容。 - 按用户要求分组调用,组内类型合并为 CSV;不按底层产品细拆或重复搜索。
- 精确标题原样传给 `--queries`,只接受精确匹配;未命中就停止,不拿近似标题、最近项或首项替代。同条件原生核验见第 5 节。
- “唯一才读取”须先证实指定来源覆盖、分页结束且仅一个精确匹配;否则报告唯一性未核实,不读正文。正文 `complete=true` 不代表搜索完整。
- 只要候选摘要或链接就不读原文。需要正文或缺必需证据时,满足读取前置条件后才按真实 ID 切对应产品;核验不能绕过“唯一才读取”。
- 空结果或无精确目标均报告本次未命中,不自行缩词或扩时间。
1. **执行(必须)**:`dws aisearch enterprise --queries "<主题>" --types <document,mail,...> --time-range "<时间>" --format json`;多主题逗号分隔。 ## 3. 行为回溯
2. **衔接(必须)**:按命中来源切到对应产品 skill 读写。**aisearch 只负责"找到",不做读写。**
**禁止**:把 aisearch 当作读写入口、跳过下游 skill 直接改数据。 ```bash
dws aisearch behavior --queries "<主题>" --types <类型CSV> --behavior-type <all|send|receive|create|edit|share> [--time-range "<时间>"] [--direction "我->某人|某人->我|我<->某人"] [--chat-scope "<完整群名>"] --format json
```
### SOP-3 行为回溯(search-behavior) - 每组“动作+方向+时间”调用一次,类型用 CSV 合并。`chat-scope` 仅用于 `im`;方向用原姓名,不先查邮箱或 userId。行为方向不代表发送者全部消息。
- 动作按当前用户视角选择,适用于所有内容类型:“我发给某人”=`behavior-type=send, direction=我->某人`;“某人发给我”=`behavior-type=receive, direction=某人->我`。不能因原句有“发”就选 `send`。“我在某群发过”另加 `types=im, chat-scope=<完整群名>`。
- 仅 IM 逐条过滤走 Chat,复用已解析的稳定身份。
- 空结果只表示本次未命中;不删除主题、不追加同义词、不缩短群名重搜,不用 recent 列表替代行为证据。需补充查询时按第 5 节保留原条件执行。
- 返回已足够回答就停止;缺少必需信息时才查询原对象。
**触发**:"我发过/收到过/创建过/分享过什么"。 ## 4. 结果核验与交付
1. **执行(必须)**:`dws aisearch behavior --queries "<主题>" --behavior-type <动作> --direction <方向> --format json`。 - **时间**:原时间词传给 `--time-range`,按当前日期、时区确定核验区间;“本周”不等于近七天。数值时间先确认秒/毫秒单位,再用程序换算。已知越界记录排除;只有聚合日期或缺少逐条时间时标为时间未核实。创建行为须有创建时间,不能用修改时间或会议开始时间代替。
2. **衔接(必须)**:按记录类型切对应 skill 操作;aisearch 不做读写。 - **身份**:核对实际发送者、收件关系和创建者;群内可见不等于发给我。Contact `userId` 与 Ding uid 等不同域 ID 不能直接比较;没有明确映射时,既不能认定同一人,也不能因值不同就排除本人。
- **主题与类型**:候选须有内容证据支持主题关联;搜索命中、群名相关或流程上“可能有关”都不足以确认。无关联证据时标为相关性未核实,不自行选成“最相关”。文本、标题或普通链接不能充当文件证据。
- **唯一与全量**:检查指定来源和分页终态;缺完整性字段就不声称唯一或全量。按稳定 ID 去重,交付清单与已核实的 ID、数量核对,避免读到却漏列。只问“有没有”时,有效命中即可回答。
- **忠实总结**:不补写未返回事实,不把“建议、待确认”改成已发生;分清搜索片段与正文、接口总数与已读取条数。部分结果伴随错误或未完分页时保留说明,不能据此说“没有其他结果”。
**禁止**:编造行为结果、跳过 `--format json`。 ## 5. 按原条件转用对应产品查询
## 高频硬约束 - 需要详情、成员清单或核验时,用返回的真实 ID,或原生产品支持的精确标题、部门等条件查询;只加载对应产品 Skill,按其参数调用。
- 保持主题、身份、方向、时间和权限,不猜 ID、不跨组织、不扩大扫库。无精确条件、失败或无权限即说明限制并停止,不自动申请权限。
- 搜索目标必须完整保真:姓名、工号、手机号、部门名按用户原文完整传入 `--query`,严禁自行截断、拆字、改昵称或扩展同音字。 ## 6. 多跳证据链
- 首次未命中时最多换维度重试一次(如 name → department/jobNumber/phone),仍必须保留完整目标值;不要用半截姓名扩大搜索。
- 找到候选后,如用户要邮箱、部门、职位、主管等详情,必须切到 `dingtalk-contact` 执行 `contact user get --ids <userId> --format json` 补全。
- 多候选且无法唯一判断时输出候选并询问;不要默认取第一个,也不要编造未返回的人员信息。
- 所有 `dws aisearch` 命令加 `--format json`。
## 跨产品协作 **定位候选 → 确认目标 → 提取真实 ID → 调用对应产品。**目标不明、身份不符或读取失败时停止依赖该对象的后续操作。
- 拿到 userId 后查详情 / 部门 → 切到 `dingtalk-contact` - 下游复用本跳真实 ID;人员用 `userId`,文档用 `nodeId`,听记用 `taskUuid`,待办用实际 `taskId`。Ding uid 不能当 Contact userId,snippet 不能当正文。
- 拿到 userId 发消息 → 切到 `dingtalk-chat` - 用户的读取前置条件必须满足;只加载下一跳所需 Skill,读取完整性以该次返回为准。ID 提取细节见 [多跳短流程](references/lite-recipes.md)。
- 拿到 userId 发 DING → 切到 `dingtalk-misc`(`references/ding.md`)
## 局部意图与短流程
- [局部意图消歧](references/intent-guide.md);[短流程](references/lite-recipes.md)。 ## 错误与成本最短路径
1. 失败且 `retryable=true`:原调用最多重试一次;否则停止,不换身份或绕过权限。
2. 成功但为空或无精确目标:不重复、改词或扩时间;必要的同条件核验见第 5 节。
3. `unknown flag`:查看一次该命令 Help 修正。API、权限和空结果不靠 Help 或猜参数解决。
4. 独立来源继续查询,依赖失败结果的步骤停止。保留标题、来源、链接、ID、数量、范围和错误;长 snippet 可外置,完整性字段不能删。
## 按需 Reference
常用路径不读 Reference。
| 仅当 | 读取 |
|---|---|
| 低频枚举、返回字段或兼容参数确实无法由本页判断 | [完整命令参考](references/aisearch.md) |
| 搜索与已知对象读取的产品边界仍不明确 | [局部意图消歧](references/intent-guide.md) |
| 多跳结果已有候选,但其稳定 ID 域或下一跳衔接不明确 | [多跳短流程](references/lite-recipes.md) |
@@ -1,249 +1,76 @@
# aisearch - AI 搜问 # AISearch 低频命令参考
> `aisearch` 模块当前有三个规范子命令:`person`(搜人)、`enterprise`(搜企业内部知识内容)和 `behavior`(搜企业内部行为记录)。 只在 `SKILL.md` 无法覆盖低频枚举、兼容参数或返回字段时读取。普通 `person/enterprise/behavior` 直接按入口 Golden Route 执行,不要为确认已知参数读取本文。
>
> **搜人容错说明**(无需主动使用):CLI 兼容下列 alias 兜底,模型偶尔写 `search` / `find` / `query` / `contact` / `people` 等也能跑通——但**搜人输出和文档以 `person` 为准**。
>
> 搜人的规范参数是 `--query`;旧 `--keyword`(含 `-w`)及历史隐藏参数 `--name` / `--q` / `--text` 继续兼容,新的示例统一使用 `--query`。
## 企业人员搜索 ## `person`
通过关键词搜索企业内人员信息,支持按维度筛选。 ```text
dws aisearch person --query <目标> [--dimension <维度>] --format json
```
Usage:
dws aisearch person [flags]
Example:
dws aisearch person --query "张三" --dimension name --format json
dws aisearch person --query "产品部" --dimension department --format json
dws aisearch person --query "五道" --dimension supervisor --format json
dws aisearch person --query "AI搜问" --dimension duty --format json
dws aisearch person --query "李四" --dimension name,department --format json
dws aisearch person --query "<手机号线索>" --dimension phone --format json
dws aisearch person --query "W12345" --dimension jobNumber --format json
Flags:
--query string 搜索关键词 (必填,如人名、技能关键词等)
--dimension string 查询维度,多个用逗号分隔 (默认 "all")
``` ```
### dimension 可选值 - `--query` 必填;旧 `--keyword/-w`、`--name`、`--q`、`--text` 仅兼容,不应主动使用。
- `--dimension` 默认 `all`,支持逗号分隔:`all,name,department,position,duty,supervisor,subordinate,phone,jobNumber`。
- 人员结果以服务端返回的 `title/name`、`userId`、`openDingTalkId`、`url` 为证据。完整手机号精确反查不属于本命令。
| 值 | 含义 | 触发词 | | 用户线索 | query | dimension |
|----|------|--------| |---|---|---|
| `all` | 全部维度(默认) | — | | “五道的上级” | 五道 | `supervisor` |
| `name` | 姓名 | "叫什么"、"是谁" | | “研发工程师职位” | 研发工程师 | `position` |
| `department` | 部门 | "部门"、"团队"、"哪个部门" | | “AI 搜问负责人” | AI 搜问 | `duty` |
| `position` | 职位 | "职位"、"岗位"、"职级" | | “工号 W12345” | W12345 | `jobNumber` |
| `duty` | 职责/技能 | "负责什么"、"职责"、"技能"、"负责人" |
| `supervisor` | 上级 | "上级"、"领导"、"主管" |
| `subordinate` | 下级 | "下级"、"下属"、"团队成员" |
| `phone` | 手机号语义线索 | "电话线索"、"联系方式相关";完整手机号精确反查走 `contact user search-mobile` |
| `jobNumber` | 工号 | "工号"、"工号是多少"、"员工编号" |
### keyword 提取规则 正确维度的空结果不触发降级搜索;无法判断维度时首次就用 `all`。
仅填入实际的搜索目标(人名、技能关键词等),不包含查询维度词。维度词必须映射到 `--dimension`: ## `enterprise`
| 用户说 | keyword | dimension | ```text
|--------|---------|-----------| dws aisearch enterprise [--queries <主题CSV>] [--types <类型CSV>] [--time-range <时间原文>] --format json
| "五道的上级是谁" | 五道 | supervisor | ```
| "张三负责什么" | 张三 | duty |
| "AI搜问的负责人是谁" | AI搜问 | duty |
| "产品部有谁" | 产品部 | department |
| "李四是哪个部门的" | 李四 | department |
| "按手机号线索找人" | 用户提供的手机号线索 | phone |
| "工号W12345是谁" | W12345 | jobNumber |
--- - `--queries` 只含主题,不含时间、类型和语气词;汇总全部内容时可留空。
- `--types` 默认 `all`,可用 `document,im,calendar,todo,minute,report,image,link,notable,baike,mail`。
## 意图判断 - `--time-range` 仅传用户明确给出的“今天/本周/最近/过去一月”等文本,不推测日期。
- 用户说"搜人/找人/谁负责/上级是谁/哪个部门的人" → `aisearch person`
- 用户提供完整手机号精确反查 → `contact user search-mobile`
- 用户说"搜资料/找方案/查文档/搜企业知识/项目相关内容/工作总结/周报总结" → `aisearch enterprise`
- 用户说"最近/本周/今天 + XX相关消息/文档/邮件有哪些" → `aisearch enterprise`,时间词进 `--time-range`,类型词进 `--types`
- 用户说"我发过/谁发给我/创建过/分享过/收到过/今天我干了什么" → `aisearch behavior`
- 用户说"搜同事" → `aisearch person`;查部门详情/部门成员/通讯录精确信息 → `contact`
**关键区分**:`aisearch person`(姓名模糊、工号、职责、手机号线索、上下级等搜索)vs `aisearch enterprise`(按内容找企业内部知识)vs `aisearch behavior`(按动作找发送/创建/分享/编辑/接收记录)vs `contact`(完整手机号反查、已知 userId 详情、部门成员列表)
### 高优先级抽取规则
- `enterprise` 抽槽顺序固定为:先抽时间词到 `--time-range`,再抽类型词到 `--types`,最后把剩余主题词放进 `--queries`。
- 所有类型词都必须从 `queries` 中剥离,不能写进 `--query/--queries`。例如“最近 OKR 相关邮件”中,`queries=OKR`、`types=mail`、`time-range=最近`。
- 错误示例:不要生成 `--query "搜索问题"`、`--query "OKR 邮件"`、`--query "AI 搜问 日程"`、`--query "项目 待办"` 这类丢失或混入类型词的命令。
- 也不要把完整自然语言原句塞进 `--query`;`enterprise` 不做自然语言解析,必须显式拆出 `--queries`、`--types`、`--time-range`。
- “相关消息/相关文档/相关邮件有哪些”默认是按内容找企业知识,走 `aisearch enterprise`;只有出现“我发过/某人发给我/我收到/我创建/我分享/我编辑”等行为动作时,才走 `aisearch behavior`。
- `queries` 只放主题词,不放“最近/本周/消息/文档/邮件/日程/待办/纪要/图片/链接/有哪些/相关”等时间、类型、语气词。
- 只要用户显式说“最近/本周/今天/昨天/本月/过去一周/Q3”等时间词,就必须填写 `--time-range`。
- 只要用户显式说出任一类型词,就必须填写对应 `--types`;多个类型同时出现时用逗号分隔,如 `--types im,mail`。
### enterprise 类型词映射
| 用户类型词 | types | | 用户类型词 | types |
|------------|-------| |---|---|
| 全部、所有、工作总结、日报总结、周报总结、月报总结 | `all` |
| 文档、资料、方案、模板 | `document` | | 文档、资料、方案、模板 | `document` |
| 消息、聊天记录、群消息、群里说了什么 | `im` | | 消息、聊天记录、群消息 | `im` |
| 邮件、邮箱、mail、email | `mail` | | 邮件、邮箱 | `mail` |
| 日程、会议邀请、会议安排 | `calendar` | | 日程、会议邀请 | `calendar` |
| 待办、任务、TODO | `todo` | | 待办、任务 | `todo` |
| 会议纪要、纪要、听记、闪记、录音摘要 | `minute` | | 听记、会议纪要、闪记 | `minute` |
| 日志 | `report` | | 日志 | `report` |
| 图片、截图 | `image` | | 图片、截图 | `image` |
| 链接、URL、网址 | `link` | | 链接、URL | `link` |
| AI 表格、多维表、notable | `notable` | | AI 表格、多维表 | `notable` |
| 企业百科、百科 | `baike` | | 企业百科 | `baike` |
## 上下文传递表 搜索结果常见定位字段是 `title`、`sourceType/sourceName`、`url` 及 `meta` 中的稳定 ID。`snippet` 只是定位预览,不能证明已读取正文。
| 操作 | 从返回中提取 | 用于 | ## `behavior`
|------|-------------|------|
| `aisearch person` | `userId`(用户ID)、`title`(姓名) | 展示搜索结果、后续操作(发消息/建待办等) |
## 重名消歧 ```text
dws aisearch behavior [--queries <主题CSV>] [--types <类型CSV>] [--behavior-type <动作>] [--time-range <时间原文>] [--direction <方向>] [--chat-scope <群名>] --format json
> **CAUTION:** 多人同名时禁止默认选第一个 — 须追加 `contact user get --ids userId1,userId2,...` 获取部门/职位后请用户确认。详见 [08-directory.md](../../dingtalk-contact/references/08-directory.md)「多命中」。
---
## 企业内部知识搜索
用于检索企业内部知识内容,例如文档、消息、日程、待办、听记、日志、图片、链接、AI 表格、企业百科、邮件等。它关注内容本身,适合查找某个主题的资料、搜索包含特定关键词的内容、了解项目/产品相关信息、准备汇报材料等场景。
```
Usage:
dws aisearch enterprise [flags]
Example:
dws aisearch enterprise --queries "智能化方案" --types document --format json
dws aisearch enterprise --queries "搜索问题" --types im --time-range "最近" --format json
dws aisearch enterprise --queries "搜问" --types im --time-range "最近" --format json
dws aisearch enterprise --queries "OKR" --types mail --time-range "最近" --format json
dws aisearch enterprise --queries "AI搜问" --types calendar --time-range "本周" --format json
dws aisearch enterprise --queries "项目" --types todo,minute --time-range "最近" --format json
dws aisearch enterprise --queries "发版" --types im --time-range "本周" --format json
dws aisearch enterprise --types all --time-range "本周" --format json
dws aisearch enterprise --queries "OKR" --types document,im,mail --format json
Flags:
--queries string 内容关键词,多个用逗号分隔;汇总类场景可留空
--types string 搜索类型,多个用逗号分隔 (默认 "all")
--time-range string 时间范围,仅当用户显式给出时间词时填写
``` ```
### enterprise types 可选值 - `--types` 与 `enterprise` 相同。
- `--behavior-type`:`all,send,receive,create,edit,share`。
- `--direction`:`我->某人`、`某人->我` 或 `我<->某人`;不要使用 `incoming/outgoing/received` 等自造枚举。
- `--chat-scope` 只和 `types=im` 组合,并保留完整群名。
| 值 | 含义 | 触发词 | 多个类型共享同一动作、方向和时间时可合并为 CSV;动作或方向不同则分别调用。`enterprise` 同理按用户要求的输出组调用:组内类型合并,明确分组则组间分开。
|----|------|--------|
| `all` | 全部类型(默认) | 全部、所有、工作总结、日报总结、周报总结、月报总结 |
| `document` | 文档 | 文档、资料、方案、模板 |
| `im` | 消息 | 消息、聊天记录、群消息、群里发了什么 |
| `calendar` | 日程 | 日程、会议邀请、会议安排 |
| `todo` | 待办 | 待办、任务、TODO |
| `minute` | 会议纪要/闪记/听记 | 会议纪要、纪要、听记、闪记、录音摘要 |
| `report` | 日志 | 仅显式出现“日志”时使用 |
| `image` | 图片 | 图片、截图 |
| `link` | 链接 | 链接、URL、网址 |
| `notable` | 多维表 / AI 表格 | AI表格、多维表、notable |
| `baike` | 企业百科 | 企业百科、百科 |
| `mail` | 邮件 | 邮件、邮箱、mail、email |
### enterprise 参数提取规则 ## 结果语义与恢复
- `queries` 只放内容关键词,不放时间、类型词。比如“本周的 OKR 文档”中,`queries=OKR`;“最近 OKR 相关邮件”中,`queries=OKR`,不要写成 `queries=OKR 邮件`。 - 成功必须以 `success=true` 或等价明确成功字段为证据;`exit 0` 不能覆盖业务错误。
- 时间信息放到 `--time-range`,仅当用户显式给出“今天/本周/最近/9月/Q3/过去一周”等时间词时填写。 - `result=[]` 只证明当前查询条件下无结果。没有 `complete` 或分页完成字段时,不声称全局全量。
- 类型词放到 `--types`,所有类型词都不能留在 `--query/--queries`;多类型用逗号分隔。文档/资料/方案类型使用底层枚举 `document`(注意不是 `doc`)。`report` 仅在用户显式说“日志”时触发;“周报/日报/月报/工作汇报”不要自动映射为 `report`。 - 目标为精确标题时必须按完整标题消歧;只有近似结果等同未命中目标。
- `mail` 仅在用户显式说“邮件/邮箱/mail/email”时触发;一旦触发,必须进入 `--types mail`,不能留在 `--query/--queries`。 - `retryable=true` 可按完全相同参数重试一次;其他失败不通过换产品、扩大时间或缩词恢复。
- “工作总结/日报总结/周报总结/月报总结”这类汇总场景,用 `--types all`,`--queries` 可留空。 - `unknown flag` 才读取一次精确 leaf Help;认证、权限、API 错误和空结果不读 Help。
| 用户说 | queries | types | time-range | ## 产品边界
|--------|---------|-------|------------|
| “智能化方案相关文档” | 智能化方案 | document | 空 |
| “最近搜索问题相关的消息都有哪些” | 搜索问题 | im | 最近 |
| “最近搜问相关的消息都有哪些” | 搜问 | im | 最近 |
| “最近 OKR 相关邮件” | OKR | mail | 最近 |
| “本周 AI 搜问相关日程” | AI 搜问 | calendar | 本周 |
| “最近项目相关待办和纪要” | 项目 | todo,minute | 最近 |
| “本周的 OKR 文档” | OKR | document | 本周 |
| “最近发版相关消息” | 发版 | im | 最近 |
| “AI 搜问相关图片和链接” | AI 搜问 | image,link | 空 |
| “OKR 相关 AI 表格和百科” | OKR | notable,baike | 空 |
| “2025-12-06 到 2025-12-19 工作总结” | 空 | all | 2025-12-06 到 2025-12-19 |
| “本周的日志” | 空 | report | 本周 |
| “我收到的邮件” | 空 | mail | 空 |
--- - 跨来源主题发现和当前用户参与的行为轨迹由 AISearch 负责;资源只限 IM、答案必须是逐条消息并带结构化消息谓词时使用 `dws chat +search-msg`。行为方向不能替代消息集合过滤。
- 已知 `userId` 后查通讯录详情执行 `dws contact user get --ids <userId> --format json`。
## 企业内部行为记录搜索 - 已知文档、听记、待办等稳定 ID 后读取原对象,才切对应产品。
- 只需列出搜索命中时不加载下游 Skill。
用于检索“谁对什么做了什么”的企业内部行为记录,例如发过、创建过、分享过、编辑过、收到过的文档、消息、日程、待办、听记、日志、图片、链接、AI 表格、企业百科、邮件等。它关注行为流向和动作,不是按内容本身找知识。
```
Usage:
dws aisearch behavior [flags]
Example:
dws aisearch behavior --queries "智能化方案" --types document --format json
dws aisearch behavior --types mail --behavior-type send --direction "我->汐峰" --format json
dws aisearch behavior --types all --behavior-type create --time-range "本周" --format json
dws aisearch behavior --types im --chat-scope "scrum群" --behavior-type send --time-range "今天" --format json
Flags:
--queries string 内容关键词,多个用逗号分隔;汇总类场景可留空
--types string 搜索类型,多个用逗号分隔 (默认 "all")
--chat-scope string 消息所在会话/群范围,仅 IM 类型且用户明确指定群名时填写
--behavior-type string 行为类型 (默认 "all")
--time-range string 时间范围,仅当用户显式给出时间词时填写
--direction string 交互方向,如 "我->汐峰"、"汐峰->我"、"我<->汐峰"
```
### behavior types 可选值
| 值 | 含义 | 触发词 |
|----|------|--------|
| `all` | 全部类型(默认) | 今天我干了什么、我最近做过什么 |
| `document` | 文档 | 文档、资料、方案、模板 |
| `im` | 消息 | 消息、聊天记录、群消息、群里发了什么 |
| `calendar` | 日程 | 日程、会议邀请、会议安排 |
| `todo` | 待办 | 待办、任务、TODO |
| `minute` | 会议纪要/闪记/听记 | 会议纪要、纪要、听记、闪记、录音摘要 |
| `report` | 日志 | 仅显式出现“日志”时使用 |
| `image` | 图片 | 图片、截图 |
| `link` | 链接 | 链接、URL、网址 |
| `notable` | 多维表 / AI 表格 | AI表格、多维表、notable |
| `baike` | 企业百科 | 企业百科、百科 |
| `mail` | 邮件 | 邮件、邮箱、mail、email |
### behavior-type 可选值
| 值 | 含义 | 示例 |
|----|------|------|
| `all` | 全部行为(默认) | “智能化方案相关内容” |
| `send` | 发送 | “我发给汐峰的消息/邮件” |
| `create` | 创建 | “我创建过哪些文档” |
| `share` | 分享 | “我分享过的资料” |
| `edit` | 编辑 | “我编辑过的文档” |
| `receive` | 接收 | “汐峰发给我的文档”、“我收到的邮件” |
### 参数提取规则
- `queries` 只放内容关键词,不放时间、类型词、行为词。比如“本周我创建的智能化方案文档”中,`queries=智能化方案`。
- `types` 放内容类型,映射规则与 enterprise 相同;所有类型词都不能留在 `--query/--queries`,多类型用逗号分隔。
- 文档/资料/方案类型使用底层枚举 `document`(注意不是 `doc`)。`report` 仅在用户显式说“日志”时触发;“周报/日报/月报/工作汇报”不要自动映射为 `report`。
- `mail` 仅在用户显式说“邮件/邮箱/mail/email”时触发;一旦触发,必须进入 `--types mail`。
- `time-range` 仅当用户显式给出时间词时填写,不要根据语义猜时间。
- `direction` 仅当用户明确指定交互对象时填写,格式为 `发起者->接收者` 或 `我<->某人`;无具体对象时留空。
- “今天我干了什么/我最近做过什么”这类行为汇总场景,用 `--types all`,`--queries` 可留空。
| 用户说 | queries | types | behavior-type | time-range | direction | chat-scope |
|--------|---------|-------|---------------|------------|-----------|------------|
| “我发给汐峰的邮件” | 空 | mail | send | 空 | 我->汐峰 | 空 |
| “我发给汐峰的消息和邮件” | 空 | im,mail | send | 空 | 我->汐峰 | 空 |
| “汐峰发给我的文档” | 空 | document | receive | 空 | 汐峰->我 | 空 |
| “我创建过哪些文档” | 空 | document | create | 空 | 空 | 空 |
| “本周我创建的智能化方案文档” | 智能化方案 | document | create | 本周 | 空 | 空 |
| “我分享过的项目链接和图片” | 项目 | link,image | share | 空 | 空 | 空 |
| “我在 scrum 群里发了什么” | 空 | im | send | 空 | 空 | scrum群 |
| “帮我总结今天干了什么” | 空 | all | all | 今天 | 空 | 空 |
## 行为搜索 vs 知识搜索
- `aisearch behavior`:用户问“我/某人做过什么动作”,有发送、创建、分享、编辑、接收、收到、发给等行为词。
- `aisearch enterprise`:用户问“是什么/怎么做/在哪里/模板/方案/总结”,目标是内容本身或跨类型知识内容汇总。
@@ -1,15 +1,18 @@
# aisearch 局部意图消歧 # AISearch 局部意图消歧
本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。 仅当“定位记录”与“操作已知对象”的边界仍不明确时读取。
| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 | | 用户目标 | 应该用 | 不要用 |
|---|---|---|---|---| |---|---|---|
| "张三在哪个部门/张三的工号是多少" | 搜人后查通讯录详情 | `aisearch person` → `contact user get` | 直接 `contact user search` | 姓名或工号先由 aisearch 获取 userId,再由 contact 补部门、工号等详情 | | 按姓名、工号、部门、职位、职责或上下级找人 | `aisearch person` | 用 Contact 做姓名模糊搜索 |
| "找一下张三/搜同事/找人" | 人员语义搜索 | `aisearch person` | `contact user search` | 姓名模糊搜索、工号、部门、职责和上下级走 aisearch;contact 在拿到 userId 后补详情 | | 查看某部门人员信息、枚举成员或完整名单 | `contact` 部门定位及成员列表 | 把 `aisearch person` 候选当部门全量 |
| "五道的上级是谁/谁负责XX/XX的下属有谁" | AI语义搜人 | `aisearch person` | `contact` | 涉及上下级、职责、负责人等语义维度搜索,用 aisearch | | 完整手机号精确反查;已知 userId 查详情 | `contact` | `aisearch person` |
| "222020这个工号是谁/查工号" | 按工号搜人 | `aisearch person --dimension jobNumber` | `contact` | 工号查人走 aisearch,dimension=jobNumber | | 按主题跨文档、消息、邮件、待办、听记等来源找记录 | `aisearch enterprise` | 分别加载各产品做跨源搜索 |
| "13800138000是谁/完整手机号反查" | 精确手机号反查 | `contact user search-mobile` | `contact user search` | 完整手机号精确匹配使用 search-mobile | | 找以我为关系端点的发送/接收记录,或我创建、编辑、分享过的记录 | `aisearch behavior` | 用 Chat/Mail/Doc recent 拼接行为结论 |
| "按手机号线索找人" | 手机号语义搜人 | `aisearch person --dimension phone` | `contact user search` | 非精确手机号匹配走 aisearch 的 phone 维度 | | <!-- dws-intent: chat.search.filtered -->资源只限 IM,答案是逐条消息并带发送者、会话、时间、reaction 等消息谓词 | `dws chat +search-msg` | 用行为方向替代消息集合过滤 |
| "搜一下智能化方案/最近 OKR 相关邮件/最近发版相关消息" | 搜企业知识内容 | `aisearch enterprise` | `doc search` / `mail search` / `chat message search` | 跨文档、消息、日程、听记、邮件等企业内容语义检索走 enterprise;具体 `queries/types/time-range` 抽槽见 `aisearch.md` | | 已有唯一稳定 ID,要求读取正文、逐字稿或详情 | 对应产品 Skill | 用 AISearch snippet 冒充原文 |
| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注“谁对什么做过什么”,走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` | | 已有唯一稳定 ID,要求修改、发送或删除 | 对应产品 Skill | 继续用 AISearch |
| "把这段文字翻译成英文/translate this" | 通用文本翻译 | `chat text translate` | `doc` / `aisearch` | 纯文本翻译,不是文档编辑或语义搜索 |
判断顺序:**资源范围 → 答案形态 → 原生谓词。**跨来源主题走 enterprise,当前用户行为轨迹走 behavior,仅 IM 的逐条消息过滤走 Chat;已知对象再读取或操作。
搜索为空只说明未命中。需要精确对象且已有稳定 ID 或原生可执行的同条件标题/部门/范围时,按主 Skill 的“按原条件转用对应产品查询”规则核验;不扩大条件,不用 recent 拼接行为证据。
@@ -1,31 +1,33 @@
# aisearch Lite Recipe # AISearch 多跳短流程
本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。 仅在稳定 ID 域、唯一性核验或按原条件转用对应产品查询仍不明确时读取。每次只推进一跳;前置证据不成立就停止。
## #8 通讯录 ## 精确标题 → 原对象
### get-contact-self 1. `dws aisearch enterprise --queries "<完整标题>" --types <类型> --format json`。
2. 只接受精确标题;用户要求唯一时须有指定来源范围及分页完成证据。只返回一条不是唯一性证明;未核实则报告候选并停止正文读取,不用读取正文来证明唯一。空结果可按主 Skill 的“按原条件转用对应产品查询”规则做同条件原生核验,不用近似标题替代。
3. 按来源提取稳定 ID:文档 `nodeId`,听记 `taskUuid`;待办优先使用结构化 `taskId`,仅在返回确实只有深链时解析其中的 `taskId`。
4. 已满足用户的读取前置条件,且需要正文或必需核验字段时,加载一个下游 Skill,并把该 ID 原样传入。搜索 `snippet` 不能替代读取结果。
`contact user get-self` → 当前用户 userId、部门、主管等 ## 人员 → 组织详情 → 动态关系
### search-person 1. `aisearch person` 返回的所有候选必须保留。
2. 只按用户给定的姓名、昵称或身份关系筛选;条件不满足就停止,不能另搜一个同名或相似人补链。
3. 将候选的 `userId` 传给 `dws contact user get --ids <userId> --format json`。
4. 后续需要按主管姓名再搜人时,query 必须来自 Contact 详情的真实主管显示名;下一次 Contact 的 ids 必须来自新一次人员搜索。
**搜人首选入口**。凡是“找人/搜人/找同事/谁负责/上级/下级/负责人/团队成员”均优先用 `aisearch person`: 人员链接中的 Ding uid 只用于和同域作者标识做身份核对,不能作为 Contact `userId`;不同域 ID 值不等也不能证明不是同一人。
1. 从用户问题中提取 keyword(人名/业务关键词)和 dimension(维度),规则见 [aisearch.md](./aisearch.md)。 ## 创建记录 → 详情 → 二次搜索
2. `aisearch person --query "<关键词>" --dimension <维度>`
3. 结果中提取 `userId` 和 `title`(姓名)展示给用户。
4. 若需要 userId 做后续操作(发消息/建待办),可直接使用结果中的 `userId`。
5. **重名消歧**:多人同名时禁止默认选第一个,须追加 `contact user get --ids` 获取部门/职位后请用户确认,详见 [08-directory.md](../../dingtalk-contact/references/08-directory.md)「多命中」。
### search-user 1. 用 `dws aisearch behavior --queries "<完整标题>" --types todo --behavior-type create --format json` 定位目标。
2. 只从精确标题候选取得 `taskId`;不得使用其他创建记录、列表第一项或历史 ID。
3. 用 `dws todo +get --task-id <taskId> --format json` 回读标题和业务字段。
4. 二次 AISearch 必须使用详情确认过的完整标题;多来源可合并到 `--types document,im`。
仅在以下**精确查询**场景使用,搜人请优先用 `search-person`: ## 完整性与停止条件
- 需要获取 userId 给其他产品使用(发消息/建待办/约日程) - 标题/昵称/身份不匹配、稳定 ID 缺失、多候选未消歧:停止,不加载下游 Skill。
- 已有 userId 需查完整详情(`contact user get --ids`) - 详情或正文接口失败:只报告该跳失败,不从搜索摘要猜内容。
- 完整手机号精确反查(`contact user search-mobile --mobile`) - 逐字稿、分页列表或“全部”结果只有明确 `complete=true` 或等价分页完成证据时才能称完整。
- 用户指定“如果不一致/读取失败就不要继续”时,该条件优先于补交后续内容。
1. 完整手机号精确反查:`contact user search-mobile --mobile "<手机号>"`;其他搜人:`aisearch person --query "<关键词>" --dimension <维度>`。
2. **重名消歧**:多人同名时禁止默认选第一个,须追加 `contact user get --ids` 获取部门/职位后请用户确认,详见 [08-directory.md](../../dingtalk-contact/references/08-directory.md)「多命中」。
3. 需详情时:`contact user get --ids <userId>`(多人可 `--ids id1,id2,...`)。
@@ -1,6 +1,6 @@
--- ---
name: dingtalk-aitable name: dingtalk-aitable
description: 钉钉 AI 表格(多维表)。Use when 用户说 AI表格/多维表/数据表/base/table/建表/查记录/写数据/字段/记录增删改查/筛选/排序/公式/模板搜索/批量导入CSV或JSON/导出/仪表盘/图表/上传附件到表格/按字段类型建表/数据源/创建数据源/更新数据源配置/触发数据源同步/按任务 ID 查询同步状态/获取数据源配置/列出数据源可用来源/获取数据源可同步字段/审批数据同步。不做电子表格单元格读写(走 dingtalk-misc)、文档编辑(走 dingtalk-doc);听记待办入表先用 dingtalk-minutes 提取,再由本 skill 写入。命令前缀:dws aitable。 description: 钉钉 AI 表格(多维表)。Use when 用户说 AI表格/多维表/数据表/base/table/应用模式/App 页面/Widget/建表/查记录/写数据/字段/记录增删改查/记录评论/评论回复/SQL/PostgreSQL/SELECT/JOIN/跨表关联查询/筛选/排序/公式/模板搜索/批量导入CSV或JSON/导出/仪表盘/图表/上传附件到表格/按字段类型建表/数据源/创建数据源/更新数据源配置/触发数据源同步/按任务 ID 查询同步状态/获取数据源配置/列出数据源可用来源/获取数据源可同步字段/审批数据同步。不做电子表格单元格读写或单元格批注(走 dingtalk-misc)、文档编辑(走 dingtalk-doc);听记待办入表先用 dingtalk-minutes 提取,再由本 skill 写入。命令前缀:dws aitable。
metadata: metadata:
cli_version: ">=0.2.14" cli_version: ">=0.2.14"
category: product category: product
@@ -19,48 +19,82 @@ metadata:
- 不猜命令、flag、字段、ID、账号或时间。后续 ID 必须来自真实返回;零命中、多候选或类型不明时停止并消歧。 - 不猜命令、flag、字段、ID、账号或时间。后续 ID 必须来自真实返回;零命中、多候选或类型不明时停止并消歧。
- 解析目标、读取上下文和最终执行必须使用同一 profile;不得跨组织复用 userId、openDingTalkId 或 openConversationId。多账号组织只使用明确的 `isOrgCurrent=true` 默认账号;没有默认账号时要求用户指定,禁止选择第一项、最近登录或最近使用账号。 - 解析目标、读取上下文和最终执行必须使用同一 profile;不得跨组织复用 userId、openDingTalkId 或 openConversationId。多账号组织只使用明确的 `isOrgCurrent=true` 默认账号;没有默认账号时要求用户指定,禁止选择第一项、最近登录或最近使用账号。
- 不输出或记录 token、refresh token、appSecret、webhook token 等凭据;宿主已注入认证时不要索要凭据。 - 不输出或记录 token、refresh token、appSecret、webhook token 等凭据;宿主已注入认证时不要索要凭据。
- 写操作必须符合用户明确意图。是否需要确认以最终 Runtime gate 和 Schema 为准;本轮用户已明确要求执行、目标与影响无歧义的非破坏性写操作时,该明确指令就是本次确认,首次调用直接携带 Runtime 所需的 `--yes`,不先制造 `confirmation_required`。删除、停用自动化等破坏性或高风险动作仍须先说明对象、动作与影响并取得独立确认。 - 写操作必须符合用户明确意图。是否需要确认以最终 Runtime gate 和 Schema 为准;本轮用户已明确要求执行、目标与影响无歧义的非破坏性写操作时,该明确指令就是本次确认,首次调用直接携带 Runtime 所需的 `--yes`,不先制造 `confirmation_required`。文件导入是非幂等操作,仍按 Golden Route 先触发 Runtime 确认门禁,确认后再追加 `--yes`。删除、停用自动化等破坏性或高风险动作仍须先说明对象、动作与影响并取得独立确认。
- 写后按任务结果契约验证;不能仅凭退出码宣称成功。部分结果、未知投递状态和失败项必须如实保留。 - 写后按任务结果契约验证;不能仅凭退出码宣称成功。部分结果、未知投递状态和失败项必须如实保留。
- Runtime Schema 是当前能力与安全语义的权威来源;同一 leaf 任务内只读一次并复用。只有 Schema 缺字段、含义不清或与真实执行冲突时才读 leaf Help;版本、profile 变化或出现契约错误时立即作废缓存。
- mutation 返回、HTTP 200 和退出码 0 都只是调用回执,不是最终对象状态。写后必须清除受影响的读取缓存,用返回的真实 ID/唯一键发起独立读回,不复用写前结果。
- 同一 Base 的写入默认串行;只有不同 Base、无数据/ID/顺序依赖且失败可独立回读时,才允许受控并发写。无依赖只读调用默认最多 4 路并发。
- 写调用超时、连接中断或返回结果未知时,禁止原样自动重放;先查真实状态。只读调用可对瞬态错误做有上限重试。
- 结构化结果可能是统一 `ok/outcome`、旧 `status` 或 boolean `success` 信封;不固定假设 `data` 层级。`partial_failure` 可以在 stdout 中并返回退出码 7,必须先解析信封再判断执行结果。
- 文件上传返回的 `uploadUrl`、token、authorization、signature 等是凭据;不输出、不写日志,结构化错误也要递归脱敏。
- 时间戳面向用户展示时转换为带时区的可读时间;默认使用当前会话时区,必要时同时保留原值。 - 时间戳面向用户展示时转换为带时区的可读时间;默认使用当前会话时区,必要时同时保留原值。
- 遇到认证、权限、profile、confirmation 或未知错误时,只加载 `dingtalk-shared` 中对应 reference;不要连续猜测替代命令。 - 遇到认证、权限、profile、confirmation 或未知错误时,只加载 `dingtalk-shared` 中对应 reference;不要连续猜测替代命令。
<!-- DWS_RUNTIME_CONTRACT_END --> <!-- DWS_RUNTIME_CONTRACT_END -->
## 表单分享用法回答契约
收到仅询问用法的请求后,第一步必须立即实际执行且仅执行下列对应命令:
- `form share update`:`dws aitable form share update --help`
- `+form-share-update`:`dws schema --cli-path "aitable +form-share-update" --compact --format json`
Shortcut 名称开头的 `+` 是命令名不可省略的一部分;不得改写、试探其他拼法或改用 `--help`/`-h`。
发现门禁:即使 Skill 或参考文档已提供完整示例,回答前也必须实际执行一次且仅执行一次目标 leaf 的安全 help/schema 查询;不得仅依据 Skill 或参考文档直接作答。本规则优先于下方 reference 导航:命中时不读取任何 reference,不执行其他命令。
用户仅询问用法时,最终回答必须先给出完整命令;缺少必填 ID 时则给出带明确占位符的完整命令模板,禁止猜测。随后明确说明“未传入的分享配置保持原值”;不得执行目标写操作或声称已经执行。上述只读查询是唯一允许的命令。
查询成功后,最终回答只能包含两行纯文本:不要 Markdown 代码围栏、标题、表格、回读命令或其他内容。第一行放用户所问入口的完整命令;已有的必填值必须原样使用,缺少的值必须保留为 `<BASE_ID>`、`<TABLE_ID>`、`<VIEW_ID>` 等明确占位符。第二行先列出需要替换的占位符(没有则省略替换说明),再给出固定的未执行说明,然后立即结束:
```text
dws aitable form share update --base-id <BASE_ID> --table-id <TABLE_ID> --view-id <VIEW_ID> --enabled true
请将 <BASE_ID>、<TABLE_ID>、<VIEW_ID> 替换为真实值;未传入的分享配置保持原值。本次仅查询 help/schema,未执行写操作。
```
> 命令参考:[aitable.md](references/aitable.md);PostgreSQL 只读查询:[aitable-psql.md](references/aitable/aitable-psql.md);复杂命令按需加载 `references/aitable/*.md`;剧本:[06-data-analytics.md](references/06-data-analytics.md)。
<!-- VISIBLE_SHORTCUTS_START --> <!-- VISIBLE_SHORTCUTS_START -->
## Shortcut 发现(按需) ## Shortcut 发现(按需)
`aitable` 当前有 100 条公开 shortcut,完整清单保留在 Runtime Catalog 与 Schema,不在高频产品根 Skill 中重复展开。已知 leaf 直接执行。只有参数不确定时,最多读取一次 `dws schema --cli-path "aitable <leaf>" --compact --format json`;仅当该 compact leaf Schema 与 Cobra 实际不一致时,才读取同一 leaf 的 `dws aitable <leaf> --help`。禁止用父级 Help、产品 Help 或完整 Catalog 探索命令;一个 Case 一旦读取 Reference,就不再读取 Help 或第二个 Reference。 `aitable` 当前有 101 条公开 shortcut,完整清单保留在 Runtime Catalog 与 Schema,不在高频产品根 Skill 中重复展开。已知 leaf 直接执行。参数只查 `dws schema --cli-path "aitable <leaf>" --compact --jq '{cli_path,parameters,constraints,confirmation}' -f json`;仅需且已发布 `result` 时查 outcomes/pagination,字段级再查 `data_schema`;缺失不以 Help/样例推断。Schema 不可用才读一次已知 leaf Help;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 禁 Help:错误 suggestion → 已加载 Skill/reference 明确入口;均无则报漂移。禁全 Catalog/root/parent/product Help;低频 reference 不默认 Help;同一任务只读一个 Reference。
仅当根路由、精确 task reference 和 `references/aitable.md` 的低频原子索引都无法定位能力时,才执行 `dws shortcut list --service aitable --format json` 做最终回退;不要为已知意图加载完整 Shortcut Catalog 或产品级 Schema。 仅当根路由、精确 task reference 和 `references/aitable.md` 的低频原子索引都无法定位能力时,才执行 `dws shortcut list --service aitable --format json` 做最终回退;不要为已知意图加载完整 Shortcut Catalog 或产品级 Schema。
<!-- VISIBLE_SHORTCUTS_END --> <!-- VISIBLE_SHORTCUTS_END -->
## Golden Route(高频复合任务) ## Golden Route(高频复合任务)
已有 ID 直接使用;完整 URL 先解析;名称先唯一解析为稳定 ID。零命中或多候选时停止,不默认选第一项。 已由当前 AITable 调用返回且类型已确认的 ID 直接使用;名称先唯一解析为稳定 ID。用户直接提供的 `/i/nodes/` URL 或来源未验证的 nodeId 先执行 `dws drive info`;若为 `extension=dlink`,将返回的 `result.fileId` 保存为快捷方式入口 ID 并传给 `dws doc info`,再逐跳读取目标 `linkSourceInfo`,最终确认 `extension=able` 后将目标 `linkSourceInfo.nodeId` 作为 baseId。解析失败、字段缺失、ID 重复或最终类型不是 able 时停止;只有明确移动、改名或删除快捷方式入口本身时才保留最初的 `result.fileId` 并切到 Drive。零命中或多候选时也停止,不默认选第一项。
数据分析,以及 PostgreSQL/SQL/SELECT/JOIN 或跨表关联查询,先读 [aitable-psql.md](references/aitable/aitable-psql.md)。原始记录筛选、排序和 Top N 使用 `record query`;单表直接标量、分组或去重统计使用 `record stats` / `record group-stats`;仅同 Base JOIN、字段间算术、CASE、聚合后派生、汇总结果 Top N 或排名、窗口计算等复杂分析使用 `psql`。选用 `psql` 时固定同一 DWS 入口,先用 `dws aitable psql -d <baseId> -l` 发现逻辑表,再用 `-t <tableId>` 查看列类型,使用返回的 `Name` 执行 `LIMIT 3` 最小查询,成功后才以 `-c <SQL>` 执行正式只读查询;`psql` 输出 PostgreSQL 表格文本,不添加 `--format json`。psql 失败后只能从原始意图重新判定:完全属于单表原始记录时重发 `record query`,完全属于单表直接统计时重发 `record stats` / `record group-stats`;必须丢弃 psql 未完成结果并说明切换原因,复杂分析、结果合并和本地等价计算均禁止降级。
| 用户意图 | 唯一推荐入口 | 关键边界 | | 用户意图 | 唯一推荐入口 | 关键边界 |
|---|---|---| |---|---|---|
| 从 URL 解析稳定 ID | `dws aitable +url-resolve --url <URL>` | 只解析 URL 中已有的 baseId/tableId/viewId/recordId,不做远端名称搜索 | | 从已确认的 AITable URL 解析稳定 ID | `dws aitable +url-resolve --url <URL>` | 只解析 URL 中已有的 baseId/tableId/viewId/recordId,不远程解析 dlink;原始 `/i/nodes/` URL 必须先按上文规范化,dlink 目标 nodeId 直接作为 baseId |
| 按名称唯一定位并操作 Base/Table | `dws aitable +resolve-base --name <名称>` → `dws aitable +resolve-table --base <ID> --name <表名>` | 默认精确匹配;只有用户明确接受模糊匹配时才加 `--fuzzy` | | 按名称唯一定位并操作 Base/Table | `dws aitable +resolve-base --name <名称>` → `dws aitable +resolve-table --base <ID> --name <表名>` | 默认精确匹配;只有用户明确接受模糊匹配时才加 `--fuzzy` |
| 搜索 Base 候选或检查是否存在 | `dws aitable +base-search --query <关键词>` | 用户说“搜索/找一下/候选/如果没有就创建”时直接走本入口,不先调用 `+resolve-base`;返回 `hasMore/nextCursor`,仅 `hasMore=true` 时续页;AITable Base 名称不得路由到 `dws aisearch person` | | 搜索 Base 候选或检查是否存在 | `dws aitable +base-search --query <关键词>` | 用户说“搜索/找一下/候选/如果没有就创建”时直接走本入口,不先调用 `+resolve-base`;返回 `hasMore/nextCursor`,仅 `hasMore=true` 时续页;AITable Base 名称不得路由到 `dws aisearch person` |
| 浏览 Base 下的数据表 | `dws aitable +list-tables --base <ID>` | 只返回 tableId/tableName,不加载字段 | | 浏览 Base 下的数据表 | `dws aitable +list-tables --base <ID>` | 只返回 tableId/tableName,不加载字段 |
| 新建 Base 与整套表字段 | `dws aitable +base-bootstrap --name <名称> --tables '[{"name":"<表名>","fields":[{"fieldName":"<字段名>","type":"text"}]}]'` | 表对象键必须是 `name`,不是 `tableName`;字段使用 `fieldName/type/config`;参数已足够时直接执行 | | 新建 Base 与整套表字段 | `dws aitable +base-bootstrap --name <名称> --tables '[{"name":"<表名>","fields":[{"fieldName":"<字段名>","type":"text"}]}]'` | 表对象键必须是 `name`,不是 `tableName`;字段使用 `fieldName/type/config`,可选 `description`;参数已足够时直接执行 |
| 复制 Base 到文档目录 | `dws aitable +base-copy --base-id <B> --target-folder-id <FOLDER_NODE_ID> [--only-struct] [--new-name <名称>]`;只有 URL 时先用 `dws doc info --node <URL> --format json` 解析 `nodeId`,然后仍传 `--target-folder-id <NODE_ID>` | `target-folder-id` 必须是文件夹 `nodeId`,不接受 URL、路径、纯数字 dentryId 或 rootFolderId;若 Runtime 返回 `target_not_supported/retryable=false`,立即报告,不查 Help、不换 ID、不建测试文件夹,也不手工降级复制 | | 复制 Base | `dws aitable +base-copy --base-id <B_OR_URL> [--target-folder-id <FOLDER_ID_OR_URL>] [--only-struct] [--new-name <名称>]` | `base-id` 可传 Base ID 或标准节点 URL;`target-folder-id` 可传 dentryUuid、标准节点 URL 或 Drive 文件夹 URL,省略时复制到源 Base 所在工作区根目录;参数解析与目标验证由 AITable MCP 负责 |
| 已有 Base 新建一张表与字段 | `dws aitable +table-bootstrap --base-id <ID> --name <表名> --fields '<JSON数组>'` | 字段使用 `fieldName/type/config`;自动按 15 个字段分片并读回验证 | | 已有 Base 新建一张表与字段 | `dws aitable +table-bootstrap --base-id <ID> --name <表名> --fields '<JSON数组>'` | 字段使用 `fieldName/type/config`,可选 `description`;自动按 15 个字段分片并读回验证 |
| 读取字段目录或完整配置 | `dws aitable field list --base-id <B> --table-id <T>` / `dws aitable +field-get --base-id <B> --table-id <T>` | 只需 fieldId/name/type 用 `field list`;需要 config 用 `+field-get`;不存在 `+field-list` 或 `+list-fields` | | 读取字段目录或完整配置 | `dws aitable field list --base-id <B> --table-id <T>` / `dws aitable +field-get --base-id <B> --table-id <T>` | 只需 fieldId/name/type 用 `field list`;需要 config 用 `+field-get`;不存在 `+field-list` 或 `+list-fields` |
| 查询记录、记录筛选/排序或字段投影 | `dws aitable +record-query --base-id <ID> --table-id <ID> [--record-ids <IDs>] [--field-ids <IDs>] [--filters <JSON>] [--sort <JSON>] [--query <关键词>]` | 用户要求“只返回/仅查看”指定字段时必须传对应 `--field-ids`,不能只在最终文本删列;明确要求全量时改用原子 `record query --all --page-limit <N>` | | 按名称解析人员、部门或群组实体 | `dws aitable entity search --entity-type PERSON\|DEPARTMENT\|GROUP --keyword <名称>` | 返回候选和可用于筛选的稳定身份;零命中、重名、模糊命中或分页不完整时停止,不默认选择第一项 |
| 查询原始记录、记录筛选/排序、原始记录 Top N 或字段投影 | `dws aitable +record-query --base-id <ID> --table-id <ID> [--record-ids <IDs>] [--field-ids <IDs>] [--filters <JSON>] [--sort <JSON>] [--query <关键词>]` | 用户要求“只返回/仅查看”指定字段时必须传对应 `--field-ids`,不能只在最终文本删列;单表直接标量、分组或去重统计改走 `record stats` / `record group-stats`,复杂服务端分析改走 psql。已获用户明确许可的非分析完整逐行明细可直接使用原子 `record query --all --page-limit <N>`,不需要也不触发 psql 降级门禁。 |
| 新增单条或批量记录 | `dws aitable record create --base-id <ID> --table-id <ID> --records <JSON>` | 当前无 `+record-create`;写前取字段定义,写后按新 ID 回读 | | 新增单条或批量记录 | `dws aitable record create --base-id <ID> --table-id <ID> --records <JSON>` | 当前无 `+record-create`;写前取字段定义,写后按新 ID 回读 |
| 更新已知 recordId | `dws aitable +record-update --base-id <ID> --table-id <ID> --records <JSON>` | 自动分片并读回;只传需修改字段 | | 更新已知 recordId | `dws aitable +record-update --base-id <ID> --table-id <ID> --records <JSON>` | 自动分片并读回;只传需修改字段 |
| 查询一条记录的变更历史 | `dws aitable +record-history-list --base-id <ID> --table-id <ID> --record-id <ID>` | 已知 recordId 时直接执行,不探测 Help、Catalog 或全量 Schema | | 查询一条记录的变更历史 | `dws aitable +record-history-list --base-id <ID> --table-id <ID> --record-id <ID>` | 已知 recordId 时直接执行,不探测 Help、Catalog 或全量 Schema |
| 管理一条记录的评论 | 查询用 `dws aitable comment list --base-id <B> --table-id <T> --record-id <R>`;创建、回复、更新和删除按需使用同组 leaf | 先读 [comment](references/aitable/aitable-comment.md);topicId/commentKey 只复用同一记录真实返回;空评论页仍读取 `meta.pagination`,仅 `meta.pagination.endpoint_exhausted=true` 时停止,否则将 `meta.pagination.next_token` 原样传给下一次 `--cursor`;写入未知状态先 list 对账 |
| 按业务键同步或按条件批改 | 唯一键用 `dws aitable +record-upsert-by-key ...`;有界批改用 `dws aitable +record-bulk-patch ... --max-matches <N>` | upsert 仅允许 0 条创建、1 条更新;批改必须有 query/filters/record-ids 边界。普通 update/upsert 直接执行;只有历史、分享、删除恢复、空行或特殊字段值才读 [record-ops](references/aitable-record-ops.md);明确 AND/OR、日期或比较操作符只读 [filter-sort](references/aitable/aitable-filter-sort.md) | | 按业务键同步或按条件批改 | 唯一键用 `dws aitable +record-upsert-by-key ...`;有界批改用 `dws aitable +record-bulk-patch ... --max-matches <N>` | upsert 仅允许 0 条创建、1 条更新;批改必须有 query/filters/record-ids 边界。普通 update/upsert 直接执行;只有历史、分享、删除恢复、空行或特殊字段值才读 [record-ops](references/aitable-record-ops.md);明确 AND/OR、日期或比较操作符只读 [filter-sort](references/aitable/aitable-filter-sort.md) |
| 生成记录分享链接并发送给联系人 | `dws aitable +record-share-links --base <B> --table <T> --record-ids <IDs>` → `dws chat +dm --to <姓名> --text <完整链接文本>` | AITable 只生成链接;用户要求“发送”时还必须完成真实发送,不能停在联系人解析 | | 生成记录分享链接并发送给联系人 | `dws aitable +record-share-links --base <B> --table <T> --record-ids <IDs>` → `dws chat +dm --to <姓名> --text <完整链接文本>` | AITable 只生成链接;用户要求“发送”时还必须完成真实发送,不能停在联系人解析 |
| 创建或复制视图 | 创建用 `dws aitable view create --base-id <B> --table-id <T> --view-type <Grid|FormDesigner|Gantt|Calendar|Kanban|Gallery> [--name <名称>]`;复制用 `dws aitable +view-duplicate --base-id <B> --table-id <T> --view-id <V> [--new-name <名称>]` | 创建和复制直接执行;需要配置时按下方“按需加载”选择一个 View Reference | | 创建或复制视图 | 创建用 `dws aitable view create --base-id <B> --table-id <T> --view-type <Grid|FormDesigner|Gantt|Calendar|Kanban|Gallery> [--name <名称>]`;复制用 `dws aitable +view-duplicate --base-id <B> --table-id <T> --view-id <V> [--new-name <名称>]` | 创建和复制直接执行;需要配置时按下方“按需加载”选择一个 View Reference |
| 创建并验证 Dashboard,按需创建 Chart | `dws aitable dashboard create --base-id <B> --name <名称>` → `dws aitable +dashboard-get --base-id <B> --dashboard-id <D>`;需要 Chart 时按下方“按需加载”处理 | 只使用创建返回的真实 dashboardId;失败时不要猜同义命令或更换 dashboardId | | 创建并验证 Dashboard,按需创建 Chart | `dws aitable dashboard create --base-id <B> --name <名称>` → `dws aitable +dashboard-get --base-id <B> --dashboard-id <D>`;需要 Chart 时按下方“按需加载”处理 | 只使用创建返回的真实 dashboardId;失败时不要猜同义命令或更换 dashboardId |
| 管理 AI 表格应用模式 | `dws aitable app get --base-id <B>` → `dws aitable app page list --base-id <B>` → 按需 `app page create/update/move/delete` 或 `app widget create/get/list/update/delete` | 一个 Base 只有一个面向用户的 App;页面 `pageId` 同时是对应 Dashboard ID。Widget 的 `config`/`layout` 是完整对象,更新前先读回;创建操作未知状态时不得自动重放 |
| Base 内创建 Section 并移动节点 | `dws aitable +section-create --base-id <B> --name <名称>` → `dws aitable +section-move-node --base-id <B> --node-id <N> --new-parent-section-id <S>` → `dws aitable +section-list-nodes --base-id <B>` | Table、Dashboard、Section 都是 AITable 的 nsheet 节点;禁止改走 Wiki/Drive 文件夹或移动命令 | | Base 内创建 Section 并移动节点 | `dws aitable +section-create --base-id <B> --name <名称>` → `dws aitable +section-move-node --base-id <B> --node-id <N> --new-parent-section-id <S>` → `dws aitable +section-list-nodes --base-id <B>` | Table、Dashboard、Section 都是 AITable 的 nsheet 节点;禁止改走 Wiki/Drive 文件夹或移动命令 |
| 将本地 CSV/XLSX/XLS 导入新表 | `python scripts/aitable_import_via_task.py <BASE_ID> <FILE_PATH>` | 首选本 Skill 自带脚本,一次完成申请凭证、空 Content-Type PUT 和 `import data`;不要猜 `+import-csv` 或给 `import upload` 传 `--file` | | 将本地 CSV/XLSX/XLS 导入新表 | `dws aitable +import-file --base-id <BASE_ID> --file <FILE_PATH>` | 存储示例不预置 `--yes`;首次调用先触发 Runtime 确认门禁,取得明确确认后由执行方追加。DWS 在进程内完成申请凭证、空 Content-Type PUT 和 `import data`,且不暴露签名 URL;不要猜 `+import-csv` 或给 `import upload` 传 `--file` |
| 接入外部数据源(审批等) | `dws aitable +datasource-list-sources --base-id <ID> --datasource-type OA` → 解析 result 构造 sourceConfig → `dws aitable +datasource-create --base-id <ID> --datasource-type OA --source-config '<JSON>'` | 当前仅支持 OA 审批;processCode/name/iconUrl/url 从 list-sources 原样透传,创建后用 `+datasource-sync-status` 查同步结果 | | 接入外部数据源(审批等) | `dws aitable +datasource-list-sources --base-id <ID> --datasource-type OA` → 解析 result 构造 sourceConfig → `dws aitable +datasource-create --base-id <ID> --datasource-type OA --source-config '<JSON>'` | 当前仅支持 OA 审批;processCode/name/iconUrl/url 从 list-sources 原样透传,创建后用 `+datasource-sync-status` 查同步结果 |
### 简单 leaf ### 简单 leaf
除 `aitable psql` 外,结构化命令使用 `--format json` 并从真实返回中提取稳定 ID;`aitable psql` 输出 PostgreSQL 表格文本且不支持 `--format json`,按 `aitable-psql.md` 执行。
意图明确时直接使用;参数不确定才读 leaf Schema: 意图明确时直接使用;参数不确定才读 leaf Schema:
| 用户意图 | 入口 | | 用户意图 | 入口 |
@@ -71,13 +105,23 @@ metadata:
| 创建 / 更新 / 删除普通字段 | `field create` / `field update` / `field delete` | | 创建 / 更新 / 删除普通字段 | `field create` / `field update` / `field delete` |
| 查看 / 删除 View | `+view-get` / `+view-delete` | | 查看 / 删除 View | `+view-get` / `+view-delete` |
| 查看 / 改名 / 删除 Dashboard | `+dashboard-get` / `+dashboard-update` / `+dashboard-delete` | | 查看 / 改名 / 删除 Dashboard | `+dashboard-get` / `+dashboard-update` / `+dashboard-delete` |
| 查看 / 修改应用模式 App | `app get` / `app update` |
| 管理应用页面 | `app page create/get/list/update/move/delete` |
| 管理页面 Widget | `app widget create/get/list/update/delete` |
命令接在 `dws aitable` 后;资源 ID 使用 `--base-id/--table-id/--field-id/--view-id/--dashboard-id`,改名使用 `--name`。`+table-copy` 参数不规则,执行前只读其 leaf Schema。不读操作 Reference、Help 或产品 Catalog。 命令接在 `dws aitable` 后;资源 ID 使用 `--base-id/--table-id/--field-id/--view-id/--dashboard-id/--page-id/--widget-id`,改名使用 `--name`。应用模式所有命令都要求 `--base-id`;`app widget create` 另要求包含 `chartType` 的 `--config` 和包含 `x/y/w/h` 的 `--layout`。`+table-copy` 参数不规则,执行前只读其 leaf Schema。不读操作 Reference、Help 或产品 Catalog。
数据源查看来源用 `+datasource-list-sources`,获取字段用 `+datasource-get-fields`,创建、更新、同步、查状态和查配置用 `+datasource-create` / `+datasource-update` / `+datasource-sync` / `+datasource-sync-status` / `+datasource-get-config`。 数据源查看来源用 `+datasource-list-sources`,获取字段用 `+datasource-get-fields`,创建、更新、同步、查状态和查配置用 `+datasource-create` / `+datasource-update` / `+datasource-sync` / `+datasource-sync-status` / `+datasource-get-config`。
## 执行约束 ## 执行约束
- 已有 ID 直接使用;URL 只解析一次;“唯一定位并操作”用 `+resolve-base` / `+resolve-table`,“搜索候选/存在性检查”直接用 `+base-search`。人员、部门或群组只有显示名称时用 `entity search` 取得稳定身份;这些路径不要串行探测。filters/sort 缺 fieldId 时才读取字段目录。
- Golden Route 已给出准确命令和参数时直接执行;不预读或默认读取通用 `references/aitable.md`。只有操作参数、JSON 结构或恢复语义确实缺失时,才读取下方一个精确操作 Reference。
- Shortcut 已含分片或验证时不重复拆步;已有 Base 新建完整表结构直接用 `+table-bootstrap`。
- 单产品线性任务直接执行,不创建 TodoWrite;只有跨产品或多个独立分支的长任务才建计划,并且只在阶段切换时更新,不在每条 CLI 后刷新状态。
- 用户要求资源名带当前时间戳时只取一次并在 Base、Table、Dashboard 等名称中复用同一值;不要为每个资源分别取时间。
- JSON 已返回所需字段时立即复用;不得为寻找同一字段改用 `--verbose`、`raw`、`pretty` 重复请求。
- 记录 filter/sort 缺 fieldId 时才读取字段目录。 - 记录 filter/sort 缺 fieldId 时才读取字段目录。
- record filter/sort 与 view filter/sort/group 的协议和 Reference 互斥。普通 record query/create/update/upsert 直达;只有历史、分享、删除恢复、空行或特殊字段值读 `record-ops`。 - record filter/sort 与 view filter/sort/group 的协议和 Reference 互斥。普通 record query/create/update/upsert 直达;只有历史、分享、删除恢复、空行或特殊字段值读 `record-ops`。
- 普通字段 type 使用 `text/number/date/singleSelect/currency`;`singleSelect` 的 config 为 `{"options":[{"name":"<选项>"}]}`,人民币 `currency` 为 `{"currencyType":"CNY","formatter":"FLOAT_2"}`。 - 普通字段 type 使用 `text/number/date/singleSelect/currency`;`singleSelect` 的 config 为 `{"options":[{"name":"<选项>"}]}`,人民币 `currency` 为 `{"currencyType":"CNY","formatter":"FLOAT_2"}`。
@@ -88,6 +132,13 @@ metadata:
## 记录稳定约束 ## 记录稳定约束
- 查询、写入、筛选或排序前,先用 `field get` 获取目标字段的 `fieldId`、`type` 和 `config`;`cells` 的 key 必须使用 `fieldId`,不是字段中文名。
- select/multipleSelect 写入传选项名称;过滤时先唯一解析 option ID。对 multipleSelect 或其他数组型字段,第二个 operand 必须是 option ID/稳定 ID 数组,不能传裸字符串。
- 人员、部门、群组和关联记录等条件先解析为稳定的结构化 ID;零命中、多命中或类型不符时停止,不得把展示名称或原值直接透传。
- 已获用户明确许可的非分析完整逐行明细可直接使用 `record query --all --page-limit 0`,不需要也不触发 psql 降级门禁;不得用它替代 psql 复杂分析,或在 Agent context、Python、jq、JavaScript、电子表格等本地工具做计算。自动翻页禁止模型手写循环;手动分页必须透传真实 `data.nextCursor`,且查询条件不变。成功空续页 `records=[]` 且 `nextCursor` 为空是正常末页,不得报错、重试或判定漏查。
- 新增或更新只使用真实返回的 ID 回读;写入效果未知时回读,不重放成功批次。
- 全量查询检查 `hasMore`,批量写检查最终状态;分页未结束或 `partial_success` 都不得声称完整完成。
- 记录 `cells` 使用当前 fieldId,按真实字段类型写值,只读字段不得写入。 - 记录 `cells` 使用当前 fieldId,按真实字段类型写值,只读字段不得写入。
- 新增或更新只使用真实返回的 ID 回读;写入效果未知时回读,不重放成功批次。 - 新增或更新只使用真实返回的 ID 回读;写入效果未知时回读,不重放成功批次。
- 全量查询检查 `hasMore`,批量写检查最终状态;分页未结束或 `partial_success` 都不得声称完整完成。 - 全量查询检查 `hasMore`,批量写检查最终状态;分页未结束或 `partial_success` 都不得声称完整完成。
@@ -95,6 +146,8 @@ metadata:
## 安全边界 ## 安全边界
- 删除不可逆,按 Runtime confirmation 核对真实目标;`base list` 只是最近访问。字段零/多候选、类型不明时停止;多批写保留已完成批次和续跑位置。 - 删除不可逆,按 Runtime confirmation 核对真实目标;`base list` 只是最近访问。字段零/多候选、类型不明时停止;多批写保留已完成批次和续跑位置。
- 评论 create/reply 非幂等,评论写操作未知状态时先 `comment list` 对账,不自动重放;delete 只删除本人评论且不可恢复,关联回复处理不作保证。
- `app get` / `app page list` 在 App 不存在时会初始化默认 App,属于幂等条件写;`app page/widget create` 非幂等且不自动重试。删除 Page 会级联删除全部 Widget,删除 Widget 会同步清理布局,均需独立确认。
- 数据源 `+datasource-create` / `+datasource-update` 会触发真实数据同步;执行前确认目标 Base 和 sourceConfig。`+datasource-sync` 单次最多 5 张表。 - 数据源 `+datasource-create` / `+datasource-update` 会触发真实数据同步;执行前确认目标 Base 和 sourceConfig。`+datasource-sync` 单次最多 5 张表。
## 按需加载(复杂 JSON 与恢复语义) ## 按需加载(复杂 JSON 与恢复语义)
@@ -106,6 +159,7 @@ Golden/次级直达覆盖时不读 Reference;否则按最终专有能力读取
| `+record-query`、upsert、bulk patch 的记录 filters/sort/date/AND/OR/比较操作符 | [filter-sort](references/aitable/aitable-filter-sort.md) | | `+record-query`、upsert、bulk patch 的记录 filters/sort/date/AND/OR/比较操作符 | [filter-sort](references/aitable/aitable-filter-sort.md) |
| 记录历史、分享、删除恢复、空行或特殊字段值 | [record-ops](references/aitable-record-ops.md) | | 记录历史、分享、删除恢复、空行或特殊字段值 | [record-ops](references/aitable-record-ops.md) |
| 记录统计、分组聚合或去重率 | [record-stats](references/aitable/aitable-record-stats.md) | | 记录统计、分组聚合或去重率 | [record-stats](references/aitable/aitable-record-stats.md) |
| 记录评论查询、创建、回复、更新或删除 | [comment](references/aitable/aitable-comment.md) |
| 查询记录的主键文档,或为记录创建主键文档 | 首次建表前读取 [primary-doc](references/aitable/aitable-primary-doc.md);普通 Base/Table/字段/记录创建与导入不读取 | | 查询记录的主键文档,或为记录创建主键文档 | 首次建表前读取 [primary-doc](references/aitable/aitable-primary-doc.md);普通 Base/Table/字段/记录创建与导入不读取 |
| AI 字段、关联字段、lookup/filterUp 或其他复杂 config | [field](references/aitable/aitable-field.md) | | AI 字段、关联字段、lookup/filterUp 或其他复杂 config | [field](references/aitable/aitable-field.md) |
| formula 字段或公式语法 | [formula-guide](references/aitable/aitable-formula-guide.md) | | formula 字段或公式语法 | [formula-guide](references/aitable/aitable-formula-guide.md) |
@@ -120,6 +174,7 @@ Golden/次级直达覆盖时不读 Reference;否则按最终专有能力读取
| 自动化工作流 | [workflow](references/aitable/aitable-workflow.md) | | 自动化工作流 | [workflow](references/aitable/aitable-workflow.md) |
| 普通角色或高级权限 | [advperm](references/aitable/aitable-advperm.md) | | 普通角色或高级权限 | [advperm](references/aitable/aitable-advperm.md) |
| 数据源接入、同步管理、sourceConfig 构造或审批数据同步 | [datasource](references/aitable/aitable-datasource.md) | | 数据源接入、同步管理、sourceConfig 构造或审批数据同步 | [datasource](references/aitable/aitable-datasource.md) |
| SQL、PostgreSQL、SELECT 或同 Base 多表 JOIN | [psql](references/aitable/aitable-psql.md) |
| 产品边界不明确 | [intent-guide](references/intent-guide.md) | | 产品边界不明确 | [intent-guide](references/intent-guide.md) |
| 只有上述 Reference 仍无法定位的低频原子能力 | [aitable.md](references/aitable.md) 的对应章节 | | 只有上述 Reference 仍无法定位的低频原子能力 | [aitable.md](references/aitable.md) 的对应章节 |
@@ -4,8 +4,8 @@
| Recipe | 行动指南(固定路线) | | Recipe | 行动指南(固定路线) |
|--------|-------------------| |--------|-------------------|
| read-aitable | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`<br>2. `aitable field get --base-id <baseId> --table-id <tableId>` → 取 `fieldId`<br>3. `aitable record query --base-id <baseId> --table-id <tableId>` → 取记录(分页)<br>  需要筛选时 `--filters` 格式见 [aitable-filter-sort.md](./aitable/aitable-filter-sort.md),根节点必须是 `{"operator":"and\|or","operands":[...]}`<br>4. 总结数据 | | read-aitable | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`<br>2. **按结果类型分流**:<br>  • 原始记录筛选、排序、Top N、预览或字段投影 → `aitable record query --limit 30 [--filters ...] [--sort ...]`<br>  • 单表直接标量、分组或去重统计 → `aitable record stats` / `aitable record group-stats`<br>  • 同 Base JOIN、字段间算术、CASE、聚合后派生、汇总结果 Top N 或排名、窗口计算 → 读 `aitable-psql.md`,固定同一 DWS 入口,按 `psql -l` → `psql -t` → `LIMIT 3` 最小查询 → 正式 SQL 在服务端完成<br>  • 已获用户明确许可的完整逐行明细或逐条业务操作(不做分析)→ `aitable field get` 取必要 `fieldId`,再服务端过滤的 `aitable record query --all --page-limit 0 --field-ids ...`;不需要也不触发 psql 降级门禁<br>  • 完整导出 → `aitable export data`<br>3. 使用 record query 时检查 `hasMore/complete/partial`;使用 psql 时检查返回行数与截断提示;不得把任何原始记录用于本地等价分析<br>4. 总结数据 |
| generate-data-report | 1. 同 read-aitable 步骤 1-3<br>2. 按[「多源并行采集」](recipes/conventions.md#多源并行采集公共模式)执行 → 补充背景<br>3. `doc create --name "<报告名>" --content "<分析报告>"` | | generate-data-report | 1. 原始记录筛选、排序和 Top N 使用 `record query`;单表直接标量、分组或去重统计使用 `record stats` / `record group-stats`;JOIN、字段间算术、CASE、聚合后派生、汇总结果排名和窗口计算使用 `aitable psql` 在服务端完成;禁止 `record query --all` 后以 Python、jq、JavaScript、电子表格或 Agent context 计算<br>2. 按业务主题规划少量查询:同粒度、同关联链合并;不同事实粒度或不同业务主题拆分;关键复杂结论另用服务端 SQL 复核<br>3. 选用 psql 后失败,先按客户端/宿主、网络/认证/权限或 SQL/函数限制分类修复并重试;不得降级为全量记录读取<br>4. 本地工具仅可格式化、绘图或合并少量服务端汇总结果,不能代替服务端计算;随后 `doc create --name "<报告名>" --content "<分析报告>"` |
| create-aitable-record | **批量导入优先**:`python scripts/import_records.py <baseId> <tableId> data.csv\|data.json [batch_size]`(自动分批创建)<br>单条/少量:1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`<br>2. `aitable field get --base-id <baseId> --table-id <tableId>` → 取 `fieldId` 与类型<br>3. `aitable record create --base-id <baseId> --table-id <tableId> --records '[{"cells":{"<fieldId>":"值"}}]'` | | create-aitable-record | **批量导入优先**:`python scripts/import_records.py <baseId> <tableId> data.csv\|data.json [batch_size]`(自动分批创建)<br>单条/少量:1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`<br>2. `aitable field get --base-id <baseId> --table-id <tableId>` → 取 `fieldId` 与类型<br>3. `aitable record create --base-id <baseId> --table-id <tableId> --records '[{"cells":{"<fieldId>":"值"}}]'` |
| update-aitable-record | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`<br>2. `aitable record query --base-id <baseId> --table-id <tableId>` → 取 `recordId`,**先展示让用户确认**<br>3. `aitable record update --base-id <baseId> --table-id <tableId> --records '[{"recordId":"<recordId>","cells":{...}}]'` | | update-aitable-record | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`<br>2. `aitable record query --base-id <baseId> --table-id <tableId>` → 取 `recordId`,**先展示让用户确认**<br>3. `aitable record update --base-id <baseId> --table-id <tableId> --records '[{"recordId":"<recordId>","cells":{...}}]'` |
| search-aitable-template | 1. `aitable template search --query "<关键词>"` → 取 `templateId`<br>2. 用户选定<br>3. `aitable base create --name "<表格名>" --template-id <templateId>` → 取 `baseId` | | search-aitable-template | 1. `aitable template search --query "<关键词>"` → 取 `templateId`<br>2. 用户选定<br>3. `aitable base create --name "<表格名>" --template-id <templateId>` → 取 `baseId` |
@@ -66,10 +66,12 @@ dws aitable +record-delete --base-id <B> --table-id <T> --record-ids <R1,R2>
| 多选 | 选项名称数组 | | 多选 | 选项名称数组 |
| 数字、评分 | JSON number | | 数字、评分 | JSON number |
| 复选框 | boolean | | 复选框 | boolean |
| 日期 | 按字段配置要求的时间值;不凭展示文本猜格式 | | 日期 | 日期字符串(如 `"2026-03-15"`、`"2026-03-15 09:00"`)、含时区的 RFC3339 字符串或整数毫秒时间戳(允许 `0` 和负值) |
| URL | 按当前字段 Schema 要求的对象或字符串 | | URL | 按当前字段 Schema 要求的对象或字符串 |
| 人员、关联记录 | 使用真实 userId/recordId,不用姓名代替 | | 人员、关联记录 | 使用真实 userId/recordId,不用姓名代替 |
| 附件 | 先用 `+attachment-put` 获得 AITable 附件 token,再写字段 | | 附件 | 本地文件用 `+attachment-put` 完成上传和记录写入;在线 URL 通过原子 `record create/update` 传 `[{"url":"https://..."}]`,独立回读验证异步转存结果 |
日期的含时区字符串和毫秒时间戳按固定 UTC+8 转为分钟精度写入,秒和毫秒不保留,转换后的年份须在 0001–9999。合法日期读出统一为东八区 RFC3339 字符串,不保留原始输入类型。写入后查询读回示例:`1788883200000` → `"2026-09-09T00:00:00+08:00"`;`0` → `"1970-01-01T08:00:00+08:00"`;`-60000` → `"1970-01-01T07:59:00+08:00"`。
公式、查找引用、创建人/时间、修改人/时间等只读字段不得写入。字段类型不明时只读取目标字段配置一次。 公式、查找引用、创建人/时间、修改人/时间等只读字段不得写入。字段类型不明时只读取目标字段配置一次。
@@ -2,7 +2,7 @@
> 返回入口:[DingTalk AITable Skill](../SKILL.md) > 返回入口:[DingTalk AITable Skill](../SKILL.md)
本文件只用于根 Skill 和精确操作 Reference 都未覆盖的低频底层能力。Base/Table 创建、记录 CRUD、筛选排序、视图、导入导出和 Dashboard 等已覆盖能力必须返回根 Skill;本文件不直接导航到其他 AITable Reference。 本文件只用于根 Skill 和精确操作 Reference 都未覆盖的低频底层能力。Base/Table 创建、应用模式、记录 CRUD、筛选排序、视图、导入导出和 Dashboard 等已覆盖能力必须返回根 Skill;本文件不直接导航到其他 AITable Reference。
## 使用边界 ## 使用边界
@@ -15,7 +15,7 @@
## 返回规则 ## 返回规则
Base、Table、普通 Field、普通 Record、View、Dashboard、筛选排序、导入导出等已覆盖能力全部返回根 Skill;本索引不重复维护高频路由,也不作为 Reference 之间的中转站。 Base、Table、应用模式 App/Page/Widget、普通 Field、普通 Record、View、Dashboard、筛选排序、导入导出等已覆盖能力全部返回根 Skill;本索引不重复维护高频路由,也不作为 Reference 之间的中转站。
## 低频底层命令族 ## 低频底层命令族
@@ -46,8 +46,19 @@ Base、Table、普通 Field、普通 Record、View、Dashboard、筛选排序、
| `record create` / `+record-query` | 当前 Table 下的 `recordId` | | `record create` / `+record-query` | 当前 Table 下的 `recordId` |
| `view create` / `+view-get` | 当前 Table 下的 `viewId` | | `view create` / `+view-get` | 当前 Table 下的 `viewId` |
| Dashboard 或 Chart 创建结果 | 当前 Base 下的 `dashboardId` / `chartId` | | Dashboard 或 Chart 创建结果 | 当前 Base 下的 `dashboardId` / `chartId` |
| `app get` / `app page list` / `app widget list` | 当前 Base 下的 `appId` / `pageId` / `widgetId`;应用页面 `pageId` 同时是对应 Dashboard ID |
`baseId` / `tableId` / `fieldId` / `recordId` / `viewId` 是不同类型,不得轮流代入试错。Base 复制目标按根 Skill 的 Golden Route 解析。 `baseId` / `tableId` / `fieldId` / `recordId` / `viewId` / `appId` / `pageId` / `widgetId` 是不同类型,不得轮流代入试错;唯一例外是应用页面 `pageId` 与其对应 Dashboard ID 同值。Base 复制目标按根 Skill 的 Golden Route 解析。
> **写 record 时**:`record create / update` 对 singleSelect/multipleSelect 传 option **name**。**做 filter 时**:先用本命令或 `field get` 将用户输入唯一解析到现有选项,优先传稳定 option **id**;不要直接透传模糊名称。
### psql (PostgreSQL 只读查询) → 详见 [aitable-psql.md](./aitable/aitable-psql.md)
| 命令模式 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `psql -l` | 列出可查询的 PostgreSQL 逻辑表 | `-d <baseId>` | 数据查询前的表发现;输出为 psql 文本,不加 `--format json` |
| `psql -t` | 查看逻辑列名和 PostgreSQL 类型 | `-d <baseId>` `-t <tableId>` | SQL 前核对列;全部属性列加 `--all-properties` |
| `psql -c` | 执行一条只读 PostgreSQL SELECT | `-d <baseId>` `-c <SQL>` | SQL 的 `FROM` / `JOIN` 自动确定主表;支持单表及同 Base 多表 JOIN;只读;输出为 psql 文本 |
## 故障处理 ## 故障处理
@@ -57,3 +68,428 @@ Base、Table、普通 Field、普通 Record、View、Dashboard、筛选排序、
- 写入结果为 `unknown`:先按稳定 ID 或业务唯一键回读,未确认前不重试非幂等写; - 写入结果为 `unknown`:先按稳定 ID 或业务唯一键回读,未确认前不重试非幂等写;
- `retryable=false` 或 ID 类型错误:停止,不换同义原子命令或其他 ID 类型试错; - `retryable=false` 或 ID 类型错误:停止,不换同义原子命令或其他 ID 类型试错;
- 部分成功:保留 completed/failed/unknown 明细,不表述为完整成功。 - 部分成功:保留 completed/failed/unknown 明细,不表述为完整成功。
| 命令 | 用途 | 必读 reference | 路由提醒 |
|------|------|----------------|----------|
| `record query` | 查询/搜索记录 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 先 `field get` 拿 fieldId 与类型;完整结果必须用 `--all --page-limit 0` 自动翻页;filters 中的实体展示名须先解析为稳定 ID/结构化值 |
| `record get` | 按 ID 取记录(`record query --record-ids` 的窄别名) | [aitable-record-query.md](./aitable/aitable-record-query.md) | 已知 recordId 时首选;必填 `--record-ids`(单次最多 100 条);未暴露 filters/sort/query/cursor/limit |
| `record create` | 新增记录 | [aitable-record-create.md](./aitable/aitable-record-create.md) | cells key 必须是 fieldId 不是字段名;单次最多 100 条 |
| `record update` | 更新记录(每条独立 cells) | [aitable-record-update.md](./aitable/aitable-record-update.md) | 需先 query 拿 recordId;`cells` key 支持 fieldId 或当前表内唯一字段名,推荐 fieldId;`--records` 是 `[{recordId,cells},...]` 数组 |
| `record batch-update` | 批量更新(同一 cells 应用到多条 recordId) | [aitable-record-update.md](./aitable/aitable-record-update.md)、[aitable-cell-value.md](./aitable/aitable-cell-value.md) | 适合"统一标记完成/统一改负责人"等共享 patch 场景;`--cells` 是 JSON object(key=fieldId,value 按字段类型见 cell-value.md),与 record update 的单条 cells 结构完全一致;必填 `--record-ids` `--cells`;单次最多 100 条 |
| `record delete` | 删除记录 | [aitable-record-delete.md](./aitable/aitable-record-delete.md) | 不可逆,需先 query 确认 |
| `record history-list` | 查询单条记录的变更历史 | [aitable-record-history.md](./aitable/aitable-record-history.md) | 必填 `--record-id`;分页 `--offset --limit`,limit 范围 [1,50] 默认 20 |
| `record query-empty` | 查询完全没填用户字段的空行 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 一页扫描 `--limit` [1,100] 默认 100;扫完前需用 `--cursor` 翻页(nextCursor 为空才表扫完) |
| `record share-url` | 批量获取记录分享链接 | [aitable-record-share.md](./aitable/aitable-record-share.md) | 必填 `--record-ids`(CSV,单次最多 20 条);可选 `--view-id` 带视图上下文 |
| `record upsert` | 批量创建或更新(按 recordId 是否存在自动拆分) | [aitable-record-upsert.md](./aitable/aitable-record-upsert.md) | --records 同 record update 格式;带 recordId 走 update,不带走 create;单次最多 100 |
| `record primary-doc-get` | 查询记录的主键文档 nodeId | [aitable-primary-doc.md](./aitable/aitable-primary-doc.md) | 返回的 nodeId 可直接用于 `dws doc read/update --node` |
| `record primary-doc-create` | 为记录创建主键文档(幂等) | [aitable-primary-doc.md](./aitable/aitable-primary-doc.md) | fieldId 必须是 primaryDoc 类型;已存在则返回已有 nodeId |
### comment (记录评论) → 详见 [aitable-comment.md](./aitable/aitable-comment.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `comment list` | 分页查询记录评论与回复 | `--base-id --table-id --record-id` | 空 `data.comments` 不代表结束;仅 `meta.pagination.endpoint_exhausted=true` 时停止,否则按 `meta.pagination.next_token` 续页 |
| `comment create` | 创建评论话题 | 定位参数 + `--content` 或 `--rich-content` | 非幂等;未知状态先 list 对账 |
| `comment reply` | 回复已有评论 | 定位参数 + `--topic-id --comment-key` + 正文 | 标识必须来自同一记录真实返回;非幂等 |
| `comment update` | 完整替换本人评论正文 | 定位参数 + `--topic-id --comment-key` + 正文 | 仅纯文本会移除旧 @和图片;无 CAS |
| `comment delete` | 删除本人评论 | 定位参数 + `--topic-id --comment-key` | 不可恢复;确认后再追加 `--yes`,关联回复处理由服务端决定 |
### view (视图管理)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `view get` | 获取视图配置(不传子命令) | `--base-id` `--table-id` | 不传 `--view-ids` 返回全部视图 |
| `view get <attr>` | 获取视图某个属性 | `--view-id` | 12 个:card/timebar/aggregate/filter/sort/group/visible-fields/field-widths(详见 [aitable-view-config.md](./aitable/aitable-view-config.md))+ lock/frozen-cols/row-height/fill-color-rule(详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md)) |
| `view list` | 列出全部视图(`view get` 的别名) | `--base-id` `--table-id` | 与 `view get` 完全等价 |
| `view create` | 创建视图 | `--base-id` `--table-id` `--view-type` | 类型: Grid/Kanban/Gantt/Calendar/Gallery/FormDesigner;用 `--config` 传 visibleFieldIds/filter/sort/group;**Gantt 创建后必须 `view update timebar` 绑定日期字段** |
| `view update` | 整体更新视图 / 多属性合并更新 | `--base-id` `--table-id` `--view-id` | 可传 `--name --desc --config '{...}'`,**`--config` 路径继续保留** |
| `view update <attr>` | 按属性局部更新(推荐)| `--view-id` + typed flag / `--json` | 12 个:card/timebar/aggregate/field-widths/visible-fields/filter/sort/group/name + frozen-cols/row-height/fill-color-rule |
| `view lock [--off]` | 锁定/解锁视图 | `--base-id` `--table-id` `--view-id` | 默认锁定;`--off` 解锁。详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) |
| `view duplicate` | 复制视图 | `--base-id` `--table-id` `--view-id` | 可选 `--new-name`;保留源视图全部配置。详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md) |
| `view delete` | 删除视图 | `--base-id` `--table-id` `--view-id` | 不可删最后一个/锁定视图 |
> **优先用 `view get <attr>` / `view update <attr>` 子命令**:每个属性独立命令,typed flag 友好,agent 不必拼 JSON。**`view update --config '{...}'` 仍可用**,适合一次性多属性更新或脚本场景。
> **属性按 attr 分类,决定该读哪份子文档**:
> - card / timebar / aggregate / filter / sort / group / visible-fields / field-widths → [aitable-view-config.md](./aitable/aitable-view-config.md)
> - lock / frozen-cols / row-height / fill-color-rule / duplicate → [aitable-view-extras.md](./aitable/aitable-view-extras.md)
> 后一类**不能**塞进 `view update --config '{...}'`,必须用各自专属子命令;如果错传 `flags` / `frozenColCount` / `cellHeight` / `conditionalFormats` 等 key 进 `--config`,CLI 会在 stderr 提示应改用的命令。
> **`view update --config` 支持的 9 个 key**:
> `visibleFieldIds` / `filter` / `sort` / `group` / `fieldWidths`(Grid) / `aggregate`(Grid) / `kanbanCard`(Kanban) / `ganttTimebar`(Gantt) / `galleryCard`(Gallery)。
> filter/sort/group 必须传**数组**格式(与 `record query --filters` 的对象格式不同;CLI 会自动容错)。其他 key 会被服务端忽略并打 warning。
### form (表单管理) → 详见 [aitable-form.md](./aitable/aitable-form.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `form list` | 列出表单视图 | `--base-id` `--table-id` | 详情见 [aitable-form.md](./aitable/aitable-form.md) |
| `form get` | 按 viewId 取单个表单详情 | `--base-id` `--table-id` `--view-id` | — |
| `form create` | 创建表单视图 | `--base-id` `--table-id` `--name` | — |
| `form update` | 更新表单配置 | `--base-id` `--table-id` `--view-id` | title/name/description 至少一项 |
| `form delete` | 删除表单 | `--base-id` `--table-id` `--view-id` | 不可逆 |
| `form field list/update/hide` | 表单字段管理 | — | 详情见子文档 |
| `form questions create/delete` | 题目管理(=field create/delete) | — | 详情见子文档 |
| `form share get/update` | 表单分享配置 | — | 详情见子文档 |
> **创建表单**有两种等价方式:`form create --name "..."`(推荐)或 `view create --view-type FormDesigner --name "..."`。
### workflow (自动化工作流) → 详见 [aitable-workflow.md](./aitable/aitable-workflow.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `workflow edit-example` | 获取编辑文档与 DSL 示例 | 无 | create/update 前优先调用,内容由服务端提供 |
| `workflow create` | 创建并发布自动化工作流 | `--base-id` `--dsl` | 按子文档 Demo 组装 DSL;必须检查返回的 `data.valid` / `issues`;create 不自动重试 |
| `workflow update` | 更新并发布已有自动化工作流 | `--base-id` `--workflow-id` `--dsl` | 先 get 留底;提交完整目标 DSL;必须检查 `data.valid` / `issues` |
| `workflow list` | 列出 Base 下所有工作流 | `--base-id` | 支持 `--limit [1,100]` / `--offset >=0`;list 出参字段叫 `flowId` |
| `workflow get` | 获取单个工作流详情(含 flowSchema) | `--base-id` `--workflow-id` | `--workflow-id` 接受 list 里的 `flowId`(同值) |
| `workflow enable` | 启用工作流 | `--base-id` `--workflow-id` | 返回 `{enabled: true}` 是动作确认;要确认真启用看 list 的 `status` |
| `workflow disable` | 禁用工作流(高危) | `--base-id` `--workflow-id` | 影响业务自动化,必须先取得用户明确确认;status 变 STOP |
> 创建/更新的 `--dsl` 使用钉钉 AI 表格 `workflow-dsl/v1`;完整格式和最小 Demo 见 [aitable-workflow.md](./aitable/aitable-workflow.md)。删除工作流暂未开放。
### dashboard & chart → 详见 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md)
| 命令 | 用途 |
|------|------|
| `dashboard get/create/update/delete` | 仪表盘管理 |
| `dashboard config-example` | 查看仪表盘配置模板 |
| `dashboard arrange` | 自动重排仪表盘图表布局(智能填满网格,避免空缺) |
| `chart get/create/update/delete` | 图表管理 |
| `chart widgets-example` | 查看图表 widgets 配置模板 |
### export & import → 详见 [aitable-export-import.md](./aitable/aitable-export-import.md)
| 命令 | 用途 |
|------|------|
| `export data` | 导出数据(异步两阶段轮询) |
| `import upload` | 申请文件导入上传凭证 |
| `import data` | 触发导入 |
### attachment → 详见 [aitable-attachment.md](./aitable/aitable-attachment.md)
| 命令 | 用途 | 路由提醒 |
|------|------|----------|
| `attachment upload` | 准备附件上传凭证 | 不要用钉盘 drive 上传! |
### template (模板搜索)
| 命令 | 用途 | 必填参数 |
|------|------|----------|
| `template search` | 搜索模板 | `--query` |
### advperm (高级权限/自定义角色) → 详见 [aitable-advperm.md](./aitable/aitable-advperm.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `advperm enable` | 开启 Base 高级权限总开关 | `--base-id` | 不开启时角色规则不生效 |
| `advperm disable` | 关闭 Base 高级权限总开关(高危) | `--base-id` `--yes` | 关闭后全员回退默认权限 |
| `advperm role-list` | 列出 Base 下所有角色 | `--base-id` | 同时返回自定义角色和系统角色;`roleType == "custom"` 是自定义,前缀 `system_` 是系统角色 |
| `advperm role-get` | 获取单角色完整配置 | `--base-id` `--role-id` | 含 subRoles 与字段/行级规则 |
| `advperm role-create` | 创建自定义角色 | `--base-id` `--name` | 可选 `--sub-roles` 同时指定子角色权限规则 |
| `advperm role-update` | 增量更新自定义角色(PATCH) | `--base-id` `--role-id` | 未传字段不变;`--sub-roles` 按 (targetId,targetType) 合并 |
| `advperm role-delete` | 删除自定义角色 | `--base-id` `--role-id` `--yes` | 不可逆;系统角色禁删;**调用者必须是该 AI 表格的管理员/Owner**,非管理员会得到 401 AUTH_ERROR |
> **角色 CRUD 已全支持**:create/get/list/update/delete 都可走 CLI。
> 所有写命令(enable/disable/role-create/role-update/role-delete)需要 Base 管理员权限;非管理员只能调 `role-list` / `role-get`(只读)。
> "角色 ↔ 成员"绑定当前 CLI 不支持,仍需在 AI 表格 Web 端 → Base 设置 → 高级权限面板手动完成。
### section (文件夹与节点管理)
> 用于在 Base 的导航树中组织 table / dashboard / 表单视图 / 文档等节点(类似文件夹)。
> 操作前建议先用 `section list-nodes` 拿到 nodeId / sectionId 与父级关系。
#### 创建文件夹
```
Usage:
dws aitable section create [flags]
Example:
dws aitable section create --base-id <BASE_ID> --name 我的文件夹
dws aitable section create --base-id <BASE_ID> --name 子文件夹 --parent-section-id <SECTION_ID> --index 0
Flags:
--base-id string Base ID (必填)
--name string 文件夹名称 (必填)
--parent-section-id string 父文件夹 ID;不传或空字符串表示创建在 Base 根目录下
--index int 在父文件夹下的目标位置(0-based);不传则追加到末尾
```
返回 `data.sectionId` 与 `data.name`。
#### 重命名文件夹
```
Usage:
dws aitable section rename [flags]
Example:
dws aitable section rename --base-id <BASE_ID> --section-id <SECTION_ID> --new-name 新名称
Flags:
--base-id string Base ID (必填)
--section-id string 目标文件夹 ID (必填)
--new-name string 新的文件夹名称 (必填)
```
#### 删除文件夹
```
Usage:
dws aitable section delete [flags]
Example:
dws aitable section delete --base-id <BASE_ID> --section-id <SECTION_ID>
Flags:
--base-id string Base ID (必填)
--section-id string 目标文件夹 ID (必填)
```
> **注意**:删除不可逆;删除前可先用 `section list-empty` 确认是否为空文件夹。
#### 调整文件夹顺序
```
Usage:
dws aitable section reorder [flags]
Example:
dws aitable section reorder --base-id <BASE_ID> --section-id <SECTION_ID> --target-index 0
Flags:
--base-id string Base ID (必填)
--section-id string 目标文件夹 ID (必填)
--target-index int 目标位置(0-based)(必填)
```
> 在**当前父文件夹下**调整展示顺序。跨父级移动请用 `section move-node`。
#### 列出空文件夹
```
Usage:
dws aitable section list-empty [flags]
Example:
dws aitable section list-empty --base-id <BASE_ID>
Flags:
--base-id string Base ID (必填)
```
返回 `data.items: [{sectionId, name, parentSectionId}]` 与 `data.total`,用于清理或诊断导航树(parentSectionId 为空串表示在根目录下)。
#### 列出全部节点
```
Usage:
dws aitable section list-nodes [flags]
Example:
dws aitable section list-nodes --base-id <BASE_ID>
Flags:
--base-id string Base ID (必填)
```
返回 `data.items: [{nodeId, nodeType, parentSectionId, name?}]` 与 `data.total`,涵盖文件夹 / AI 表格 / 表单视图 / 仪表盘 / 文档 / 查询视图。
> **与其他命令的关联**:是 `section move-node` / `section reorder` 的前置定位命令——先用它拿到 nodeId 与 parentSectionId。
#### 移动节点
```
Usage:
dws aitable section move-node [flags]
Example:
dws aitable section move-node --base-id <BASE_ID> --node-id <NODE_ID> --new-parent-section-id <SECTION_ID>
dws aitable section move-node --base-id <BASE_ID> --node-id <NODE_ID> --new-parent-section-id "" --target-index 0
Flags:
--base-id string Base ID (必填)
--node-id string 要移动的节点 ID(文件夹/AI表格/表单视图/仪表盘/文档/查询视图)(必填)
--new-parent-section-id string 目标父文件夹 ID;空字符串表示移到 Base 根目录 (必填)
--target-index int Base 内节点的全局位置(0-based);不传则不调整
```
> 服务端自动识别节点类型,无需区分文件夹与非文件夹。返回 `data.nodeId / newParentSectionId / nodeType`。
> 对文件夹节点带 `--target-index` 时会先 move 再 reorder,中间失败会返回 `MOVE_OK_REORDER_FAILED`,可用 `section reorder` 重试。
## 复杂操作
### 仪表盘 / 图表(建议顺序)
```bash
# 1) 先看配置模板(JSONC)
dws aitable dashboard config-example --format json
dws aitable chart widgets-example --format json
# 2) 先拿 dashboard,再拿 chart 详情
dws aitable dashboard get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --format json
dws aitable chart get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID> --format json
```
要点:
- `dashboard get` 返回的 `charts[].chartId` 可直接给 `chart get` 使用。
- `dashboard share get` 可能返回 `404`(资源不存在或未开通),需按可重试错误处理,不要误判为参数拼错。
- `chart share get` 可正常返回 `enabled/shareUrl`,用于分享状态判断。
### 导出数据(两阶段轮询)
`export data` 常见为异步任务:首次调用可能只返回 `taskId`,需要继续轮询。
```bash
# 第一步:创建任务(按 scope 传必要参数)
dws aitable export data --base-id <BASE_ID> --scope table --table-id <TABLE_ID> --format excel --timeout-ms 1000
# 第二步:拿 taskId 继续轮询,直到返回 downloadUrl
dws aitable export data --base-id <BASE_ID> --task-id <TASK_ID> --timeout-ms 3000
```
参数约束
- `scope=all`:只需 `base-id`
- `scope=table`:必须 `table-id`
- `scope=view`:必须同时 `table-id + view-id`
## 意图判断
用户说"表格/多维表/AI表格":
- 查看/查找/列表 → `base search`(优先)或 `base list`(仅浏览最近访问)
- 详情 → `base get`
- 创建 → `base create`
- 修改 → `base update`
- 删除 → `base delete`
用户说"数据表/子表/table":
- 查看 → `table get`
- 创建 → `table create`
- 重命名 / 改备注 / 改行命名规则 → `table update`(三选一:`--name` / `--description` / `--record-name-key`)
- 用户说"行命名规则/记录别名/卡片显示成 task/project/event 这种" → `table update --record-name-key <枚举键>`,**中文 → 枚举键**对照见 [aitable-record-name-key.md](./aitable/aitable-record-name-key.md)
- 删除 → `table delete`
用户说"字段/列/column":
- 查看 → `field get`
- 添加 → `field create`(读 [aitable-field.md](./aitable/aitable-field.md))
- 修改 → `field update`
- 删除 → `field delete`
用户说"记录/行/数据/row":
- 查看/搜索 → `record query`(读 [aitable-record-query.md](./aitable/aitable-record-query.md))
- 找空行 / 没填东西的行 → `record query-empty`(读 [aitable-record-query.md](./aitable/aitable-record-query.md))
- 已知 recordId 反查字段值 → `record get`(按 ID 取专用,等价 `record query --record-ids`)
- 添加/写入 → `record create`(读 [aitable-record-create.md](./aitable/aitable-record-create.md))
- 修改/更新(每条独立 cells) → `record update`(读 [aitable-record-update.md](./aitable/aitable-record-update.md))
- **批量更新同一字段值**(统一标记/统一改值) → `record batch-update --record-ids ... --cells '{...}'`
- 删除 → `record delete`
- **查记录的字段变更历史 / 操作审计** → `record history-list`(读 [aitable-record-history.md](./aitable/aitable-record-history.md))
- **取记录分享链接 / 把这行发给同事** → `record share-url`(读 [aitable-record-share.md](./aitable/aitable-record-share.md))
- **不知道有没有 → 有就改、没有就建** → `record upsert`(读 [aitable-record-upsert.md](./aitable/aitable-record-upsert.md))
用户说"视图/view":
- 列出/查看全部视图 → `view list`(或 `view get` 不传 --view-ids,二者等价)
- 看某个视图详情 → `view get --view-ids <ID>`
- 创建 → `view create`
- 修改(含"调整字段顺序/隐藏字段") → `view update --config '{"visibleFieldIds":[...]}'`
- 修改某一项配置(filter/sort/group/card/timebar/aggregate 等)→ `view update <attr>`(读 [aitable-view-config.md](./aitable/aitable-view-config.md))
- 锁定 / 冻结列 / 行高 / 数据高亮规则 / 复制视图 → 读 [aitable-view-extras.md](./aitable/aitable-view-extras.md)
- 删除 → `view delete`
用户说"锁定视图/解锁视图/lock view" → `view lock` / `view lock --off`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md)
用户说"冻结列/冻结首列/frozen columns" → `view update frozen-cols --count N`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md)
用户说"行高/单元格高度/紧凑模式/cell height" → `view update row-height --cell-height N`(合法档位 32/56/88/128),详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md)
用户说"数据高亮/条件格式/单元格上色/fill color rule" → `view update fill-color-rule --json '[...]'`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md)
用户说"复制视图/duplicate view" → `view duplicate --view-id ... [--new-name ...]`,详见 [aitable-view-extras.md](./aitable/aitable-view-extras.md)
用户说"查看可查询表/SQL 表结构/SQL 字段类型/查询前 N 条/按列查询/SQL/PostgreSQL/SELECT/JOIN/两表关联" → 读 [aitable-psql.md](./aitable/aitable-psql.md)。数据查询前的表清单和逻辑列发现走 `psql`;普通按记录 ID、关键词或 filters 查行仍走 `record query`;两表关联不得误路由为 LOOKUP/FILTER_UP 公式配置。
用户说"筛选/过滤/filter" → 读 [aitable-filter-sort.md](./aitable/aitable-filter-sort.md)
用户说"统计/分析/聚合/TOP N/全量" → 读 [aitable-data-analysis-sop.md](./aitable/aitable-data-analysis-sop.md)
用户说"公式/formula/计算字段/派生指标" → 读 [aitable-formula-guide.md](./aitable/aitable-formula-guide.md)
用户说"查找引用/lookup/filterUp/跨表" → 读 [aitable-formula-guide.md](./aitable/aitable-formula-guide.md)(§5.4 跨表引用)
用户说"表单/form/收集表/问卷/催办填写" → 读 [aitable-form.md](./aitable/aitable-form.md)
用户说"自动化/工作流/流程/触发/automation/workflow" → 读 [aitable-workflow.md](./aitable/aitable-workflow.md)
- 新建自动化 → 按子文档的最小 Demo 组装完整 DSL,再 `workflow create --dsl @file`
- 修改自动化 → `workflow get` 留底,按最新 DSL 文档生成完整目标 DSL,再 `workflow update --dsl @file`
- 看 Base 里有哪些流程 / 哪些在跑 → `workflow list`(看 `recordCount` / `runningCount`)
- 看某个流程具体配置(触发条件、动作步骤) → `workflow get`
- 启用流程 → `workflow enable`
- 临时停掉流程(调试 / 数据迁移)→ `workflow disable`(必须先取得用户明确确认)
- 删除流程:当前不支持,引导用户到 AI 表格 Web 端 → 数据表 → 自动化 面板手动完成
用户说"仪表盘/图表/chart" → 读 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md)
用户说"仪表盘排版乱了/图表对不齐/重新排布/自动布局/美化仪表盘" → `dashboard arrange`(读 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md))
用户说"附件/上传文件" → 读 [aitable-attachment.md](./aitable/aitable-attachment.md)
用户说"导入/导出/import/export" → 读 [aitable-export-import.md](./aitable/aitable-export-import.md)
用户说"模板" → `template search`
用户说"高级权限/角色/权限控制/谁能看/谁能改" → 读 [aitable-advperm.md](./aitable/aitable-advperm.md)
- 开/关高级权限 → `advperm enable` / `advperm disable --yes`
- 看角色配置 → `advperm role-list` 或 `advperm role-get`
- 建角色(可同时指定子角色权限) → `advperm role-create --name ... --sub-roles '[...]'`
- 改角色名 / 改子角色权限(PATCH 语义,未传字段不变) → `advperm role-update --role-id ... [--name ...] [--sub-roles '[...]']`
- 删角色 → `advperm role-delete --yes`
- **角色 ↔ 成员绑定**:当前 CLI 不支持,仍需在 AI 表格 Web 端面板手动完成
命令报错/操作失败 → 读 [aitable-error-recovery.md](./aitable/aitable-error-recovery.md)
**关键区分**: base=表格文件, table=数据表, field=列, record=行
## 核心工作流
```bash
# 1. 搜索/列出 Base — 提取 baseId
dws aitable base search --query "项目" --format json
# 2. 获取 Base 信息 — 提取 tableId
dws aitable base get --base-id <BASE_ID> --format json
# 3. 获取字段目录 — 提取 fieldId
dws aitable field get --base-id <BASE_ID> --table-id <TABLE_ID> --format json
# 4. 查询记录
dws aitable record query --base-id <BASE_ID> --table-id <TABLE_ID> --format json
# 5. 新增记录 (cells 用 fieldId 作 key)
dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
--records '[{"cells":{"fldXXX":"值"}}]' --format json
```
## 上下文传递表
| 操作 | 从返回中提取 | 用于 |
|------|-------------|------|
| `base list/search` | `baseId` | 所有后续命令的 --base-id,拼接文档 URI |
| `base create` | `baseId` | 后续命令 + 文档 URI |
| `base get` | `tables[].tableId` | --table-id,拼接指定数据表 URI |
| `table create` | `tableId` | 后续命令 + 拼接指定数据表 URI |
| `table get` | `tables[].tableId`、视图目录 | 定位数据表和视图;字段需继续调用 `field get` |
| `field get` | `fields[].fieldId` | record 操作的 cells key, field update/delete |
| `record query` | `recordId` | record update/delete;按 ID 反查字段值用 `record get` |
| `template search` | `templateId` | base create --template-id,拼接模板预览 URI |
## URL → baseId 提取
用户提供 `https://alidocs.dingtalk.com/i/nodes/{baseId}` 链接时:
1. 提取 `/nodes/` 后的路径段作为 `baseId`
2. 去掉尾部的查询参数(`?` 及其后内容)
3. 传入 `--base-id` 参数
> 如果该 URL 来自 `dws aitable` 返回或已在当前链路 probe 过,可直接复用;
> 如果是用户直接提供的原始 `alidocs` URL,则先按 [链接规范](url-patterns.md#alidocs-url-类型探测流程) probe,确认 `extension=able` 后再继续。
## 注意事项
- 所有管理和记录写入操作使用 ID(baseId/tableId/fieldId/recordId),不使用名称;`psql` 的用户 SQL 使用 `psql -l/-t` 返回的真实逻辑表名和列名
- `aitable psql` 输出 PostgreSQL 表格文本,不支持也不添加 `--format json`;其他结构化读取仍按全局规则使用 `--format json`
- records 的 cells key 是 fieldId,不是字段名称
- cells 写入/读取格式见 [aitable-cell-value.md](./aitable/aitable-cell-value.md)
- 最佳实践见 [aitable-best-practices.md](./aitable/aitable-best-practices.md)
## 自动化脚本
| 脚本 | 场景 |
|------|------|
| [bulk_add_fields.py](../scripts/bulk_add_fields.py) | 批量添加字段 |
| [import_records.py](../scripts/import_records.py) | 从 JSON/CSV 批量导入记录 |
| [aitable_export_via_task.py](../scripts/aitable_export_via_task.py) | 文件导出(export_data 轮询 + 下载) |
| [upload_attachment.py](../scripts/upload_attachment.py) | 上传附件到 AI 表格记录 |
## 相关产品
- [doc](../../dingtalk-doc/references/doc.md) — 富文本文档编辑,不是结构化数据表格
@@ -2,7 +2,7 @@
> **STOP — 不要使用钉盘 (drive) 上传!** 钉盘 fileId 无法写入 attachment 字段。必须使用以下流程。 > **STOP — 不要使用钉盘 (drive) 上传!** 钉盘 fileId 无法写入 attachment 字段。必须使用以下流程。
> >
> **STOP — 严禁在 record create/update 的 cells 里直接传图片 URL!** 直传 `{"url":"https://..."}` 会导致服务端同步下载图片,批量写入时触发 TIMEOUT_ERROR。正确做法:先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。 > 本地文件用 `+attachment-put`(上传并写入已有记录)或以下上传流程;已有可直接下载的在线 URL,可在原子 `record create/update` 的附件字段直接传 `[{"url":"https://..."}]`。URL 转存为 best-effort 异步操作,成功回执只表示已受理,需独立回读并确认附件可用。
## 准备附件上传 ## 准备附件上传
@@ -47,3 +47,20 @@ curl -X PUT "<uploadUrl>" -H "Content-Type: application/pdf" --data-binary @repo
dws aitable record update --base-id <BASE_ID> --table-id <TABLE_ID> \ dws aitable record update --base-id <BASE_ID> --table-id <TABLE_ID> \
--records '[{"recordId":"recXXX","cells":{"fldAttachId":[{"fileToken":"ft_xxx"}]}}]' --format json --records '[{"recordId":"recXXX","cells":{"fldAttachId":[{"fileToken":"ft_xxx"}]}}]' --format json
``` ```
## 在线 URL 写入
```bash
dws aitable record update --base-id <BASE_ID> --table-id <TABLE_ID> \
--records '[{"recordId":"<RECORD_ID>","cells":{"<FIELD_ID>":[{"url":"https://example.com/report.pdf"}]}}]' --format json
```
附件写入会整体覆盖原列表;传 `[]` 或 `null` 表示清空。同一批 `update_records` 不能混合附件清空与新增/替换,应分开请求。不要将临时下载链接当作持久资源身份,也不要仅凭转存受理回执宣称文件已可下载。
## 移除附件
- 清空字段:`dws aitable +attachment-remove --base-id <B> --table-id <T> --record-id <R> --field-id <F> --clear-all`。
- 按文件名移除:同一命令改用 `--remove-name <文件名>`,从回读结果解析真实 resourceId 后删除并验证;目标缺 resourceId 时仅在剩余项都有 fileToken 的情况下使用替换路径,否则停止。
- 已知 resourceId:`dws aitable attachment remove --base-id <B> --table-id <T> --record-id <R> --field-id <F> --resource-ids <RESOURCE_IDS>`,随后独立回读。
按 Runtime 要求确认删除范围后执行。服务端删除仍有并发覆盖窗口,避免同时修改同一附件字段。
@@ -1,13 +1,13 @@
# AI 表格最佳实践 # AI 表格最佳实践
## 1. 字段可写性分类 ## 1. 记录单元格可写性分类
| 字段类型 | 可写 | 正确方式 | | 字段类型 | 可写 | 正确方式 |
|----------|------|----------| |----------|------|----------|
| 文本/数字/日期/单选/多选/复选框/URL | ✅ | record create/update | | 文本/数字/日期/单选/多选/复选框/URL | ✅ | record create/update |
| 附件 | ⚠️ | 必须先走 [attachment upload 流程](./aitable-attachment.md) | | 附件 | ✅ | 本地文件上传获取 fileToken;在线 URL 可直接写入,见 [附件流程](./aitable-attachment.md) |
| 创建人/修改人/创建时间/修改时间 | ❌ | 系统字段,只读 | | 创建人/修改人/创建时间/修改时间 | ❌ | 系统字段,只读 |
| 公式/查找引用 | ❌ | 只读,由系统计算 | | 公式/查找引用 | ❌ | 单元格只读,由系统计算;字段定义能否创建/更新以当前 leaf Schema 为准 |
| AI 字段 | ❌ | 只读,由 AI 自动计算 | | AI 字段 | ❌ | 只读,由 AI 自动计算 |
## 2. 查询执行契约 ## 2. 查询执行契约
@@ -28,14 +28,14 @@
| 分组/去重统计 | `record group-stats` | 不要先拉全量再本地 groupby | | 分组/去重统计 | `record group-stats` | 不要先拉全量再本地 groupby |
| 全量导出为文件 | `export data` | 不要 `--all` 拉全量再写文件 | | 全量导出为文件 | `export data` | 不要 `--all` 拉全量再写文件 |
| 批量写入 | `record create`(分批 100 条) | 不要一次传超过 100 条 | | 批量写入 | `record create`(分批 100 条) | 不要一次传超过 100 条 |
| 附件/图片上传 | `attachment upload` 获取 fileToken → `record create/update` 用 fileToken 写入 | **严禁直接传图片 URL 到附件字段**(服务端同步下载会超时) | | 附件/图片上传 | 本地文件用 `+attachment-put`;在线 URL 通过原子 `record create/update` 写入 | URL 转存异步执行,受理回执不代表附件已可下载,需独立验证 |
| 文件级导入 | `import upload` + `import data` | 不要手动解析 xlsx 再逐条写入 | | 文件级导入 | `import upload` + `import data` | 不要手动解析 xlsx 再逐条写入 |
## 4. 创建/修改后回读确认 ## 4. 创建/修改后回读确认
执行写操作后,建议立即回读确认结果: 执行写操作后,必须使用独立读取立即回读确认结果;不能用 mutation 回执、HTTP 200 或退出码 0 代替。
| 写操作 | 建议回读命令 | 确认内容 | | 写操作 | 必须回读命令 | 确认内容 |
|--------|-------------|----------| |--------|-------------|----------|
| `table create` | `table get --table-ids <新tableId>` | 表名、字段列表是否符合预期 | | `table create` | `table get --table-ids <新tableId>` | 表名、字段列表是否符合预期 |
| `field create` | `field get --table-id <tableId>` | 新字段是否出现在字段列表中 | | `field create` | `field get --table-id <tableId>` | 新字段是否出现在字段列表中 |
@@ -46,3 +46,13 @@
- AI 字段的 prompt **必须至少包含一个 `fieldRef` 引用**,纯文本 prompt 会被后端拒绝 - AI 字段的 prompt **必须至少包含一个 `fieldRef` 引用**,纯文本 prompt 会被后端拒绝
- 先创建/确认被引用字段的 fieldId,再在 prompt 中引用 - 先创建/确认被引用字段的 fieldId,再在 prompt 中引用
- `outputType` 必须与字段类型一致(如 `outputType=text` 配 `--type text`) - `outputType` 必须与字段类型一致(如 `outputType=text` 配 `--type text`)
## 6. DWS 与 MCP 交互契约
1. **Schema 优先** — 先读精确 leaf Runtime Schema,任务内按 canonical leaf path 缓存。只有 Schema 不足或与实际结果冲突时才读 leaf Help;版本、profile 变化或出现契约错误时废弃缓存。
2. **写后缓存失效** — mutation 返回真实 ID/名称后立即更新对象索引,清除受影响对象、Table、Dashboard 及相关查询缓存。写后验证不得复用写前数据。
3. **独立回读** — 创建/更新返回可能带旧名称或旧配置。使用真实 ID、recordId 或唯一键再读取,比较业务值和 JSON 类型;删除操作必须证明对象已不存在。
4. **未知写入不重放** — 写调用超时、断连或回执不完整时,副作用可能已发生。先用最小只读范围查真实状态,不原样自动重放创建、更新、删除或导入。
5. **并发有边界** — 同一 Base 的写入默认串行;无依赖只读默认最多 4 路并发。只有不同 Base、无数据/ID/顺序依赖且失败可独立回读时,才允许受控并发写。
6. **信封需归一化** — 按“合法 `ok/outcome` → 已知旧 `status` → JSON boolean `success`”判断,不假设固定 `data` 层。`partial_failure` 可在 stdout 并使用退出码 7;先解析 stdout/stderr 中的结构化信封,再处理退出码。
7. **上传凭据必须脱敏** — `uploadUrl`、token、authorization、signature、secret 不能输出或记录;错误对象、嵌套 `data/result` 和字符串内 URL 都要递归脱敏。
@@ -79,11 +79,11 @@
### date(日期) ### date(日期)
**写入**:日期字符串、RFC3339 字符串、或毫秒时间戳 **写入**:日期字符串、含时区 RFC3339 字符串、或整数毫秒时间戳。含时区字符串和毫秒时间戳按固定 UTC+8 转为分钟精度写入,秒和毫秒部分不保留;时间戳转换后的年份须在 0001–9999。不要按数值大小猜测秒单位。
```json ```json
{"fldDateId": "2026-03-15"} {"fldDateId": "2026-03-15"}
{"fldDateId": "2026-03-15 09:00"} {"fldDateId": "2026-03-15 09:00"}
{"fldDateId": "2026-03-15T09:00+08:00"} {"fldDateId": "2026-03-15T09:00:00+08:00"}
``` ```
**读取**:RFC3339 字符串(带时区) **读取**:RFC3339 字符串(带时区)
@@ -194,12 +194,13 @@
### group(群组) ### group(群组)
**写入**:对象数组,每项含 `cid` **写入**:对象数组,每项含 `cid` 或 `openConversationId`,二选一
```json ```json
{"fldGroupId": [{"cid": "74577067501"}]} {"fldGroupId": [{"cid": "74577067501"}]}
{"fldGroupId": [{"openConversationId": "cidxxxxxxxx"}]}
``` ```
> ⚠️ key 是 **`cid`**,不是 `openConversationId` > MCP 会把 `openConversationId` 转换为内部 `cid`;不要在同一项中同时提供两者。
**读取**:对象数组 **读取**:对象数组
```json ```json
@@ -241,14 +242,13 @@
### attachment(附件) ### attachment(附件)
**写入**:对象数组,**必须使用 `fileToken`** **写入**:对象数组,可使用 `fileToken`、可下载在线 URL,或 snapshot 定义的完整附件对象。
```json ```json
{"fldAttachId": [{"fileToken": "ft_xxx"}]} {"fldAttachId": [{"fileToken": "ft_xxx"}]}
``` ```
> ⚠️ **必须先通过 [attachment upload 流程](./aitable-attachment.md) 上传文件获取 `fileToken`,再将 `fileToken` 写入 cells。** > 本地文件先通过 [attachment upload 流程](./aitable-attachment.md) 获取 `fileToken`;在线 URL 可直接写入 `[{"url":"https://..."}]`。URL 转存异步执行,写入成功仅表示已受理,应回读并确认附件可用。
> ❌ **严禁直接传 `{"url": "https://..."}` 形式写入附件/图片字段** — 服务端会同步下载图片,10 条记录即触发 TIMEOUT_ERROR 超时。
> 写入会**整体覆盖**原附件列表,不是追加。 > 写入会**整体覆盖**原附件列表,不是追加。
**读取**:对象数组(含下载链接、文件名、大小) **读取**:对象数组(含下载链接、文件名、大小)
@@ -339,8 +339,8 @@
|------|----------| |------|----------|
| cells key 用字段名称 `"课程名称"` | 用 fieldId `"fldXXX"` | | cells key 用字段名称 `"课程名称"` | 用 fieldId `"fldXXX"` |
| progress 写入 `75` | 写入 `0.75`(范围 0~1) | | progress 写入 `75` | 写入 `0.75`(范围 0~1) |
| attachment 直接传文件路径或图片 URL | 必须先 `attachment upload` 获取 fileToken,再用 fileToken 写入(直传 URL 会超时) | | attachment 直接传本地文件路径 | 本地文件先上传获得 fileToken;在线 URL 使用 `[{"url":"https://..."}]`,写后验证异步转存结果 |
| user 字段传用户名字符串 | 传对象数组 `[{"userId":"...", "corpId":"..."}]` | | user 字段传用户名字符串 | 传对象数组 `[{"userId":"...", "corpId":"..."}]` |
| group 字段用 `openConversationId` | 用 `cid` | | group 字段同时传 `cid` 和 `openConversationId` | 每项只传其中一个,转换由 MCP 负责 |
| singleSelect 传 option id 字符串 | 传 name 字符串或 `{"id":"...", "name":"..."}` 对象 | | singleSelect 传 option id 字符串 | 传 name 字符串或 `{"id":"...", "name":"..."}` 对象 |
| 对只读字段写入值 | 不传该字段,由系统自动填充 | | 对只读字段写入值 | 不传该字段,由系统自动填充 |
@@ -0,0 +1,42 @@
# AI 表格记录评论
本 Reference 仅适用于 `dws aitable comment ...`。它操作的是绑定到一条 AI 表格记录的评论,不是在线电子表格单元格批注,也不修改记录字段。
## 命令
| 意图 | 命令 | 关键参数 |
|---|---|---|
| 分页查询评论与回复 | `comment list` | `--base-id --table-id --record-id [--limit 1..100] [--cursor <next_token>]` |
| 创建评论话题 | `comment create` | 定位参数 + `--content <文本>` 或 `--rich-content <JSON>` |
| 回复已有评论 | `comment reply` | 定位参数 + `--topic-id --comment-key` + 正文 |
| 完整替换本人评论正文 | `comment update` | 定位参数 + `--topic-id --comment-key` + 正文 |
| 删除本人评论 | `comment delete` | 定位参数 + `--topic-id --comment-key`;确认后再追加 `--yes` |
`--content` 是纯文本便捷入口,CLI 会转换成单个 text 节点。`--rich-content` 是 1~100 个有序节点组成的 JSON 数组:
```json
[
{"type":"text","text":"请确认截图 "},
{"type":"mention","userId":"<USER_ID>","corpId":"<CORP_ID>"},
{"type":"image","url":"/core/api/resources/<RESOURCE_ID>/detail","width":800,"height":600}
]
```
- text 总长度最多 10000 个 UTF-16 字符。
- mention 最多 20 个;`userId` 必须是外部用户 ID,`corpId` 省略时使用当前调用企业。
- image 最多 9 个;只接受当前 Base 已上传图片的 `/core/api/resources/<resourceId>/detail` 路径。命令不负责上传或转存图片,宽高是可选的 1~20000 整数。
- `--content` 与 `--rich-content` 必须且只能提供一个;update 会完整替换正文,仅传纯文本会移除旧的 @和图片。
## 稳定标识与分页
`topicId`、`commentKey` 必须来自同一 `baseId/tableId/recordId` 的 create/list 真实返回。reply 的 CLI 参数仍叫 `--comment-key`,底层会按接口协议发送为 `replyCommentKey`,不要自行猜测或拼接标识。
list 的 `data.comments=[]` 不代表结束。只有 `meta.pagination.endpoint_exhausted=true` 才能停止;为 false 时保持三项定位参数不变,将 `meta.pagination.next_token` 原样作为下一次 `--cursor`。过滤后空页是正常结果;不要从业务 `data` 读取已移除的 `hasMore` 或 `nextToken`。
## 权限与失败处理
- list 要求 Base 预览权限且目标记录可见;写操作要求 Base 编辑权限。
- 当前只支持新评论服务;旧评论服务返回 `COMMENT_LEGACY_UNSUPPORTED` 时停止,不自动迁移或改走其他存储。
- create/reply 非幂等,所有评论写操作都不会自动重试。超时、连接中断或结果未知时先 list 对账,再决定后续动作。
- update 无 CAS,并发修改存在最后写入覆盖窗口。
- delete 不可恢复,只能删除本人评论;关联回复如何处理由评论服务决定,执行前必须独立确认目标和影响。
@@ -21,6 +21,12 @@ dws aitable chart get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-
## 要点 ## 要点
- `dashboard get` 返回的 `charts[].chartId` 可直接给 `chart get` 使用 - `dashboard get` 返回的 `charts[].chartId` 可直接给 `chart get` 使用
- `chart create` 以及携带 `--layout` 的 `chart update` 会在写入前自动调用 `get_dashboard` 并强制校验根网格。只有 `schemaVersionTypeVerified=true` 时才使用原始类型判据:JSON number `2` 为 48 列,其他已验证值为 12 列;证据缺失时命令停止且不调用 Chart 写工具
- 任务上下文已确认应用模式时,Chart 写命令显式传 `--is-app-mode=true`,DWS 按只读上下文强制使用 48 列;该 flag 不会进入 MCP payload。未确认应用模式时省略,不能为了绕过 schemaVersion 证据传 true
- 全局 12/48 列只适用于根布局。存在非根 `parentId` 时使用容器自己的坐标系;拿不到容器真实列数时,创建操作停止,更新操作省略 layout 以保留原值
- `schemaVersion` 与 `isAppMode` 都是只读字段,不能写入或透传到 Dashboard config
- 删除 dashboard 会级联删除其全部 chart;确认前必须说明该影响
- `dashboard share get` 可能返回 `404`(资源不存在或未开通),需按可重试错误处理,不要误判为参数拼错
- `chart share get` 可正常返回 `enabled/shareUrl`,用于分享状态判断 - `chart share get` 可正常返回 `enabled/shareUrl`,用于分享状态判断
- Dashboard 普通 CRUD 不在本文重新维护;需要完整配置时才读取 `dashboard config-example` - Dashboard 普通 CRUD 不在本文重新维护;需要完整配置时才读取 `dashboard config-example`
@@ -36,7 +42,7 @@ dws aitable chart get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-
| 命令 | 用途 | 必填参数 | | 命令 | 用途 | 必填参数 |
|------|------|----------| |------|------|----------|
| `chart get` | 获取图表详情 | `--base-id` `--dashboard-id` `--chart-id` | | `chart get` | 获取图表详情 | `--base-id` `--dashboard-id` `--chart-id` |
| `chart create` | 创建图表 | `--base-id` `--dashboard-id` `--config` `--layout` | | `chart create` | 创建图表 | `--base-id` `--dashboard-id` `--config` `--layout`;根布局按 Dashboard 元信息选择 12/48 列 |
| `chart update` | 更新图表配置 | `--base-id` `--dashboard-id` `--chart-id` `--config` | | `chart update` | 更新图表配置 | `--base-id` `--dashboard-id` `--chart-id` `--config` |
| `chart delete` | 删除图表 | `--base-id` `--dashboard-id` `--chart-id` | 不可逆;由 Runtime 请求确认,Reference 不携带确认绕过参数 | | `chart delete` | 删除图表 | `--base-id` `--dashboard-id` `--chart-id` | 不可逆;由 Runtime 请求确认,Reference 不携带确认绕过参数 |
| `+chart-widgets-example` | 查看所有图表类型的 widgets 模板 | 无 | | `+chart-widgets-example` | 查看所有图表类型的 widgets 模板 | 无 |
@@ -20,10 +20,10 @@
│ → record stats --stats '[{"fieldId":"...","statsType":"COUNT|SUM|AVG|..."}]' │ → record stats --stats '[{"fieldId":"...","statsType":"COUNT|SUM|AVG|..."}]'
│ │
├─ 分组统计(如"每个状态各有多少条") ├─ 分组统计(如"每个状态各有多少条")
│ → record group-stats --group '[...]' --stats '[{"fieldId":"...","statsType":"count"}]' │ → record group-stats --group '[...]' --stats '[{"fieldId":"...","statsType":"COUNT"}]'
│ │
├─ 条件唯一实体数(如"有索赔单的门店共有多少家") ├─ 条件唯一实体数(如"有索赔单的门店共有多少家")
│ → record group-stats --filters '{...}' --stats '[{"fieldId":"<实体字段>","statsType":"distinct"}]' │ → record group-stats --filters '{...}' --stats '[{"fieldId":"<实体字段>","statsType":"DISTINCT"}]'
│ │
└─ 判断全局结论(如"是否所有记录都满足条件") └─ 判断全局结论(如"是否所有记录都满足条件")
→ record stats 对反向过滤结果 COUNT;只有需要行级证据时再 record query → record stats 对反向过滤结果 COUNT;只有需要行级证据时再 record query
@@ -7,11 +7,11 @@
| 命令 | 用途 | 读/写 | | 命令 | 用途 | 读/写 |
|------|------|-------| |------|------|-------|
| `+datasource-list-sources` | 列出可用数据源条目,获取 processCode/name/iconUrl/url | 读 | | `+datasource-list-sources` | 列出可用数据源条目,获取 processCode/name/iconUrl/url | 读 |
| `+datasource-get-fields` | 获取可同步字段列表,用于决定 field-ids | 读 | | `+datasource-get-fields` | 查看来源字段结构;当前仅支持全量同步 | 读 |
| `+datasource-create` | 创建数据源表并触发首次全量同步 | 写 | | `+datasource-create` | 创建数据源表并触发首次全量同步 | 写 |
| `+datasource-update` | 更新已有数据源表的同步配置 | 写 | | `+datasource-update` | 更新已有数据源表的同步配置 | 写 |
| `+datasource-sync` | 手动触发一次同步(最多 5 张表) | 写 | | `+datasource-sync` | 手动触发一次同步(最多 5 张表) | 写 |
| `+datasource-sync-status` | 查询同步任务状态(RUNNING/FINISHED/FAILED) | 读 | | `+datasource-sync-status` | 查询同步任务状态(含未找到、未知状态) | 读 |
| `+datasource-get-config` | 获取数据源表当前同步配置 | 读 | | `+datasource-get-config` | 获取数据源表当前同步配置 | 读 |
## 典型工作流 ## 典型工作流
@@ -32,14 +32,14 @@ Step 0.5 (OA 审批) 选择模板 list-sources 返回的 approvals 数组通
不一定需要选择步骤。 不一定需要选择步骤。
Step 1 (可选) 获取可同步字段 +datasource-get-fields --base-id <B> --datasource-type OA --source-config '<JSON>' Step 1 (可选) 获取可同步字段 +datasource-get-fields --base-id <B> --datasource-type OA --source-config '<JSON>'
→ 决定需要同步哪些字段,得到 field-ids → 了解来源字段结构;创建和更新均为全量同步,不传 --field-ids
Step 2 创建数据源 +datasource-create --base-id <B> --datasource-type OA --source-config '<JSON>' Step 2 创建数据源 +datasource-create --base-id <B> --datasource-type OA --source-config '<JSON>'
→ sourceConfig 中的 processCode/name/iconUrl/url 来自 Step 0.5 选中的模板 → sourceConfig 中的 processCode/name/iconUrl/url 来自 Step 0.5 选中的模板
→ 返回 tableId + taskId → 返回 tableId + taskId
Step 3 查询同步结果 +datasource-sync-status --base-id <B> --table-id <T> --task-ids <TASK_ID> Step 3 查询同步结果 +datasource-sync-status --base-id <B> --table-id <T> --task-ids <TASK_ID>
→ FINISHED=完成,FAILED=看 errorCode 排查,RUNNING=轮询 → 仅 RUNNING 轮询;其他状态停止并按 tasks[] 逐项处理
Step 4 (后续) 手动触发同步 +datasource-sync --base-id <B> --table-ids <T1>,<T2> Step 4 (后续) 手动触发同步 +datasource-sync --base-id <B> --table-ids <T1>,<T2>
→ 返回新 taskId,再用 sync-status 查结果 → 返回新 taskId,再用 sync-status 查结果
@@ -152,7 +152,7 @@ dws aitable +datasource-get-fields --base-id BASE_ID --datasource-type OA \
| `--datasource-type` | 是 | 数据源类型,当前仅支持 `OA` | | `--datasource-type` | 是 | 数据源类型,当前仅支持 `OA` |
| `--source-config` | 是 | 源配置 JSON 字符串,结构同 create 的 --source-config | | `--source-config` | 是 | 源配置 JSON 字符串,结构同 create 的 --source-config |
返回字段列表(字段 ID、名称、类型等),用于在 create/update 中指定 `--field-ids`。 返回字段列表(字段 ID、名称、类型等),用于了解来源结构;当前仅支持全量同步。
### +datasource-create — 创建数据源表 ### +datasource-create — 创建数据源表
@@ -175,15 +175,16 @@ dws aitable +datasource-create --base-id BASE_ID --datasource-type OA \
| `--source-config` | 是 | 源配置 JSON 字符串(见上方字段协议) | | `--source-config` | 是 | 源配置 JSON 字符串(见上方字段协议) |
| `--auto` | 否 | 是否开启自动同步,默认 false;无论是否传入,CLI 都会把该字段下发给下游 | | `--auto` | 否 | 是否开启自动同步,默认 false;无论是否传入,CLI 都会把该字段下发给下游 |
| `--auto-sync-setting` | 否 | 自动同步频率配置 JSON 字符串,仅 --auto=true 时生效 | | `--auto-sync-setting` | 否 | 自动同步频率配置 JSON 字符串,仅 --auto=true 时生效 |
| `--field-ids` | 否 | 需要同步的字段 ID 列表,不传时同步全部字段 | | `--field-ids` | 否 | 不受支持;当前仅支持全量同步,传入时在本地拒绝 |
返回新建数据源表 tableId 和同步任务 taskId。创建后自动触发一次全量同步,需用 `+datasource-sync-status` 查最终结果。 返回新建数据源表 tableId 和同步任务 taskId。创建后自动触发一次全量同步,需用 `+datasource-sync-status` 查最终结果。
### +datasource-update — 更新数据源配置 ### +datasource-update — 更新数据源配置
```bash ```bash
# 仅开启自动同步 # 开启自动同步,CLI 自动读取并保留现有源配置
dws aitable +datasource-update --base-id BASE_ID --table-id TABLE_ID --auto --format json dws aitable +datasource-update --base-id BASE_ID --table-id TABLE_ID \
--auto --format json
# 更新源配置 # 更新源配置
dws aitable +datasource-update --base-id BASE_ID --table-id TABLE_ID \ dws aitable +datasource-update --base-id BASE_ID --table-id TABLE_ID \
@@ -195,12 +196,16 @@ dws aitable +datasource-update --base-id BASE_ID --table-id TABLE_ID \
|------|------|------| |------|------|------|
| `--base-id` | 是 | 目标 Base ID | | `--base-id` | 是 | 目标 Base ID |
| `--table-id` | 是 | 已有数据源表 ID(sync=true) | | `--table-id` | 是 | 已有数据源表 ID(sync=true) |
| `--source-config` | 否 | 新的源配置 JSON 字符串,不传时保持原配置;传入时整体覆盖 | | `--source-config` | 否 | 省略时先读取当前 sourceConfig 并原样提交;显式传入时须提供完整 JSON 对象字符串,整体覆盖 |
| `--auto` | 否 | 是否开启自动同步,不传时保持原设置 | | `--auto` | 否 | 是否开启自动同步,不传时保持原设置 |
| `--auto-sync-setting` | 否 | 自动同步频率配置 JSON 字符串,仅 --auto=true 时生效;不传时保持原频率配置 | | `--auto-sync-setting` | 否 | 自动同步频率配置 JSON 字符串,仅 --auto=true 时生效;不传时保持原频率配置 |
| `--field-ids` | 否 | 需要同步的字段 ID 列表,不传时保持现有字段配置 | | `--field-ids` | 否 | 不受支持;当前仅支持全量同步,传入时在本地拒绝 |
更新后自动触发一次全量同步,返回新 taskId。 更新至少需要显式提供 `--source-config`、`--auto` 或 `--auto-sync-setting` 中的一项;仅触发同步使用 `+datasource-sync`。
省略 `--source-config` 时,CLI 先对同一 Base 和数据源表调用 `get-config`,保留返回的完整源配置(包括未知字段),再提交更新。读取失败、配置缺失或格式异常时不执行更新,可显式提供完整 `--source-config` 后重试。显式提供配置时不进行这次读取。原生命令 `dws aitable datasource update` 使用相同行为。
读取与更新是两次请求,不提供版本锁;避免同时修改同一数据源配置。更新后自动触发一次全量同步,返回新 taskId。
### +datasource-sync — 手动触发同步 ### +datasource-sync — 手动触发同步
@@ -227,7 +232,7 @@ dws aitable +datasource-sync-status --base-id BASE_ID --table-id TABLE_ID --task
| `--table-id` | 是 | 数据源表 ID(sync=true) | | `--table-id` | 是 | 数据源表 ID(sync=true) |
| `--task-ids` | 是 | 同步任务 ID 列表(由 create/update/sync 返回),1-5 个 | | `--task-ids` | 是 | 同步任务 ID 列表(由 create/update/sync 返回),1-5 个 |
任务状态:`RUNNING`(进行中)、`FINISHED`(完成)、`FAILED`(失败,含 errorCode + errorMessage)。 任务状态:`RUNNING`(进行中,需继续轮询)、`FINISHED`(完成)、`FAILED`(失败)、`NOT_FOUND`(任务不存在或已过期)、`UNKNOWN`(查询链路失败,状态不可断言)、`IDLE`(未提供 taskId)。仅 `RUNNING` 继续轮询;批量返回整体为 success 时仍必须遍历 `tasks[]`,检查每项 `status/errorCode/errorMessage`。DWS 为避免无意义的 `IDLE` 查询,仍要求传入 1-5 个 taskId。
### +datasource-get-config — 获取数据源配置 ### +datasource-get-config — 获取数据源配置
@@ -269,4 +274,5 @@ dws aitable +datasource-get-config --base-id BASE_ID --table-id TABLE_ID --forma
- create/update 后自动触发一次同步,返回 taskId;用 sync-status 查最终结果 - create/update 后自动触发一次同步,返回 taskId;用 sync-status 查最终结果
- sync 单次最多 5 张表,超出拆分多次调用 - sync 单次最多 5 张表,超出拆分多次调用
- sync-status 单次最多 5 个 taskId,超出拆分多次调用 - sync-status 单次最多 5 个 taskId,超出拆分多次调用
- sync-status 仅在单项状态为 `RUNNING` 时继续轮询;`UNKNOWN` 停止轮询并报告状态不可断言
- get-config 仅适用于数据源表(sync=true),普通表会报错 - get-config 仅适用于数据源表(sync=true),普通表会报错
@@ -2,7 +2,7 @@
> 当 CLI 命令返回错误时,按本文档的映射表判断恢复动作。 > 当 CLI 命令返回错误时,按本文档的映射表判断恢复动作。
## 1. 错误响应结构 ## 1. 响应信封与结果判定
```json ```json
{ {
@@ -12,9 +12,13 @@
} }
``` ```
- `status: "error"` 表示操作失败 DWS 运行时可能返回统一 `ok/outcome`、旧 `status` 或 JSON boolean `success` 信封,并可能包在 `data` / `result` 中。按以下顺序归一化:
- `summary` 包含错误摘要信息
- `trace_id` 用于问题追踪 1. 合法且不矛盾的 `ok/outcome`;
2. 已知旧 `status`;
3. JSON boolean `success`。
`outcome` 只接受 `success/pending/partial_failure/failure`;`ok=true` 只能与 `success/pending` 组合。`partial_failure` 可以写在 stdout 并返回退出码 7,`failure` 可以写在 stderr 并返回非零码;必须先解析两个输出通道的结构化信封,再处理退出码。信封矛盾、无法识别或写回结果不完整时按 `unknown` 处理,不假定成功。`summary/trace_id` 可用于诊断,不能代替对象回读。
## 2. 常见错误与恢复动作 ## 2. 常见错误与恢复动作
@@ -68,24 +72,32 @@
## 3. 重试策略 ## 3. 重试策略
### 3.1 可重试的错误 ### 3.1 先查真实状态
| 错误类型 | 重试方式 | 最大重试次数 | 任何写调用报错、超时、断连或返回 `pending/unknown/partial_failure` 时,立即停止依赖步骤,用真实 ID、recordId、唯一键或精确范围做最小只读检查:
|---------|---------|------------|
| 网络超时 / 5xx | 等待 2s 后原样重试 | 2 |
| 导出任务未完成 | 轮询 task-id | 5(间隔 3s) |
| 并发写入冲突 | 串行重试 | 1 |
### 3.2 不可重试的错误(立即停止) - 真实状态已达到全部预期:标记“回执异常但结果已确认”,不重试。
- 状态未达预期但已知部分副作用:从 shortcut checkpoint/未完成项续跑,不重放成功项。
- 无法确认副作用:保持 `unknown`,禁止原样重放非幂等或幂等性未知的写操作。
### 3.2 可重试的错误
- 只读调用遇到网络中断、超时、5xx 或限流,可做有上限退避重试。
- 写操作只有在真实状态证明副作用尚未发生,且当前 Runtime Schema 明确具有幂等契约、稳定唯一键或可续跑 checkpoint 时,才能重试。
- 导出/导入任务未完成时先查询任务或目标的真实状态;仅当当前 Runtime Schema/Help 明确支持原 ID 续等时,才复用同一 taskId/importId。无法确认时保持 `pending/unknown`,不重新 prepare、PUT 或发起新任务。
- 同一 Base 写入默认串行;不用并发重放解决冲突。
### 3.3 不可重试的错误(立即停止)
| 错误类型 | 原因 | 处理方式 | | 错误类型 | 原因 | 处理方式 |
|---------|------|---------| |---------|------|---------|
| 权限不足 / 403 | 用户对该 Base 无权限 | 停止操作,提示用户确认权限 | | 权限不足 / 403 | 用户对该 Base 无权限 | 停止操作,提示用户确认权限 |
| 参数格式错误 | 请求结构不合法 | 修正参数后重试,不要原样重试 | | 参数格式错误 | 请求结构不合法 | 修正参数后重试,不要原样重试 |
| 资源不存在 / 404 | ID 错误或资源已删除 | 重新查询定位资源 | | 资源不存在 / 404 | ID 错误或资源已删除 | 重新查询定位资源 |
| 配额超限 / 429 | API 调用频率过高 | 等待后重试,并降低并发 | | 非幂等写入效果未知 | 服务端可能已经执行 | 先回读;无法确认时保持 unknown |
| 稳定重现的类型污染/UI 语义错误 | 同一请求不会因重放恢复 | 停止受影响分支并保留证据 |
### 3.3 重试前检查清单 ### 3.4 重试前检查清单
在重试前,先确认: 在重试前,先确认:
1. ❓ 错误是暂时性的还是永久性的? 1. ❓ 错误是暂时性的还是永久性的?
@@ -22,12 +22,27 @@ dws aitable export data --base-id <BASE_ID> --task-id <TASK_ID> --timeout-ms 300
| `table` | 必须 `--table-id` | | `table` | 必须 `--table-id` |
| `view` | 必须 `--table-id` + `--view-id` | | `view` | 必须 `--table-id` + `--view-id` |
## 导入文件(三步流程) ## 导入文件(DWS 闭环)
当用户要求将 Excel(`.xlsx`)或 CSV 文件完整导入 AI 表格时,**不需要自己解析文件内容**,直接使用文件级导入。 当用户要求将 Excel(`.xlsx`)或 CSV 文件完整导入 AI 表格时,**不需要自己解析文件内容**,直接使用文件级导入。
> **无需手动解析 CSV/Excel 再逐条 record create**,效率极低且容易出错。 > **无需手动解析 CSV/Excel 再逐条 record create**,效率极低且容易出错。
先展示来源、目标、字段映射、异常处理和回退方式,获得这次迁移的专门确认。使用 `+import-file`,由 DWS 在进程内完成申请、PUT 和触发,不向调用方暴露签名 URL:
```bash
dws aitable +import-file --base-id <BASE_ID> --file ./data.xlsx
# 追加到已有表并指定映射
dws aitable +import-file --base-id <BASE_ID> --file ./data.xlsx \
--table-id <TABLE_ID> --field-mapping '{"目标字段名":"源列名"}'
# 终态未知时,先检查目标数据;需要继续等待时只能复用同一 importId
dws aitable +import-file --resume-import-id <IMPORT_ID> --timeout 30
```
`+import-file` 只接受 CSV/XLS/XLSX,自动校验本地普通文件并隐藏上传地址和凭据。导入触发后超时会保留 `importId` 并返回 `unknown`;必须先查目标表真实状态,不得重新申请上传地址、重复 PUT 或更换 importId。续等模式只允许 `--resume-import-id` 和可选 `--timeout`,不能改变目标表、表头、源 Sheet 或字段映射。下方手动命令仅用于诊断三段协议。
```bash ```bash
# 第 1 步:申请上传凭证 # 第 1 步:申请上传凭证
dws aitable import upload --base-id <BASE_ID> \ dws aitable import upload --base-id <BASE_ID> \
@@ -52,7 +67,7 @@ dws aitable import data --import-id <importId> --table-id <TABLE_ID> --format js
|------|------|------| |------|------|------|
| 申请上传凭证 | `import upload --base-id <ID> --file-name <名称> --file-size <字节>` | `--file-size` 必须与实际文件大小一致 | | 申请上传凭证 | `import upload --base-id <ID> --file-name <名称> --file-size <字节>` | `--file-size` 必须与实际文件大小一致 |
| 上传文件 | HTTP PUT(curl 等) | **必须** 带 `-H "Content-Type:"` 将 Content-Type 设为空,否则 OSS 返回 403 | | 上传文件 | HTTP PUT(curl 等) | **必须** 带 `-H "Content-Type:"` 将 Content-Type 设为空,否则 OSS 返回 403 |
| 触发导入 | `import data --import-id <ID> [--table-id <TABLE_ID>]` | 同步等待,大多一次调用即返回结果;超时可用相同 importId 重试 | | 触发导入 | `import data --import-id <ID> [--table-id <TABLE_ID>]` | 同步等待;超时先查真实状态,仅在当前契约允许时用相同 importId 续等,不重新提交 |
### import data 参数 ### import data 参数
@@ -8,8 +8,26 @@
1. **根节点必须是逻辑操作符**:`"operator"` 必须是 `"and"` 或 `"or"`,不能是 `"eq"` 等比较操作符 1. **根节点必须是逻辑操作符**:`"operator"` 必须是 `"and"` 或 `"or"`,不能是 `"eq"` 等比较操作符
2. 比较操作必须放在根节点的 `"operands"` 数组内的对象中 2. 比较操作必须放在根节点的 `"operands"` 数组内的对象中
3. `singleSelect` 和 `multipleSelect` 字段,推荐使用 **选项的 exact String 名称 (name)** 作为比较值 3. **查询指定数据前必须先读一遍完整表头**:先用 `field get`(不加 `--field-ids`)取回所有字段的 `fieldId`/`name`/`type`/`config`,从表头中确定用户条件对应哪个字段,再按该字段类型解析值并传入查询条件;不能直接用字段名称、凭用户原话猜字段或猜类型
4. fieldId 必须通过 `field get` 获取,不能直接用字段名称 4. `singleSelect` 和 `multipleSelect` 必须先通过 `field get` 或 `field search-options` 唯一解析选项,filter 优先传稳定 option ID;`record create/update` 才传 option name
5. 人员、部门、群组必须先调用对应的通讯录/会话查询解析稳定 ID,再传结构化 ID 数组;禁止把姓名、部门名、群名、裸 ID 字符串直接放进 filter
### 按字段类型解析比较值
用户提供的是自然语言展示值时,必须先解析再组装 filter,禁止原值透传:
| 字段类型 | filter 比较值 | 前置解析 |
|---|---|---|
| `text` | JSON string | 无;按用户原文本使用 |
| `number` | JSON number | 将明确数值转换为数字,禁止传字符串数字 |
| `date` | `YYYY-MM-DD`、RFC3339 或毫秒时间戳 | 使用日期专用操作符;范围拆为 `not_before` + `not_after` |
| `singleSelect` / `multipleSelect` | singleSelect 通常传 option ID 标量;multipleSelect 或数组型条件传 option ID 数组,如 `["optA"]` | `field get` 或 `field search-options` 唯一匹配;写记录时才使用 option name |
| 人员 | `[{"userId":"..."}]`(需要时同时带 `corpId`) | `dws aisearch person --keyword "<姓名>" --dimension name --format json`;取唯一 `userId`,重名必须消歧 |
| 部门 | `[{"departmentId":"..."}]` | `dws contact +resolve-dept --name "<部门名>" --format json`;取唯一 `deptId` 并写入 `departmentId`,零/多命中必须停止 |
| 群组 | `[{"cid":"..."}]` | `dws chat +chat-search --query "<群名>" --page-all --format json`;取唯一 `openConversationId` 并写入 `cid`,零/多命中必须停止 |
| 关联记录 | 稳定 `recordId` 或字段协议要求的 recordId 结构 | 先查询关联目标表并唯一定位记录;不得传记录标题 |
`exist` / `un_exist` 不传第二个操作数。数组、对象、布尔值和数字必须保持 JSON 原生类型,不得统一字符串化。人员、部门、群组属于数组型字段,即使只筛一个目标也必须保留数组外层,例如 `"operands":["fldUser",[{"userId":"u1"}]]`,不能传姓名、`"u1"` 或单个对象。当 `eq` / `ne` / `any_of` / `none_of` / `all_of` 等条件涉及 multipleSelect 或其他数组型字段时,第二个操作数必须是 option ID/稳定 ID 数组,例如 `"operands":["fldMulti",["optA"]]`;不要传裸字符串 `"operands":["fldMulti","optA"]`。任何解析出现零命中或多命中时,必须停止并让用户补充或选择,禁止默认取第一项。
### 精简防呆模板 ### 精简防呆模板
@@ -47,9 +65,9 @@ CLI 同时兼容两种子条件写法(推荐格式 A):
|--------|------|--------------| |--------|------|--------------|
| `eq` / `ne` | 等于 / 不等于 | `["fieldId", "value"]` | | `eq` / `ne` | 等于 / 不等于 | `["fieldId", "value"]` |
| `contain` / `exclusive` | 包含 / 不包含(文本模糊) | `["fieldId", "value"]` | | `contain` / `exclusive` | 包含 / 不包含(文本模糊) | `["fieldId", "value"]` |
| `gt` / `gte` / `lt` / `lte` | 大于 / ≥ / 小于 / ≤ | `["fieldId", "numStr"]` | | `gt` / `gte` / `lt` / `lte` | 大于 / ≥ / 小于 / ≤ | `["fieldId", 123]`(JSON number,禁止字符串数字) |
| `exist` / `un_exist` | 有值 / 为空 | `["fieldId"]`(无需第二项) | | `exist` / `un_exist` | 有值 / 为空 | `["fieldId"]`(无需第二项) |
| `any_of` / `none_of` / `all_of` | 包含任一 / 不包含任一 / 全包含(多选字段) | `["fieldId", "optionName"]` | | `any_of` / `none_of` / `all_of` | 包含任一 / 不包含任一 / 全包含 | `singleSelect` 可传 option ID 标量;涉及 `multipleSelect` / 数组型字段时传 `["fieldId", ["optionId"]]`(第二项必须是数组) |
| `date_eq` / `before` / `after` | 日期等于 / 早于 / 晚于 | `["fieldId", "dateStr"]` | | `date_eq` / `before` / `after` | 日期等于 / 早于 / 晚于 | `["fieldId", "dateStr"]` |
| `not_before` / `not_after` | 不早于(≥) / 不晚于(≤) | `["fieldId", "2026-05-22"]` | | `not_before` / `not_after` | 不早于(≥) / 不晚于(≤) | `["fieldId", "2026-05-22"]` |
@@ -80,6 +98,32 @@ dws aitable record query --base-id X --table-id Y \
--filters '{"operator":"and","operands":[{"operator":"not_before","operands":["fldDate","2026-05-01"]},{"operator":"not_after","operands":["fldDate","2026-05-31"]}]}' --filters '{"operator":"and","operands":[{"operator":"not_before","operands":["fldDate","2026-05-01"]},{"operator":"not_after","operands":["fldDate","2026-05-31"]}]}'
``` ```
### View 日期 Scheme(仅 `view update filter`)
持久化 View 使用经过真实写入、读回和 UI 验证的结构化日期 Scheme,不能与上面的 `record query --filters` 日期字符串协议混用。最外层是数组,内部保留显式 `and/or` 根节点。
| UI 语义 | operator / value | JSON 类型要求 |
|---|---|---|
| 今天/本周/本月/今年及前后周期 | `date_eq` + `{"type":"relative","period":"day|week|month|year","offset":N}` | `offset` 必须是 JSON number 整数 |
| 过去/未来 X 天 | `from_now` + `{"type":"relative","period":"day","offset":"N"}` | `offset` 必须是 JSON string;过去为负、未来为正 |
| 指定日期 | `date_eq` + `{"type":"exact","timestamp":TIMESTAMP_MS}` | `timestamp` 必须是目标时区当天 00:00 的 Unix 毫秒 JSON number 整数 |
```bash
# 本月
dws aitable view update filter --base-id X --table-id Y --view-id Z \
--json '[{"operator":"and","operands":[{"operator":"date_eq","operands":["fldDate",{"type":"relative","period":"month","offset":0}]}]}]'
# 过去 30 天;注意 offset 是字符串
dws aitable view update filter --base-id X --table-id Y --view-id Z \
--json '[{"operator":"and","operands":[{"operator":"from_now","operands":["fldDate",{"type":"relative","period":"day","offset":"-30"}]}]}]'
# 指定日期;timestamp 是毫秒数字,不是字符串
dws aitable view update filter --base-id X --table-id Y --view-id Z \
--json '[{"operator":"and","operands":[{"operator":"date_eq","operands":["fldDate",{"type":"exact","timestamp":1786896000000}]}]}]'
```
写入成功后仍要执行 `view get filter`。`date_eq.offset` 被保存为字符串、`from_now.offset` 被保存为数字、`timestamp` 被字符串化,或页面出现 `[object Object]` / `Invalid Date`,都不能判为成功。明确日期范围不从上述等值 Scheme 推测;在当前 Runtime Schema 没有真实可验证结构时停止。
### 常见错误拼写(CLI 会自动提示纠正) ### 常见错误拼写(CLI 会自动提示纠正)
| 错误写法 | 正确写法 | 说明 | | 错误写法 | 正确写法 | 说明 |
@@ -98,23 +142,23 @@ dws aitable record query --base-id X --table-id Y \
{"operator":"eq","operands":["fldXXX","本科"]} {"operator":"eq","operands":["fldXXX","本科"]}
``` ```
❌ **传入选项 ID 而非名称**(可能导致匹配不到 0 记录): ❌ **直接透传未确认的选项名称或展示文本**(可能重名、改名或无法转换):
```json ```json
{"operator":"and","operands":[{"operator":"eq","operands":["fldXXX","CXzrOHK9JI"]}]} {"operator":"and","operands":[{"operator":"eq","operands":["fldXXX","进行中"]}]}
``` ```
### 完整示例 ### 完整示例
单条件: 单条件(select 字段已先经 `field get`/`field search-options` 唯一解析为 option ID):
```bash ```bash
dws aitable record query --base-id X --table-id Y \ dws aitable record query --base-id X --table-id Y \
--filters '{"operator":"and","operands":[{"operator":"eq","operands":["fldStatusId","进行中"]}]}' --filters '{"operator":"and","operands":[{"operator":"eq","operands":["fldStatusId","optDoing"]}]}'
``` ```
多条件 AND: 多条件 AND:
```bash ```bash
dws aitable record query --base-id X --table-id Y \ dws aitable record query --base-id X --table-id Y \
--filters '{"operator":"and","operands":[{"operator":"eq","operands":["fldStatusId","进行中"]},{"operator":"gt","operands":["fldStockId","0"]}]}' --filters '{"operator":"and","operands":[{"operator":"eq","operands":["fldStatusId","optDoing"]},{"operator":"gt","operands":["fldStockId",0]}]}'
``` ```
分页时只透传服务端真实 `nextCursor`。出现 `pagination_cursor_cycle`、相同 cursor 或重复页时,保留已取记录并立即停止;不要用相同参数重跑,也不要切换到其他查询命令把不完整结果误报为完整。 分页时只透传服务端真实 `nextCursor`。出现 `pagination_cursor_cycle`、相同 cursor 或重复页时,保留已取记录并立即停止;不要用相同参数重跑,也不要切换到其他查询命令把不完整结果误报为完整。
@@ -13,10 +13,23 @@
| `form field update` | 更新字段必填/描述 | | `form field update` | 更新字段必填/描述 |
| `form field hide` | 在表单中隐藏/显示字段(不影响底层数据表字段) | | `form field hide` | 在表单中隐藏/显示字段(不影响底层数据表字段) |
| `form share get` | 获取分享配置 | | `form share get` | 获取分享配置 |
| `form share update` | 开启/关闭分享 | | `form share update` | 部分更新分享开关、授权、有效期和通知等配置 |
| `form questions create` | 添加题目(等价于 `field create`,命令位置上的别名) | | `form questions create` | 添加题目(等价于 `field create`,命令位置上的别名) |
| `form questions delete` | 删除题目(等价于 `field delete`,命令位置上的别名) | | `form questions delete` | 删除题目(等价于 `field delete`,命令位置上的别名) |
## 仅询问分享更新用法时
收到仅询问用法的请求后,第一步必须立即实际执行且仅执行对应命令:原子入口用 `dws aitable form share update --help`;Shortcut 入口用 `dws schema --cli-path "aitable +form-share-update" --compact --format json`。Shortcut 名称开头的 `+` 是命令名不可省略的一部分;不得改写、试探其他拼法或改用 `--help`/`-h`。
发现门禁:即使 Skill 或参考文档已提供完整示例,回答前也必须实际执行一次且仅执行一次目标 leaf 的安全 help/schema 查询;不得仅依据 Skill 或参考文档直接作答。用户仅询问用法时,最终回答必须先给出完整命令;缺少必填 ID 时则给出带明确占位符的完整命令模板,禁止猜测。随后明确说明“未传入的分享配置保持原值”;不得执行目标写操作或声称已经执行。上述只读查询是唯一允许的命令。
查询成功后,最终回答只能包含两行纯文本:不要 Markdown 代码围栏、标题、表格、回读命令或其他内容。第一行放用户所问入口的完整命令;已有的必填值必须原样使用,缺少的值必须保留为 `<BASE_ID>`、`<TABLE_ID>`、`<VIEW_ID>` 等明确占位符。第二行先列出需要替换的占位符(没有则省略替换说明),再给出固定的未执行说明,然后立即结束:
```text
dws aitable form share update --base-id <BASE_ID> --table-id <TABLE_ID> --view-id <VIEW_ID> --enabled true
请将 <BASE_ID>、<TABLE_ID>、<VIEW_ID> 替换为真实值;未传入的分享配置保持原值。本次仅查询 help/schema,未执行写操作。
```
## 建议操作顺序 ## 建议操作顺序
```bash ```bash
@@ -37,7 +50,7 @@ dws aitable form share get --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_
```bash ```bash
dws aitable form create --base-id BASE_ID --table-id TABLE_ID --name "表单名" --format json dws aitable form create --base-id BASE_ID --table-id TABLE_ID --name "表单名" --format json
dws aitable +form-share-update --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --enabled true --format json dws aitable +form-share-update --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --enabled true --form-name "表单名" --format json
dws aitable +form-share-get --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --format json dws aitable +form-share-get --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --format json
``` ```
@@ -88,7 +101,7 @@ dws aitable +form-share-get --base-id BASE_ID --table-id TABLE_ID --view-id VIEW
| 命令 | 用途 | 必填参数 | 说明 | | 命令 | 用途 | 必填参数 | 说明 |
|------|------|----------|------| |------|------|----------|------|
| `form share get` | 获取分享配置 | `--base-id` `--table-id` `--view-id` | 返回 enabled/status/shareFormUuid | | `form share get` | 获取分享配置 | `--base-id` `--table-id` `--view-id` | 返回 enabled/status/shareFormUuid |
| `form share update` | 开启/关闭分享 | `--base-id` `--table-id` `--view-id` `--enabled` | `--enabled true` 开启 / `--enabled false` 关闭。注意:UI 上"发布并分享"按钮是另一概念,本命令只切换内部 enabled 标志,开启后需在 UI 刷新页面才会看到分享面板 | | `form share update` | 部分更新分享配置 | `--base-id` `--table-id` `--view-id` + 至少一个配置参数 | 开关用 `--enabled`;访问范围用 `--auth-type-code/--auth-data`;还可更新提交次数、有效期、名称描述、匿名提交、回填和通知配置;未传字段保持原值 |
## 完整工作流示例 ## 完整工作流示例
@@ -118,13 +131,20 @@ dws aitable form field update --base-id BASE_ID --table-id TABLE_ID --view-id VI
dws aitable form field hide --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID \ dws aitable form field hide --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID \
--field-id FIELD_ID --hidden true --format json --field-id FIELD_ID --hidden true --format json
# 6) 开启分享(注意:开启后需 UI 刷新页面才会看到分享面板) # 6) 开启分享;把已知表单标题同时传给分享配置
dws aitable form share update --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID \ dws aitable form share update --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID \
--enabled true --format json --enabled true --form-name "员工信息收集" --format json
``` ```
## 返回结构补充 ## 返回结构补充
- `form list` 返回 `data.formViews[]`,**每条仅含** `viewId/name/title/createdAt`;`shareFormUuid` 不在此返回,请用 `form share get` 单独获取。 - `form list` 返回 `data.formViews[]`,**每条含** `viewId/name`(以及服务端当前可用的 `title/createdAt`);`shareFormUuid` 不在此返回,请用 `form share get` 单独获取。
- `form get` 返回结构与 `form list` 完全一致(`data.formViews[]`),仅含一条记录(与请求 viewId 一致)。Agent 提取时仍走 `data.formViews[0]`。 - `form get` 的 `data` 是客户端按 `viewId` 精确筛出的单个表单对象,不是 `formViews` 数组。Agent 直接读取 `data.viewId` / `data.name`;不存在的 `viewId` 会明确失败。
- `form field list` 仅返回**未隐藏**的字段;`hidden=true` 的字段不在此返回,如需查看全部字段请用 `field get`。 - `form field list` 仅返回**未隐藏**的字段;`hidden=true` 的字段不在此返回,如需查看全部字段请用 `field get`。
## MCP 交互注意事项
- `form field hide` 当前每次只接收一个 `fieldId`。多字段必须在同一 Base 写队列中逐个串行设置,全部完成后统一回读一次;不传数组,不并发写。
- 新建表单首次开启分享时,复用 `form create`/`form update` 中已知的标题,通过 `--form-name` 与 `--enabled true` 同时传入;不要为取标题额外调用 `form get`。已有分享仅调整其他配置时,不覆盖原名称。
- 分享开启后回读 `enabled/status/shareFormUuid`。“已开启分享”不等于“已允许匿名/免登录/组织外提交”;需按用户意图显式传入 `--anonymous-submit` 和 `--auth-type-code/--auth-data`,再通过 `form share get` 回读确认。
- 分享和字段 mutation 回执不是最终状态;必须独立读回,写超时时不原样重放。
@@ -221,4 +221,6 @@ dws aitable field update \
--format json --format json
``` ```
更新时只需传新的 `formula` 表达式,系统会自动重新计算所有记录。 更新时只需传新的 `formula` 表达式,但 mutation 成功不等于历史记录已经完成计算。先用 `field get` 核对表达式,再用 `record query --field-ids <输入字段ID,结果字段ID>` 对少量已知输入同时投影输入和结果;省略结果 fieldId 后的缺值不能判为计算失败。
公式结果持续为空或 `null` 时,先用页面、计算状态或等价只读路径交叉验证。不得通过更新业务记录来“触发重算”;配置正确但仍无法证明结果时,标记为 MCP 查询链路或平台计算问题。lookup/filterUp 同样需要用已知关联/筛选命中的小样本验证实际值,COUNT 必须与相同条件的真实记录数核对,不自动换字段规避错误。
@@ -19,7 +19,7 @@ dws aitable +record-primary-doc-get --base-id BASE_ID --table-id TABLE_ID --reco
- `--table-id`(必填):Table ID - `--table-id`(必填):Table ID
- `--record-id`(必填):Record ID - `--record-id`(必填):Record ID
**返回:** `data.nodeId` — 主键文档的 nodeId,可直接传给 `dws doc read/update` 的 `--node` 参数。若该记录尚未创建主键文档,`nodeId` 为 null。 **返回:** `data.exists` 明确表示主键文档是否已创建。`exists=true` 时同时返回 `data.nodeId`,可直接传给 `dws doc read/update` 的 `--node` 参数;`exists=false` 时不返回 `nodeId`,可调用下方创建命令。
### 创建主键文档 ### 创建主键文档
@@ -0,0 +1,139 @@
# AI 表格 PostgreSQL 只读查询
## 适用场景
仅当用户明确要求 SQL / PostgreSQL / `SELECT` 且目标需要以下任一 psql 专属能力,或需求本身包含以下任一复杂服务端分析时,使用 `dws aitable psql`:
- 查看 PostgreSQL 逻辑表清单或逻辑列类型
- 最多 8 张同 Base 数据表的 `INNER`、`LEFT`、`RIGHT`、`FULL OUTER JOIN`;`CROSS JOIN` 仅支持两张表
- 字段间算术、`CASE`、聚合后派生指标、汇总结果 Top N 或排名
- `ROW_NUMBER`、`RANK`、`DENSE_RANK` 等窗口函数,或必须依赖 PostgreSQL 类型语义的计算
原始记录筛选、排序和 Top N 使用 `record query`;单表直接标量、分组或去重统计使用 `record stats` / `record group-stats`。读取字段配置、选项、公式配置时仍使用 `field get`;只有用户关心 SQL 可查询列及 PostgreSQL 类型时才使用 `psql -t`。
## 查询路由与降级
| 查询需求 | 首选接口 | 原因 |
|---|---|---|
| 按 recordId、关键词或字段条件读取原始记录,或对原始记录筛选、排序、取 Top N | `record query` | 直接返回 `recordId`、`cells` 和 cursor 等记录模型。 |
| 单表直接标量、分组或去重统计 | `record stats` / `record group-stats` | 由原生统计接口完成,不需要 SQL。 |
| 已获用户明确许可的完整逐行明细或逐条业务操作(不做分析) | `dws aitable record query --all --page-limit 0` | 由 CLI 统一处理完整扫描;必须服务端过滤、只取必要字段并先取小样本。 |
| 完整数据导出 | `dws aitable export data` | 导出是文件交付,不得把记录拉到 Agent 上下文做分析。 |
| 同 Base 多表关联、字段间算术、CASE、聚合后派生、汇总结果 Top N 或排名、窗口函数 | `psql` | 先核对表和列,再用 SQL 在服务端完成原生接口无法直接表达的分析。 |
当需求需要 `psql` 的复杂分析能力时,即使用户没有明确说 SQL,也必须使用 `psql`。`psql` 执行失败后,先保留真实错误并从原始用户意图重新判定:仅当需求完全是单表原始记录筛选、排序、Top N 或逐条操作时,才可重新发起 `record query`;仅当需求完全是单表直接标量、分组或去重统计时,才可重新发起 `record stats` / `record group-stats`。切换时必须说明原因、丢弃 psql 的未完成结果,且不合并两类结果;不得用这些接口拆分或模拟 JOIN、字段计算、CASE、聚合后派生、汇总排名、窗口计算或本地分析。
### 正反边界案例
| 用户请求或结果需求 | 应选 | 不应选 / 原因 |
|---|---|---|
| “金额最高的 10 条记录”“按日期筛选原始记录” | `record query --sort ... --limit 10` | 排序对象是原始记录,不因 SQL 也支持 `ORDER BY` 改走 psql。 |
| “本月订单总金额”“各状态分别多少条”“唯一门店数” | `record stats` / `record group-stats` | 单表直接标量、分组或去重统计,不需要 SQL。 |
| “把两张表关联,找未匹配记录”“各门店销售额占总额比例” | `psql` | 需要 JOIN 或聚合后派生,不能分多次 `record query` 后拼接。 |
| “销售额最高的 10 个门店并排名” | `psql` | 排序对象是聚合结果,需要汇总后排序或窗口排名。 |
| “下载/交付完整原始数据文件” | `export data` | `psql` 不承担无界文件导出,`record query --all` 也不替代导出。 |
| “读取这几个 recordId 的 cells”“按字段条件查看一页记录”“需要 cursor、recordId 或原始字段类型” | `record query` | 这是结构化记录读取,不应为了少量详情改写成 SQL。 |
| “按条件找出记录后逐条更新/删除/分享” | `record query` | 仅用它定位有限的业务记录;范围依赖聚合后派生或关联时,先用 `psql` 确定范围。 |
## 服务端分析强制规则
- 已选用 `psql` 的 JOIN、字段间算术、CASE、聚合后派生、汇总排名和窗口计算,必须在服务端 SQL 中完成。禁止拉取明细后使用 Python、jq、JavaScript、电子表格或其他本地工具做等价加工。
- 全程固定使用一个已验证可执行的 DWS 二进制或包装脚本,不得混用内置 shim、本地版或其他版本。依次执行 `psql -l`、每张目标表的 `psql -t`、使用真实 `Name` 的 `LIMIT 3` 最小查询;最小查询成功后才执行正式 SQL。
- 按业务主题拆分 SQL;同一事实粒度、同一关联链的过滤、JOIN、聚合和派生指标应合并到一条 SQL。关键结论必须用独立 SQL 在服务端复核。
- 出现 `pending-post-tool-use`、`host-side execution`、`PostToolUse hook did not activate` 或 `real result was not produced`,属于客户端或宿主执行失败:切换到正确的同一 DWS 入口后重试原 psql 命令。网络、认证或权限失败也必须先修复后重试;这些故障不得以其他查询接口绕过。
- SQL 或 psql 能力失败时,先缩减为最小查询,再逐步加入 WHERE、JOIN、GROUP BY 和窗口计算;语法或函数不支持时先在服务端改写或拆成多条 SQL。仅当回看原始意图后完全符合单表原始记录,或单表直接统计时,才允许丢弃 psql 未完成结果并重新发起 `record query`,或 `record stats` / `record group-stats`;必须说明切换原因,禁止合并结果或本地补算。对 JOIN、字段间算术、CASE、聚合后派生、汇总结果 Top N 或排名、窗口计算,始终先修复或在服务端等价改写,禁止降级。已获用户明确许可的非分析完整逐行明细不依赖也不触发此 psql 门禁,按 `record query --all` 的规则执行;即使获准,也必须服务端过滤、只取必要字段、先取小样本;预计超过 5,000 条、20 个字段、20MB 或需要 `--all` 时,必须再次确认。
## 命令模式
`-l`、`-t`、`-c` 三种模式互斥。`-t` 仅用于查看表结构;执行 SQL 时表由 `FROM` / `JOIN` 自动解析,无需传 `-t`。
### 命令生成约束
- 必须按以下模板生成完整命令,不得省略 `-d` 及其值。
- `<BASE_ID>`、`<TABLE_ID>` 是文档占位符;实际执行前必须替换为上下文中的真实 ID,并用单引号包裹。
- 常规模式参数仅使用 `-d`、`-l`、`-t`、`-c`;查看全属性列可使用 `--all-properties`,执行约束可使用 `--limit`、`--timeout`;禁止改写为 `--base-id`、`--table-id`、`--sql` 或 `--base`。
- 查看表结构时,默认只查看与 AI 表格页面一致的基础字段(默认属性),不得因字段类型复杂而自动添加 `--all-properties`。仅当用户显式要求查看全部扩展属性时,才添加该参数。
- 不得使用不存在的 `dws aitable table list` 作为回退;表发现只能使用 `psql -d '<BASE_ID>' -l`。
- 本文已明确命令契约时,不得通过 `dws aitable psql --help` 判断功能是否存在。
- 执行前向用户展示命令时,必须展示即将执行的完整命令,不能简写为 `dws aitable psql -l`。
- 命令失败时保留并依据真实错误处理;不得据此猜测“命令尚未实现”或“功能未发布”。
```bash
# 列出 Base 内可通过 PostgreSQL 查询的逻辑表
dws aitable psql -d <BASE_ID> -l
# 默认查看与页面一致的基础字段;仅用户显式要求全部扩展属性时增加 --all-properties
dws aitable psql -d <BASE_ID> -t <TABLE_ID>
dws aitable psql -d <BASE_ID> -t <TABLE_ID> --all-properties
# 执行只读 SQL
dws aitable psql -d <BASE_ID> \
-c 'SELECT * FROM "数据表1" LIMIT 10'
```
`psql` 输出是面向用户的 PostgreSQL 表格文本,不支持也不添加全局 `--format json`。这是 AI 表格 Skill 中“结构化读取使用 `--format json`”规则的明确例外。
## 返回模型与类型边界
- `psql` 的 `SELECT` 结果是 PostgreSQL 列/行投影后渲染出的表格文本;复杂值可能被展示为 JSON 文本。SQL 列名、别名、PostgreSQL 类型和输出行均不等同于 `record query` 的 `fieldId`、`recordId`、`cells`、`status` 或 `nextCursor`。
- `record query` 返回结构化记录模型及分页元数据,字段和值遵循 AI 表格字段类型和记录语义;它不是 PostgreSQL 行集,也不能直接作为 SQL 表名、列名、类型或 JOIN 条件。
- 同一次业务查询只能选择一个结果模型:禁止把 `psql` 的表格值、列名或别名拼入 `record query` 的参数或响应;也禁止把 `record query` 的记录、cells 或 cursor 当作 `psql` 的 SQL 输入或结果继续处理。
- 后续步骤确实需要另一种模型时,必须从原始用户意图重新发起对应查询,并明确说明切换原因和结果来源;不得合并两类结果后再做关联、聚合、类型推导或权限判断。需要 SQL 列和类型时先执行 `psql -t`;需要记录 ID、字段 ID、cells、status 或 cursor 时使用 `record query` / `field get`。
## 自然语言路由
| 用户意图 | 执行方式 |
|---|---|
| “这个 AI 表格里有哪些可查询的数据表” | `psql -d <baseId> -l` |
| “数据表1有哪些 SQL 字段和类型” | 先 `-l` 解析真实 `tableId`,再 `psql -d <baseId> -t <tableId>` |
| “查询数据表1前 10 条”,即使上下文明确要求 SQL | `record query --limit 10`;这是原始记录读取,SQL 表述不改变结果模型路由 |
| “把数据表1、数据表2和数据表3关联起来”或“分析不同表之间的关系” | 优先使用 psql:先列出表并查看每张表的结构,再生成一条多表 JOIN SQL;表由 SQL 自动解析 |
| “按业务状态统计数量” | `record group-stats`,单表直接分组统计不需要 SQL |
| 明确要求 SQL,且统计后还需派生、聚合结果 Top N 或排名、窗口计算 | 优先使用 psql:先查看逻辑结构,再生成对应 SQL;仅明确要求 SQL 的单表直接标量、分组或去重统计仍使用 `record stats` / `record group-stats`。 |
| “按分组排名/生成行号” | 先查看逻辑结构,再生成使用 `ROW_NUMBER/RANK/DENSE_RANK ... OVER (...)` 的 SQL |
用户只给表名时,必须先用 `psql -l` 获取真实 `tableId`;零命中或重名时要求用户消歧,禁止猜测。编写 SQL 前必须用 `psql -t` 核对实际逻辑列名和 PostgreSQL 类型。
## SQL 生成规则
- 仅允许一条标准 PostgreSQL / pgsql 语法的只读 `SELECT`;禁止 `INSERT`、`UPDATE`、`DELETE`、DDL 和多语句。
- 必须使用 PostgreSQL 方言:禁止 MySQL/SQLite/Oracle 等非 PostgreSQL 写法,例如反引号标识符、`LIMIT offset,count`、`IFNULL`、`DATE_FORMAT`、`NVL`、`DUAL` 等。
- SQL 生成完成后、执行前必须自检:所有来自 AI 表格元数据的逻辑表名、逻辑字段名和中文结果别名均使用双引号;SQL 关键字、函数名、表别名不加引号。禁止依赖未加引号的中文标识符“恰好可被解析”。
- 双引号仅用于 SQL 标识符,单引号仅用于字符串字面量。例如必须生成 `SELECT COUNT(*) AS "记录数" FROM "核销明细" LIMIT 200`,不得生成 `SELECT COUNT(*) AS 记录数 FROM 核销明细`,也不得将字符串写成双引号。
- 命令中的完整 SQL 使用 shell 单引号包裹,SQL 标识符使用双引号;不得用 shell 外层双引号代替 SQL 标识符的双引号。
- `-c` 传入的 SQL 末尾不得包含分号(`;`);服务会将 SQL 封装为子查询,尾部分号会导致 PostgreSQL 语法错误。
- JOIN 表必须属于同一个 Base;最多引用 8 张表,执行时由 SQL 的 `FROM` / `JOIN` 自动解析表。
- 支持 `INNER`、`LEFT`、`RIGHT`、`FULL OUTER JOIN`;`ON` 仅支持相同 PostgreSQL 标量类型的跨表字段等值比较及 `AND` 组合。
- `CROSS JOIN` 仅支持两张表,不能串联其他 JOIN,且不得携带 `ON` 或 `USING`。
- 支持聚合函数 `COUNT`、`SUM`、`AVG`、`MIN`、`MAX`;`GROUP BY` 仅支持列引用,`HAVING` 仅用于分组或聚合查询。
- 支持窗口函数 `ROW_NUMBER`、`RANK`、`DENSE_RANK`,以及上述聚合函数的 `OVER` 形式;不生成命名 `WINDOW`、窗口框架或窗口内 `DISTINCT`。
- 支持多列 `ORDER BY`、`ASC/DESC`、`NULLS FIRST/LAST`、`LIMIT` 和 `OFFSET`。
- `LIMIT` 生成规则:
- 用户明确限制条数、页大小或“前 N 条”时,必须把该限制写入 SQL 的 `LIMIT n`。
- 用户没有明确限制条数时,必须在 SQL 的合法位置主动添加 `LIMIT 200`;若同时使用 `OFFSET`,应生成 PostgreSQL 合法顺序 `LIMIT 200 OFFSET n`。
- 用户要求全量数据时,也必须说明实际执行仍受工具 `--limit 1..1000` 和 `--timeout 1..60` 上限约束,禁止生成无限制大结果查询。
## 查询结果返回规则
- 将原始 psql 表格结果返回用户,避免擅自改写为 JSON。
- 每次成功展示查询结果后,必须追加温馨提示:`温馨提示:请确认当前 AI 表格是否开启高级权限;开启后,返回的字段和数据均会受到高级权限影响。`
## 多表 JOIN 示例
```bash
dws aitable psql \
-d '<BASE_ID>' \
-c 'SELECT a."业务名称", COUNT(c."文本") AS "数量" FROM "数据表1" a LEFT JOIN "数据表2" b ON a."业务名称" = b."文本" LEFT JOIN "数据表3" c ON b."文本" = c."文本" GROUP BY a."业务名称" LIMIT 200'
```
## 执行流程
1. 固定一个已验证可执行的 DWS 二进制或包装脚本;从用户提供且已确认是 AI 表格的 URL 提取 `baseId`,没有 URL 或 ID 时按现有 Base 搜索流程定位。
2. 执行 `psql -d <baseId> -l`,获取真实表名与 `tableId`;SQL 的 `FROM`/`JOIN` 只能使用返回的 `Name`。
3. 对 SQL 涉及的每张表执行 `psql -d <baseId> -t <tableId>`,默认仅查看与页面一致的基础字段;仅当用户显式要求查看全部扩展属性时增加 `--all-properties`。
4. 先执行 `LIMIT 3` 的最小 PostgreSQL 只读查询;失败时按服务端分析强制规则分类并缩减 SQL。仅当从原始意图重新判定为单表原始记录或单表直接统计时,才可丢弃未完成的 psql 结果并重新发起对应原生接口;复杂分析禁止降级。
5. 最小查询成功后,根据用户自然语言生成正式 SQL;用户没有明确限制条数时必须添加 `LIMIT 200`,遇到重名或语义不明确先询问;同一事实粒度和关联链尽量合并为一条 SQL。
6. 执行 `psql -d ... -c ...`,由 SQL 中的 `FROM` / `JOIN` 确定目标表;关键结果使用独立 SQL 在服务端复核,并将原始 psql 表格结果返回用户。
7. 查询结果展示完成后,追加高级权限温馨提示,提醒用户开启高级权限后字段和数据均会受权限影响。
不得为了模拟真实用户而要求用户自己编写 SQL、提供 fieldId,或在每次对话中粘贴 dws 命令;Agent 应完成表和列发现、SQL 生成与执行。
@@ -25,23 +25,48 @@ Flags:
两种模式: 按 ID 取(传 record-ids,忽略 filters/sort)或条件查(filters+sort+cursor 分页)。 两种模式: 按 ID 取(传 record-ids,忽略 filters/sort)或条件查(filters+sort+cursor 分页)。
## 与 psql 的正反边界案例
| 用户请求或结果需求 | 应选 | 不应选 / 原因 |
|---|---|---|
| “给我 rec1、rec2 的完整 cells”“按已知字段条件看前 30 条”“继续下一页” | `record query` | 需要 `recordId`、`cells`、字段类型或 cursor 的少量结构化记录,不需要统计接口或 psql。 |
| “找出符合条件的记录后逐条更新、删除、分享或写回” | `record query` | 仅拉取实际要操作的有限记录,并用 `field-ids`、filters 和 limit 缩小范围。 |
| “金额最高的 10 条记录” | `record query --sort ... --limit 10` | 排序对象是原始记录;不要仅因 psql 也能执行 `ORDER BY` 就改走 SQL。 |
| “本月订单总金额”“各状态分别多少条”“满足条件的唯一门店数” | `record stats` / `record group-stats` | 这是单表直接标量、分组或去重统计;禁止 `record query --all` 后本地聚合。 |
| “各门店销售额占总额比例”“销售额最高的 10 个门店并排名” | `psql` | 需要先聚合,再计算占比、排序或排名;不能依赖 group-stats 的 limit 产生 Top N。 |
| “跨表匹配、关联、派生指标或窗口计算” | `psql` | record query 不支持 SQL JOIN 或 PostgreSQL 类型语义,不能靠多次读取后拼接。 |
| “交付完整原始数据文件” | `export data` | record query 不是导出接口;psql 也不承担无界文件导出。 |
| psql 已失败,且回看原始意图后完全是有限单表记录读取、筛选、排序、Top N 或逐条操作 | `record query` | 丢弃 psql 未完成结果,说明切换原因并重新以记录模型查询;不得复用 psql 输出或借此模拟复杂分析。 |
大量拉取数据用于分析或后续计算时,先判断能否由 `record stats` / `record group-stats` 直接完成;涉及 JOIN、字段间算术、聚合后派生、CASE、Top N 或排名时使用 `psql`。只有用户明确需要完整逐行明细时才用 `record query --all`,需要完整原始文件时使用 `export data`。
## 自动翻页(--all + --page-limit) ## 自动翻页(--all + --page-limit)
- 传入 `--all` 启用自动翻页,CLI 自动循环获取并合并所有记录后统一输出 - `--all` 仅用于用户已明确许可的完整逐行明细或逐条业务操作;它不是数据分析接口。未经明确许可,禁止 `record query --all`、无限制分页、整表导出或拉取大量字段。
- `--page-limit` 控制最大翻页次数,默认 50 页(5000 条),设为 0 表示无限制 - 原始记录过滤、排序和 Top N 直接使用 `record query` 的服务端 filters/sort/limit;单表直接标量、分组或去重统计使用 `record stats` / `record group-stats`;JOIN、字段间算术、聚合后派生、分档、日期运算、窗口计算和汇总结果排名使用 `dws aitable psql`。禁止因任一服务端能力报错、超时或查询编写困难而全量拉取记录后在 Agent、本地脚本或电子表格中计算。
- 页间间隔 200ms,中途网络错误会 graceful stop 并输出已获取的数据 - 用户需要导出完整数据时,优先使用 `dws aitable export data`;导出的文件不得被拉回 Agent 上下文用于等价分析。
- **被截断时**(达到 page-limit 但仍有数据):输出中包含 `"hasMore": true` 和 `"cursor": "..."` 字段,可通过 `--cursor` 从断点继续拉取 - 已获明确许可的非分析完整逐行明细或逐条业务操作可直接使用 `--all`,不需要也不触发 `psql -l`、`psql -t` 或最小查询探测。psql 失败后,只有回看原始意图确认其完全属于该非分析明细场景时,才可丢弃 psql 未完成结果并重新以 `--all` 查询;它不能作为复杂 psql 分析的降级。使用前必须说明字段、范围、预计记录数和体积;即使获准也必须服务端过滤、仅取必要字段、先取小样本;预计超过 5,000 条、20 个字段、20MB 或需要 `--page-limit 0` 时必须再次确认。
- 适用于需要一次性获取全量数据的场景(如导出、统计、批量处理) - CLI 首次请求不传 cursor,后续原样使用上一页 `data.nextCursor`,并检测 cursor 循环;页间间隔 200ms。
- 同一分页会话的 `base-id/table-id/filters/sort/query/field-ids/limit` 必须保持不变,禁止重发第一页、复用旧会话 cursor、修改排序或自行构造 cursor。
- 只有自动分页输出 `complete=true`,或手动分页时 `data.nextCursor` 为空,才表示完整结束。普通扫描某页恰好返回 `limit` 条时,服务端可能返回 `nextCursor`;用它续页后若调用成功、`records=[]` 且 `nextCursor` 为空,这是正常的末页探测,应正常完成,不能报错、重试或判定漏查。
- 成功页的 `records=[]` 本身既不是错误也不是结束条件:`nextCursor` 非空就继续,`nextCursor` 为空就正常结束。
- 达到 page-limit、网络错误、缺少 `records` 的空响应、非法响应或 cursor 循环时,命令返回非零结构化错误;错误详情保留已取记录和续传 cursor。必须标记结果不完整,禁止把 partial 当 success 或据此给出全量结论。
```bash ```bash
# 默认(最多 50 页 = 5000 条) # 仅限已获明确许可的非分析明细:自动翻页直到 nextCursor 为空
dws aitable record query --base-id X --table-id Y --all dws aitable record query --base-id X --table-id Y --all --page-limit 0 --format json
# 无限制(拉完为止)
dws aitable record query --base-id X --table-id Y --all --page-limit 0 # 从结构化错误 details.incomplete_result.cursor 断点续传;其余查询条件必须与原请求完全一致
# 从上次断点继续 dws aitable record query --base-id X --table-id Y --filters '<原 filters>' --sort '<原 sort>' --all --page-limit 0 --cursor '<原样 cursor>' --format json
dws aitable record query --base-id X --table-id Y --all --cursor "上次返回的cursor"
``` ```
## 手动分页状态机
1. 第一页不传 `--cursor`,读取 `data.records` 和 `data.nextCursor`。
2. `nextCursor` 非空时,将其原样作为下一次 `--cursor`;不得使用当前请求 cursor,也不得自行拼接或复用更早页 cursor。
3. 每一页保持全部查询条件不变并累计记录;如需防御性校验,以 `recordId` 检测重复,但重复 cursor 必须按异常停止,不能静默继续。
4. `nextCursor` 为空才结束。成功返回的空页是合法结果:若 `records=[]` 且 `nextCursor` 为空,则正常完成;若 `records=[]` 但 `nextCursor` 非空,则继续查询,不能把空页当作异常或自行重试当前 cursor。
## 排序参数规范 ## 排序参数规范
`--sort` 需要传 JSON 数组,排序方向字段必须是 `direction`(`asc` 或 `desc`),不要使用 `order`。 `--sort` 需要传 JSON 数组,排序方向字段必须是 `direction`(`asc` 或 `desc`),不要使用 `order`。
@@ -55,12 +80,16 @@ dws aitable record query --base-id X --table-id Y --all --cursor "上次返回
详细语法见 [aitable-filter-sort.md](./aitable-filter-sort.md)。 详细语法见 [aitable-filter-sort.md](./aitable-filter-sort.md)。
> **查询指定数据前先读表头**:先用 `dws aitable field get --base-id <BASE_ID> --table-id <TABLE_ID> --format json`(不加 `--field-ids`)完整读一遍表头,拿到所有字段的 `fieldId`/`name`/`type`/`config`;从表头中确定用户条件对应哪个字段,再按该字段类型解析值并传入 filters/sort,禁止跳过读表头凭猜测选字段。
快速模板: 快速模板:
```json ```json
{"operator":"and","operands":[{"operator":"eq","operands":["<fieldId>","<value>"]}]} {"operator":"and","operands":[{"operator":"eq","operands":["<fieldId>","<value>"]}]}
``` ```
> **singleSelect/multipleSelect 过滤**:filters 中可传 option id 或 option name,但建议优先用 **option id**(通过 `field get` 获取),更可靠。 > **singleSelect/multipleSelect 过滤**:先通过 `field get` 或 `field search-options` 将用户输入唯一解析到现有选项,filter 优先传稳定 option ID;当条件涉及 multipleSelect 或其他数组型字段时,第二个 operand 必须是 option ID/稳定 ID 数组(如 `"operands":["fldMulti",["optA"]]`),不能传裸字符串;`record create/update` 仍传 option name。不要把模糊名称或未确认的原始文本直接透传。
>
> **人员/部门/群组过滤**:禁止把姓名、部门名或群名直接放入 filters。必须先调用 `dws aisearch person --keyword "<姓名>" --dimension name`、`dws contact +resolve-dept --name "<部门名>"`、`dws chat +chat-search --query "<群名>" --page-all`,唯一取得 `userId`、`deptId`、`openConversationId`,再分别传 `[{"userId":"..."}]`、`[{"departmentId":"..."}]`、`[{"cid":"..."}]`。零命中或多命中必须停止并消歧。
## 减少响应体积 ## 减少响应体积
@@ -68,10 +97,13 @@ dws aitable record query --base-id X --table-id Y --all --cursor "上次返回
## 常见错误 ## 常见错误
- 不先读表头就组装条件 → 必须先 `field get` 完整读一遍表头,确定用户条件对应的字段后再传值,禁止凭字段名猜 fieldId 或猜类型
- 直接把字段中文名、人员姓名、部门名、群名或关联记录标题放进 filters → 必须先解析为 fieldId 和对应稳定 ID;人员/部门/群组还必须传结构化 ID 数组,不能传裸字符串、标量 ID 或单个对象
- `--filters` 根节点直接用 `"operator":"eq"` → API 静默忽略,返回全表 - `--filters` 根节点直接用 `"operator":"eq"` → API 静默忽略,返回全表
- `--sort` 用 `"order":"desc"` → 必须用 `"direction":"desc"` - `--sort` 用 `"order":"desc"` → 必须用 `"direction":"desc"`
- 不加 `--field-ids` 拉全字段 → 大表响应体积过大 - 不加 `--field-ids` 拉全字段 → 大表响应体积过大
- 全量拉取后在 context 里手动统计 → 应优先用 `--filters` 服务端过滤 - 只读取第一页或以 `records=[]` 判定结束 → 必须检查 `nextCursor`,完整任务优先 `--all --page-limit 0`
- 全量拉取后在 context、本地脚本、Python、jq、JavaScript 或电子表格中手动分析 → 必须停止;原始记录过滤、排序和 Top N 仍使用 `record query` 的服务端能力;仅同 Base JOIN、字段间算术、CASE、聚合后派生、汇总结果排序或排名、窗口计算等复杂分析改用 `aitable psql` 在服务端完成。psql 失败后,只有原始意图完全属于单表原始记录或单表直接统计时才可重新发起对应原生接口;复杂分析必须修复并重试,禁止以 `record query --all` 降级。
## record query-empty — 找空行 ## record query-empty — 找空行
@@ -33,15 +33,15 @@ dws aitable record group-stats \
--base-id <BASE_ID> \ --base-id <BASE_ID> \
--table-id <TABLE_ID> \ --table-id <TABLE_ID> \
--group '[{"fieldId":"<GROUP_FIELD_ID>","direction":"ASC","fieldConfig":null,"arraySplitMode":true}]' \ --group '[{"fieldId":"<GROUP_FIELD_ID>","direction":"ASC","fieldConfig":null,"arraySplitMode":true}]' \
--stats '[{"fieldId":"<VALUE_FIELD_ID>","statsType":"avg"}]' \ --stats '[{"fieldId":"<VALUE_FIELD_ID>","statsType":"AVG"}]' \
--limit 1000 \ --limit 1000 \
--format json --format json
``` ```
- `statsType` 必须小写;基础类型为 `sum`、`avg`、`count`、`max`、`min`,后端还可能支持 `median`、`distinct`、`distinct_ratio` 等高级类型。 - `statsType` 与 `record stats` 统一使用大写枚举,支持 `SUM`、`AVG`、`COUNT`、`MEDIAN`、`DISTINCT`、`DISTINCT_RATIO` 等类型。
- `--group` 和 `--sort` 都是 JSON 数组编码后的字符串,CLI 会原样映射到 MCP 的 `group` / `sortDsl`。 - `--group` 和 `--sort` 都是 JSON 数组编码后的字符串;`--sort` 的 fieldId 必须同时出现在 `--group` 中。
- 分组结果最多 1000 行。不要依赖服务端 limit 选择 Top N;应在基数不超过 1000 时取完整分组结果后排序。 - 分组结果最多 1000 行;省略 `--limit` 时使用服务端默认上限 1000,服务端在聚合和排序完成后应用限制。
- 条件唯一实体计数不传 `--group`,对实体字段使用 `distinct`。 - 条件唯一实体计数不传 `--group`,对实体字段使用 `DISTINCT`。
## 过滤条件 ## 过滤条件
@@ -54,6 +54,16 @@ dws aitable record group-stats \
- 单选/多选字段建议使用 `field get` 返回的 option ID。 - 单选/多选字段建议使用 `field get` 返回的 option ID。
- 所有 Base、table、field 和 option ID 都必须从当前目标 Base 的实时元数据取得,不能复用示例或历史 ID。 - 所有 Base、table、field 和 option ID 都必须从当前目标 Base 的实时元数据取得,不能复用示例或历史 ID。
## 与 record query / psql 的正反边界
| 用户请求 | 应选 | 不应选 / 原因 |
|---|---|---|
| “本月订单总金额”“平均客单金额”“最大单笔金额” | `record stats` | 单表直接标量聚合;禁止 `record query --all` 后本地计算。 |
| “各状态分别多少条”“满足条件的唯一门店数” | `record group-stats` | 单表直接分组或去重;不需要 SQL。 |
| “金额最高的 10 条记录” | `record query --sort ... --limit 10` | 排序对象是原始记录,不是聚合结果。 |
| “销售额最高的 10 个门店并排名” | `psql` | 需要先分组聚合,再对汇总结果排序或排名;不能依赖 group-stats 的 limit 产生 Top N。 |
| “各门店销售额占总额比例”“关联订单表与门店表统计城市销售额” | `psql` | 涉及聚合后派生或同 Base 多表 JOIN。 |
## 降级边界 ## 降级边界
只有用户要求记录明细、少量校验样本、精确分位数输入,或聚合接口明确失败时,才使用 `record query`。需要先逐行运算再聚合的指标必须依赖表内已有且可直接聚合的公式字段;没有该字段时停止并请用户先在 AI 表格页面创建,不能遍历全表本地二次计算。 原生统计不能直接表达时,先判断是否可由 `psql` 完成。只有用户明确要求记录明细或少量校验样本,才使用 `record query`;聚合接口明确失败不构成直接全量拉取的理由。需要先逐行运算再聚合的指标优先由 `psql` 在服务端计算;若 PSQL 也无法表达,停止并说明具体限制,不得遍历全表后在 Agent、脚本或电子表格中二次计算。
@@ -59,6 +59,10 @@ dws aitable record query --base-id <BASE_ID> --table-id <TABLE_ID> \
更新响应不返回“受影响字段”;以 `data.recordIds[]` 确定成功记录,再用查询回读验证。 更新响应不返回“受影响字段”;以 `data.recordIds[]` 确定成功记录,再用查询回读验证。
## 附件更新边界
同一批 `update_records` 不能同时包含附件清空(值为 `[]` 或 `null`)与附件新增/替换。有这两类变更时必须拆成两次 `record update` 调用,每次分别回读;DWS 不会根据 cell 形状猜测字段类型并自动拆批。
## 引号转义提示 ## 引号转义提示
- Linux/macOS:外层用单引号 `'[...]'`,内部 JSON 用双引号即可 - Linux/macOS:外层用单引号 `'[...]'`,内部 JSON 用双引号即可
@@ -103,12 +103,43 @@ Kanban/Gallery 等视图只调整列顺序,不尝试冻结。
### view update filter / sort / group(通用,纯 --json) ### view update filter / sort / group(通用,纯 --json)
```bash ```bash
dws aitable view update filter --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --json '[{"operator":"eq","operands":["fldX","value"]}]' # 平铺条件默认 AND
dws aitable view update filter --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --json '[{"operator":"eq","operands":["fldA","x"]},{"operator":"eq","operands":["fldB","y"]}]'
# OR 的 filter 数组中只放一个 OR 根节点
dws aitable view update filter --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --json '[{"operator":"or","operands":[{"operator":"eq","operands":["fldA","x"]},{"operator":"eq","operands":["fldB","y"]}]}]'
dws aitable view update sort --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --json '[{"fieldId":"fldX","direction":"asc"}]' dws aitable view update sort --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --json '[{"fieldId":"fldX","direction":"asc"}]'
dws aitable view update group --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --json '[{"fieldId":"fldX","direction":"asc"}]' dws aitable view update group --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --json '[{"fieldId":"fldX","direction":"asc"}]'
``` ```
> filter/sort/group 入参格式与 `record query --filters`(对象格式)**不同**:view config 这边外层必须是数组。传对象 CLI 会自动 wrap,建议直接用数组。详见 [aitable-filter-sort.md](./aitable-filter-sort.md)。 日期筛选使用 View 专用结构化 Scheme,不使用 `record query` 的日期字符串写法:
```json
[{"operator":"and","operands":[
{"operator":"date_eq","operands":["fldDate",{"type":"relative","period":"month","offset":0}]},
{"operator":"from_now","operands":["fldDate",{"type":"relative","period":"day","offset":"-30"}]}
]}]
```
`date_eq.relative.offset` 和 `date_eq.exact.timestamp` 必须是 JSON number 整数;`from_now.offset` 必须是 JSON string。完整规则与指定日期示例见 [aitable-filter-sort.md](./aitable-filter-sort.md#view-日期-scheme仅-view-update-filter)。
人员、部门、群组筛选必须使用稳定身份;只有显示名称时,也可以显式传 `entityName`,CLI 会先调用 `search_entities`,仅在唯一精确匹配且候选分页完整时执行更新:
```bash
# 只搜索候选,不更新 View
dws aitable entity search --entity-type PERSON --keyword "张三" --format json
dws aitable entity search --entity-type DEPARTMENT --keyword "客户成功部" --format json
dws aitable entity search --entity-type GROUP --keyword "项目群" --format json
# 已知稳定身份直接写;只有名称时让 CLI 先解析
dws aitable view update filter --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --json '[{"operator":"eq","operands":["fldOwner",{"userId":"staff1","corpId":"dingCorp"}]}]'
dws aitable view update filter --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --json '[{"operator":"eq","operands":["fldDept",{"entityName":"客户成功部"}]}]'
```
稳定身份分别是:人员 `{"userId":"...","corpId":"..."}` 或 `{"userRef":"..."}`,部门 `{"departmentId":"..."}`,群组 `{"cid":"..."}` 或 `{"openConversationId":"..."}`(二选一)。DWS 原样传递群组标识,MCP 负责将 `openConversationId` 转成持久化的 `cid`;不要传裸名称字符串,也不要同时传两种群组标识。零命中、重名、模糊命中或候选分页不完整时停止,不执行 `update_view`。
人员身份写入后,旧服务可能只回读内部人员 key,CLI 无法把它与外部稳定身份安全比较。此时返回 `reason=view_filter_verification_unknown`、`execution_started=true`、`details.status=unknown` 和 `details.verified=false`;这表示更新已经提交但未证实最终状态,禁止自动重放,应先读取当前 View 再决定后续动作。
> filter/sort/group 入参格式与 `record query --filters`(根对象格式)**不同**:view config 写入时外层必须是数组。平铺 filter 表示 AND;数组中唯一的 `and`/`or` 根节点表示显式逻辑。当前只支持一层统一 AND 或 OR,拒绝混合嵌套;`[]` 清空筛选。传单个对象时 CLI 会自动 wrap,建议直接使用数组。详见 [aitable-filter-sort.md](./aitable-filter-sort.md)。
### view update name(重命名) ### view update name(重命名)
@@ -13,7 +13,7 @@
| `workflow list` | 列出 Base 下所有工作流(含状态/创建人/最后修改时间),支持分页 | | `workflow list` | 列出 Base 下所有工作流(含状态/创建人/最后修改时间),支持分页 |
| `workflow get` | 获取单个工作流详情(含 flowSchema 完整节点定义) | | `workflow get` | 获取单个工作流详情(含 flowSchema 完整节点定义) |
| `workflow enable` | 启用指定工作流(按配置的触发条件自动执行) | | `workflow enable` | 启用指定工作流(按配置的触发条件自动执行) |
| `workflow disable` | 禁用指定工作流(高危,建议 `--yes` 二次确认) | | `workflow disable` | 禁用指定工作流(高危,必须先取得用户明确确认) |
| `workflow run` | 立即执行指定工作流(会产生真实副作用,需确认) | | `workflow run` | 立即执行指定工作流(会产生真实副作用,需确认) |
| `workflow history` | 按状态、时间和分页条件查询工作流执行历史 | | `workflow history` | 按状态、时间和分页条件查询工作流执行历史 |
@@ -91,7 +91,7 @@ create 和 update 都必须同时满足 `status=success`、`data.valid=true`、`
dws aitable workflow edit-example --format json dws aitable workflow edit-example --format json
``` ```
该命令无业务参数,调用 `aitable/edit_workflow_example` 返回服务端提供的工作流编辑文档和示例。创建或更新复杂工作流前优先调用它,避免依赖可能过期的本地 DSL 结构。 该命令无业务参数,优先调用 `aitable/get_workflow_dsl_docs`;若当前 MCP 仅注册了历史名称,则在明确收到 `TOOL_NOT_FOUND` 后回退到 `aitable/edit_workflow_example`。创建或更新复杂工作流前优先调用它,避免依赖可能过期的本地 DSL 结构。
### workflow create — 创建并发布工作流 ### workflow create — 创建并发布工作流
@@ -286,14 +286,14 @@ dws aitable workflow enable --base-id BASE_ID --workflow-id WORKFLOW_ID --format
### workflow disable — 禁用工作流(高危) ### workflow disable — 禁用工作流(高危)
```bash ```bash
dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --yes --format json dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --format json
``` ```
返回 `{workflowId, disabled: true}` —— 同样是动作确认。禁用后该工作流不再自动触发。 返回 `{workflowId, disabled: true}` —— 同样是动作确认。禁用后该工作流不再自动触发。
**风险**:直接影响业务自动化(如停掉「记录创建后自动发通知」会让通知断流)。建议: **风险**:直接影响业务自动化(如停掉「记录创建后自动发通知」会让通知断流)。建议:
- 操作前先 `workflow get` 留底当前配置 - 操作前先 `workflow get` 留底当前配置
- 脚本场景显式传 `--yes`;交互场景让用户在 prompt 中再次确认 - 首次调用保留 Runtime 确认门禁;收到 `confirmation_required` 后说明目标和影响,只有取得本次明确确认才按 Runtime 指引重试
## 能力边界 ## 能力边界
@@ -354,7 +354,7 @@ dws aitable workflow list --base-id BASE_ID --format json | jq '.data | {total:
dws aitable workflow get --base-id BASE_ID --workflow-id WORKFLOW_ID --format json > /tmp/wf-backup.json dws aitable workflow get --base-id BASE_ID --workflow-id WORKFLOW_ID --format json > /tmp/wf-backup.json
# 2. 禁用 # 2. 禁用
dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --yes --format json dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --format json
# 3. 调试做完后重启 # 3. 调试做完后重启
dws aitable workflow enable --base-id BASE_ID --workflow-id WORKFLOW_ID --format json dws aitable workflow enable --base-id BASE_ID --workflow-id WORKFLOW_ID --format json
@@ -365,9 +365,11 @@ dws aitable workflow list --base-id BASE_ID --format json | jq '.data.list[] | s
### 批量关掉某个 Base 下所有 workflow(调试 / 迁移前清场) ### 批量关掉某个 Base 下所有 workflow(调试 / 迁移前清场)
先列出将被停用的 workflow 并取得用户对本批次的明确确认。下列命令保留 Runtime 门禁;确认后由执行方为同一批次逐条附加确认参数。
```bash ```bash
for WF in $(dws aitable workflow list --base-id BASE_ID --limit 100 --format json | jq -r '.data.list[] | select(.status == "RUNNING") | .flowId'); do for WF in $(dws aitable workflow list --base-id BASE_ID --limit 100 --format json | jq -r '.data.list[] | select(.status == "RUNNING") | .flowId'); do
dws aitable workflow disable --base-id BASE_ID --workflow-id "$WF" --yes --format json | jq .status dws aitable workflow disable --base-id BASE_ID --workflow-id "$WF" --format json | jq .status
done done
``` ```
@@ -377,8 +379,12 @@ done
- create / update 的 `--dsl` 必须是 JSON object,不能传数组、字符串化的二次 JSON 或 FlowSchema。 - create / update 的 `--dsl` 必须是 JSON object,不能传数组、字符串化的二次 JSON 或 FlowSchema。
- 本文 Demo 可直接用于最小定时消息工作流;复杂节点应以钉钉 AI 表格 MCP 最新 DSL 文档为准。 - 本文 Demo 可直接用于最小定时消息工作流;复杂节点应以钉钉 AI 表格 MCP 最新 DSL 文档为准。
- `status=success` 且 `data.valid=false` 仍是 DSL 校验失败;`issues` 才是下一步修复依据。 - `status=success` 且 `data.valid=false` 仍是 DSL 校验失败;`issues` 才是下一步修复依据。
- create 不自动重试;update 仅对网络/5xx/`retryable:true` 瞬态错误自动重试。 - create / update 都不在原子调用层自动重试;结果不确定时先用 `workflow list/get` 检查真实状态,不重放已发布的定义。
- enable / disable 出参里的 `enabled` / `disabled` 是 **动作确认 flag**,不是当前状态字段。要确认真生效请走 `workflow list` 查 `status`。 - enable / disable 出参里的 `enabled` / `disabled` 是 **动作确认 flag**,不是当前状态字段。要确认真生效请走 `workflow list` 查 `status`。
- `workflow get` 的 `flowSchema` 结构随触发器/动作类型变化,不要假设固定字段。 - `workflow get` 的 `flowSchema` 结构随触发器/动作类型变化,不要假设固定字段。
- `workflow run` 不自动重试;结果不确定时用 `workflow history` 按 executionId / instanceId 核对。 - `workflow run` 不自动重试;结果不确定时用 `workflow history` 按 executionId / instanceId 核对。
- 删除工作流当前仍未开放。 - 删除工作流当前仍未开放。
- 每个任务只读一次 `workflow edit-example` 和当前 leaf Schema,从真实示例构造 DSL;不凭记忆复用旧结构。
- DSL 中的 boolean(如 `onceClick`)必须保持 JSON boolean。读回为 `"True"/"False"` 或 `"true"/"false"` 字符串时判定为 MCP 类型污染,停止该分支;不用字符串或删除语义规避。
- `ConditionBranch` 的每个 `to` 必须指向当前 DSL 中真实节点,不使用 `to:null` 表示结束。业务需要空分支但 Schema 没有合法表达时停止,不改写成其他触发语义。
- 本次新建且未经用户专门确认启用的工作流,最终必须用 `workflow list` 证明为 `STOP`。如创建后意外为 `RUNNING`,立即停止依赖步骤;先调用 `workflow disable` 触发 Runtime 确认门禁并向用户说明影响,只有取得本次明确确认后才按 Runtime 指引重试并读回 `STOP`,同时如实说明曾短暂启用和可能副作用。
@@ -4,9 +4,9 @@
|---|---|---| |---|---|---|
| AI 表格、多维表、Base、Table、字段、记录、视图、表单、仪表盘、自动化 | AITable | 操作 AITable 的业务数据与配置 | | AI 表格、多维表、Base、Table、字段、记录、视图、表单、仪表盘、自动化 | AITable | 操作 AITable 的业务数据与配置 |
| 搜索 Base 候选、按关键词找 Base、检查某 Base 是否存在 | AITable | 直接使用 `+base-search --query`;即使关键词像人名,只要对象是 Base,也不得改走 `aisearch person` | | 搜索 Base 候选、按关键词找 Base、检查某 Base 是否存在 | AITable | 直接使用 `+base-search --query`;即使关键词像人名,只要对象是 Base,也不得改走 `aisearch person` |
| 表格链接,需要读取记录 | AITable | 先用 `+url-resolve` 取稳定 ID,再用 `+record-query` | | 已确认是 able 的 AITable 链接,需要读取记录 | AITable | 用 `+url-resolve` 取稳定 ID,再用 `+record-query`;用户原始 `/i/nodes/` URL 先 `drive info`,`extension=dlink` 时将 `result.fileId` 作为入口 ID 传给 `doc info`,再按目标 `linkSourceInfo.nodeId` 继续解析,不能把入口 ID 当 baseId |
| 只有 Base/Table 名称,需要读取记录 | AITable | 先用 `+resolve-base` / `+resolve-table` 唯一解析,再查询记录 | | 只有 Base/Table 名称,需要读取记录 | AITable | 先用 `+resolve-base` / `+resolve-table` 唯一解析,再查询记录 |
| 只复制 Base 结构、删除整个 Base | AITable | 复制到已知文档文件夹用 `+base-copy --target-folder-id ... --only-struct`;删除用真实 baseId。不要 Drive 完整复制后逐表删数据 | | 复制 Base、只复制 Base 结构、删除整个 Base | AITable | 复制用 `+base-copy`;可选传 `--target-folder-id <ID_OR_URL>`,省略时落到源 Base 工作区根目录;仅结构加 `--only-struct`。删除用真实 baseId,不要改走 Drive |
| Base 整体移动到普通文件夹、外层存储重命名 | Drive | 这是 Base 作为单个存储节点的外层位置/名称动作 | | Base 整体移动到普通文件夹、外层存储重命名 | Drive | 这是 Base 作为单个存储节点的外层位置/名称动作 |
| Base 内 Table、Dashboard、Section 的复制/移动/重命名/删除 | AITable | 这些是 Base 内 nsheet/业务结构,不是独立 Drive dentry | | Base 内 Table、Dashboard、Section 的复制/移动/重命名/删除 | AITable | 这些是 Base 内 nsheet/业务结构,不是独立 Drive dentry |
| Base 角色、高级权限 | AITable | `+role-*` / `+advperm-*`;仅普通文件 ACL 才走 Drive | | Base 角色、高级权限 | AITable | `+role-*` / `+advperm-*`;仅普通文件 ACL 才走 Drive |
@@ -14,4 +14,4 @@
| Excel 式单元格、区域、工作表、公式 | Sheet(dingtalk-misc) | 二维电子表格,不是多维表记录模型 | | Excel 式单元格、区域、工作表、公式 | Sheet(dingtalk-misc) | 二维电子表格,不是多维表记录模型 |
| CSV/JSON 数据进入现有 AI 表格 | AITable import 或 record create | 需要保留导入任务语义时用 import;已映射字段时直接写记录 | | CSV/JSON 数据进入现有 AI 表格 | AITable import 或 record create | 需要保留导入任务语义时用 import;已映射字段时直接写记录 |
若链接类型不明确,先做 URL 类型预检;不要仅凭 URL 文本猜产品。明确是 AI 表格后才加载本 Skill。 若链接类型不明确,先做 URL 类型预检;明确说“AI 表格”只决定候选产品,不能跳过用户原始 `/i/nodes/` 节点的 dlink 规范化。确认最终目标 `extension=able` 后才执行本 Skill 的 AITable 命令。
@@ -1,21 +1,24 @@
# URL 格式与处理规范 # URL 格式与处理规范
## 路由第 0 步:意图直达(优先级高于 URL 探测) > 这是兼容入口;完整且权威的跨产品规则在 [dingtalk-shared URL 规范](../../dingtalk-shared/references/url-patterns.md)。若两处存在差异,以 shared 版本为准。
用户已经明确表达某产品的内容意图时,直接进入对应产品场域,不要先做 URL 类型 ## 路由第 0 步:意图选路与节点规范化
探测。尤其:
用户已经明确表达某产品的内容意图时,可直接选择对应产品场域,不再做通用产品
消歧;但用户直接提供的 `/i/nodes/` URL 或来源未验证的 nodeId 仍必须先规范化。
产品意图不能替代节点类型证据,也不能跳过 dlink 目标解析。尤其:
- 明确提到 Markdown / `.md` 文件的读取或修改,按普通文件走 `drive` 场域: - 明确提到 Markdown / `.md` 文件的读取或修改,按普通文件走 `drive` 场域:
先 `dws drive download` 下载到本地处理,再用 `dws drive upload` 回传。 先 `dws drive download` 下载到本地处理,再用 `dws drive upload` 回传。
- 明确“读这篇文档 / 编辑文档正文”进入 `doc`;明确“看这个在线表格数据”进入 - 明确“读这篇文档 / 编辑文档正文”选择 `doc`;明确“看这个在线表格数据”选择
`sheet`。 `sheet`,随后仍按本文件探测原始 `/i/nodes/` 节点。
仅当用户只粘贴 URL、没有明确产品意图,或意图与链接类型可能冲突时,才执行下方 当前调用链刚创建、搜索或读取并已返回真实类型的稳定 ID 可以复用,不重复探测。
类型探测。 除此之外,原始 `/i/nodes/` URL 或来源未验证的 nodeId 均执行下方类型探测。
## alidocs URL 分流决策(意图不明确时执行) ## alidocs URL 分流决策
收到 `alidocs.dingtalk.com` URL 且无法从指令判断产品时,必须按以下顺序判断: 收到 `alidocs.dingtalk.com` URL 时,必须按以下顺序判断其路径形态:
1. URL 路径含 `/i/p/` → **分享短链**,禁止调用 `dws doc` 任何子命令 → 按下方 [分享短链处理](#分享短链处理) 执行 1. URL 路径含 `/i/p/` → **分享短链**,禁止调用 `dws doc` 任何子命令 → 按下方 [分享短链处理](#分享短链处理) 执行
2. URL 路径含 `/i/nodes/` → **节点链接**,需探测类型 → 按下方 [alidocs URL 类型探测流程](#alidocs-url-类型探测流程) 执行 2. URL 路径含 `/i/nodes/` → **节点链接**,需探测类型 → 按下方 [alidocs URL 类型探测流程](#alidocs-url-类型探测流程) 执行
@@ -94,8 +97,9 @@ dws doc read --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39
``` ```
Step 1 → dws drive info --node "<URL>" --format json Step 1 → dws drive info --node "<URL>" --format json
Step 2 → 从返回中提取 extension、nodeType 字段 Step 2 → 从 result 提取 extension、nodeType,并将 result.fileId 保存为 entryFileId(语义为 dentryUuid)
Step 3 → 按下方路由规则映射到对应产品 Step 3 → 若 extension=dlink,调用 dws doc info --node "<entryFileId>" --format json,取 linkSourceInfo
Step 4 → 按最终目标的 extension、nodeType 映射到对应产品
``` ```
> 路由依据是 `extension`,不是 `contentType`。`drive info` 检测到 > 路由依据是 `extension`,不是 `contentType`。`drive info` 检测到
@@ -105,6 +109,7 @@ Step 3 → 按下方路由规则映射到对应产品
| 条件 | 路由到产品 | 后续操作 | | 条件 | 路由到产品 | 后续操作 |
|------|-----------|---------| |------|-----------|---------|
| `extension=dlink` | — | 不把快捷方式当普通文件;按下方“快捷方式解析边界”解析目标后重新匹配本表 |
| `extension=adoc` | `doc` | 加载 `dingtalk-doc` 操作内容 | | `extension=adoc` | `doc` | 加载 `dingtalk-doc` 操作内容 |
| `extension=axls` | `sheet` | 加载 `dingtalk-misc` 的 `references/sheet.md` 操作(仅 `axls` 在线电子表格) | | `extension=axls` | `sheet` | 加载 `dingtalk-misc` 的 `references/sheet.md` 操作(仅 `axls` 在线电子表格) |
| `extension=able` | `aitable` | 将 nodeId 作为 baseId,加载 `dingtalk-aitable` 操作 | | `extension=able` | `aitable` | 将 nodeId 作为 baseId,加载 `dingtalk-aitable` 操作 |
@@ -113,6 +118,12 @@ Step 3 → 按下方路由规则映射到对应产品
| `nodeType=folder` | `drive` / `wiki` | 调用 `dws drive list --workspace <WS_ID>` 或 `dws wiki node list` 列出子节点 | | `nodeType=folder` | `drive` / `wiki` | 调用 `dws drive list --workspace <WS_ID>` 或 `dws wiki node list` 列出子节点 |
| 以上均不匹配 | — | 告知用户当前暂不支持该类型 | | 以上均不匹配 | — | 告知用户当前暂不支持该类型 |
### 快捷方式解析边界
- `linkSourceInfo` 的字段名沿用服务端定义,实际语义是快捷方式的**目标节点**。内容读取、编辑、导出和类型路由使用 `linkSourceInfo.nodeId`,并以目标类型字段重新匹配上表。
- 若目标仍为 `extension=dlink`,以目标 nodeId 再调用 `dws doc info`,逐跳解析并记录已访问 ID。请求失败、字段缺失或 ID 重复时立即停止,不把 dlink 降级成普通文件。
- 用户明确移动、重命名或删除快捷方式入口本身时,仍使用最初 `drive info` 的 `result.fileId`(即 `entryFileId`)。
> axls vs xlsx 关键区分: > axls vs xlsx 关键区分:
> - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作) > - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作)
> - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws drive download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错) > - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws drive download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错)
@@ -127,6 +138,10 @@ dws drive info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format jso
# 返回 extension=axls → 在线电子表格,路由到 sheet # 返回 extension=axls → 在线电子表格,路由到 sheet
dws sheet list --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json dws sheet list --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json
# 返回 extension=dlink → 保存 result.fileId 为 entryFileId,再解析目标
# 内容操作后续改用 linkSourceInfo.nodeId
dws doc info --node "<entryFileId>" --format json
# 返回 extension=xlsx/xls/csv → 本地表格文件,必须下载处理(禁止走 sheet) # 返回 extension=xlsx/xls/csv → 本地表格文件,必须下载处理(禁止走 sheet)
dws drive download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" --output <PATH> --format json dws drive download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456" --output <PATH> --format json
@@ -139,6 +154,6 @@ dws drive list --workspace <WS_ID> --format json
### 何时可跳过探测 ### 何时可跳过探测
当用户指令中已明确指定产品(如"帮我读这个文档"、"看下这个表格的数据"),可结合用户意图**跳过探测**直接路由。仅在以下情况**必须执行探测**: 只有 nodeId 来自当前调用链中已验证类型的创建、搜索或读取结果时,才能跳过探测并复用。用户明确说“文档”“表格”只决定候选产品,不能作为跳过原始 `/i/nodes/` URL 或来源未验证 nodeId 规范化的理由。
- 用户只粘贴 URL,无其他上下文
- 用户指令与 URL 实际类型可能不一致(如说"文档"但实际是表格) 任何阶段一旦得到 `extension=dlink`,都必须按“快捷方式解析边界”处理;禁止将快捷方式入口 ID 直接传给内容读取、编辑、导出或目标产品命令。
@@ -1,172 +1,94 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """兼容旧入口,将文件导入参数转交给 DWS +import-file。"""
通过 MCP 文件导入任务(prepare_import_upload -> PUT -> import_data)导入 AI 表格。
与 import_records.py 的区别:
- 本脚本:走“文件导入任务”链路,通常会新建导入数据表。
- import_records.py:走 create_records,写入已有 table。
用法:
python scripts/aitable_import_via_task.py <baseId> <filePath>
python scripts/aitable_import_via_task.py <baseId> <filePath> --timeout 30
python scripts/aitable_import_via_task.py <baseId> <filePath> --dws /tmp/dws
"""
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import json import os
import re
import subprocess
import sys import sys
from pathlib import Path
from typing import Any, Dict, Optional, Tuple
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
RESOURCE_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]{8,128}$")
ALLOWED_EXTENSIONS = {".csv", ".xlsx", ".xls"}
def validate_resource_id(resource_id: str) -> bool: def build_parser() -> argparse.ArgumentParser:
return bool(resource_id and RESOURCE_ID_PATTERN.match(resource_id.strip())) parser = argparse.ArgumentParser(
description="兼容入口:使用 DWS +import-file 导入 CSV/XLSX/XLS 文件"
)
parser.add_argument("base_id", help="目标 AI 表格 baseId;续等时仅为旧接口占位")
parser.add_argument(
"file_path",
nargs="?",
help="待导入文件路径;使用 --resume-import-id 时省略",
)
parser.add_argument("--table-id")
parser.add_argument("--header-row", type=int)
parser.add_argument("--src-sheet-name")
parser.add_argument("--field-mapping")
parser.add_argument("--timeout", type=int, default=30)
parser.add_argument("--dws", default="dws", help="DWS 可执行文件路径")
parser.add_argument("--resume-import-id")
parser.add_argument(
"--confirmed",
action="store_true",
help="确认已向用户展示迁移范围、异常处理和回退方案",
)
return parser
def run_dws(dws_bin: str, args: list[str], timeout_sec: int = 120) -> Tuple[int, str, str]: def build_dws_command(args: argparse.Namespace) -> list[str]:
cmd = [dws_bin] + args if not args.confirmed:
try: raise ValueError("执行迁移前必须获得专门确认,并传入 --confirmed")
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout_sec) if not args.resume_import_id and not args.file_path:
return result.returncode, result.stdout.strip(), result.stderr.strip() raise ValueError("首次导入必须提供文件路径")
except subprocess.TimeoutExpired:
return 124, "", f"dws command timeout after {timeout_sec}s"
except FileNotFoundError:
return 127, "", f"dws binary not found: {dws_bin}"
command = [args.dws, "aitable", "+import-file"]
if args.resume_import_id:
incompatible = [
name
for name, value in (
("file_path", args.file_path),
("--table-id", args.table_id),
("--header-row", args.header_row),
("--src-sheet-name", args.src_sheet_name),
("--field-mapping", args.field_mapping),
)
if value is not None
]
if incompatible:
raise ValueError(
"续等模式只允许 --resume-import-id 和可选 --timeout,不能指定 "
+ ", ".join(incompatible)
)
command.extend(["--resume-import-id", args.resume_import_id])
else:
command.extend(["--base-id", args.base_id, "--file", args.file_path])
def parse_json_output(raw: str) -> Optional[Dict[str, Any]]: passthrough = []
try: if not args.resume_import_id:
obj = json.loads(raw) passthrough = [
return obj if isinstance(obj, dict) else None ("--table-id", args.table_id),
except json.JSONDecodeError: ("--header-row", args.header_row),
return None ("--src-sheet-name", args.src_sheet_name),
("--field-mapping", args.field_mapping),
]
def put_file(upload_url: str, file_path: Path) -> Tuple[bool, str]: passthrough.append(("--timeout", args.timeout))
payload = file_path.read_bytes() for flag, value in passthrough:
req = Request(upload_url, data=payload, method="PUT") if value is not None:
# 关键:清空 Content-Type,避免 SignatureDoesNotMatch。 command.extend([flag, str(value)])
req.add_header("Content-Type", "") command.extend(["--format", "json", "--yes"])
try: return command
with urlopen(req, timeout=180) as resp:
if resp.status == 200:
return True, ""
return False, f"unexpected HTTP status: {resp.status}"
except HTTPError as e:
body = e.read().decode("utf-8", "ignore")
return False, f"HTTP {e.code}: {body[:300]}"
except URLError as e:
return False, f"URL error: {e.reason}"
def fail(msg: str, exit_code: int = 1) -> None:
print(f"错误:{msg}", file=sys.stderr)
sys.exit(exit_code)
def main() -> None: def main() -> None:
parser = argparse.ArgumentParser(description="通过文件导入任务导入 AI 表格") parser = build_parser()
parser.add_argument("base_id", help="目标 AI 表格 baseId")
parser.add_argument("file_path", help="待导入文件路径(.csv/.xlsx/.xls)")
parser.add_argument("--timeout", type=int, default=30, help="import_data 等待秒数,默认 30")
parser.add_argument("--dws", default="dws", help="dws 可执行文件路径,默认 dws")
args = parser.parse_args() args = parser.parse_args()
try:
base_id = args.base_id.strip() command = build_dws_command(args)
file_path = Path(args.file_path).expanduser().resolve() except ValueError as error:
parser.error(str(error))
if not validate_resource_id(base_id): try:
fail("无效的 baseId 格式") os.execvp(command[0], command)
if not file_path.exists() or not file_path.is_file(): except FileNotFoundError:
fail(f"文件不存在或不可读: {file_path}") print(f"错误:找不到 DWS 可执行文件:{command[0]}", file=sys.stderr)
if file_path.suffix.lower() not in ALLOWED_EXTENSIONS: raise SystemExit(127)
fail(f"仅支持 {sorted(ALLOWED_EXTENSIONS)},当前文件: {file_path.name}")
file_size = file_path.stat().st_size
if file_size <= 0:
fail("文件为空")
print(f"[1/3] prepare import upload: {file_path.name} ({file_size} bytes)", file=sys.stderr)
rc, out, err = run_dws(
args.dws,
[
"aitable",
"import",
"upload",
"--base-id",
base_id,
"--file-name",
file_path.name,
"--file-size",
str(file_size),
"--format",
"json",
],
)
if rc != 0:
fail(f"prepare_import_upload 失败: {err or out}", rc)
prepare_obj = parse_json_output(out)
if not prepare_obj:
fail(f"prepare_import_upload 返回非 JSON: {out[:300]}")
if prepare_obj.get("status") != "success":
fail(f"prepare_import_upload 返回失败: {json.dumps(prepare_obj, ensure_ascii=False)}")
pdata = prepare_obj.get("data") or {}
upload_url = pdata.get("uploadUrl")
import_id = pdata.get("importId")
if not upload_url or not import_id:
fail(f"prepare_import_upload 缺少 uploadUrl/importId: {json.dumps(pdata, ensure_ascii=False)}")
print("[2/3] upload file bytes via PUT", file=sys.stderr)
ok, put_err = put_file(upload_url, file_path)
if not ok:
fail(f"PUT 上传失败: {put_err}")
print("[3/3] trigger import_data", file=sys.stderr)
rc2, out2, err2 = run_dws(
args.dws,
[
"aitable",
"import",
"data",
"--import-id",
import_id,
"--timeout",
str(args.timeout),
"--format",
"json",
],
timeout_sec=max(120, args.timeout + 30),
)
if rc2 != 0:
fail(f"import_data 调用失败: {err2 or out2}", rc2)
import_obj = parse_json_output(out2)
if not import_obj:
fail(f"import_data 返回非 JSON: {out2[:300]}")
result = {
"baseId": base_id,
"fileName": file_path.name,
"fileSize": file_size,
"importId": import_id,
"status": import_obj.get("status"),
"summary": import_obj.get("summary"),
"data": import_obj.get("data", {}),
"error": import_obj.get("error", {}),
}
print(json.dumps(result, ensure_ascii=False, indent=2))
if import_obj.get("status") != "success":
sys.exit(2)
if __name__ == "__main__": if __name__ == "__main__":
@@ -20,7 +20,7 @@ metadata:
<!-- VISIBLE_SHORTCUTS_START --> <!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先) ## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "calendar +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws calendar <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service calendar --format json` 批量发现。 以下 shortcut 同时进入公开 catalog 与 Runtime Schema。按本 skill/recipe 路由,命中时 Shortcut 优先于原子命令。参数只查 `dws schema --cli-path "calendar +<shortcut>" --compact --jq '{cli_path,parameters,constraints,confirmation}' -f json`;仅需且已发布 `result` 时查 `--jq '{cli_path,outcomes:.result.outcomes,pagination}'`,字段级再查 `data_schema`;缺失不以 Help/样例推断。Schema 不可用才读一次已知 leaf Help;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 禁 Help:错误 suggestion → 已加载 Skill/reference 明确入口;均无则报漂移。禁全 Catalog/root/parent/product Help;仅映射、接口或 provenance 审计省略 `--compact`。现有路由和 reference 均无法定位低频能力时,才用 `dws shortcut list --service calendar --format json` 发现。
| Shortcut | 风险 | 适用场景 | | Shortcut | 风险 | 适用场景 |
|---|---|---| |---|---|---|
@@ -203,9 +203,11 @@ Example:
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \ --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \
--remind-minutes 5,10 # 开始前5分钟和10分钟各提醒一次 --remind-minutes 5,10 # 开始前5分钟和10分钟各提醒一次
Flags: Flags:
--is-all-day 全天日程;true 时起止时间为 yyyy-MM-dd,不传则不发送此字段
--add-online-meeting 添加视频会议(默认 true;不传沿用服务端默认添加,false 不添加)
--title string 日程标题 (必填,最大2048字符) --title string 日程标题 (必填,最大2048字符)
--start string 开始时间 ISO-8601 (必填,例如 2026-03-10T14:00:00+08:00) --start string 开始时间 (必填;全天为 yyyy-MM-dd,否则为 ISO-8601)
--end string 结束时间 ISO-8601 (必填,例如 2026-03-10T15:00:00+08:00) --end string 结束时间 (必填;全天为 yyyy-MM-dd 且不包含当天,否则为 ISO-8601)
--calendar-id string 日历 ID (可选,默认 primary 主日历;仅在共享/订阅日历本下创建时填写,通过 `book list` 获取) --calendar-id string 日历 ID (可选,默认 primary 主日历;仅在共享/订阅日历本下创建时填写,通过 `book list` 获取)
--timezone string 时区 IANA 格式 (例如 Asia/Shanghai,默认 Asia/Shanghai) --timezone string 时区 IANA 格式 (例如 Asia/Shanghai,默认 Asia/Shanghai)
--desc string 日程描述 (最大5000字符) --desc string 日程描述 (最大5000字符)
@@ -231,6 +233,24 @@ Flags:
> **说明**:个人日程也走 `event create`。如果只是给自己安排时间,不传 `--attendees` / `--open-dingtalk-ids` 即可。 > **说明**:个人日程也走 `event create`。如果只是给自己安排时间,不传 `--attendees` / `--open-dingtalk-ids` 即可。
### 全天日程与视频会议
`event create` / `event update` 支持 `--is-all-day` 和 `--add-online-meeting`。
全天日程的 `--start` / `--end` 使用真实有效的 `yyyy-MM-dd` 日期,结束日期不包含当天(1 月 1 日至 1 月 2 日表示 1 月 1 日全天),无需设置时区;CLI 保留日期字符串,不转换为午夜时间戳。普通日程继续使用 ISO-8601 时间。
```bash
dws calendar event create --title "全天安排" --is-all-day --start 2030-01-01 --end 2030-01-02 --add-online-meeting=false
dws calendar event update --id <EVENT_ID> --is-all-day=false --start "2030-01-01T09:00:00+08:00" --end "2030-01-01T10:00:00+08:00"
dws calendar event update --id <EVENT_ID> --add-online-meeting=true
```
创建时默认由服务端添加视频会议,无需添加时使用 `--add-online-meeting=false`。单人会议或全天日程通常不需要视频会议,建议设置 `--add-online-meeting=false`。
更新时显式 `true` 表示创建新的视频会议并覆盖已有会议;`false` 不创建且保留已有视频会议。
两个布尔参数均仅在显式传入时发送,更新标题等其他字段不会自动补发它们。
更新时显式设置 `--is-all-day`(true 或 false),必须同时重新提供 `--start` 和 `--end`;true 使用日期,false 使用带时区的 ISO-8601 时间。只修改起止时间、不设置全天状态时,仍支持单独更新一个时间字段。
需要新增能力时使用上述原子命令;`+create` 等快捷指令未暴露这些参数。
修改已有日程时沿用其 eventId,不要重新创建日程。
### 修改日程 ### 修改日程
``` ```
Usage: Usage:
@@ -244,9 +264,11 @@ Example:
Flags: Flags:
--id string 日程 ID (必填) --id string 日程 ID (必填)
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取) --calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取)
--is-all-day 全天状态;显式设置时必须重传 --start/--end,true 使用 yyyy-MM-dd,false 使用带时区 ISO-8601
--add-online-meeting 添加视频会议(true 重新添加并覆盖已有会议,false 保留已有会议;不传沿用原有更新逻辑)
--title string 新标题 --title string 新标题
--start string 新开始时间 ISO-8601 --start string 新开始时间 (全天为 yyyy-MM-dd,否则为 ISO-8601)
--end string 新结束时间 ISO-8601 --end string 新结束时间 (全天为 yyyy-MM-dd 且不包含当天,否则为 ISO-8601)
--desc string 新描述 (最大5000字符) --desc string 新描述 (最大5000字符)
--timezone string 时区 IANA 格式 (例如 Asia/Shanghai) --timezone string 时区 IANA 格式 (例如 Asia/Shanghai)
# 以下 --recurrence-* 在 修改周期日程的循环规则时必须**整体**传入:MCP 不合并部分字段,只改其中一项(例如只传 --recurrence-count)会把规则覆盖成不完整状态 # 以下 --recurrence-* 在 修改周期日程的循环规则时必须**整体**传入:MCP 不合并部分字段,只改其中一项(例如只传 --recurrence-count)会把规则覆盖成不完整状态
@@ -601,8 +623,8 @@ Flags:
用户说"日程/会议/约会/日历": 用户说"日程/会议/约会/日历":
- 查看 → `event list` - 查看 → `event list`
- 详情 → `event get` - 详情 → `event get`
- 创建/约/给自己留时间块/个人日程 → `event create`(带参会人时加 `--attendees`,循环日程加 `--recurrence-*`,自定义提醒加 `--remind-minutes`) - 创建/约/给自己留时间块/个人日程/全天日程 → `event create`(带参会人时加 `--attendees`,循环日程加 `--recurrence-*`,自定义提醒加 `--remind-minutes`)
- 修改/改时间/改描述 → `event update`(支持修改标题、时间、描述、时区、循环规则) - 修改/改时间/改描述/切换全天状态/重新添加视频会议 → `event update`(支持修改标题、时间、描述、时区、循环规则、全天状态和视频会议)
- 取消/删除 → `event delete` - 取消/删除 → `event delete`
- 推荐时间/什么时候有空/协调时间 → `event suggest` - 推荐时间/什么时候有空/协调时间 → `event suggest`
- 接受/拒绝/暂定日程 → `event respond` - 接受/拒绝/暂定日程 → `event respond`
@@ -1,6 +1,6 @@
--- ---
name: dingtalk-chat name: dingtalk-chat
description: 钉钉群聊与消息。Use when 发消息、单聊/群聊、建群、群设置/成员、搜索/回复、机器人/Webhook、消息文件。DING 和班级群走 dingtalk-misc;邮件走 dingtalk-mail。前缀 dws chat。 description: 钉钉群聊与消息。Use when 收发/搜索消息、建群、群治理、Bot/Webhook、文件,或仅限 IM 的消息谓词筛选。跨源主题/行为轨迹走 dingtalk-aisearch;DING/班级群走 dingtalk-misc;邮件走 dingtalk-mail。
metadata: metadata:
cli_version: ">=0.2.14" cli_version: ">=0.2.14"
category: product category: product
@@ -9,119 +9,96 @@ metadata:
- dws - dws
--- ---
# 钉钉群聊 / 消息 Skill # 群聊/消息
<!-- DWS_RUNTIME_CONTRACT_START --> <!-- DWS_RUNTIME_CONTRACT_START -->
## 最小 DWS 执行契约 ## 最小 DWS 执行契约
- 只通过 `dws` CLI 操作钉钉;结构化读取使用 `--format json`,按真实返回判断结果。 - 只用 `dws`;结构化读取加 `--format json`,按真实返回判断。
- 已知命令直接执行。只有 leaf 参数或安全语义不确定时读取精确 Schema,只有 Cobra flag 不确定时读取精确 leaf Help;不要加载产品级 Catalog 代替选路。 - 已知命令直调;参数/约束/安全不明查 leaf 窄 Schema。Schema 不可用才读已知 leaf Help 一次;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 不查 Help:优先错误中的明确 suggestion,其次已加载 Skill/reference 中的明确兼容入口;均无则报漂移并停,禁全 Catalog。低频 reference 不默认 Help,禁 root/parent/product Help。发现后必须执行或说明阻塞。
- 不猜命令、flag、字段、ID、账号或时间。后续 ID 必须来自真实返回;零命中、多候选或类型不明时停止并消歧。 - 不猜命令/flag/字段/ID/账号/业务事实;ID 来自真实返回。目标零命中/多候选/类型不明先消歧;仅可选时间/展示范围用契约默认,缺必需信息即停。
- 解析目标、读取上下文和最终执行必须使用同一 profile;不得跨组织复用 userId、openDingTalkId 或 openConversationId。多账号组织只使用明确的 `isOrgCurrent=true` 默认账号;没有默认账号时要求用户指定,禁止选择第一项、最近登录或最近使用账号。 - 解析/读/写同一 profile,ID 不跨组织。多账号只用唯一 `isOrgCurrent=true`;否则用户指定,禁止选择第一项、最近登录或最近使用账号。
- 不输出或记录 token、refresh token、appSecret、webhook token 等凭据;宿主已注入认证时不要索要凭据。 - 不输出/记录 token、refresh token、appSecret、webhook token;已注入认证时不索要。
- 写操作必须符合用户明确意图。是否需要确认以最终 Runtime gate 和 Schema 为准;需要确认时先说明对象、动作与影响,再追加 `--yes`。 - 写须符合明确意图;确认以最终 Runtime gate/Schema 为准,确认后才加 `--yes`。
- 写后按任务结果契约验证;不能仅凭退出码宣称成功。部分结果、未知投递状态和失败项必须如实保留。 - 写后验证结果,不凭退出码宣称成功。退出须最终答复,区分完成、部分、阻塞、待确认、失败;保留已有数据及 `complete/hasMore/stopReason/failures`。
- 时间戳面向用户展示时转换为带时区的可读时间;默认使用当前会话时区,必要时同时保留原值。 - 时间戳按会话时区展示,必要时保留原值。
- 遇到认证、权限、profile、confirmation 或未知错误时,只加载 `dingtalk-shared` 中对应 reference;不要连续猜测替代命令。 - 认证/权限/profile/confirmation/未知错误只读 `dingtalk-shared` 对应 reference,禁连续猜替代命令。
<!-- DWS_RUNTIME_CONTRACT_END --> <!-- DWS_RUNTIME_CONTRACT_END -->
## 对象与路由边界
- `openConversationId` 是会话,`messageId/openMessageId` 是消息,`openTaskId` 是发送任务,不可混用。
- 会话分组/分类走 `category`,群聊/聊天群走 `chat group`;会话列表无正文,总结/统计读消息。Pin/Top/Favorite 不是分类。
<!-- VISIBLE_SHORTCUTS_START --> <!-- VISIBLE_SHORTCUTS_START -->
## Shortcut 发现(按需) ## Shortcut 发现(Shortcut-first)
`chat` 当前有 98 条公开 shortcut,完整清单保留在 Runtime Catalog 与 Schema,不在高频产品根 Skill 中重复展开。已知意图直接使用下方的优先路由、意图表或任务 reference;命令已选中时直接执行,只在参数/安全语义不确定时读取 leaf Schema,在当前 Cobra flags 不确定时读取 leaf Help。 按 Golden Route/reference 选 Shortcut;仅缺底层字段用 atomic,低频走 reference/Catalog。
仅当现有路由和 reference 都无法定位低频能力时,才执行 `dws shortcut list --service chat --format json` 做最后回退;不要为已知高频意图加载完整 Shortcut Catalog 或产品级 Schema。 参数查 `dws schema --cli-path "chat <leaf>" --compact --jq '{cli_path,parameters,constraints,confirmation}' -f json`;仅需且已发布 `result` 时查 outcomes/pagination,字段级再查 `data_schema`;缺失不以 Help/样例推断。Schema 不可用才读一次已知 leaf Help;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 禁 Help:错误 suggestion → 已加载 Skill/reference 明确入口;均无则报漂移。禁全 Catalog/root/parent/product Help;低频 reference 不默认 Help。
<!-- VISIBLE_SHORTCUTS_END --> <!-- VISIBLE_SHORTCUTS_END -->
## Golden Route ## Golden Route
按用户任务选择最小充分入口。公开层按意图分流;Resolver、发送执行、消息投影和错误契约在 Runtime 内复用,不把所有能力塞进一个万能命令。 | 用户终点 | 唯一推荐入口 | 关键边界 |
| 用户意图 | 唯一推荐入口 | 关键边界 |
|---|---|---| |---|---|---|
| <!-- dws-intent: chat.send.dm -->按姓名发简单文本或 Markdown | `dws chat +dm --to <姓名> --content <内容>` | CLI 解析唯一用户;多候选时停止,不先手工查 ID | | <!-- dws-intent: chat.read.conversation -->读取指定群聊/单聊 | `dws chat +chat-messages --no-reactions` | 全部时加 `--page-all` |
| <!-- dws-intent: chat.send.group -->按群名或 ID 发简单文本或 Markdown | `dws chat +send-to-group --group <群名或ID> --content <内容>` | 稳定 ID 直接使用;群名多候选时停止 | | <!-- dws-intent: chat.search.filtered --><!-- dws-intent: chat.read.reactions -->按关键词/发送者/@/类型/reaction 过滤 | `dws chat +search-msg`(reaction 加 `--has-reactions`) | 默认 7 天;范围用 `--start/--end` |
| <!-- dws-intent: chat.send.advanced -->文件、Bot、Webhook、复杂 @ 或高级发送 | `dws chat +messages-send` | Bot 多群用 `--groups/--groups-file` 并检查逐项 ledger | | 跨会话读取/总结/统计 | `dws chat message list-all --start <开始> --end <结束> --page-all --no-reactions` | 不先列会话逐群循环 |
| <!-- dws-intent: chat.read.conversation -->读取指定会话、返回较多消息 | `dws chat +chat-messages` | 粗粒度读取;目标条件明确时优先 `+search-msg` | | <!-- dws-intent: chat.conversation.active-since -->时间后活跃会话 | `dws chat +recent-conversations --start <时间>` | 摘要;查 `complete`;`+active-conversations` 仅兼容 |
| <!-- dws-intent: chat.search.filtered -->多维度条件搜索(发送者/关键词/@/类型,单/跨会话) | `dws chat +search-msg` | 目标条件明确时使用 | | 查看 @我的消息 | `dws chat +at-me [--group <群名或ID>] --page-all --no-reactions` | 未指定群则跨会话;默认 7 天 |
| 查看指定群成员(用户/机器人) | `dws chat +chat-members-list --group <群名或ID>` | 唯一解析并全量读取 | | 查看未读消息 | `dws chat +unread-chats` | 需正文时沿 CID 读消息 |
| 获取群邀请链接 | `dws chat +chat-invite-url --group <群名或ID>` | 多候选时停止 | | 已知消息 ID 批量取详情 | `dws chat +messages-mget` | 看 leaf Schema;保留会话上下文 |
| 查看群机器人 | `dws chat +chat-bots --group <群名或ID>` | 返回稳定 `bots[]` | | <!-- dws-intent: chat.send.dm -->按姓名发文本/Markdown | `dws chat +dm --to <姓名> --content <内容>` | 唯一解析;多候选停止 |
| 个人收藏表情列表/发送/收藏 | `dws chat emotion list/send/favorite` | 约束见 leaf Schema | | <!-- dws-intent: chat.send.group -->按群名/ID 发文本/Markdown | `dws chat +send-to-group --group <群名或ID> --content <内容>` | 多候选停止 |
| 修改群名称 | `dws chat group rename --id <openConversationId> --name <新名称>` | 只知群名时先用 `+chat-search --query <群名>` 唯一解析 ID;不猜 `+chat-rename` | | <!-- dws-intent: chat.send.advanced -->文件/Bot/Webhook/复杂 @ | `dws chat +messages-send` | Bot 多群检查逐项 ledger |
| 查看指定群内 @我的消息 | `dws chat +at-me --group <群名> --page-all` | 检查 `complete`;空结果仍返回数组 | | 全部会话 | `dws chat +conversation-list --page-all` | 含群聊/单聊,非正文 |
| 查看全部会话 | `dws chat +conversation-list --page-all` | 检查 `complete` / `failures` | | 查加入/管理的群 | `+my-groups --page-all` / `+chat-list-mine` | 后者无 `--page-all`;flag 不跨 leaf |
| 读取并下载消息资源 | 查询命令加 `--download-resources` | 不另起手工下载循环;下载失败项保留在结果中 | | 搜群或查看全部成员 | `+chat-search --query <词>` / `+chat-members-list --group <群名或ID>` | 多候选停止;检查 buckets/完整性 |
| <!-- dws-intent: chat.conversation.list-top -->查看置顶会话 | `dws chat +conversation-list-top` | 会话 Top 与消息 Pin、消息 Top、Favorite 不同 | | 查群资料/Bot/邀请链接 | `+conversation-info` / `+chat-bots` / `+chat-invite-url` | 只读 |
| 监听未来 IM 事件 | [`dingtalk-event`](../dingtalk-event/SKILL.md) | 常规监听走 `+listen-im`;生命周期/高级控制走 `consume` | | <!-- dws-intent: chat.create.group -->创建/清理临时群 | `dws chat +chat-create --name <名称> --member-query <姓名列表>` → 保存 CID → `+chat-dismiss --group <cid>` | 已知 ID 用 `--users`;清理须确认、验证 |
| 改群资料/设置/禁言/管理员 | `+chat-update` / `+chat-update-settings` / `+chat-mute` / `+chat-mute-member` / `+chat-set-admin` | 用真实群/用户 ID;写后读回 |
| 管理群身份 | 读 [group-admin](references/chat/group-admin.md) 角色 family | 角色 CRUD、成员绑定/解绑/查询;不切 atomic,写后回读 |
| <!-- dws-intent: chat.category.list-conversations -->列分类内会话 | `dws chat +category-list-conversations --category-id <ID>` | 分类≠群;先取 ID |
| <!-- dws-intent: chat.reply.quote -->引用回复 | `dws chat +messages-reply` | 用真实消息/CID;未知投递状态非成功 |
| 撤回/转发 | `+messages-recall`;`+messages-forward` / `+messages-combine-forward` / `+messages-forward-topic` | 不复制正文冒充原生转发 |
| Pin/消息 Top/Favorite | `+messages-set-pin` / `+messages-unset-pin`;`+messages-set-top` / `+messages-unset-top`;`+flag-create` / `+flag-cancel` | 对象互不替代;用对应查询验证 |
| 添加/移除 reaction | `+messages-add-emoji` / `+messages-remove-emoji` | 扩展动作读 `message-actions` |
| 会话置顶/免打扰/隐藏 | [chat-conversation](references/chat/chat-conversation.md) | 用真实 CID;会话 Top 非消息 Top |
| 已读/未读/清红点/清空 | `+conversation-mark-read` / `+conversation-mark-unread` / `+conversation-clear-red-point` / `+conversation-clear-all-red-point` / `+conversation-clear-messages` | 已读需消息 ID;清空按 Runtime 确认 |
| 下载消息资源 | 查询加 `--download-resources --output-dir <目录>`;已有引用用 `+messages-resource-download` | 不猜 ID;保留 ledger;临时 URL 不交付 |
以下次级入口在意图明确时直接使用,不需要先加载完整 Catalog: 次级:Thread `+thread-replies`;<!-- dws-intent: chat.conversation.list-top -->置顶 `dws chat +conversation-list-top`;上传 `conversation-file upload`;IM 事件走 [`dingtalk-event`](../dingtalk-event/SKILL.md)。
| 用户意图 | 入口 |
|---|---|
| 已知消息 ID 批量读取详情 | `dws chat +messages-mget` |
| 已知资源引用单独下载 | `dws chat +messages-resource-download` |
| 只上传会话文件,不发消息 | `dws chat conversation-file upload --conversation-id <cid> --file <路径>`;返回文件 ID,仅本地路径 |
| 按关键词搜索群 | `dws chat +chat-search` |
| 查看消息收藏 | `dws chat +flag-list` |
| <!-- dws-intent: chat.reply.quote -->引用回复 | 人:`dws chat +messages-reply`;成功结果保留新消息/会话/投递与原消息来源上下文。Bot 群:`dws chat message send-by-bot --conversation-id <cid> --reply <mid> --ref-sender <sid>` |
| 撤回当前用户消息 | `dws chat +messages-recall --msg-id <openMessageId>`;可省略会话 ID,由 CLI 只读补齐;兼容单值 `--message-ids` |
| 已知话题主消息 ID 或 thread/topic ID 读取回复 | `dws chat +thread-replies` |
| <!-- dws-intent: chat.create.group -->按成员 ID 或姓名创建群聊 | `dws chat +chat-create`;成员/群主均可自然解析,任一歧义都会在创建前整体停止 |
| 跨全部会话查看 @我的消息 | `dws chat +at-me --page-all` |
### 发送入口边界
- `+dm`:姓名目标的简单文本/Markdown,参数空间最小。
- `+send-to-group`:群名或稳定 ID 目标的简单文本/Markdown,避免暴露无关身份矩阵。
- Markdown 中的公网图片必须写成 `![图片标题](https://example.com/image.png)` 才会内联展示;
省略开头的 `!` 时只会显示为链接。
- `+messages-send`:文件、Bot、Webhook、复杂 @ 或幂等控制。user 已知 ID 可直接传,也可用 `--user-query` / `--chat-query` 运行同一只读解析链;Bot 多群使用 `--groups/--groups-file`,返回 `im.batch-write.v1`;bot/webhook 只使用下层真实支持的文本/Markdown 能力。
- 发文件消息用 `+messages-send --file <路径>`;只存会话空间、不发消息才用 `chat conversation-file upload`,返回 `dentryId`/`spaceId`。
- Webhook 使用 `+messages-send --as webhook --webhook-token <token>`;不要退回原子 Webhook 命令。
- 流式卡片用 `+messages-send-card`;群聊@传 ID/`--at-all`,Runtime 把 create 返回前缀加到 `--content`;禁写占位符;仅 text。
## 关键结果语义 ## 关键结果语义
- `openTaskId` 是发送任务 ID,不是回复或撤回所需的消息 ID;消息 ID 必须来自真实查询结果。 - 查询保留范围、数量、完整性、停止原因、失败/下载 ledger;不完整不得称成功。消息仅 `--page-all` 翻页,达预算返回有界 partial。
- 消息查询默认保留稳定 ID、会话/thread、发送者、文本、时间、reaction、引用、转发和 `resourceRefs`;`--no-reactions` 可关闭 reaction。 - 本地图片仅已有 `mediaId` 才发内联 image,否则发 file;只上传不发送用 `conversation-file upload`。
- 查询结果必须检查 `complete`、`hasMore`、`failures` 和资源下载 ledger;partial result 不得表述为完整成功。 - 子消息用自身 messageId;缺 CID 才继承父 conversationId。写操作沿真实结果传 ID。
- 子消息使用自己的 `messageId`;仅缺会话 ID 时继承父消息的 `conversationId`。
- 下载只允许工作目录内安全相对路径,默认不覆盖并原子落盘;覆盖必须由用户显式传 `--overwrite`。读取和下载不需要 `--yes`。 ## 写生命周期
- Favorite、消息 Pin、消息 Top、会话 Top 是不同对象层级,不能互换。
- 多步骤写先校验目标、限制、内容、确认,再沿稳定 ID 串行执行并验证;不截断或换目标。临时资源保留 ID/阶段,失败仅做已授权清理。
## 上下文预算
- Skill/Reference/leaf Schema/Help 各读一次;Schema 已给参数禁 Help。总结/统计/检索默认 `--no-reactions`,资源下载仅显式 opt-in。
- 用 `--fields`/`--jq`/limit/compact 预览,禁 `head` 截 JSON;大正文落文件,保留 ID、正文证据、下步字段、完整性和失败项。
## 按需加载 ## 按需加载
只在任务命中时读取一个精确 reference: 任务≤1个
[话题与话题圈](references/chat/thread.md)
| 场景 | Reference | | 场景 | Reference |
|---|---| |---|---|
| 需要跨步骤传递真实结果的消息/群组合流程 | [01-messaging.md](references/01-messaging.md) | | 消息 | [查询](references/chat/message-query.md) / [动作](references/chat/message-actions.md) / [资源](references/chat/message-media.md) |
| 消息读取与查询 | [message-query](references/chat/message-query.md) | | 群 | [读取](references/chat/group-discovery.md) / [治理](references/chat/group-admin.md) |
| 编辑、撤回、回复、转发、Pin、Top、Favorite 或 reaction 写入 | [message-actions](references/chat/message-actions.md) | | 会话/Bot | [会话](references/chat/chat-conversation.md) / [Bot](references/chat/chat-bot.md) |
| 位置、联系人名片、底层媒体与资源下载 | [message-media](references/chat/message-media.md) | | 组合/话题/表情/卡片 | [组合](references/01-messaging.md) / [话题](references/chat/thread.md) / [表情](references/chat-emoji-list.md) / [卡片/A2UI](references/card/schema.md) |
| 群列表、群搜索、共同群、成员与群内机器人读取 | [group-discovery](references/chat/group-discovery.md) | | 结果/其他原子能力 | [contracts](references/contracts.md) / [chat](references/chat.md) |
| 建群、成员或已知机器人增删、管理员、公告与群设置 | [group-admin](references/chat/group-admin.md) |
| 搜索未知机器人、机器人消息发送/撤回与 Webhook | [chat-bot.md](references/chat/chat-bot.md) |
| 会话置顶、分类、红点、免打扰和隐藏 | [chat-conversation.md](references/chat/chat-conversation.md) |
| 低频意图之间仍需消歧 | [intent-guide.md](references/intent-guide.md) |
| 表情名称与 ID | [chat-emoji-list.md](references/chat-emoji-list.md) |
| 稳定结果、身份矩阵与能力边界 | [contracts.md](references/contracts.md) |
| 流式卡片创建 | [card/create.md](references/card/create.md) |
| 流式卡片更新 | [card/update.md](references/card/update.md) |
| 卡片 callback 是否可用 | [card/callback.md](references/card/callback.md) |
| 卡片公开 Schema 边界 | [card/schema.md](references/card/schema.md) |
| 只有上述 reference 仍无法定位的原子能力 | [chat.md](references/chat.md) 的对应章节 |
不要预加载 reference。Shortcut Catalog 只在根路由和精确 reference 都无法定位低频能力时使用。
## 错误最短路径 ## 错误最短路径
1. resolution 返回零命中或多候选:停止写操作,展示候选并让用户消歧;禁止默认第一项。 1. 零命中/多候选:停止写并消歧,禁默认第一项。
2. `unknown command` / `unknown flag`:读取精确 leaf Help,修正后最多重试一次。 2. 参数/确认不明查 leaf Schema;Schema 不可用才查已知 leaf Help。`unknown flag` 用同 leaf Help 修正一次;`unknown command` 不查 Help:先用错误的明确 suggestion,再用已加载 Skill/reference 的明确兼容入口;无则报漂移并停,禁全 Catalog。
3. 参数约束或 confirmation 不清楚:读取精确 leaf Schema,以 Runtime gate 为准。 3. 仅 `retryable=true` 按原命令重试一次;API/internal/MCP、写后不一致或无重试性即停,不换 Shortcut/atomic。保留 Trace ID、已完成/待清理项并最终答复;`confirmation_required` 等用户确认,不补 `--yes`。
4. 认证、权限、profile 或 confirmation 错误:读取 `dingtalk-shared` 的对应 reference;正常 IM 不读取完整 shared Skill。
5. `backend_dependency_unavailable`:保持原参数,对只读命令最多重试一次;不要改 flag、猜认证命令或切换同义原子命令,持续失败时保留 Trace ID。
6. 其他错误:保留真实错误和已完成/失败项;不要连续尝试同义原子命令。
@@ -53,6 +53,7 @@ dws chat +chat-messages --group <openConversationId> \
- 群名 + 文件/高级控制:`+messages-send --as user --chat-query <群名> --file <相对路径>`。 - 群名 + 文件/高级控制:`+messages-send --as user --chat-query <群名> --file <相对路径>`。
- Bot 多群文本/Markdown:`+messages-send --as bot --robot-code <code> --groups <cid1,cid2>`; - Bot 多群文本/Markdown:`+messages-send --as bot --robot-code <code> --groups <cid1,cid2>`;
Runtime 去重并返回 `im.batch-write.v1` 逐目标 ledger,最多 100 个稳定群 ID。 Runtime 去重并返回 `im.batch-write.v1` 逐目标 ledger,最多 100 个稳定群 ID。
- Markdown 中的公网图片必须写成 `![图片标题](https://example.com/image.png)` 才会内联;省略 `!` 只显示链接。
`--user-query` 和 `--chat-query` 会在 CLI 内运行真实只读解析;零命中或多候选时在上传或发送前停止。Bot/Webhook 不接受这两个自然目标参数。 `--user-query` 和 `--chat-query` 会在 CLI 内运行真实只读解析;零命中或多候选时在上传或发送前停止。Bot/Webhook 不接受这两个自然目标参数。
@@ -0,0 +1,56 @@
# A2UI 展示卡片
使用 `dws chat message send-a2ui-card` 创建卡片,使用 `update-a2ui-card` 更新和完结。`--content` 接受 JSON 字符串数组,每个元素是一条 A2UI 消息。
下面以任务进度卡片为例。布局、组件 ID、数据路径和文案可按需求调整,保持引用一致;组件属性可按需查阅[钉钉公开 Catalog](https://dingtalk.com/card/a2ui/catalogs/public/catalog.json)。
## 创建
用 `createSurface` 指定 Catalog 和初始数据,再用 `updateComponents` 定义组件。替换接收人后发送:
```bash
dws chat message send-a2ui-card \
--open-dingtalk-id '<接收人openDingTalkId>' \
--content '["{\"version\":\"v1.0\",\"createSurface\":{\"surfaceId\":\"example-card\",\"catalogId\":\"https://dingtalk.com/card/a2ui/catalogs/public/catalog.json\",\"dataModel\":{\"answer\":{\"displayText\":\"任务已开始。\"},\"status\":\"doing\"}}}","{\"version\":\"v1.0\",\"updateComponents\":{\"surfaceId\":\"example-card\",\"components\":[{\"id\":\"root\",\"component\":\"Column\",\"align\":\"stretch\",\"children\":[\"executionPanel\"]},{\"id\":\"executionPanel\",\"component\":\"CollapsiblePanel\",\"title\":\"正在处理任务\",\"children\":[\"answer\"],\"fallbackMarkdown\":\"任务进度\"},{\"id\":\"answer\",\"component\":\"Markdown\",\"content\":{\"path\":\"/answer/displayText\"}}]}}"]' \
-f json
```
群聊时将 `--open-dingtalk-id` 换成 `--conversation-id '<群openConversationId>'`,选择一个目标。创建状态默认 `PROCESSING`;发送成功后保存返回的 `bizId`,用于后续更新。
允许转发时,在创建命令中加上 `--support-forward`,默认 `false`。
## 更新和完结
更新使用创建时的 `bizId` 和 `surfaceId`。本例修改面板标题和正文:
```bash
dws chat message update-a2ui-card \
--biz-id '<本次发送返回的bizId>' \
--flow-status INPUTTING \
--content '["{\"version\":\"v1.0\",\"updateComponents\":{\"surfaceId\":\"example-card\",\"components\":[{\"id\":\"executionPanel\",\"component\":\"CollapsiblePanel\",\"title\":\"正在处理第 2 步\",\"children\":[\"answer\"],\"fallbackMarkdown\":\"任务进度\"}]}}","{\"version\":\"v1.0\",\"updateDataModel\":{\"surfaceId\":\"example-card\",\"path\":\"/answer/displayText\",\"value\":\"第 2 步正在执行。\"}}","{\"version\":\"v1.0\",\"updateDataModel\":{\"surfaceId\":\"example-card\",\"path\":\"/status\",\"value\":\"doing\"}}"]' \
-f json
```
可按任务进度继续更新。任务完成后写入实际结果,并将 `--flow-status` 设为 `FINISH`:
```bash
dws chat message update-a2ui-card \
--biz-id '<本次发送返回的bizId>' \
--flow-status FINISH \
--content '["{\"version\":\"v1.0\",\"updateComponents\":{\"surfaceId\":\"example-card\",\"components\":[{\"id\":\"executionPanel\",\"component\":\"CollapsiblePanel\",\"title\":\"处理完成\",\"children\":[\"answer\"],\"fallbackMarkdown\":\"任务进度\"}]}}","{\"version\":\"v1.0\",\"updateDataModel\":{\"surfaceId\":\"example-card\",\"path\":\"/answer/displayText\",\"value\":\"任务已完成。\"}}","{\"version\":\"v1.0\",\"updateDataModel\":{\"surfaceId\":\"example-card\",\"path\":\"/status\",\"value\":\"finished\"}}"]' \
-f json
```
`--flow-status` 控制卡片流转状态;示例中的 `dataModel.status` 用于记录业务状态,可按需要维护。
## 组件注解
发送或更新时可传 `--a2ui-annotations '[{"surfaceId":"example-card","componentId":"answer","type":"artifact"}]'`。注解为 JSON 对象数组,与 A2UI 消息通过以下字段对应:
- `surfaceId` 对应消息中的 surface。
- `componentId` 对应该 surface 下 `updateComponents.components[].id`,按需要关联的实际组件填写。
- `type` 表示注解类型,按实际支持的类型和用途选择。
`answer` 和 `artifact` 是本例的取值。更新时也可关联此前已创建的组件。该参数可选,支持 `[]`;省略时创建请求不携带注解字段,更新请求沿用原有的空数组。
发送后核对创建结果和客户端展示,再检查同一 `bizId` 的更新和完结效果。遇到 `a2ui dws catalog validation failed` 时,可对照 Catalog 检查组件属性、JSON 格式、Catalog 地址和 surface 创建顺序。
@@ -1,10 +1,17 @@
# 卡片回调边界 # 卡片回调边界
当前 DWS lower interface 没有卡片按钮/action callback 的订阅、验签或回复能力, `callback_supported=false` 仅描述 `dingtalk-chat` 的卡片创建/更新 lower interface:
`callback_supported=false`。因此: 它不提供服务端 callback URL 注册、签名密钥/验签或 callback 回复能力。因此:
- 不生成 callback URL、签名密钥或虚构的监听命令; - 不生成 callback URL、签名密钥、验签或回复命令;
- 不把 `dws event consume` 当作卡片 callback 的替代; - 需要接收并处理服务端 HTTP callback 时,停止并说明当前尚未开放该能力。
- 用户必须使用按钮交互时,停止并说明当前不支持,等待平台接口和 Runtime 正式发布。
卡片的 create/update 能力不代表 callback 可用。 监听当前 OAuth 用户收到的互动卡片操作事件是另一条已开放能力,路由到
[`dingtalk-event`](../../../dingtalk-event/SKILL.md):
```bash
dws event consume user_card_action_triggered --flatten -f ndjson
```
该命令通过个人事件长连接订阅和消费回调事件,不会创建 callback URL,也不提供服务端
验签或 callback 回复。卡片 create/update、个人事件监听和服务端 callback 接入是三个不同边界。
@@ -1,14 +1,50 @@
# 流式卡片 Schema # 卡片 Schema
DWS 当前公开的是 `im.streaming-card.v1` 工作流契约,不是任意组件 Schema: 文本展示或持续输出文本可参考[创建流式卡片](create.md);需要组件布局可参考 [A2UI 展示卡片](a2ui.md)。
DWS 公开两类卡片命令契约:
- `chat +messages-send-card` / `chat +messages-update-card`:仍是 `im.streaming-card.v1`
Shortcut 工作流,只处理 streaming text。
- `chat message send-card` / `chat message update-card`:仅保留 Shortcut 未发布字段或原始响应的 streaming 原子 fallback。
- `chat message send-a2ui-card` / `chat message update-a2ui-card`:A2UI 原子命令。
streaming 不是任意组件 Schema:
- target:group、direct user、direct openDingTalkId; - target:group、direct user、direct openDingTalkId;
- content:streaming text; - content:streaming text;
- lifecycle:create 可选串联 update,后续按 `bizId` update; - lifecycle:create 可选串联 update,后续按 `bizId` update;
- flowStatus:1–5; - flowStatus:1–5;
- callback:不支持。 - server callback:卡片命令不提供 callback URL 注册、验签或回复;监听当前用户的互动卡片
操作事件走 [`dingtalk-event`](../../../dingtalk-event/SKILL.md):
`dws event consume user_card_action_triggered --flatten -f ndjson`。
`update-card --flow-status` 的 CLI 类型为 string,但仍只接受兼容数字 1–5
(包括历史 pflag int 支持的 base-0 写法),并向 streaming RPC 发送 integer。
下方 `updateDataModel` 片段只演示已有 surface 的更新格式,完整创建示例见上述 A2UI 指南。
A2UI 原子命令规则:
- `send-a2ui-card` 调用 `im.create_and_send_a2ui_card`。
- `update-a2ui-card` 调用 `im.update_a2ui_card`。
- `--content` 必须是 JSON 字符串数组,元素为 A2UI 协议 JSON,例如
`'["{\"version\":\"v1.0\",\"updateDataModel\":{\"surfaceId\":\"surface\",\"path\":\"/status\",\"value\":\"finished\"}}"]'`;
CLI 解析为 `a2uiMessages`,send-a2ui-card 额外生成 `summary`,值为数组元素按换行拼接
(真实 MCP 契约无 `fallbackText` 字段)。
- 群聊目标写入顶层 `openConversationId`;单聊目标写入顶层 `receiverOpenDingTalkId`
(传入 userId 时由 CLI 自动解析转换为 openDingTalkId)。
- A2UI wire 层 `flowStatus` 为字符串枚举;CLI `--flow-status` 接受枚举名和兼容数字 1–9:
1 PROCESSING、2 INPUTTING、3 FINISH、4 EXECUTING、5 ERROR、6 ABORTED、
7 TIMEOUT、8 CONFIRMING、9 CONFIRMED,由 CLI 映射为枚举字符串发送。
- A2UI `send-a2ui-card` 在 CLI 侧自动生成 `requestId`、`bizCardId`,并固定
`protocolVersion="1.0"`;创建默认 `flowStatus=PROCESSING`。
- 两个 A2UI 命令均支持可选 `--a2ui-annotations`,接受 JSON 对象数组并透传为
`a2uiAnnotations`。省略时创建请求不携带该字段,更新请求保持发送 `[]`。
- A2UI 命令不提供 `--at-open-dingtalk-ids` / `--at-all`;@ 仅 streaming
`send-card` 群聊可用。
参数、required 和 confirmation 读取 参数、required 和 confirmation 读取
`dws schema --cli-path "chat +messages-send-card" --compact -f json` 或 `dws schema --cli-path "chat +messages-send-card" --compact -f json` 或
`dws schema --cli-path "chat +messages-update-card" --compact -f json`。不要把 Lark card JSON 字段翻译成 `dws schema --cli-path "chat message send-a2ui-card" --compact -f json`。不要把 Lark card JSON 字段翻译成
未发布的 DWS flags。 未发布的 DWS flags;A2UI 内容复用 `--content`,不要发明 `--a2ui-content`。
@@ -10,8 +10,8 @@
> Schema/Skill 生成器不得机械重写它。最后复核:2026-08-03。 > Schema/Skill 生成器不得机械重写它。最后复核:2026-08-03。
> >
> 使用规则: > 使用规则:
> - 用户描述的表情命中下表中的 `name` → 使用 `chat message add-emoji --emoji <name>` 贴 emoji 回应 > - 用户描述的表情命中下表中的 `name` → 使用 `+messages-add-emoji --emoji <name>` 贴 emoji 回应
> - 用户描述的表情未命中下表 → 先 `chat message create-text-emotion` 创建文字表情获取 emotionId,再 `chat message add-text-emotion` 贴文字表情回应 > - 用户描述的表情未命中下表 → 先 `+messages-create-text-emotion` 获取 emotionId,再用 `+messages-add-text-emotion` 贴文字表情回应
| # | emotionId | name | en_US | | # | emotionId | name | en_US |
|---|-----------|------|-------| |---|-----------|------|-------|
@@ -8,7 +8,7 @@
## 使用边界 ## 使用边界
1. 先确认任务确实需要 Shortcut 未发布的底层字段、原始响应或运维控制; 1. 先确认任务确实需要 Shortcut 未发布的底层字段、原始响应或运维控制;
2. 读取精确原子 leaf Schema/Help,不加载产品级 Catalog 猜参数; 2. 读取精确原子 leaf 窄 Schema,不默认读 Help,也不加载产品级 Catalog 猜参数;
3. 自然目标仍必须唯一解析,禁止选择搜索结果第一项; 3. 自然目标仍必须唯一解析,禁止选择搜索结果第一项;
4. 原子写 leaf 的 confirmation 若与对应 Golden Shortcut 不一致,停止并报告交付漂移; 4. 原子写 leaf 的 confirmation 若与对应 Golden Shortcut 不一致,停止并报告交付漂移;
5. 后续 ID 只使用当前 profile 的真实返回,不跨组织复用; 5. 后续 ID 只使用当前 profile 的真实返回,不跨组织复用;
@@ -38,52 +38,56 @@
| `chat message list` | 需要原始响应或显式手工 continuation;普通浏览使用 `+chat-messages` | | `chat message list` | 需要原始响应或显式手工 continuation;普通浏览使用 `+chat-messages` |
| `chat message list-all` | 指定时间范围的原始全会话分页接口 | | `chat message list-all` | 指定时间范围的原始全会话分页接口 |
| `chat message list-by-sender` | 需要原始按发送者响应;普通组合搜索使用 `+search-msg` | | `chat message list-by-sender` | 需要原始按发送者响应;普通组合搜索使用 `+search-msg` |
| `chat message list-mentions` / `list-focused` | 精确的 @我或特别关注原始列表 | | `chat message list-mentions` | `+at-me` 未发布的字段或原始响应 |
| `chat message list-focused` | 特别关注原始列表 |
| `chat message search` / `search-advanced` | `+search-msg` 未发布的底层过滤字段或原始响应 | | `chat message search` / `search-advanced` | `+search-msg` 未发布的底层过滤字段或原始响应 |
| `chat message query-send-status` | 使用真实 `openTaskId` 查询用户消息投递任务 | | `chat message query-send-status` | `+messages-query-send-status` 未发布的字段或原始响应 |
| `chat message recall` / `edit` | 撤回或编辑已知消息 | | `chat message recall` | `+messages-recall` 未发布的字段或原始响应 |
| `chat message read-status` | 查询已知消息的已读/未读状态 | | `chat message edit` | 编辑已知消息 |
| `chat message read-status` | `+messages-read-status` 未发布的字段或原始响应 |
| `chat message reply` | `+messages-reply` 未发布的底层引用字段,且安全门禁已对齐 | | `chat message reply` | `+messages-reply` 未发布的底层引用字段,且安全门禁已对齐 |
| `chat message forward` / `combine-forward` | Shortcut 未覆盖的精确转发字段 | | `chat message forward` / `combine-forward` | Shortcut 未覆盖的精确转发字段 |
| `chat message download-media` | Shortcut 无法消费的已知底层 mediaId/fileId 引用 | | `chat message download-media` | `+messages-resource-download` 未发布的底层字段或原始响应 |
消息对象管理: 消息对象管理:
| 原子命令 | 对象 | | 原子命令 | 对象 |
|---|---| |---|---|
| `message set-pin-msg` / `unset-pin-msg` / `list-pin-msg` | 消息 Pin | | `message set-pin-msg` / `unset-pin-msg` / `list-pin-msg` | 对应 `+messages-set-pin` / `+messages-unset-pin` / `+messages-list-pin` 未发布的字段或原始响应 |
| `message set-top-msg` / `unset-top-msg` | 会话内消息 Top | | `message set-top-msg` / `unset-top-msg` | `+messages-set-top` / `+messages-unset-top` 的底层 fallback |
| `message add-favorite` / `remove-favorite` / `list-favorites` | 当前用户 Favorite | | `message add-favorite` / `remove-favorite` / `list-favorites` | `+flag-create` / `+flag-cancel` / `+flag-list` 的底层 fallback |
| `message add-emoji` / `remove-emoji` | 默认 emoji reaction | | `message add-emoji` / `remove-emoji` | `+messages-add-emoji` / `+messages-remove-emoji` 的底层 fallback |
| `message create-text-emotion` / `add-text-emotion` / `update-text-emotion` / `remove-text-emotion` | 文字表情 | | `message create-text-emotion` / `add-text-emotion` / `remove-text-emotion` | 对应 `+messages-create-text-emotion` / `+messages-add-text-emotion` / `+messages-remove-text-emotion` 的底层 fallback |
| `message update-text-emotion` | Shortcut 未覆盖的文字表情更新 |
| `message list-emotion-replies` | 批量 reaction/文字回应 | | `message list-emotion-replies` | 批量 reaction/文字回应 |
| `emotion list` / `send` / `favorite` | 当前用户个人收藏表情列表、发送和新增 | | `emotion list` / `send` / `favorite` | 当前用户个人收藏表情列表、发送和新增 |
Favorite、消息 Pin、消息 Top 与会话 Top 是四种对象,不能互换。 Favorite、消息 Pin、消息 Top 与会话 Top 是四种对象,不能互换。
个人收藏表情与消息 reaction/文字回应不同;发送收藏表情使用 `chat emotion send`,给已有消息贴表情使用 `chat message add-emoji` 或 `chat message add-text-emotion`。 个人收藏表情与消息 reaction/文字回应不同;发送收藏表情使用 `chat emotion send`,给已有消息贴表情使用 `+messages-add-emoji` 或 `+messages-add-text-emotion`。
## 群与成员底层能力 ## 群与成员底层能力
| 原子命令 | 用途 | | 原子命令 | 用途 |
|---|---| |---|---|
| `chat search` / `search-common` | 群管理前解析唯一群、查询共同群 | | `chat search` | `+chat-search` 未发布的字段或原始响应 |
| `chat group get-by-group-id` | 数字群号转 `openConversationId` | | `chat search-common` | 查询共同群 |
| `chat group get-by-group-id` | `+chat-get-by-id` 未发布的字段或原始响应 |
| `chat group create` | `+chat-create` 尚未发布的真实底层创建字段;显式群主已由 Shortcut 覆盖 | | `chat group create` | `+chat-create` 尚未发布的真实底层创建字段;显式群主已由 Shortcut 覆盖 |
| `chat group members` / `members list-by-ids` | 群成员分页和精确详情 | | `chat group members` / `members list-by-ids` | 群成员分页和精确详情 |
| `chat group members add` / `remove` | 添加/移除已知成员 ID | | `chat group members add` / `remove` | 添加/移除已知成员 ID |
| `chat group members add-bot` / `remove-bot` / `group bots` | 机器人进群、移除和列表 | | `chat group members add-bot` / `remove-bot` / `group bots` | 对应 `+chat-add-bot` / `+chat-remove-bot` / `+chat-bots` 的底层 fallback |
| `chat group rename` / `update-icon` | 群名和群头像 | | `chat group rename` / `update-icon` | 对应 `+chat-update` / `+chat-update-icon` 的底层 fallback |
| `chat group transfer-owner` / `set-admin` | 群主和管理员 | | `chat group transfer-owner` / `set-admin` | 对应 `+chat-transfer-owner` / `+chat-set-admin` 的底层 fallback |
| `chat group upgrade-to-external` | 普通群升级外部群;不可逆 | | `chat group upgrade-to-external` | 普通群升级外部群;不可逆 |
| `chat group invite-url` / `share-invite` | 群邀请链接及分享 | | `chat group invite-url` / `share-invite` | `+chat-invite-url` 未发布的邀请链接字段或分享 |
| `chat group update-settings` / `user-settings query|set` | 管理员群开关或当前用户群偏好 | | `chat group update-settings` / `user-settings query|set` | `+chat-update-settings` 未发布的管理员字段,或当前用户群偏好 |
| `chat group update-nick` / `update-alias` | 当前用户群昵称和群备注 | | `chat group update-nick` / `update-alias` | 对应 `+chat-update-nick` / `+chat-update-alias` 的底层 fallback |
| `chat group set-history` | 新成员历史消息可见范围 | | `chat group set-history` | `+chat-set-history` 未发布的字段或原始响应 |
| `chat group-mute` / `group-mute-member` | 全员或指定成员禁言 | | `chat group-mute` / `group-mute-member` | `+chat-mute` / `+chat-mute-member` 的底层 fallback |
| `chat group notice create|edit|get|list` | 群公告 | | `chat group notice create|edit|get|list` | 群公告 |
| `chat group list-my-groups` / `list-all` | 当前用户相关群列表 | | `chat group list-my-groups` / `list-all` | `+my-groups` / `+chat-list-mine` 未投影的字段或原始响应 |
| `chat group list-join-validations` / `audit-join-validation` | 入群审批 | | `chat group list-join-validations` / `audit-join-validation` | `+chat-list-join-requests` / `+chat-audit-join` 的底层 fallback |
| `chat group-role *` | 群身份定义与成员分配 | | `chat group-role *` | 对应 `+chat-role-*` 未发布的字段或原始响应 |
退出、解散群、踢人、转让群主、升级外部群、禁言、管理员和公告写入都属于高影响操作; 退出、解散群、踢人、转让群主、升级外部群、禁言、管理员和公告写入都属于高影响操作;
必须以最终 Runtime gate/Schema 为准确认对象与影响。 必须以最终 Runtime gate/Schema 为准确认对象与影响。
@@ -92,10 +96,10 @@ Favorite、消息 Pin、消息 Top 与会话 Top 是四种对象,不能互换
| 原子命令 | 用途 | | 原子命令 | 用途 |
|---|---| |---|---|
| `chat bot search` | 搜索当前用户创建的机器人并取得 `robotCode` | | `chat bot search` | `+bot-search` 未发布的字段或原始响应 |
| `chat bot find` | 搜索可用机器人并取得机器人 `openDingTalkId` | | `chat bot find` | `+bot-find` 未发布的字段或原始响应 |
| `chat message send-by-bot` | `+messages-send --as bot` 未发布的真实底层字段,包括机器人群聊引用回复的 `--reply` / `--ref-sender` | | `chat message send-by-bot` | `+messages-send --as bot` 未发布的真实底层字段,包括机器人群聊引用回复的 `--reply` / `--ref-sender` |
| `chat message recall-by-bot` | 使用 `processQueryKey` 撤回机器人消息 | | `chat message recall-by-bot` | `+messages-recall-by-bot` 未发布的字段或原始响应 |
| `chat message send-by-webhook` | `+messages-send --as webhook` 未发布的真实底层字段 | | `chat message send-by-webhook` | `+messages-send --as webhook` 未发布的真实底层字段 |
新发送流程统一使用 `+messages-send`。不得因看见 bot/webhook 原子命令就绕开统一身份能力矩阵。 新发送流程统一使用 `+messages-send`。不得因看见 bot/webhook 原子命令就绕开统一身份能力矩阵。
@@ -104,18 +108,17 @@ Favorite、消息 Pin、消息 Top 与会话 Top 是四种对象,不能互换
| 原子命令 | 用途 | | 原子命令 | 用途 |
|---|---| |---|---|
| `chat conversation-info` | 已知稳定用户/群 ID 的会话详情 | | `chat conversation-info` | `+conversation-info` 未发布的字段或原始响应 |
| `chat list-all-conversations` | 全部会话原始分页列表 | | `chat list-all-conversations` | `+conversation-list` 未投影的字段或原始响应 |
| `chat list-top-conversations` | 需要原始响应时的置顶会话 fallback;普通查看使用 `+conversation-list-top` | | `chat list-top-conversations` | 需要原始响应时的置顶会话 fallback;普通查看使用 `+conversation-list-top` |
| `chat set-top` | 设置/取消整个会话置顶 | | `chat set-top` | `+conversation-set-top` 未发布的字段或原始响应 |
| `chat mute` / `hide` / `mute-at-all` / `mute-red-envelope` | 会话通知与可见状态 | | `chat mute` / `hide` / `mute-at-all` / `mute-red-envelope` | 对应会话 Shortcut 未发布的字段或原始响应 |
| `chat mark-unread` / `mark-read` | 会话未读或消息已读状态 | | `chat mark-unread` / `mark-read` | 对应会话 Shortcut 未发布的字段或原始响应 |
| `chat clear-red-point` / `clear-all-red-point` | 清除会话红点 | | `chat clear-red-point` / `clear-all-red-point` | 对应会话 Shortcut 未发布的字段或原始响应 |
| `chat clear-messages` | 清空当前用户视角的会话记录 | | `chat clear-messages` | `+conversation-clear-messages` 未发布的字段或原始响应 |
| `chat category *` | 自定义/智能会话分组 | | `chat category *` | 对应 `+category-*` 未发布的字段或原始响应;对象是会话分组,不是群聊 |
消息 Top 使用 `message set-top-msg`,整个会话 Top 使用 `chat set-top`,查看置顶会话使用 消息 Top 使用 `+messages-set-top`,整个会话 Top 使用 `+conversation-set-top`,查看置顶会话使用 `+conversation-list-top`。
`+conversation-list-top`。
## 稳定 ID 传递 ## 稳定 ID 传递
@@ -125,7 +128,7 @@ Favorite、消息 Pin、消息 Top 与会话 Top 是四种对象,不能互换
| 唯一人员解析 | 当前 profile 下的 `userId` / `openDingTalkId` | | 唯一人员解析 | 当前 profile 下的 `userId` / `openDingTalkId` |
| `+messages-send` | `openTaskId` 查询投递状态;它不是消息 ID | | `+messages-send` | `openTaskId` 查询投递状态;它不是消息 ID |
| `+chat-messages` / `+search-msg` / `+messages-mget` | 回复、转发、撤回、资源操作使用的真实消息/会话/thread ID | | `+chat-messages` / `+search-msg` / `+messages-mget` | 回复、转发、撤回、资源操作使用的真实消息/会话/thread ID |
| `chat bot search` | `robotCode`;不能当机器人 `openDingTalkId` | | `+bot-search` | `robotCode`;不能当机器人 `openDingTalkId` |
| `chat message send-by-bot` | `processQueryKey` 用于机器人撤回;群聊引用回复还需消息查询返回的 `openMessageId` 与原发送者 `openDingTalkId` | | `chat message send-by-bot` | `processQueryKey` 用于机器人撤回;群聊引用回复还需消息查询返回的 `openMessageId` 与原发送者 `openDingTalkId` |
显式稳定 ID 当前不携带可验证的 profile provenance;调用方必须保证来源,不得宣称所有 显式稳定 ID 当前不携带可验证的 profile provenance;调用方必须保证来源,不得宣称所有
@@ -133,7 +136,8 @@ Favorite、消息 Pin、消息 Top 与会话 Top 是四种对象,不能互换
## 故障处理 ## 故障处理
- `unknown command` / `unknown flag`:读取精确 leaf Help,最多修正一次; - `unknown flag`:读取同一 leaf Help,最多修正一次;
- `unknown command`:不查 Help;先用错误返回的明确 suggestion,再用已加载 Skill/reference 的明确兼容入口,仍无则报漂移并停;不枚举全 Catalog;
- confirmation 或参数约束不清:读取精确 leaf Schema,以最终 Runtime gate 为准; - confirmation 或参数约束不清:读取精确 leaf Schema,以最终 Runtime gate 为准;
- 自然目标零命中/多候选:停止并展示候选,不选择第一项; - 自然目标零命中/多候选:停止并展示候选,不选择第一项;
- 权限、认证或 profile:按 `dingtalk-shared` 对应 reference 分流; - 权限、认证或 profile:按 `dingtalk-shared` 对应 reference 分流;
@@ -13,8 +13,8 @@
- 用户明确要求“用机器人/机器人身份/robot”发送时,使用 - 用户明确要求“用机器人/机器人身份/robot”发送时,使用
`dws chat +messages-send --as bot --robot-code <robotCode>`;不得改成当前用户身份。 `dws chat +messages-send --as bot --robot-code <robotCode>`;不得改成当前用户身份。
- `chat bot search` 只返回我创建的机器人,没有 `openDingTalkId`;给机器人发单聊必须用 `chat bot find`。 - `+bot-search` 只返回我创建的机器人,没有 `openDingTalkId`;给机器人发单聊先用 `+bot-find`。
- 机器人发群消息前需确认机器人已在群中;报“机器人不存在”时先 `group members add-bot`。 - 机器人发群消息前确认机器人已在群中;报“机器人不存在”时先 `+chat-add-bot`。
- `send-by-bot` 支持 Markdown、图片 URL 和文件,具体参数见下方消息类型路由。 - `send-by-bot` 支持 Markdown、图片 URL 和文件,具体参数见下方消息类型路由。
- 机器人在群聊中引用回复已有消息时,使用原子命令 `send-by-bot --reply --ref-sender`;该能力仅支持 Markdown,不走 `+messages-reply` 的当前用户身份。 - 机器人在群聊中引用回复已有消息时,使用原子命令 `send-by-bot --reply --ref-sender`;该能力仅支持 Markdown,不走 `+messages-reply` 的当前用户身份。
- 公网图片 URL 使用 `--msg-type image --image-url`,按图片消息发送。 - 公网图片 URL 使用 `--msg-type image --image-url`,按图片消息发送。
@@ -34,16 +34,16 @@
| 命令 | 范围 | 返回 openDingTalkId | 典型触发词 | | 命令 | 范围 | 返回 openDingTalkId | 典型触发词 |
|------|------|---------------------|------------| |------|------|---------------------|------------|
| `chat bot search` | 仅当前用户自己创建的机器人 | 否 | “我的机器人”“我创建的机器人” | | `+bot-search` | 仅当前用户自己创建的机器人 | 否 | “我的机器人”“我创建的机器人” |
| `chat bot find` | 当前用户可用的全部机器人(含他人/官方) | 是 | “找机器人”“搜索机器人”“给机器人发单聊” | | `+bot-find` | 当前用户可用的全部机器人(含他人/官方) | 是 | “找机器人”“搜索机器人”“给机器人发单聊” |
```bash ```bash
dws chat bot search --page 1 --size 10 --name "日报" dws chat +bot-search --page 1 --size 10 --name "日报"
dws chat bot find --query "日报" --limit 20 dws chat +bot-find --query "日报" --limit 20
dws chat bot find --query "日报" --limit 20 --cursor <nextCursor> dws chat +bot-find --query "日报" --limit 20 --cursor <nextCursor>
``` ```
`bot find` 翻页时 `cursor` 必须使用上次返回的 `nextCursor` 字符串原值,不要传 `"0"` 或数字字面量。 `+bot-find` 翻页时 `cursor` 使用上次返回的 `nextCursor` 字符串原值,不传 `"0"` 或数字字面量。原子 `bot search/find` 仅在 Shortcut 缺少底层字段或原始响应时使用。
### 机器人发送与撤回 ### 机器人发送与撤回
@@ -52,7 +52,7 @@ dws chat bot find --query "日报" --limit 20 --cursor <nextCursor>
```bash ```bash
dws chat +messages-send --as bot --robot-code <robot-code> \ dws chat +messages-send --as bot --robot-code <robot-code> \
--groups <openConversationId1>,<openConversationId2> \ --groups <openConversationId1>,<openConversationId2> \
--markdown "## 通知\n\n请提交周报" --format json --text "## 通知\n\n请提交周报" --format json
``` ```
读取 `requestedCount/succeededCount/failedCount/results/failures`;unknown 或失败目标不自动重发。 读取 `requestedCount/succeededCount/failedCount/results/failures`;unknown 或失败目标不自动重发。
@@ -86,7 +86,7 @@ dws chat message send-by-bot --robot-code <robot-code> --group <openConversation
| Flag | 说明 | | Flag | 说明 |
|------|------| |------|------|
| `--robot-code` | 机器人 Code,必填 | | `--robot-code` | 机器人 Code,必填 |
| `--conversation-id` | 群聊 openConversationId;`--group` 为兼容别名 | | `--conversation-id` | 群聊 openConversationId;`--group` 为公开兼容入口 |
| `--users` | 单聊 userId 列表,逗号分隔,最多 20 个 | | `--users` | 单聊 userId 列表,逗号分隔,最多 20 个 |
| `--open-dingtalk-ids` | 单聊 openDingTalkId 列表 | | `--open-dingtalk-ids` | 单聊 openDingTalkId 列表 |
| `--msg-type` | `markdown`、`image` 或 `file`;省略时为 Markdown;公网图片使用 `image --image-url`,本地图片和文件使用 `file --file-path` | | `--msg-type` | `markdown`、`image` 或 `file`;省略时为 Markdown;公网图片使用 `image --image-url`,本地图片和文件使用 `file --file-path` |
@@ -101,14 +101,14 @@ dws chat message send-by-bot --robot-code <robot-code> --group <openConversation
引用回复不会设置 `msgType=reply`;CLI 在普通群消息参数顶层透传 `referenceOpenMessageId` 和 `srcMsgSendOpenDingTalkId`。只传其中一个参数、用于单聊或用于图片/文件消息都会在本地失败。 引用回复不会设置 `msgType=reply`;CLI 在普通群消息参数顶层透传 `referenceOpenMessageId` 和 `srcMsgSendOpenDingTalkId`。只传其中一个参数、用于单聊或用于图片/文件消息都会在本地失败。
#### `dws chat message recall-by-bot` #### 撤回机器人消息
```bash ```bash
dws chat message recall-by-bot --robot-code <robot-code> --group <openConversationId> --keys <processQueryKey> dws chat +messages-recall-by-bot --robot-code <robot-code> --group <openConversationId> --keys <processQueryKey>
dws chat message recall-by-bot --robot-code <robot-code> --keys key1,key2 dws chat +messages-batch-recall-by-bot --robot-code <robot-code> --keys key1,key2
``` ```
群聊撤回传 `--group`;单聊撤回不传 `--group`。`--keys` 来自 `send-by-bot` 返回的 `processQueryKey`。 群聊撤回用 `+messages-recall-by-bot --group`;单聊批量撤回用 `+messages-batch-recall-by-bot`。`--keys` 来自发送结果的 `processQueryKey`。原子 `message recall-by-bot` 仅作未公开字段/原始响应 fallback。
### Webhook ### Webhook
@@ -135,39 +135,41 @@ dws chat message send-by-webhook --token <webhook-token> --title "test" --conten
| 命令 | 用途 | 必填参数 | | 命令 | 用途 | 必填参数 |
|------|------|----------| |------|------|----------|
| `group members add-bot` | 将自定义机器人加入群 | `--id` `--robot-code` | | `+chat-add-bot` | 将自定义机器人加入群 | `--id` `--robot-code` |
| `group members remove-bot` | 从群移除机器人 | `--id` `--bot-id` | | `+chat-remove-bot` | 从群移除机器人 | `--id` `--bot-id` |
| `+chat-bots` | 查看群内机器人列表 | `--group <群名或openConversationId>`;自然群名内部唯一解析 | | `+chat-bots` | 查看群内机器人列表 | `--group <群名或openConversationId>`;自然群名内部唯一解析 |
```bash ```bash
dws chat group members add-bot --id <openConversationId> --robot-code <robot-code> dws chat +chat-add-bot --id <openConversationId> --robot-code <robot-code>
dws chat +chat-bots --group "项目群" dws chat +chat-bots --group "项目群"
dws chat group members remove-bot --id <openConversationId> --bot-id <openBotId> dws chat +chat-remove-bot --id <openConversationId> --bot-id <openBotId>
``` ```
对应 `group members add-bot/remove-bot` 原子命令仅作未公开字段或原始响应 fallback。
## 常见工作流 ## 常见工作流
### 机器人发消息后撤回 ### 机器人发消息后撤回
```bash ```bash
dws chat bot search --name "日报" --format json dws chat +bot-search --name "日报" --format json
dws chat +messages-send --as bot --robot-code <robot-code> --group <openConversationId> --title "日报" --markdown "## 今日完成\n\n- 事项 A\n\n- 事项 B" --format json dws chat +messages-send --as bot --robot-code <robot-code> --group <openConversationId> --title "日报" --text "## 今日完成\n\n- 事项 A\n\n- 事项 B" --format json
dws chat message recall-by-bot --robot-code <robot-code> --group <openConversationId> --keys <processQueryKey> --format json dws chat +messages-recall-by-bot --robot-code <robot-code> --group <openConversationId> --keys <processQueryKey> --format json
``` ```
### 机器人不在群内时先邀请再发送 ### 机器人不在群内时先邀请再发送
```bash ```bash
dws chat bot search --name "日报" --format json dws chat +bot-search --name "日报" --format json
dws chat group members add-bot --id <openConversationId> --robot-code <robot-code> --format json dws chat +chat-add-bot --id <openConversationId> --robot-code <robot-code> --format json
dws chat +messages-send --as bot --robot-code <robot-code> --group <openConversationId> --title "通知" --text "内容" --format json dws chat +messages-send --as bot --robot-code <robot-code> --group <openConversationId> --title "通知" --text "内容" --format json
``` ```
### 给机器人发单聊 ### 给机器人发单聊
```bash ```bash
dws chat bot find --query "玉澜" --format json dws chat +bot-find --query "玉澜" --format json
dws chat message send --open-dingtalk-id <openDingTalkId> --content "你好" --format json dws chat +messages-send --open-dingtalk-id <openDingTalkId> --text "你好" --format json
``` ```
### 机器人 @ 指定人 ### 机器人 @ 指定人
@@ -179,8 +181,8 @@ dws chat +messages-send --as bot --robot-code <robot-code> --group <openConversa
## 常见错误与回退 ## 常见错误与回退
- 机器人单聊没有 openDingTalkId:改用 `chat bot find`,不要用 `bot search`。 - 机器人单聊没有 openDingTalkId:改用 `+bot-find`,不要用 `+bot-search`。
- 机器人发群消息报“机器人不存在”:先 `group members add-bot`。 - 机器人发群消息报“机器人不存在”:先 `+chat-add-bot`。
- 撤回失败:确认使用 `processQueryKey`,不是 `openMessageId`。 - 撤回失败:确认使用 `processQueryKey`,不是 `openMessageId`。
- 机器人引用回复失败:确认目标是群聊 Markdown,且 `--reply` 来自被引用消息的 `openMessageId`、`--ref-sender` 来自同一消息的发送者 `openDingTalkId`。 - 机器人引用回复失败:确认目标是群聊 Markdown,且 `--reply` 来自被引用消息的 `openMessageId`、`--ref-sender` 来自同一消息的发送者 `openDingTalkId`。
- @ 不生效:检查正文是否包含 `@userId` / `@openDingTalkId` / `@10`。 - @ 不生效:检查正文是否包含 `@userId` / `@openDingTalkId` / `@10`。
@@ -15,8 +15,8 @@
## 必读约束 ## 必读约束
- 会话状态类命令通常需要 `openConversationId`。群聊只用 `+chat-search --query` 获取唯一候选,单聊可由 `chat conversation-info --user/--open-dingtalk-id` 获取。 - 会话状态类命令通常需要 `openConversationId`。群聊只用 `+chat-search --query` 获取唯一候选;单聊默认用 `+conversation-info --open-dingtalk-id`,仅 Shortcut 缺少的 `--user` 解析才用原子命令。
- `set-top` 是会话置顶;`message set-top-msg` 是会话内消息置顶,二者不能混用。 - `+conversation-set-top` 是会话置顶;`+messages-set-top` 是会话内消息置顶,二者不能混用。
- `clear-messages` 只清空当前用户视角的消息,不影响其他成员。 - `clear-messages` 只清空当前用户视角的消息,不影响其他成员。
- 智能分组规则中的成员使用 openDingTalkId;如果用户只给姓名,先用 `aisearch person --dimension name` 获取。 - 智能分组规则中的成员使用 openDingTalkId;如果用户只给姓名,先用 `aisearch person --dimension name` 获取。
@@ -25,12 +25,13 @@
### 会话基础信息 ### 会话基础信息
```bash ```bash
dws chat conversation-info --group <openConversationId> --format json dws chat +conversation-info --group <openConversationId> --format json
dws chat conversation-info --user <userId> --format json dws chat +conversation-info --open-dingtalk-id <openDingTalkId> --format json
dws chat conversation-info --open-dingtalk-id <openDingTalkId> --format json
``` ```
`--group`、`--user`、`--open-dingtalk-id` 互斥且必须指定一个。文件/音视频发送不依赖调用方预先读取 spaceId;直接用 `message send --msg-type file|audio|video --file`。 `--group`、`--open-dingtalk-id` 互斥且必须指定一个。只有需要 Shortcut 未公开的 `--user`
解析或底层原始响应时才使用原子 `conversation-info`。文件/音视频发送不依赖调用方预先读取
spaceId;直接用 `message send --msg-type file|audio|video --file`。
### 只上传到会话文件空间 ### 只上传到会话文件空间
@@ -52,9 +53,9 @@ dws chat conversation-file upload --open-dingtalk-id <openDingTalkId> --file ./r
| `+chat-list-all` | 获取当前用户加入的全部群 | 要求全部时加 `--page-all`;沿数字 `nextCursor` 去重聚合 | | `+chat-list-all` | 获取当前用户加入的全部群 | 要求全部时加 `--page-all`;沿数字 `nextCursor` 去重聚合 |
| `+my-groups` | 获取并投影当前用户加入的群 | 要求全部时加 `--page-all`;读完后再应用 `--type` 本地过滤 | | `+my-groups` | 获取并投影当前用户加入的群 | 要求全部时加 `--page-all`;读完后再应用 `--type` 本地过滤 |
| `+conversation-list-top` | 获取置顶会话列表 | 可选 `--limit` `--cursor` `--exclude-muted`;使用稳定 `conversations[]` | | `+conversation-list-top` | 获取置顶会话列表 | 可选 `--limit` `--cursor` `--exclude-muted`;使用稳定 `conversations[]` |
| `message list-unread-conversations` | 获取未读会话列表 | 可选 `--count` `--exclude-muted` | | `+unread-chats` | 获取未读会话列表 | 可选 `--count` `--exclude-muted` |
| `clear-red-point` | 清除指定会话红点 | `--conversation-id`,别名 `--id` / `--chat` | | `+conversation-clear-red-point` | 清除指定会话红点 | `--conversation-id` |
| `clear-all-red-point` | 清除所有会话红点,一键全部已读 | 无参数 | | `+conversation-clear-all-red-point` | 清除所有会话红点,一键全部已读 | 无参数 |
翻页时,`hasMore=true` 用返回的 `nextCursor` 作为下次 `--cursor`。Shortcut 全量读取应检查 翻页时,`hasMore=true` 用返回的 `nextCursor` 作为下次 `--cursor`。Shortcut 全量读取应检查
`complete`、`stopReason` 和 `failures`;达到 `--page-limit` 时会保留可继续的 `nextCursor`。 `complete`、`stopReason` 和 `failures`;达到 `--page-limit` 时会保留可继续的 `nextCursor`。
@@ -63,9 +64,9 @@ dws chat conversation-file upload --open-dingtalk-id <openDingTalkId> --file ./r
| 命令 | 用途 | 必填参数 | | 命令 | 用途 | 必填参数 |
|------|------|----------| |------|------|----------|
| `set-top` | 设置/取消会话置顶 | `--conversation-id`;默认置顶,`--off` 取消 | | `+conversation-set-top` | 设置/取消会话置顶 | `--conversation-id` 或 `--conversation-ids`;`--off` 取消 |
| `mute` | 开启/关闭会话免打扰 | `--conversation-id`;默认开启,`--off` 关闭 | | `+conversation-mute` | 开启/关闭会话免打扰 | `--conversation-id`;默认开启,`--off` 关闭 |
| `hide` | 隐藏会话 | `--conversation-id` | | `+conversation-hide` | 隐藏会话 | `--conversation-id` |
| `mute-at-all` | 关闭/恢复 @所有人通知 | `--conversation-id`;默认关闭,`--off` 恢复;必须先开启会话总免打扰 | | `mute-at-all` | 关闭/恢复 @所有人通知 | `--conversation-id`;默认关闭,`--off` 恢复;必须先开启会话总免打扰 |
| `mute-red-envelope` | 关闭/恢复红包通知 | `--conversation-id`;默认关闭,`--off` 恢复;必须先开启会话总免打扰 | | `mute-red-envelope` | 关闭/恢复红包通知 | `--conversation-id`;默认关闭,`--off` 恢复;必须先开启会话总免打扰 |
@@ -73,52 +74,60 @@ dws chat conversation-file upload --open-dingtalk-id <openDingTalkId> --file ./r
总免打扰状态,此时要先重新开启总免打扰,再操作红包通知。 总免打扰状态,此时要先重新开启总免打扰,再操作红包通知。
```bash ```bash
dws chat set-top --conversation-id <openConversationId> dws chat +conversation-set-top --conversation-id <openConversationId>
dws chat set-top --conversation-id <openConversationId> --off dws chat +conversation-set-top --conversation-id <openConversationId> --off
dws chat mute --conversation-id <openConversationId> dws chat +conversation-mute --conversation-id <openConversationId>
dws chat mute --conversation-id <openConversationId> --off dws chat +conversation-mute --conversation-id <openConversationId> --off
dws chat hide --conversation-id <openConversationId> dws chat +conversation-hide --conversation-id <openConversationId>
``` ```
### 已读未读与清理 ### 已读未读与清理
| 命令 | 用途 | 必填参数 | | 命令 | 用途 | 必填参数 |
|------|------|----------| |------|------|----------|
| `mark-unread` | 标记指定会话为未读 | `--conversation-id` | | `+conversation-mark-unread` | 标记指定会话为未读 | `--conversation-id` |
| `mark-read` | 将指定消息及之前消息标记为已读 | `--conversation-id` `--message-id` | | `+conversation-mark-read` | 将指定消息及之前消息标记为已读 | `--conversation-id` `--message-id` |
| `clear-messages` | 清空当前用户指定会话的消息 | `--conversation-id` | | `+conversation-clear-messages` | 清空当前用户指定会话的消息 | `--conversation-id` |
```bash ```bash
dws chat mark-unread --conversation-id <openConversationId> dws chat +conversation-mark-unread --conversation-id <openConversationId>
dws chat mark-read --conversation-id <openConversationId> --message-id <openMessageId> dws chat +conversation-mark-read --conversation-id <openConversationId> --message-id <openMessageId>
dws chat clear-messages --conversation-id <openConversationId> dws chat +conversation-clear-messages --conversation-id <openConversationId>
``` ```
原子 `set-top`、`mute`、`hide`、`mark-unread`、`mark-read`、`clear-red-point`、
`clear-all-red-point`、`clear-messages` 保持可执行,但只在需要对应 Shortcut 未公开的底层参数、
原始响应或不同执行语义时使用,不作为普通会话状态请求的候选入口。
### 会话分组 ### 会话分组
会话分组/分类是当前用户的分类容器,统一走 `category`;群聊/聊天群是真实会话对象,统一走 `chat group`。即使分类名含“群”,也不能按聊天群处理。删除分类不会删除其中的真实会话。高频生命周期统一使用 Shortcut:
| 命令 | 用途 | 必填参数 | | 命令 | 用途 | 必填参数 |
|------|------|----------| |------|------|----------|
| `category list` | 获取用户自定义会话分组 | 无 | | `+category-list` | 获取用户自定义会话分组 | 无 |
| `category list-conversations` | 拉取指定分组下会话 | `--category-id`,可选 `--exclude-muted` | | `+category-create` | 创建会话分组 | `--title` |
| `category list-by-conv` | 拉取指定会话所属的用户自定义会话分组 | `--group` | | `+category-add-conversation` | 将会话加入分组 | `--group` `--category-ids` |
| `category batch-info` | 批量拉取用户自定义会话分组信息 | `--category-ids` | | <!-- dws-intent: chat.category.list-conversations -->`dws chat +category-list` → `dws chat +category-list-conversations` | 先解析真实分组,再按稳定 ID 列出其中会话 | 第二步必填 `--category-id`;可选 `--exclude-muted` |
| `category create` | 创建会话分组 | `--title` | | `+category-remove-conversation` | 将会话移出分组 | `--group` `--category-ids` |
| `category create-smart` | 创建智能会话分组,可按群名称关键词和群内成员匹配 | `--name`,可选 `--keywords` `--members` | | `+category-rename` | 修改分组名称 | `--category-id` `--title` |
| `category delete` | 删除会话分组 | `--category-id` | | `+category-delete` | 删除会话分组 | `--category-id` |
| `category rename` | 修改分组名称 | `--category-id` `--title` |
| `category add-conv` | 将会话加入分组 | `--group` `--category-ids` |
| `category remove-conv` | 将会话移出分组 | `--group` `--category-ids` |
```bash ```bash
dws chat category list dws chat +category-create --title "工作群" --format json
dws chat category list-by-conv --group <openConversationId> dws chat +category-add-conversation --group <openConversationId> --category-ids 123,456 --format json
dws chat category batch-info --category-ids 123,456 dws chat +category-list --format json
dws chat category create --title "工作群" dws chat +category-list-conversations --category-id 123 --format json
dws chat category create-smart --name "重点群" --keywords "重点,项目" --members openDingTalkId1,openDingTalkId2 dws chat +category-delete --category-id 123 --format json
dws chat category add-conv --group <openConversationId> --category-ids 123,456
``` ```
`create-smart` 中 `--keywords` 是群名称关键词列表,`--members` 是群内成员 openDingTalkId 列表;两者可单独使用,也可组合使用。 只有分类标题时,先在 `+category-list` 的明确 `categories[]` 中做完整标题唯一匹配,再把该项的稳定 `categoryId` 传给第二步。零命中或重名时停止;只有用户明确授权“任意一个”时,才按服务端返回顺序选择首项。
只有 Shortcut 尚未覆盖的低频查询和智能规则才使用原子 `category list-by-conv`、
`category batch-info`、`category create-smart`。`create-smart --keywords` 是群名关键词列表,
`--members` 是群成员 openDingTalkId 列表,两者可单独或组合使用。写操作在首次调用前按
Runtime gate 完成确认;上述示例不代表可以绕过确认。
## 常见工作流 ## 常见工作流
@@ -126,8 +135,8 @@ dws chat category add-conv --group <openConversationId> --category-ids 123,456
```bash ```bash
dws aisearch person --query "张三" --dimension name --format json dws aisearch person --query "张三" --dimension name --format json
dws chat conversation-info --user <userId> --format json dws chat +conversation-info --open-dingtalk-id <openDingTalkId> --format json
dws chat set-top --conversation-id <openConversationId> --format json dws chat +conversation-set-top --conversation-id <openConversationId> --format json
``` ```
### 查看置顶会话并拉消息 ### 查看置顶会话并拉消息
@@ -140,11 +149,12 @@ dws chat +chat-messages --group <openConversationId> --time "2026-03-10 00:00:00
### 会话分组 ### 会话分组
```bash ```bash
dws chat category create --title "重点项目" --format json dws chat +category-create --title "重点项目" --format json
dws chat category add-conv --group <openConversationId> --category-ids <categoryId> --format json dws chat +category-add-conversation --group <openConversationId> --category-ids <categoryId> --format json
dws chat +category-list-conversations --category-id <categoryId> --format json
dws chat category list-by-conv --group <openConversationId> --format json dws chat category list-by-conv --group <openConversationId> --format json
dws chat category batch-info --category-ids <categoryId> --format json dws chat category batch-info --category-ids <categoryId> --format json
dws chat category list-conversations --category-id <categoryId> --format json dws chat +category-delete --category-id <categoryId> --format json
``` ```
### 智能会话分组 ### 智能会话分组
@@ -158,8 +168,8 @@ dws chat category create-smart --name "重点群" --keywords "重点" --members
## 常见错误与回退 ## 常见错误与回退
- 用户说“置顶消息”:用 `message set-top-msg`,不是 `chat set-top`。 - 用户说“置顶消息”:用 `+messages-set-top`,不是 `+conversation-set-top`。
- 用户说“置顶会话”:设置/取消用 `chat set-top`,查看列表用 `+conversation-list-top`。 - 用户说“置顶会话”:设置/取消用 `+conversation-set-top`,查看列表用 `+conversation-list-top`。
- 单聊没有会话 ID:先 `conversation-info --user` 或 `--open-dingtalk-id`。 - 单聊没有会话 ID:默认用 `+conversation-info --open-dingtalk-id`;只有 userId 时才用原子 `conversation-info --user`。
- 清空聊天记录前必须确认目标会话;该操作只影响当前用户视角。 - 清空聊天记录前必须确认目标会话;该操作只影响当前用户视角。
- 智能分组没有匹配条件:至少确认分组名称;关键词和成员规则不明确时先向用户确认,不要自行猜成员。 - 智能分组没有匹配条件:至少确认分组名称;关键词和成员规则不明确时先向用户确认,不要自行猜成员。
@@ -9,7 +9,7 @@
## 安全与目标 ## 安全与目标
- 群目标统一使用当前 profile 下真实 `openConversationId`;支持自然群名的 Shortcut 由 CLI - 群目标统一使用当前 profile 下真实 `openConversationId`;支持自然群名的 Shortcut 由 CLI
唯一解析,多候选时停止。 唯一解析,多候选时停止;需要先定位真实群聊/聊天群时使用 `+chat-search`,不搜索会话分组名。
- 解散群、踢人、转让群主、禁言、管理员和外部群升级都是高影响操作;以最终 Runtime gate - 解散群、踢人、转让群主、禁言、管理员和外部群升级都是高影响操作;以最终 Runtime gate
和精确 leaf Schema 为准确认对象、动作与影响。 和精确 leaf Schema 为准确认对象、动作与影响。
- 所有自然成员和群主必须先完成唯一解析并按稳定 ID 去重,再开始任何写入;不得边解析边 - 所有自然成员和群主必须先完成唯一解析并按稳定 ID 去重,再开始任何写入;不得边解析边
@@ -47,9 +47,9 @@ Schema。普通内部/外部群、话题群和显式群主已经由 `+chat-creat
|---|---| |---|---|
| 添加成员 | `group members add --id <cid> --users <userIds>` | | 添加成员 | `group members add --id <cid> --users <userIds>` |
| 移除成员 | `group members remove --id <cid> --users <userIds>` | | 移除成员 | `group members remove --id <cid> --users <userIds>` |
| 添加已知机器人 | `+chat-add-bot` 或精确原子 `group members add-bot` | | 添加已知机器人 | `+chat-add-bot` |
| 查看群内机器人 | `+chat-bots --group <群名或cid>` | | 查看群内机器人 | `+chat-bots --group <群名或cid>` |
| 移除群内机器人 | `+chat-remove-bot` 或精确原子 `group members remove-bot` | | 移除群内机器人 | `+chat-remove-bot` |
普通成员增删的 `--users` 只接受组织 `userId`,必须来自真实人员解析结果;不得把 普通成员增删的 `--users` 只接受组织 `userId`,必须来自真实人员解析结果;不得把
`+chat-members-list` / `+chat-members-get` 返回的 `openDingTalkId` 直接传入。添加已知机器人 `+chat-members-list` / `+chat-members-get` 返回的 `openDingTalkId` 直接传入。添加已知机器人
@@ -57,6 +57,7 @@ Schema。普通内部/外部群、话题群和显式群主已经由 `+chat-creat
缺少 `openBotId` 时在同一流程中先执行 `+chat-bots`,不必额外读取群发现 reference。只有需要 缺少 `openBotId` 时在同一流程中先执行 `+chat-bots`,不必额外读取群发现 reference。只有需要
搜索未知机器人、区分 `bot search` / `bot find`、机器人发送或撤回、Webhook 时,才读取 搜索未知机器人、区分 `bot search` / `bot find`、机器人发送或撤回、Webhook 时,才读取
[chat-bot.md](chat-bot.md)。 [chat-bot.md](chat-bot.md)。
原子 `group members add-bot/remove-bot` 只作 Shortcut 未公开字段或原始响应 fallback。
## 邀请卡片、群主、管理员与禁言 ## 邀请卡片、群主、管理员与禁言
@@ -71,27 +72,28 @@ dws chat group share-invite --source <sourceCid> --receiver <openDingTalkId> --f
| 动作 | 入口与关键参数 | | 动作 | 入口与关键参数 |
|---|---| |---|---|
| 转让群主 | `+chat-transfer-owner --group <cid> --new-owner <稳定ID>` | | 转让群主 | `+chat-transfer-owner --group <cid> --new-owner <稳定ID>` |
| 设置/取消管理员 | `group set-admin --group <cid> --users <ids> [--off]` | | 设置/取消管理员 | `+chat-set-admin --group <cid> --users <ids> [--off]` |
| 全员禁言/解除 | `group-mute --group <cid> [--off]` | | 全员禁言/解除 | `+chat-mute --group <cid> [--off]` |
| 成员禁言/解除 | `+chat-mute-member` 或 `group-mute-member` | | 成员禁言/解除 | `+chat-mute-member` |
| 查询禁言配置 | `group get-mute-config --group <cid>` | | 查询禁言配置 | `group get-mute-config --group <cid>` |
原子 `group-mute-member --mute-time` 单位为毫秒。不要用展示名称代替稳定用户 ID,也不要 原子 `group-mute-member --mute-time` 单位为毫秒。不要用展示名称代替稳定用户 ID,也不要
在未确认影响时执行转让、踢人或禁言。 在未确认影响时执行转让、踢人或禁言。`group set-admin`、`group-mute`、
`group-mute-member` 仅在对应 Shortcut 未公开所需底层字段或原始响应时使用。
## 群设置与当前用户偏好 ## 群设置与当前用户偏好
管理员级群开关使用 `+chat-update-settings` 或原子 `group update-settings`。常见 settingKey 管理员级群开关使用 `+chat-update-settings`;原子 `group update-settings` 只作未公开字段或原始响应 fallback。常见 settingKey
包括 `authority`、`joinValidation`、`onlyAdminCanAtAll`、`searchable`、 包括 `authority`、`joinValidation`、`onlyAdminCanAtAll`、`searchable`、
`addFriendForbidden`、`onlyAdminCanDING`、`onlyAdminCanPinMsg` 和 `addFriendForbidden`、`onlyAdminCanDING`、`onlyAdminCanPinMsg` 和
`onlyAdminCanSendFile`、`groupEmailDisabled`、`groupLiveAuthority`、 `onlyAdminCanSendFile`、`groupEmailDisabled`、`groupLiveAuthority`、
`groupBillAuthority`;只修改用户明确要求的字段。 `groupBillAuthority`;只修改用户明确要求的字段。
新成员历史消息可见范围使用 `group set-history --group <cid> --option <值>`;`option` 只取 新成员历史消息可见范围使用 `+chat-set-history --group <cid> --option <值>`;`option` 只取
精确 leaf Schema 发布值,不按自然语言猜枚举。 精确 leaf Schema 发布值,不按自然语言猜枚举。
当前登录用户自己的置顶、免打扰、群昵称和群备注使用 `group user-settings query/set`, 当前登录用户自己的置顶、免打扰批量设置使用 `group user-settings query/set`,不是管理员群开关。
不是管理员群开关。单个群昵称/备注优先 `group update-nick/update-alias`。 单个群昵称/备注使用 `+chat-update-nick` / `+chat-update-alias`;对应原子命令只作底层 fallback。
```bash ```bash
dws chat group user-settings query --groups <cid1>,<cid2> --format json dws chat group user-settings query --groups <cid1>,<cid2> --format json
@@ -114,21 +116,28 @@ dws chat group user-settings set \
## 入群审批与群身份 ## 入群审批与群身份
先用 `group list-join-validations` 取得真实 `record-id/applicant/inviter`,再执行 先用 `+chat-list-join-requests` 取得真实 `record-id/applicant/inviter`,再执行
`group audit-join-validation` 或 `+chat-audit-join`。审批状态只使用精确 leaf Schema 发布值。 `+chat-audit-join`。对应原子命令只作未公开字段或原始响应 fallback;审批状态只用 leaf Schema 发布值。
群身份使用 `group-role` / `+chat-role-*`: 群身份生命周期默认使用 Shortcut:
- `list/add/update/remove` 管理身份定义; - `+chat-role-list` / `+chat-role-add` / `+chat-role-update` / `+chat-role-remove` 管理身份定义;
- `set-user/remove-user/query-user` 管理成员身份; - `+chat-role-set-user` / `+chat-role-remove-user` / `+chat-role-query-user` 管理成员身份;
- `openRoleId` 必须来自真实身份列表。 - Shortcut 的 `--group` 可传群名或 `openConversationId`,群名多候选时在业务调用前停止;
- Shortcut `+chat-role-set-user` / `+chat-role-remove-user` 使用复数 `--role-ids`;前者必须至少包含一个真实 `openRoleId`;
- 原子 `group-role set-user` 使用公开主参数 `--role-id` 且一次只接收一个身份;
- 原子 `group-role remove-user` 使用 `--role-ids`;
- 删除一个身份定义使用 `group-role remove --role-id`;只撤销成员的指定身份使用 `remove-user`,不传空字符串猜“清空”。
覆盖或清除成员身份前确认用户、群和完整角色集合,不能用展示名称猜 `openRoleId`。 原子 `group-role` 保持可执行,但只在对应 Shortcut 未公开所需底层参数、原始响应或不同执行
语义时使用;普通列出、创建、改名、删除、分配、撤销和成员身份查询不同时选择两套入口。
整体覆盖或撤销成员身份前确认用户、群和完整角色集合,不能用展示名称猜 `openRoleId`。
## 退出、解散与外部群升级 ## 退出、解散与外部群升级
- 当前用户退出群:`+chat-quit` 或精确原子 `group quit`。 - 当前用户退出群:`+chat-quit --group <cid>`;只有需要未公开底层字段或原始响应时才用 `group quit`。
- 解散群:`group dismiss`,不可逆。 - 解散群:`+chat-dismiss --group <cid>`,不可逆;原子 `group dismiss` 仅作底层精确 fallback。
- 普通群升级外部群:`group upgrade-to-external`,不可逆。 - 普通群升级外部群:`group upgrade-to-external`,不可逆。
这些动作必须以最终 Runtime gate 为准,不把示例中的确认参数当固定事实。 这些动作必须以最终 Runtime gate 为准,不把示例中的确认参数当固定事实。
@@ -64,7 +64,7 @@ dws chat +chat-list-mine --role ADMIN --exclude-muted --format json
dws chat +chat-search --query "项目冲刺" --page-all --format json dws chat +chat-search --query "项目冲刺" --page-all --format json
``` ```
只有数字群号时,使用 `chat group get-by-group-id --group-id <数字>` 转换为 只有数字群号时,使用 `+chat-get-by-id --group-id <数字>` 转换为
`openConversationId`。需要搜索共同群时使用原子 `chat search-common`;`AND` 表示所有人 `openConversationId`。需要搜索共同群时使用原子 `chat search-common`;`AND` 表示所有人
都在群里,`OR` 表示任一人在群里。自然人员必须先解析为当前 profile 的真实身份。 都在群里,`OR` 表示任一人在群里。自然人员必须先解析为当前 profile 的真实身份。
@@ -106,7 +106,7 @@ dws chat +chat-members-get --id <openConversationId> \
| 原子命令 | 仅用于 | | 原子命令 | 仅用于 |
|---|---| |---|---|
| `chat search` / `search-common` | Shortcut 未发布的搜索字段或共同群 | | `chat search` / `search-common` | Shortcut 未发布的搜索字段或共同群 |
| `chat group get-by-group-id` | 数字群号转换 | | `chat group get-by-group-id` | Shortcut 未公开字段或原始响应 |
| `chat group members` | 需要原始成员分页响应 | | `chat group members` | 需要原始成员分页响应 |
| `chat group members list-by-ids` | 需要原始批量成员详情 | | `chat group members list-by-ids` | 需要原始批量成员详情 |
| `chat group list-all` / `list-my-groups` | 需要 Shortcut 未投影的真实底层字段 | | `chat group list-all` / `list-my-groups` | 需要 Shortcut 未投影的真实底层字段 |
@@ -30,32 +30,33 @@
- 子消息使用自己的 `messageId`;只在缺会话 ID 时继承父消息 `conversationId`。 - 子消息使用自己的 `messageId`;只在缺会话 ID 时继承父消息 `conversationId`。
- Bot 撤回使用 `processQueryKey`,不使用本文件的 `openMessageId` 路线。 - Bot 撤回使用 `processQueryKey`,不使用本文件的 `openMessageId` 路线。
刚由用户身份发送的消息如果只得到 `openTaskId`,先查询发送状态: 刚由用户身份发送的消息如果只得到 `openTaskId`,用 `+messages-query-send-status` 查询;
只有需要 Shortcut 未公开字段或原始响应时才用原子 `message query-send-status`:
```text ```text
+messages-send 或 message send +messages-send
→ openTaskId → openTaskId
→ message query-send-status → +messages-query-send-status
→ openMessageId + openConversationId → openMessageId + openConversationId
→ 编辑或撤回 → 编辑或撤回
``` ```
## 撤回与编辑 ## 撤回与编辑
`+messages-recall` 可只传 `--msg-id`;省略会话 ID 时 CLI 会通过只读消息详情补齐。 `+messages-recall` 可只传公开主参数 `--msg-id`;省略会话 ID 时 CLI 会通过只读消息详情补齐。
兼容单值 `--message-ids`,但不要把 `processQueryKey` 当消息 ID。 不要把 `processQueryKey` 当消息 ID。
```bash ```bash
dws chat +messages-recall --msg-id <openMessageId> --format json dws chat +messages-recall --msg-id <openMessageId> --format json
dws chat +messages-recall --conversation-id <openConversationId> --msg-id <openMessageId> --format json dws chat +messages-recall --conversation-id <openConversationId> --msg-id <openMessageId> --format json
``` ```
编辑使用 `message edit --conversation-id <cid> --msg-id <id>`,并在 `--text` 与 `--content` 编辑使用 `message edit --conversation-id <cid> --message-id <id>`,并在 `--text` 与 `--content`
中二选一。`--text` 由 CLI 生成 Markdown content;`--content` 必须是完整 content JSON。 中二选一。`--text` 由 CLI 生成 Markdown content;`--content` 必须是完整 content JSON。
```bash ```bash
dws chat message edit --conversation-id <openConversationId> --msg-id <openMessageId> --text "更新后的内容" dws chat message edit --conversation-id <openConversationId> --message-id <openMessageId> --text "更新后的内容"
dws chat message edit --conversation-id <openConversationId> --msg-id <openMessageId> --content '{"title":"标题","text":"更新后的内容"}' dws chat message edit --conversation-id <openConversationId> --message-id <openMessageId> --content '{"title":"标题","text":"更新后的内容"}'
``` ```
群聊 @所有人使用 `--at-all`;指定人员使用 `--at-open-dingtalk-ids`。正文中的占位符以 群聊 @所有人使用 `--at-all`;指定人员使用 `--at-open-dingtalk-ids`。正文中的占位符以
@@ -37,7 +37,7 @@ dws chat message send --conversation-id <openConversationId> \
用户要求真实发送结果时,保留发送返回的 `openTaskId`,再执行: 用户要求真实发送结果时,保留发送返回的 `openTaskId`,再执行:
```bash ```bash
dws chat message query-send-status --open-task-id <openTaskId> --format json dws chat +messages-query-send-status --open-task-id <openTaskId> --format json
``` ```
检查真实 `sendStatus`、`openMessageId` 和 `openConversationId`。 检查真实 `sendStatus`、`openMessageId` 和 `openConversationId`。
@@ -56,14 +56,16 @@ dws chat message query-send-status --open-task-id <openTaskId> --format json
若 `fileId` 返回 `RESOURCE_NOT_FOUND`,不得把同一个 ID 改称 `mediaId` 重试,也不得原样 若 `fileId` 返回 `RESOURCE_NOT_FOUND`,不得把同一个 ID 改称 `mediaId` 重试,也不得原样
重复调用;应回到消息查询并使用 `--download-resources`。 重复调用;应回到消息查询并使用 `--download-resources`。
底层 fallback: 已有完整资源引用时使用公开 Shortcut:
```bash ```bash
dws chat message download-media --type mediaId --resource-id <mediaId> \ dws chat +messages-resource-download --type mediaId --resource-id <mediaId> \
--message-id <openMessageId> --open-conversation-id <openConversationId> \ --message-id <openMessageId> --open-conversation-id <openConversationId> \
--output ./downloads/ --output ./downloads/
``` ```
原子 `message download-media` 仅用于 Shortcut 未发布的底层字段或原始响应。
`resource-id`、`message-id` 和会话 ID 必须来自同一 profile 下的真实消息查询结果。 `resource-id`、`message-id` 和会话 ID 必须来自同一 profile 下的真实消息查询结果。
当前没有 Range/断点续传;失败时保留 ledger 或错误,显式重试整个文件,不拼接残片。 当前没有 Range/断点续传;失败时保留 ledger 或错误,显式重试整个文件,不拼接残片。
@@ -11,19 +11,65 @@ Favorite、Pin 和 reaction。只读任务优先使用 Shortcut;只有 Shortcu
| 用户终点 | 唯一推荐入口 | | 用户终点 | 唯一推荐入口 |
|---|---| |---|---|
| <!-- dws-intent: chat.read.conversation -->浏览或导出一个指定群聊/单聊 | `dws chat +chat-messages` | | <!-- dws-intent: chat.read.conversation -->浏览或导出一个指定群聊/单聊 | `dws chat +chat-messages` |
| <!-- dws-intent: chat.read.reactions -->筛选指定会话中存在 reaction 的消息 | `dws chat +search-msg --group <群名或ID> --has-reactions --page-all` |
| <!-- dws-intent: chat.search.filtered -->发送者、关键词、@对象或消息类型是主要条件 | `dws chat +search-msg` | | <!-- dws-intent: chat.search.filtered -->发送者、关键词、@对象或消息类型是主要条件 | `dws chat +search-msg` |
| <!-- dws-intent: chat.conversation.active-since -->最近 24 小时或指定时间以来哪些会话有新消息,只要会话摘要 | `dws chat +recent-conversations`;可选 `--start/--end` |
| 跨全部可见会话读取、总结或统计时间范围 | `dws chat message list-all` |
| 已知消息 IDs 读取详情 | `dws chat +messages-mget` | | 已知消息 IDs 读取详情 | `dws chat +messages-mget` |
| 查看 @我的消息 | `dws chat +at-me` | | 查看 @我的消息 | `dws chat +at-me` |
| 查看未读消息所在会话 | `dws chat +unread-chats` |
| 查看 Favorite | `dws chat +flag-list` | | 查看 Favorite | `dws chat +flag-list` |
| 已知话题主消息或 thread/topic ID 读取回复 | `dws chat +thread-replies` | | 已知话题主消息或 thread/topic ID 读取回复 | `dws chat +thread-replies` |
`+chat-messages` 是指定会话的粗粒度读取;`+search-msg` 是目标条件明确的单/跨会话检索。 `+chat-messages` 是指定会话的粗粒度读取;`+search-msg` 是目标条件明确的单/跨会话检索。
`chat message list-all` 是跨全部可见会话按时间范围读取的唯一推荐入口;虽然路径不带 `+`,
它提供受控翻页、完整性和 Result,不需要先列会话逐群循环。
不要先读完整会话再补跑搜索,也不要把群名或姓名直接填入只接受稳定 ID 的参数。 不要先读完整会话再补跑搜索,也不要把群名或姓名直接填入只接受稳定 ID 的参数。
旧名 `+active-conversations` 保留为隐藏但可执行的兼容入口;新调用使用 `+recent-conversations`。
两者共用实现、参数和结果,使用旧名不会额外告警。
`+recent-conversations` 不返回消息正文。它自动沿 `nextCursor` 翻页,按 `openConversationId`
去重,并返回 `conversationId`、`name`、
`nameKnown`、`direct/group/unknown` 类型及时间窗内的最大消息时间。名称未知时仍返回
`name=""`、`nameKnown=false`。两个时间参数都省略时查询最近 24 小时;仅传 `--end` 时,
默认 `start=end-24h`,不是当天零点。显式 `--start` 保持原有时间解析规则,空值或纯空白仍报错;
`--page-delay` 默认 200ms,范围 0–60000ms,分页等待可被取消。`--total-timeout` 默认
300 秒(1–3600),约束本次所有页、重试和等待共用的查询预算;全局 `--timeout` 仍是单次请求上限。
查询边界 `--start/--end` 仅支持整秒;显式传入非零小数秒会在参数校验时失败。省略
`--end` 时,将本次查询取到的当前时间向下取整秒后固定,因此不包含当前尚未结束的
这一秒,再从有效 `end` 倒推默认 `start`。最终有效整秒区间必须满足 `end > start`;同一 `start/end` 用于所有页请求、结果返回
和续查。该限制只作用于查询边界,消息 `latestMessageTime` 仍保留毫秒精度。
成功时会话摘要位于 `data`,其中 `pageSize` 记录本次 `--limit`。达到 `--page-limit` 仍有
下一页时,保留 `data.complete=false` 和 `meta.pagination.next_token`。后续页失败时返回
`outcome=partial_failure`(退出码 7):`data.succeeded[0]` 的 `id=completed-pages`,其余
字段是已成功页面的同形聚合摘要,`complete=false`;`data.failed[0]` 的 `id=page:N`,
其 `error` 保留原始错误诊断,`error.details.failedPage/failedCursor` 标明失败页及输入
游标,`meta.pagination.next_token` 也指向该失败页。首个请求页失败时返回普通 `failure`。
遇到分页错误先按真实错误排查,不自动重试;不能只因拿到部分数据就报告查询完成。
本命令不保存磁盘进度,进程强制终止后需要重新查询。`pagesFetched` 仅计本次已验证页面。
分页耗尽不保证服务端索引无延迟或提供一致性快照。
`--cursor` 用于手工续查,CLI 不能跨次校验参数绑定或游标循环,也不自动恢复历史聚合。
使用 `--cursor` 续查时,必须保持同一 profile,并显式复用上次摘要中的 `start`、`end`
和 `pageSize`(分别传给 `--start/--end/--limit`);非首页游标缺少显式 `--start` 或 `--end` 会报错,不能重新使用滚动默认窗口。续页响应不包含此前批次,所以即使
`meta.pagination.endpoint_exhausted=true`,该批自身的 `complete` 仍为 `false`。调用方应将
前后批次按 `conversationId` 合并,保留更大的 `latestMessageTime`;只有从首页起无遗漏地
接续全部批次、处理完失败页且最终确认 endpoint 耗尽,才能将合并结果称为全量。游标过期时需从首页重查。
```bash
dws chat +recent-conversations --format json
dws chat +recent-conversations --end "2026-09-08T15:30:00+08:00" --format json
dws chat +recent-conversations --start "2026-09-07T00:00:00+08:00" --format json
```
## 指定会话读取 ## 指定会话读取
群聊 `--group` 可传群名或 `openConversationId`;也可用 `--chat-query` 显式解析群名、 群聊 `--group` 可传群名或 `openConversationId`;也可用 `--chat-query` 显式解析群名。
用 `--conversation-id` 显式传稳定 ID。单聊使用 `--user` 或 `--open-dingtalk-id`。 单聊使用 `--user` 或 `--open-dingtalk-id`。
```bash ```bash
dws chat +chat-messages --group <群名或openConversationId> --format json dws chat +chat-messages --group <群名或openConversationId> --format json
@@ -54,14 +100,8 @@ dws chat +chat-messages --group <openConversationId> \
--order asc --page-all --format json --order asc --page-all --format json
``` ```
完整读取后只需消息字段可判断的子集时,在同一次调用中使用全局 `--jq`,保留根信封并 只需消息字段可判断的子集可在同一次调用中使用全局 `--jq`,但必须保留根信封并同步
同步改写 `messages/count`;不得丢失 `complete`、`hasMore`、`failures` 等 ledger。 改写 `messages/count`;发送者姓名仍用 `--sender-query` 解析稳定身份,不按展示名过滤。
发送者姓名仍使用 `--sender-query` 解析稳定身份,不用 `--jq` 比较展示名。
```bash
dws chat +chat-messages --group "项目群" --page-all --format json \
--jq '. as $root | [.messages[] | select((.reactions // []) | length > 0)] as $matched | $root | .messages = $matched | .count = ($matched | length)'
```
要求导出时用 `--output <工作目录内相对.json>` 原子写入;需要资源时在读取命令上加 要求导出时用 `--output <工作目录内相对.json>` 原子写入;需要资源时在读取命令上加
`--download-resources`,不要让 Agent 先输出全量 JSON 再手工遍历资源引用。 `--download-resources`,不要让 Agent 先输出全量 JSON 再手工遍历资源引用。
@@ -75,11 +115,13 @@ dws chat +chat-messages --group "项目群" --page-all --format json \
- 不传会话过滤时搜索全部会话;默认时间范围为最近 7 天。 - 不传会话过滤时搜索全部会话;默认时间范围为最近 7 天。
- `--page-all` 只翻完当前时间范围内的游标页;精确范围使用成对的 `--start/--end`。 - `--page-all` 只翻完当前时间范围内的游标页;精确范围使用成对的 `--start/--end`。
- `--order` 只稳定排列已经取得的结果;未全量或 `complete=false` 时不得称为完整范围全局排序。 - `--order` 只稳定排列已经取得的结果;未全量或 `complete=false` 时不得称为完整范围全局排序。
- reaction 是消息原生谓词:使用 `--has-reactions --page-all`,不能与 `--no-enrich` 组合;CLI 在详情富化后过滤并返回 `reactionFilter` 证据。
```bash ```bash
dws chat +search-msg --chat-query "项目群" --sender-query "测试用户甲" --page-all --format json dws chat +search-msg --chat-query "项目群" --sender-query "测试用户甲" --page-all --format json
dws chat +search-msg --chat-query "项目群" --query "发布计划" --page-all --format json dws chat +search-msg --chat-query "项目群" --query "发布计划" --page-all --format json
dws chat +search-msg --sender-query "测试用户甲" --page-all --format json dws chat +search-msg --sender-query "测试用户甲" --page-all --format json
dws chat +search-msg --group "项目群" --has-reactions --page-all --format json
``` ```
需要 Shortcut 未发布的原始过滤字段或响应时,才评估 `message search-advanced`。它支持 需要 Shortcut 未发布的原始过滤字段或响应时,才评估 `message search-advanced`。它支持
@@ -106,7 +148,7 @@ dws chat +search-msg --sender-query "测试用户甲" --page-all --format json
| Favorite 列表 | `+flag-list`;要求全部时加 `--page-all`,页大小 1–30 | | Favorite 列表 | `+flag-list`;要求全部时加 `--page-all`,页大小 1–30 |
| 消息 Pin 列表 | `message list-pin-msg --open-conversation-id <cid>` | | 消息 Pin 列表 | `message list-pin-msg --open-conversation-id <cid>` |
| 批量 reaction/文字回应 | `message list-emotion-replies --msg-ids <id...>` | | 批量 reaction/文字回应 | `message list-emotion-replies --msg-ids <id...>` |
| 已读/未读状态 | `message read-status --group <cid> --message-id <id>` | | 已读/未读状态 | `message read-status --conversation-id <cid> --message-id <id>` |
Favorite、消息 Pin、消息 Top 和会话 Top 是不同对象。写入或取消这些状态读取 Favorite、消息 Pin、消息 Top 和会话 Top 是不同对象。写入或取消这些状态读取
[message-actions.md](message-actions.md),这里只负责查询。 [message-actions.md](message-actions.md),这里只负责查询。
@@ -116,7 +158,7 @@ Favorite、消息 Pin、消息 Top 和会话 Top 是不同对象。写入或取
| 原子命令 | 仅用于 | | 原子命令 | 仅用于 |
|---|---| |---|---|
| `message list` | 指定会话原始响应或显式手工 continuation | | `message list` | 指定会话原始响应或显式手工 continuation |
| `message list-all` | 时间范围内全部会话的原始分页响应 | | `message list-all` | 需要时间范围内全部会话的原始消息明细或手工 continuation;只要会话摘要使用 `+recent-conversations` |
| `message list-by-sender` | 已有稳定发送者 ID 且需要底层原始响应 | | `message list-by-sender` | 已有稳定发送者 ID 且需要底层原始响应 |
| `message list-mentions` / `list-focused` | @我或特别关注的原始列表 | | `message list-mentions` / `list-focused` | @我或特别关注的原始列表 |
| `message search` / `search-advanced` | Shortcut 未发布的真实过滤字段 | | `message search` / `search-advanced` | Shortcut 未发布的真实过滤字段 |
@@ -13,8 +13,8 @@
| 将普通群中的已有消息升级为 Thread | `dws chat thread promote --conversation-id <openConversationId> --message-id <openMessageId>` | | 将普通群中的已有消息升级为 Thread | `dws chat thread promote --conversation-id <openConversationId> --message-id <openMessageId>` |
| 浏览话题主消息 | `dws chat thread list --conversation-id <openConversationId>` | | 浏览话题主消息 | `dws chat thread list --conversation-id <openConversationId>` |
| 向具体话题直接追加回复 | `dws chat thread reply --conversation-id <openConvThreadId>` | | 向具体话题直接追加回复 | `dws chat thread reply --conversation-id <openConvThreadId>` |
| 分页读取一个话题的回复 | `dws chat thread list-replies --conversation-id <openConversationId> --topic-id <openConvThreadId>` | | 读取一个话题的回复 | `dws chat +thread-replies --group <openConversationId> --thread-id <openConvThreadId>` |
| 转发整条话题 | `dws chat thread forward --src-msg-id <openMessageId> --src-conversation-id <openConversationId> --src-thread-id <openConvThreadId> --dest-conversation-id <openConversationId>` | | 转发整条话题 | `dws chat +messages-forward-topic --src-msg-id <openMessageId> --src-conversation-id <openConversationId> --src-thread-id <openConvThreadId> --dest-conversation-id <openConversationId>` |
| 撤回话题中的一条消息 | `dws chat thread recall-message --conversation-id <openConversationId> --message-id <openMessageId>` | | 撤回话题中的一条消息 | `dws chat thread recall-message --conversation-id <openConversationId> --message-id <openMessageId>` |
| 添加或移除 emoji | `dws chat thread add-emoji` / `remove-emoji` | | 添加或移除 emoji | `dws chat thread add-emoji` / `remove-emoji` |
| 查询 Thread 消息的表情回复 | `dws chat thread list-emotion-replies --msg-ids <openMessageId,...>` | | 查询 Thread 消息的表情回复 | `dws chat thread list-emotion-replies --msg-ids <openMessageId,...>` |
@@ -28,15 +28,15 @@
`thread reply` 沿用原发送命令的 `--conversation-id`,但这里传 Thread 子会话的 `openConvThreadId`。它直接追加回复,不使用消息引用回复,也不创建新的顶层 Thread。 `thread reply` 沿用原发送命令的 `--conversation-id`,但这里传 Thread 子会话的 `openConvThreadId`。它直接追加回复,不使用消息引用回复,也不创建新的顶层 Thread。
已有父会话 `openConversationId` 和 Thread `openConvThreadId` 且只需读取一页时,使用 `thread list-replies --conversation-id ... --topic-id ...`。需要按主消息自动解析、全量翻页、排序或下载资源时,使用 `+thread-replies` Shortcut。 已有父会话 `openConversationId` 和 Thread `openConvThreadId` 时使用 `+thread-replies --group ... --thread-id ...`;只有主消息 ID 时用 `--message-id` 自动解析。按需加 `--page-all`、排序或资源下载。原子 `thread list-replies` 仅用于 Shortcut 未发布的底层字段或原始响应。只浏览话题主消息时使用 `thread list`。
用户需要逐条查看、列出或概括具体回复内容时,使用 `thread list-replies`;只浏览话题主消息时使用 `thread list`。需要自动读取全部页面、排序或下载资源时,使用 `+thread-replies` Shortcut。
整条 Thread 可转发到普通群;当前不支持从话题圈向另一个话题圈转发整条 Thread。 整条 Thread 可转发到普通群;当前不支持从话题圈向另一个话题圈转发整条 Thread。
## 消息操作 ## 消息操作
撤回、emoji 和文字表情命令沿用对应 `chat message` 命令的主参数。Runtime 会先读取消息并校验其属于 Thread,再执行操作;批量查询会逐条校验 `--msg-ids`。文字表情的 `emotionId`、`backgroundId`、名称和文字使用 `chat message create-text-emotion` 返回的实际值;移除时使用已添加的值,更新时用 `--old-emotion-id` 传当前值、其余表情参数传新值。 撤回、emoji 和文字表情命令沿用对应 `chat message` 命令的主参数。Runtime 会先读取消息并校验其属于 Thread,再执行操作;批量查询会逐条校验 `--msg-ids`。文字表情先用 `+messages-create-text-emotion`,添加/移除用 `+messages-add-text-emotion` / `+messages-remove-text-emotion`;其 `emotionId`、`backgroundId`、名称和文字必须来自真实返回。仅更新使用未被 Shortcut 覆盖的 `thread update-text-emotion`。
整条话题默认用 `+messages-forward-topic`;原子 `thread forward` 仅用于未公开字段或原始响应。
## 完成与错误 ## 完成与错误
@@ -11,7 +11,7 @@
<!-- DWS_MESSAGE_RESULT_CONTRACT_START --> <!-- DWS_MESSAGE_RESULT_CONTRACT_START -->
- `version`: `im.message-list.v1` - `version`: `im.message-list.v1`
- `message_fields`: `messageId`, `conversationId`, `threadId`, `sender`, `senderId`, `senderType`, `messageType`, `text`, `createTime`, `updateTime`, `reactions`, `quotedMessage`, `forwarded`, `resourceRefs` - `message_fields`: `messageId`, `conversationId`, `threadId`, `sender`, `senderId`, `senderType`, `messageType`, `messageAiSendFlag`, `text`, `createTime`, `updateTime`, `reactions`, `quotedMessage`, `forwarded`, `resourceRefs`
- `envelope_fields`: `contractVersion`, `messages`, `count`, `resolvedFilters`, `queryRange`, `pagesFetched`, `paginationKnown`, `complete`, `hasMore`, `nextPage`, `stopReason`, `truncated`, `truncatedByPageLimit`, `truncatedByResultLimit`, `failedCount`, `failures`, `partial`, `scope`, `resourceDownloads` - `envelope_fields`: `contractVersion`, `messages`, `count`, `resolvedFilters`, `queryRange`, `pagesFetched`, `paginationKnown`, `complete`, `hasMore`, `nextPage`, `stopReason`, `truncated`, `truncatedByPageLimit`, `truncatedByResultLimit`, `failedCount`, `failures`, `partial`, `scope`, `resourceDownloads`
<!-- DWS_MESSAGE_RESULT_CONTRACT_END --> <!-- DWS_MESSAGE_RESULT_CONTRACT_END -->
@@ -29,8 +29,8 @@
<!-- DWS_IDENTITY_CAPABILITY_CONTRACT_START --> <!-- DWS_IDENTITY_CAPABILITY_CONTRACT_START -->
| identity | targets | content types | natural targets | mention targets | idempotency keys | batch ledger | | identity | targets | content types | natural targets | mention targets | idempotency keys | batch ledger |
|---|---|---|---|---|---:|---:| |---|---|---|---|---|---:|---:|
| `user` | `group`<br>`direct-user`<br>`direct-open-dingtalk-id` | `text`<br>`markdown`<br>`image-media-id`<br>`file`<br>`audio-as-file`<br>`video-as-file` | `chat-query`<br>`user-query` | `open-dingtalk-id`<br>`all` | `true` | `false` | | `user` | `group`<br>`direct-user`<br>`direct-open-dingtalk-id` | `text`<br>`markdown`<br>`image-media-id`<br>`file`<br>`audio-as-file`<br>`video-as-file`<br>`profile`<br>`share-chat`<br>`a2ui` | `chat-query`<br>`user-query` | `open-dingtalk-id`<br>`all` | `true` | `false` |
| `bot` | `group`<br>`groups`<br>`direct-users`<br>`direct-open-dingtalk-ids` | `text`<br>`markdown` | — | `user-id`<br>`open-dingtalk-id`<br>`all` | `false` | `true` | | `bot` | `group`<br>`groups`<br>`direct-users`<br>`direct-open-dingtalk-ids` | `text`<br>`markdown`<br>`image-url`<br>`file` | — | `user-id`<br>`open-dingtalk-id`<br>`all` | `false` | `true` |
| `webhook` | `token-owned-group` | `text`<br>`markdown` | — | `user-id`<br>`mobile`<br>`all` | `false` | `false` | | `webhook` | `token-owned-group` | `text`<br>`markdown` | — | `user-id`<br>`mobile`<br>`all` | `false` | `false` |
<!-- DWS_IDENTITY_CAPABILITY_CONTRACT_END --> <!-- DWS_IDENTITY_CAPABILITY_CONTRACT_END -->
@@ -38,6 +38,8 @@ Bot 多群用 `--groups` 或 `--groups-file`,Runtime 去重后输出
`im.batch-write.v1` 逐目标 ledger。Bot/Webhook 不支持的内容类型会在写前失败,不能降级为 `im.batch-write.v1` 逐目标 ledger。Bot/Webhook 不支持的内容类型会在写前失败,不能降级为
另一身份或偷偷改成纯文本。 另一身份或偷偷改成纯文本。
Bot 图片使用 `--image-url`;本地文件只接受单个群或单个 `--users` 接收者,媒体不接受 @。旧文本路线的 openDingTalkIds 支持不扩展到尚未声明该字段的媒体路线。个人名片使用 `--contact-id`,群邀请使用 `--share-chat-id`;A2UI 使用 `--a2ui-messages`,requestId 是链路追踪,不能使用 uuid/idempotency-key 冒充幂等。
## 流式卡片 ## 流式卡片
<!-- DWS_CARD_WORKFLOW_CONTRACT_START --> <!-- DWS_CARD_WORKFLOW_CONTRACT_START -->
@@ -56,8 +58,8 @@ Bot 多群用 `--groups` 或 `--groups-file`,Runtime 去重后输出
<!-- DWS_CAPABILITY_BOUNDARY_CONTRACT_START --> <!-- DWS_CAPABILITY_BOUNDARY_CONTRACT_START -->
| capability | supported | current route / boundary | | capability | supported | current route / boundary |
|---|---:|---| |---|---:|---|
| `thread-write` | `false` | quote reply with +messages-reply; thread reading with +thread-replies | | `thread-write` | `true` | personal +messages-reply --reply-in-thread; original chat thread reply remains available; Bot Thread write unsupported |
| `bot-rich-media` | `false` | bot text/markdown, or current-user file/image send | | `bot-rich-media` | `true` | bot image-url and single-target local file via +messages-send; native audio/video and arbitrary interactive cards unsupported |
| `card-action-callback` | `false` | streaming text card create/update only | | `card-action-callback` | `false` | streaming text card create/update only |
| `resource-resume` | `false` | atomic whole-file download with explicit retry | | `resource-resume` | `false` | atomic whole-file download with explicit retry |
| `group-member-full-pagination` | `true` | +chat-members-list or +group-members | | `group-member-full-pagination` | `true` | +chat-members-list or +group-members |
@@ -67,4 +69,11 @@ Bot 多群用 `--groups` 或 `--groups-file`,Runtime 去重后输出
`supported=false` 是执行门禁,不是待猜测字段。只有 lower interface、Runtime、测试、Schema 和 `supported=false` 是执行门禁,不是待猜测字段。只有 lower interface、Runtime、测试、Schema 和
此页同时升级后,才能改变对外承诺。 此页同时升级后,才能改变对外承诺。
其中 `card-action-callback=false` 只约束 `dingtalk-chat` 的服务端 callback URL、验签和回复
接口;当前用户的互动卡片操作可通过 [`dingtalk-event`](../../dingtalk-event/SKILL.md) 监听:
`dws event consume user_card_action_triggered --flatten -f ndjson`。个人事件监听不改变上述 chat
lower interface 门禁。
话题圈会话仍禁止引用消息回复;向 Thread 追加回复使用 `chat thread reply --conversation-id <openConvThreadId>`。 话题圈会话仍禁止引用消息回复;向 Thread 追加回复使用 `chat thread reply --conversation-id <openConvThreadId>`。
命名对齐的 alias 与固定置顶动作、个人编辑以及开发后的完整边界,见仓库 `docs/chat-parity/development-report.md`;下游需求见 `docs/chat-parity/im-team-requirements.md`。旧普通和专用入口保留。
@@ -43,7 +43,7 @@ Favorite、消息 Pin、消息 Top 和会话 Top 不能互换,即使用户都
| 已有成员 IDs 创建群 | `+chat-create` | | 已有成员 IDs 创建群 | `+chat-create` |
| 查群、查看成员、邀请链接 | [group-discovery.md](chat/group-discovery.md) | | 查群、查看成员、邀请链接 | [group-discovery.md](chat/group-discovery.md) |
| 加人、踢人、管理员、群公告、群设置 | [group-admin.md](chat/group-admin.md) | | 加人、踢人、管理员、群公告、群设置 | [group-admin.md](chat/group-admin.md) |
| 找可用机器人并取得单聊 ID | `chat bot find`,不是只查自己创建机器人的 `bot search` | | 找可用机器人并取得单聊 ID | `+bot-find`,不是只查自己创建机器人的 `+bot-search` |
| 已知 robotCode 发送 | `+messages-send --as bot` | | 已知 robotCode 发送 | `+messages-send --as bot` |
| 机器人入群、移除、批量群发或撤回 | [chat-bot.md](chat/chat-bot.md) | | 机器人入群、移除、批量群发或撤回 | [chat-bot.md](chat/chat-bot.md) |
@@ -20,7 +20,7 @@ metadata:
<!-- VISIBLE_SHORTCUTS_START --> <!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先) ## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "contact +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws contact <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service contact --format json` 批量发现。 以下 shortcut 同时进入公开 catalog 与 Runtime Schema。按本 skill/recipe 路由,命中时 Shortcut 优先于原子命令。参数只查 `dws schema --cli-path "contact +<shortcut>" --compact --jq '{cli_path,parameters,constraints,confirmation}' -f json`;仅需且已发布 `result` 时查 `--jq '{cli_path,outcomes:.result.outcomes,pagination}'`,字段级再查 `data_schema`;缺失不以 Help/样例推断。Schema 不可用才读一次已知 leaf Help;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 禁 Help:错误 suggestion → 已加载 Skill/reference 明确入口;均无则报漂移。禁全 Catalog/root/parent/product Help;仅映射、接口或 provenance 审计省略 `--compact`。现有路由和 reference 均无法定位低频能力时,才用 `dws shortcut list --service contact --format json` 发现。
| Shortcut | 风险 | 适用场景 | | Shortcut | 风险 | 适用场景 |
|---|---|---| |---|---|---|
@@ -1,6 +1,6 @@
--- ---
name: dingtalk-doc name: dingtalk-doc
description: 钉钉在线文字文档(adoc)本体及其内容的操作:查找、创建、读取、文档信息、编辑、块、评论、附件与媒体、白板卡片容器插入删除、导入、导出(docx/markdown/pdf)、版本、模板、协作者权限、分享及Markdown/JSONML写入。不包括:白板/画布内的 OpenNodes 图形、文本、分组、连线、Vector 和布局读写(归 dingtalk-misc 的 Whiteboard)、文档空间与钉盘的文件管理(归 dingtalk-drive,doc 同名原子命令已弃用)、知识库空间与节点管理(归 dingtalk-wiki)、原生 .md 文件读写(归 dingtalk-misc)、电子表格 axls(归 dingtalk-misc)、AI 表格 able(归 dingtalk-aitable)。命令前缀:dws doc。 description: 钉钉在线文字文档(adoc)内容:查找、创建、读取编辑、块、评论、媒体/附件、白板卡片容器、导入导出、版本、模板、权限、分享及 Markdown/JSONML。不做白板图形/原生 .md/电子表格(dingtalk-misc)、文档空间与钉盘存储(dingtalk-drive)、知识库组织(dingtalk-wiki)、AI 表格(dingtalk-aitable)。命令前缀:dws doc。
metadata: metadata:
cli_version: ">=0.2.14" cli_version: ">=0.2.14"
category: product category: product
@@ -14,41 +14,42 @@ metadata:
<!-- DWS_RUNTIME_CONTRACT_START --> <!-- DWS_RUNTIME_CONTRACT_START -->
## 最小 DWS 执行契约 ## 最小 DWS 执行契约
- 只通过 `dws` CLI 操作钉钉;结构化读取使用 `--format json`,按真实返回判断结果。 - 只用 `dws`;结构化读取加 `--format json`,按真实返回判断。
- 已知命令直接执行。只有 leaf 参数或安全语义不确定时读取精确 Schema,只有 Cobra flag 不确定时读取精确 leaf Help;不要加载产品级 Catalog 代替选路。 - 已知命令直调;参数/约束/安全不明查 leaf 窄 Schema。Schema 不可用才读已知 leaf Help 一次;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 不查 Help:优先错误中的明确 suggestion,其次已加载 Skill/reference 中的明确兼容入口;均无则报漂移并停,禁全 Catalog。低频 reference 不默认 Help,禁 root/parent/product Help。发现后必须执行或说明阻塞。
- 不猜命令、flag、字段、ID、账号或时间。后续 ID 必须来自真实返回;零命中、多候选或类型不明时停止并消歧。 - 不猜命令/flag/字段/ID/账号/业务事实;ID 来自真实返回。目标零命中/多候选/类型不明先消歧;仅可选时间/展示范围用契约默认,缺必需信息即停。
- 解析目标、读取上下文和最终执行必须使用同一 profile;不得跨组织复用 userId、openDingTalkId 或 openConversationId。多账号组织只使用明确的 `isOrgCurrent=true` 默认账号;没有默认账号时要求用户指定,禁止选择第一项、最近登录或最近使用账号。 - 解析/读/写同一 profile,ID 不跨组织。多账号只用唯一 `isOrgCurrent=true`;否则用户指定,禁止选择第一项、最近登录或最近使用账号。
- 不输出或记录 token、refresh token、appSecret、webhook token 等凭据;宿主已注入认证时不要索要凭据。 - 不输出/记录 token、refresh token、appSecret、webhook token;已注入认证时不索要。
- 写操作必须符合用户明确意图。是否需要确认以最终 Runtime gate 和 Schema 为准;需要确认时先说明对象、动作与影响,再追加 `--yes`。 - 写须符合明确意图;确认以最终 Runtime gate/Schema 为准,确认后才加 `--yes`。
- 写后按任务结果契约验证;不能仅凭退出码宣称成功。部分结果、未知投递状态和失败项必须如实保留。 - 写后验证结果,不凭退出码宣称成功。退出须最终答复,区分完成、部分、阻塞、待确认、失败;保留已有数据及 `complete/hasMore/stopReason/failures`。
- 时间戳面向用户展示时转换为带时区的可读时间;默认使用当前会话时区,必要时同时保留原值。 - 时间戳按会话时区展示,必要时保留原值。
- 遇到认证、权限、profile、confirmation 或未知错误时,只加载 `dingtalk-shared` 中对应 reference;不要连续猜测替代命令。 - 认证/权限/profile/confirmation/未知错误只读 `dingtalk-shared` 对应 reference,禁连续猜替代命令。
<!-- DWS_RUNTIME_CONTRACT_END --> <!-- DWS_RUNTIME_CONTRACT_END -->
<!-- VISIBLE_SHORTCUTS_START --> <!-- VISIBLE_SHORTCUTS_START -->
## Shortcut 发现(按需) ## Shortcut 发现(按需)
`doc` 当前有 45 条公开 shortcut,完整清单保留在 Runtime Catalog 与 Schema,不在高频产品根 Skill 中重复展开。已知意图按下方路由。 `doc` 当前有 45 条公开 shortcut,完整清单保留在 Runtime Catalog 与 Schema,不在高频产品根 Skill 中重复展开。已知意图按下方路由;参数/约束/安全不明时读一次 leaf 窄 Schema。仅需且已发布 `result` 时查 outcomes/pagination,字段级再查 `data_schema`;缺失不以 Help/样例推断。Schema 不可用才读一次已知 leaf Help;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 禁 Help:错误 suggestion → 已加载 Skill/reference 明确入口;均无则报漂移。禁全 Catalog/root/parent/product Help;低频 reference 不默认 Help。
仅当现有路由和 reference 都无法定位低频能力时,才执行 `dws shortcut list --service doc --format json` 做最后回退;不要为已知高频意图加载完整 Shortcut Catalog 或产品级 Schema。 仅当现有路由和 reference 都无法定位低频能力时,才执行 `dws shortcut list --service doc --format json` 做最后回退;不要为已知高频意图加载完整 Shortcut Catalog 或产品级 Schema。
<!-- VISIBLE_SHORTCUTS_END --> <!-- VISIBLE_SHORTCUTS_END -->
## Golden Route ## Golden Route
ID/URL 直用;标题先搜索,唯一命中再执行.顺序:稳定 ID → shortcut → 局部读 → 精确写;禁以产品 Schema、全文或原子命令起步. ID/URL 直用;标题唯一命中后执行。顺序:稳定 ID → shortcut → 局部读 → 精确写;禁以产品 Schema/全文/atomic 起步。
| 用户意图 | 唯一推荐入口 | 关键边界 | | 用户意图 | 唯一推荐入口 | 关键边界 |
|---|---|---| |---|---|---|
| <!-- dws-intent: doc.search.by_title -->按标题或主题定位文档 | `dws doc +search --query <精确标题>` | `complete=true,count=0,failures=[]` 即权威零命中:如实报告;禁缩词、跨产品、无 query/无端 `--page-all` | | <!-- dws-intent: doc.search.by_title -->按标题或主题定位文档 | `dws doc +search --query <精确标题>` | `complete=true,count=0,failures=[]` 即权威零命中:如实报告;禁缩词、跨产品、无 query/无端 `--page-all` |
| 最近访问或最近编辑文档 | 加载 `dingtalk-drive`,执行 `dws drive +recent [--operate-type 1] --limit <N>` | 默认最近访问,`1` 为最近编辑;不要用 `doc +search` 替代最近列表 | | 最近访问或最近编辑文档 | 加载 `dingtalk-drive`,执行 `dws drive +recent [--operate-type 1] --limit <N>` | 默认最近访问,`1` 为最近编辑;不要用 `doc +search` 替代最近列表 |
| 已知 alidocs 文档目录 URL,列出当前层 | `dws doc +list --folder <URL> --page-all` | 复用完整 URL;不要改用 `drive +list` |
| <!-- dws-intent: doc.content.read -->已知 ID/URL 读取正文或局部内容 | `dws doc +fetch --node <ID或URL>` | 术语用 `keyword`;章节 `outline` → `section`;整篇才用 `full` | | <!-- dws-intent: doc.content.read -->已知 ID/URL 读取正文或局部内容 | `dws doc +fetch --node <ID或URL>` | 术语用 `keyword`;章节 `outline` → `section`;整篇才用 `full` |
| 聚合查看信息、权限、版本、媒体或评论 | `dws doc +inspect --node <ID或URL>` | 基础元信息默认返回;样式、权限、历史、媒体、评论才用对应 `--include-*`,无 `--include-info` | | 聚合查看信息、权限、版本、媒体或评论 | `dws doc +inspect --node <ID或URL>` | 基础元信息默认返回;样式、权限、历史、媒体、评论才用对应 `--include-*`,无 `--include-info` |
| 新建在线文字文档并写入内容 | `dws doc +create --name <标题> --content <文本\|-\|@文件> [--folder <ID>\|--workspace <ID>]` | 指定位置复用真实 ID,二者互斥;`-`=stdin,禁 `@-`;Runtime 分片回读,不拆写 | | 新建文档并写入 | `dws doc +create --name <标题> --content <文本\|-\|@文件> [--folder <ID>\|--workspace <ID>]` | 指定位置复用真实 ID,二者互斥;`-`=stdin,禁 `@-`;Runtime 分片回读、不拆写 |
| <!-- dws-intent: doc.content.update -->追加、覆盖或精确编辑 block | `dws doc +update --node <ID或URL> --command <动作>` | 唯一文本 `str_replace`;章节/block 局部取 ID;整篇才 overwrite | | <!-- dws-intent: doc.content.update -->追加、覆盖或精确编辑 block | `dws doc +update --node <ID或URL> --command <动作>` | 唯一文本 `str_replace`;章节/block 局部取 ID;整篇才 overwrite |
| 重要内容更新且需要恢复点 | `dws doc +checkpoint-update` | 自动保存版本,更新并回读;检查 `steps` 和 `compensation` | | 重要内容更新且需要恢复点 | `dws doc +checkpoint-update` | 自动保存版本,更新并回读;检查 `steps` 和 `compensation` |
| 版本操作 | `dws doc +version-save --node` / `dws doc +version-list --node` / `dws doc +version-revert --node --version` | 快照/列表/回滚 | | 版本操作 | `dws doc +version-save --node` / `dws doc +version-list --node` / `dws doc +version-revert --node --version` | 快照/列表/回滚 |
| <!-- dws-intent: doc.export.format -->导出为 docx/markdown/pdf | `dws doc +export --export-format <格式>` | 格式必须显式指定;普通文件下载切 `dingtalk-drive` | | <!-- dws-intent: doc.export.format -->导出为 docx/markdown/pdf | `dws doc +export --export-format <格式>` | 格式必须显式指定;普通文件下载切 `dingtalk-drive` |
| <!-- dws-intent: doc.import.local_file -->本地文件转在线文档 | `dws doc +import --file <相对路径> [--folder <ID>\|--workspace <ID>] [--name <用户指定文档名>]` | 指定位置复用真实 ID,二者互斥;目标是已创建知识库时必须带其 workspaceId;未指定才由 Runtime 取默认根并回读验证落点;`doc +create` 不能替代知识库内导入;仅保留原文件走 `dingtalk-drive` | | <!-- dws-intent: doc.import.local_file -->本地文件转在线文档 | `dws doc +import --file <相对路径> [--folder <ID>\|--workspace <ID>] [--name <文档名>]` | 指定位置复用真实 ID,二者互斥;知识库用 workspaceId;未指定才由 Runtime 取默认根并回读;`+create` 不代替库内导入;仅保原文件走 Drive |
| 封面/背景 | `+resource-update/+resource-delete`;`+background-update/+background-delete` | 写后 `+inspect --include-style`;禁查 Catalog | | 封面/背景 | `+resource-update/+resource-delete`;`+background-update/+background-delete` | 写后 `+inspect --include-style`;禁查 Catalog |
| 浏览模板 | `dws doc +template-list [--source MY\|PUBLIC] [--page-all]` | “我的/我这边”只查 MY;明确公开才查 PUBLIC;“有哪些/全部”加 `--page-all` 并检查 `complete` | | 浏览模板 | `dws doc +template-list [--source MY\|PUBLIC] [--page-all]` | “我的/我这边”只查 MY;明确公开才查 PUBLIC;“有哪些/全部”加 `--page-all` 并检查 `complete` |
| 搜索模板 | `dws doc +template-search --query <名称或关键词>` | 来源可选 MY/PUBLIC;零命中停止,禁止拿无关模板替代;多候选消歧 | | 搜索模板 | `dws doc +template-search --query <名称或关键词>` | 来源可选 MY/PUBLIC;零命中停止,禁止拿无关模板替代;多候选消歧 |
@@ -62,12 +63,13 @@ ID/URL 直用;标题先搜索,唯一命中再执行.顺序:稳定 ID → shortcu
## 关键结果语义 ## 关键结果语义
- 保留真实 `nodeId`/URL/类型/容器;复用 ID,禁标题/钉盘重搜。 - 保留真实 `nodeId`/URL/类型/容器;复用 ID,禁标题/钉盘重搜。
- 用完整回执;Runtime 回读后不复读;仅局部验收、`partial_success`/commit-unknown 再 `+fetch`。 - `extension=dlink`:内容用 `linkSourceInfo.nodeId`;嵌套逐跳,失败/缺失/循环即停;入口移动/改名/删除用顶层 ID。
- 复用 Runtime 回执;仅局部验收、`partial_success`/commit-unknown 再 `+fetch`。
- 恢复:`partial_success` 只补未完成;`unknown` 先回读、禁重写;`retryable` 仅限明确未开始;权限/参数/认证失败即停。 - 恢复:`partial_success` 只补未完成;`unknown` 先回读、禁重写;`retryable` 仅限明确未开始;权限/参数/认证失败即停。
- 结果明确且回读匹配才报完成。 - 回读匹配才报完成。
- 搜索/列表检查 `complete`/`hasMore`/cursor/失败项;“全部”翻完页,前 N 条须声明范围。 - 搜索/列表检查 `complete`/`hasMore`/cursor/失败项;“全部”翻完页,前 N 条须声明范围。
- `+import` 检查 `success=true`、`verified=true`、`taskId/nodeId/documentUrl`;复用返回 ID,禁 Drive 重找;中断查原任务,禁重导。 - `+import` 检查 `success/verified=true` 及 `taskId/nodeId/documentUrl`;复用返回 ID,禁 Drive 重找;中断查原任务,禁重导。
- 跨产品任务“新知识库内导入后再移到我的文档”:先用 Wiki 创建返回的 workspaceId 执行 `doc +import --workspace`,再把导入 nodeId 交给 `wiki +move-to-drive --workspace`;禁止在个人域创建后用 Drive 移动伪造入库步骤。 - 知识库导入再移到我的文档:`doc +import --workspace <Wiki ID>` → `wiki +move-to-drive`,复用 nodeId;禁先建在个人域。
- 导出/下载用 cwd 相对路径;`+export` 有 `localPath` 且 `sizeBytes>0` 即终态,禁 `ls/stat`。 - 导出/下载用 cwd 相对路径;`+export` 有 `localPath` 且 `sizeBytes>0` 即终态,禁 `ls/stat`。
## 参数与安全边界 ## 参数与安全边界
@@ -95,7 +97,7 @@ Golden Route 已给出命令且参数足够时,禁止读取 reference;其余
## 错误最短路径 ## 错误最短路径
1. 零命中、多候选、类型不明或分页不完整:停止写入,展示候选或 continuation;禁止默认第一项。 1. 零命中、多候选、类型不明或分页不完整:停止写入,展示候选或 continuation;禁止默认第一项。
2. Help 不参与选路;先读一次精确 leaf Schema。仅真实 `unknown flag`/契约漂移查一次 leaf Help;`unknown command` 只查一次 shortcut 清单,禁止试探后缀和 `dws doc --help | grep/head`。 2. `+fetch` 若确认目标是目录,浏览请求复用完整 URL 执行 `+list --folder`,不要改用 `drive +list`。Help 不参与选路;先读一次精确 leaf Schema。仅真实 `unknown flag`/契约漂移查一次 leaf Help;`unknown command` 只查一次 shortcut 清单,禁止试探后缀和 `dws doc --help | grep/head`。
3. `REVISION_CONFLICT`:重新读取当前 revision,展示差异;未经用户确认不得改成无 revision 覆盖。 3. `REVISION_CONFLICT`:重新读取当前 revision,展示差异;未经用户确认不得改成无 revision 覆盖。
4. `doc_write_commit_unknown`:先回读;禁止自动重试创建或追加。 4. `doc_write_commit_unknown`:先回读;禁止自动重试创建或追加。
5. 认证、权限或 profile 错误:只读 `dingtalk-shared` 对应 reference,禁底层命令绕过。 5. 认证、权限或 profile 错误:只读 `dingtalk-shared` 对应 reference,禁底层命令绕过。
@@ -11,7 +11,7 @@
| status | 含义 | 后续动作 | | status | 含义 | 后续动作 |
|---|---|---| |---|---|---|
| `success` | 所有计划步骤完成,且需要验证的内容已经回读 | 可以向用户报告完成 | | `success` | 所有计划步骤完成,且需要验证的内容已经回读 | 可以向用户报告完成 |
| `partial_success` | 已发生部分副作用,后续步骤失败 | 检查 `steps` 与 `compensation`,不得重放成功步骤 | | `partial_success` | 已发生部分副作用,后续步骤失败 | 检查 `steps` 与 `compensation`,不得重放成功步骤;创建后验证失败用返回 nodeId 只读恢复,核对所需章节,不能只凭 fetch 成功或截断正文宣称完整 |
| `unknown` | 请求已发出但无法确定服务端是否提交 | 先回读目标;创建和追加禁止自动重试 | | `unknown` | 请求已发出但无法确定服务端是否提交 | 先回读目标;创建和追加禁止自动重试 |
| `retryable` | 服务端明确业务执行尚未开始,且允许重试 | 遵循 `retry_after_seconds`,最多有界重试一次 | | `retryable` | 服务端明确业务执行尚未开始,且允许重试 | 遵循 `retry_after_seconds`,最多有界重试一次 |
| `failed` | 已确认没有完成目标动作 | 根据 `retryable`、`actions` 和 details 决定是否重试 | | `failed` | 已确认没有完成目标动作 | 根据 `retryable`、`actions` 和 details 决定是否重试 |
@@ -2,6 +2,16 @@
本页只在根 Skill 的 Golden Route 和精确任务 Reference 都无法选路时加载。它不是创建、读取或更新任务的前置必读,也不要求预加载样式、JSONML 或完整产品帮助。 本页只在根 Skill 的 Golden Route 和精确任务 Reference 都无法选路时加载。它不是创建、读取或更新任务的前置必读,也不要求预加载样式、JSONML 或完整产品帮助。
## Shortcut边界与代价
- `doc +search` 用于按关键词或属性定位待阅读编辑的在线文档;文字文档可加 `--extensions adoc`。`--folder` 是搜索后的直接成员过滤,不递归,必须配 `--page-all`,需要分别完整读取搜索候选和目录成员;触及上限不能宣称完整。只浏览文档文件夹用 `doc +list`,钉盘目录用 `drive +list`,普通钉盘文件搜索用 `drive +search`。不要为目录浏览重复走搜索与列表。
- `doc +script --command init-draft` 是在线文字文档编辑前的本地准备步骤:生成Markdown/JSONML草稿,编辑后用 `doc +create/+update --content @相对路径`;不会上传或创建远端文档。`parse`只检查结构和字数。现有统一入口保留,整条命令因包含本地建文件而声明write;这不表示parse会写入。通用本地文件用编辑器或本地工具;`markdown create`创建远端原生.md,不能替代本地建文件。
- `doc +download-overwrite`仅覆盖下载在线文字文档的正文媒体或封面,要求确认。普通钉盘文件用 `drive +download`,该入口仍不覆盖已有文件;不借用Doc覆盖入口绕过这个限制。
- `doc +media-upload`只上传同一文字文档的资源并校验字节,不插入正文;正文插入用 `doc +media-insert`,电子表格用 `sheet media-upload`,普通文件入库用 `drive +upload`。
- 封面写入优先 `doc +resource-update/+resource-delete`,原子 `doc style cover set`保留既有兼容用途;不串行重复调用两个入口。
## 高频入口 ## 高频入口
| 意图 | 推荐命令 | 精确 Reference | | 意图 | 推荐命令 | 精确 Reference |
@@ -14,8 +14,11 @@ dws doc +fetch --node <DOC_ID> --detail with-ids --scope section --start-block-i
dws doc +update --node <DOC_ID> --command block_replace --block-id <BLOCK_ID> --content "新内容" dws doc +update --node <DOC_ID> --command block_replace --block-id <BLOCK_ID> --content "新内容"
dws doc +update --node <DOC_ID> --command block_insert_after --after-block-id <BLOCK_ID> --content "补充内容" dws doc +update --node <DOC_ID> --command block_insert_after --after-block-id <BLOCK_ID> --content "补充内容"
dws doc +update --node <DOC_ID> --command block_delete --block-id <BLOCK_ID> dws doc +update --node <DOC_ID> --command block_delete --block-id <BLOCK_ID>
dws doc +update --node <DOC_ID> --command block_delete --block-id <BLOCK_A>,<BLOCK_B>,<BLOCK_C>
``` ```
`block_delete` 的 `--block-id` 支持逗号分隔一次删除多个块。
`block-id` 必须来自真实 `+fetch --detail with-ids`、`+review` 或原子 block 列表返回。确认、写入与验证统一由 `+update` 处理,正常成功不追加整篇回读。 `block-id` 必须来自真实 `+fetch --detail with-ids`、`+review` 或原子 block 列表返回。确认、写入与验证统一由 `+update` 处理,正常成功不追加整篇回读。
## 标题块例外 ## 标题块例外
@@ -31,6 +31,7 @@ dws doc +create --name "<文档名>" --content @body.json --doc-format jsonml --
## 结果处理 ## 结果处理
- `status=success` 且 `verified=true`:可以报告创建完成,并保留真实 `nodeId`/URL。 - `status=success` 且 `verified=true`:可以报告创建完成,并保留真实 `nodeId`/URL。
- `status=success` 且 `verified=false` 带 `unverified=["mention_targets"]`(verify 步骤为 `partial`):正文已写入并通过回读,只有 @人 指向的人员本地无法核对;照常报告创建完成,不要重读或重写,把待核对项转述给用户。
- `status=partial_success`:文档或部分分片已经创建;按 `steps` 回读现状,禁止重跑整条创建。 - `status=partial_success`:文档或部分分片已经创建;按 `steps` 回读现状,禁止重跑整条创建。
- `status=unknown`:服务端可能已经提交;先定位并读取文档,禁止自动重试。 - `status=unknown`:服务端可能已经提交;先定位并读取文档,禁止自动重试。
- 没有真实 `nodeId` 或写回执时,禁止声称“已创建”。 - 没有真实 `nodeId` 或写回执时,禁止声称“已创建”。
@@ -47,6 +48,10 @@ dws doc +create --name "<文档名>" --content @body.json --doc-format jsonml --
交付前检查标题是否重复、段落是否连贯、编号是否统一;只有真实行列数据才使用表格,富组件服务于理解而不是装饰。明确字数要求时应在写入前完成本地统计,不能凭模型估算宣称达标。 交付前检查标题是否重复、段落是否连贯、编号是否统一;只有真实行列数据才使用表格,富组件服务于理解而不是装饰。明确字数要求时应在写入前完成本地统计,不能凭模型估算宣称达标。
## @人(markdown mention)
markdown 里可写 `[@姓名](alidocs-mcp://doc/mention?openDingTalkId=<openDingTalkId>)`,服务端会改写为该用户的钉钉个人资料链接;`openDingTalkId` 取自 `dws aisearch +search-person` 的 `openDingTalkId`。仅 markdown 生效,`--doc-format jsonml` 不改写。完整说明见 doc-update.md 的「@人」小节。
## 高级通道 ## 高级通道
只有 shortcut 未公开所需的底层参数或需要原始响应时,才读取精确 leaf Schema 后使用 `dws doc create`。不要因为熟悉旧参数就默认退回原子命令,也不要使用已删除的 Python 创建脚本。 只有 shortcut 未公开所需的底层参数或需要原始响应时,才读取精确 leaf Schema 后使用 `dws doc create`。不要因为熟悉旧参数就默认退回原子命令,也不要使用已删除的 Python 创建脚本。
@@ -32,7 +32,7 @@ dws doc +checkpoint-update --node <DOC_ID> --mode overwrite --content @full.md -
| `block_insert_before` | 在指定 block 前插入段落或标题 | `--before-block-id --content`;标题加 `--heading-level 1..6` | | `block_insert_before` | 在指定 block 前插入段落或标题 | `--before-block-id --content`;标题加 `--heading-level 1..6` |
| `block_insert_after` | 在指定 block 后插入段落或标题 | `--after-block-id --content`;标题加 `--heading-level 1..6` | | `block_insert_after` | 在指定 block 后插入段落或标题 | `--after-block-id --content`;标题加 `--heading-level 1..6` |
| `block_replace` | 替换指定 block | `--block-id --content` | | `block_replace` | 替换指定 block | `--block-id --content` |
| `block_delete` | 删除指定 block | `--block-id` | | `block_delete` | 删除指定 block(`--block-id` 可逗号分隔一次删多个) | `--block-id` |
| `str_replace` | 唯一普通文本替换 | `--old --new` | | `str_replace` | 唯一普通文本替换 | `--old --new` |
| `block_copy_insert_after` | 复制 block 后插入 | `--block-id --after-block-id` | | `block_copy_insert_after` | 复制 block 后插入 | `--block-id --after-block-id` |
@@ -68,10 +68,26 @@ dws doc block insert --node <DOC_ID> --heading "发布说明 v1.0" --level 1 --r
同一篇文档的正文由一个主上下文串行维护:Plan(确定最小变更)→ Execute(一次写)→ Observe(先消费回执)→ Iterate(只修未达标部分)。不要按章节并行写同一文档,也不要每次迭代都重新读取全文或 Schema。 同一篇文档的正文由一个主上下文串行维护:Plan(确定最小变更)→ Execute(一次写)→ Observe(先消费回执)→ Iterate(只修未达标部分)。不要按章节并行写同一文档,也不要每次迭代都重新读取全文或 Schema。
## @人(markdown mention)
在 `append` / `overwrite` 的 markdown 正文里通过该协议插入链接:
```
[@姓名](alidocs-mcp://doc/mention?openDingTalkId=<openDingTalkId>)
```
服务端会改写为指向该用户的钉钉个人资料链接。
- `openDingTalkId` 取自 `dws aisearch +search-person --query "姓名"` 返回的 `openDingTalkId`(或 `dws contact +search-user`)。
- `@` 与显示名由你写在方括号里;服务端只替换链接、保留显示文本。
- 仅 markdown 正文生效:本命令的 `append` / `overwrite`(`+create` 初始正文同样生效);`--doc-format jsonml` 与 `block_insert_*` / `block_replace` 不解析 markdown,写进去的协议原文不会被改写。
- 回读不校验 @ 到的是谁:含 @人 的写入返回 `verified=false`、`unverified=[mention_targets]`,verify 步骤为 `partial`(操作本身仍 success);正文其余部分已通过回读,不要为此重读或重写,把待核对项转述给用户。
## Block ID 生命周期与保真 ## Block ID 生命周期与保真
- `block_replace` 成功后 Runtime 使用同一 `blockId` 回读验证,该 ID 可继续作为锚点;验证失败时先局部 `+fetch` 核对现状。`block_delete` 成功后旧 ID 失效,不得继续复用。 - `block_replace` 成功后 Runtime 使用同一 `blockId` 回读验证,该 ID 可继续作为锚点;验证失败时先局部 `+fetch` 核对现状。`block_delete` 成功后旧 ID 失效,不得继续复用。
- `block_insert_before` / `block_insert_after` / `block_copy_insert_after` 后,原锚点通常仍可识别,但新 block 的 ID 必须来自真实返回或局部回读,禁止按顺序猜测。 - `block_insert_before` / `block_insert_after` / `block_copy_insert_after` 后,原锚点通常仍可识别,但新 block 的 ID 必须来自真实返回或局部回读,禁止按顺序猜测。
- `block_delete` 的 `--block-id` 支持逗号分隔(`a,b,c`,单次最多 50 个)。
- `str_replace` 的简单行内替换通常不要求重新取 ID;若后续依赖块结构,仍以局部回读为准。 - `str_replace` 的简单行内替换通常不要求重新取 ID;若后续依赖块结构,仍以局部回读为准。
- 从 Markdown 读取后覆盖整篇可能丢失图片、附件、@人/@文档、评论锚点、表格样式和嵌套块。只改局部时使用 block 手术;确需整篇保真改写时使用 `full` JSONML,并以 `--expected-revision` 防止覆盖并发修改。 - 从 Markdown 读取后覆盖整篇可能丢失图片、附件、@人/@文档、评论锚点、表格样式和嵌套块。只改局部时使用 block 手术;确需整篇保真改写时使用 `full` JSONML,并以 `--expected-revision` 防止覆盖并发修改。
@@ -9,7 +9,7 @@
| "帮我写个项目周报" | 创建钉钉文档 | `doc` | `aitable` | 富文本内容创作,不是数据表 | | "帮我写个项目周报" | 创建钉钉文档 | `doc` | `aitable` | 富文本内容创作,不是数据表 |
| "参照这个生成同样的 / 按模板生成 / 复刻 X / 同样的模板 X 月份的" + 已有 alidocs URL | 模板保形生成同形态变体 | `drive copy + drive rename + doc block update` → 见 [best_practices/04-document.md `template-based-generation`](../../dingtalk-doc/references/04-document.md#template-based-generation) | `doc read + doc create`(重写链) | adoc → markdown 是有损投影,read+create 会丢行高/单元格背景色/字号;copy 在 adoc 层保形复制后只在副本上局部修改 | | "参照这个生成同样的 / 按模板生成 / 复刻 X / 同样的模板 X 月份的" + 已有 alidocs URL | 模板保形生成同形态变体 | `drive copy + drive rename + doc block update` → 见 [best_practices/04-document.md `template-based-generation`](../../dingtalk-doc/references/04-document.md#template-based-generation) | `doc read + doc create`(重写链) | adoc → markdown 是有损投影,read+create 会丢行高/单元格背景色/字号;copy 在 adoc 层保形复制后只在副本上局部修改 |
| "复制一份这个文档 / 把这篇文档挂到 X 文件夹"(不涉模板变体) | 在线文档节点的存储层复制/移动 | `dws drive +copy --node <ID> [--folder <目标ID>]` / `dws drive +move --node <ID> --folder <目标ID>` | doc 同名 `+copy`/`+move`;`wiki node copy` | 复制/移动属节点存储管理,归 drive;drive 版先 probe 对象类型并拒绝普通文件,doc 裸版对普通文件会生成 `.dlink` 快捷方式而非副本 | | "复制一份这个文档 / 把这篇文档挂到 X 文件夹"(不涉模板变体) | 在线文档节点的存储层复制/移动 | `dws drive +copy --node <ID> [--folder <目标ID>]` / `dws drive +move --node <ID> --folder <目标ID>` | doc 同名 `+copy`/`+move`;`wiki node copy` | 复制/移动属节点存储管理,归 drive;drive 版先 probe 对象类型并拒绝普通文件,doc 裸版对普通文件会生成 `.dlink` 快捷方式而非副本 |
| "这个 alidocs 表格链接帮我看下"(粘贴原始 URL) | 先 probe 节点类型 | `dws drive info --node` → 按 `extension` 路由 | 直接调 `sheet` | `alidocs/i/nodes/{id}` 可能是文档/axls/able/xlsx 等,禁止凭 URL 猜类型 | | "这个 alidocs 表格链接帮我看下"(粘贴原始 URL) | 先 probe 节点类型 | `dws drive info --node`;`extension=dlink` 时将 `result.fileId` 传给 `dws doc info` 取 `linkSourceInfo`,最后按目标 `extension` 路由 | 直接调 `sheet` | `alidocs/i/nodes/{id}` 可能是文档/快捷方式/axls/able/xlsx 等,禁止凭 URL 猜类型 |
| "帮我记一下明天要做的事" | 创建个人待办 | `todo` | `doc` | 个人待办提醒,非文档内容 | | "帮我记一下明天要做的事" | 创建个人待办 | `todo` | `doc` | 个人待办提醒,非文档内容 |
| "在知识库里创建一个文档" | 创建空文件实体 | `wiki node create --type adoc` | `doc create` | 空间内创建节点归 wiki;doc create 是向已有文档写入内容,不是创建文件节点 | | "在知识库里创建一个文档" | 创建空文件实体 | `wiki node create --type adoc` | `doc create` | 空间内创建节点归 wiki;doc create 是向已有文档写入内容,不是创建文件节点 |
| "帮我看看收到的日报" | 收到的日志 | `report` | `doc` | 钉钉日志系统(日报/周报),不是文档 | | "帮我看看收到的日报" | 收到的日志 | `report` | `doc` | 钉钉日志系统(日报/周报),不是文档 |
@@ -14,21 +14,21 @@ metadata:
<!-- DWS_RUNTIME_CONTRACT_START --> <!-- DWS_RUNTIME_CONTRACT_START -->
## 最小 DWS 执行契约 ## 最小 DWS 执行契约
- 只通过 `dws` CLI 操作钉钉;结构化读取使用 `--format json`,按真实返回判断结果。 - 只用 `dws`;结构化读取加 `--format json`,按真实返回判断。
- 已知命令直接执行。只有 leaf 参数或安全语义不确定时读取精确 Schema,只有 Cobra flag 不确定时读取精确 leaf Help;不要加载产品级 Catalog 代替选路。 - 已知命令直调;参数/约束/安全不明查 leaf 窄 Schema。Schema 不可用才读已知 leaf Help 一次;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 不查 Help:优先错误中的明确 suggestion,其次已加载 Skill/reference 中的明确兼容入口;均无则报漂移并停,禁全 Catalog。低频 reference 不默认 Help,禁 root/parent/product Help。发现后必须执行或说明阻塞。
- 不猜命令、flag、字段、ID、账号或时间。后续 ID 必须来自真实返回;零命中、多候选或类型不明时停止并消歧。 - 不猜命令/flag/字段/ID/账号/业务事实;ID 来自真实返回。目标零命中/多候选/类型不明先消歧;仅可选时间/展示范围用契约默认,缺必需信息即停。
- 解析目标、读取上下文和最终执行必须使用同一 profile;不得跨组织复用 userId、openDingTalkId 或 openConversationId。多账号组织只使用明确的 `isOrgCurrent=true` 默认账号;没有默认账号时要求用户指定,禁止选择第一项、最近登录或最近使用账号。 - 解析/读/写同一 profile,ID 不跨组织。多账号只用唯一 `isOrgCurrent=true`;否则用户指定,禁止选择第一项、最近登录或最近使用账号。
- 不输出或记录 token、refresh token、appSecret、webhook token 等凭据;宿主已注入认证时不要索要凭据。 - 不输出/记录 token、refresh token、appSecret、webhook token;已注入认证时不索要。
- 写操作必须符合用户明确意图。是否需要确认以最终 Runtime gate 和 Schema 为准;需要确认时先说明对象、动作与影响,再追加 `--yes`。 - 写须符合明确意图;确认以最终 Runtime gate/Schema 为准,确认后才加 `--yes`。
- 写后按任务结果契约验证;不能仅凭退出码宣称成功。部分结果、未知投递状态和失败项必须如实保留。 - 写后验证结果,不凭退出码宣称成功。退出须最终答复,区分完成、部分、阻塞、待确认、失败;保留已有数据及 `complete/hasMore/stopReason/failures`。
- 时间戳面向用户展示时转换为带时区的可读时间;默认使用当前会话时区,必要时同时保留原值。 - 时间戳按会话时区展示,必要时保留原值。
- 遇到认证、权限、profile、confirmation 或未知错误时,只加载 `dingtalk-shared` 中对应 reference;不要连续猜测替代命令。 - 认证/权限/profile/confirmation/未知错误只读 `dingtalk-shared` 对应 reference,禁连续猜替代命令。
<!-- DWS_RUNTIME_CONTRACT_END --> <!-- DWS_RUNTIME_CONTRACT_END -->
<!-- VISIBLE_SHORTCUTS_START --> <!-- VISIBLE_SHORTCUTS_START -->
## Shortcut 发现(按需) ## Shortcut 发现(按需)
`drive` 当前有 28 条公开 shortcut,完整清单保留在 Runtime Catalog 与 Schema,不在高频产品根 Skill 中重复展开。已知意图按下方路由。 `drive` 当前有 28 条公开 shortcut,完整清单保留在 Runtime Catalog 与 Schema,不在高频产品根 Skill 中重复展开。已知意图按下方路由;参数/约束/安全不明时读一次 leaf 窄 Schema。仅需且已发布 `result` 时查 outcomes/pagination,字段级再查 `data_schema`;缺失不以 Help/样例推断。Schema 不可用才读一次已知 leaf Help;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 禁 Help:错误 suggestion → 已加载 Skill/reference 明确入口;均无则报漂移。禁全 Catalog/root/parent/product Help;低频 reference 不默认 Help。
仅当现有路由和 reference 都无法定位低频能力时,才执行 `dws shortcut list --service drive --format json` 做最后回退;不要为已知高频意图加载完整 Shortcut Catalog 或产品级 Schema。 仅当现有路由和 reference 都无法定位低频能力时,才执行 `dws shortcut list --service drive --format json` 做最后回退;不要为已知高频意图加载完整 Shortcut Catalog 或产品级 Schema。
<!-- VISIBLE_SHORTCUTS_END --> <!-- VISIBLE_SHORTCUTS_END -->
@@ -47,6 +47,7 @@ metadata:
| 上传普通文件到钉盘或知识库 | `dws drive +upload --file <相对路径> [--workspace <ID>]` | 默认进钉盘;指定 workspace 时成为知识库/文档空间中的独立文件节点;folder 与 node、workspace 与 space-id 分别互斥 | | 上传普通文件到钉盘或知识库 | `dws drive +upload --file <相对路径> [--workspace <ID>]` | 默认进钉盘;指定 workspace 时成为知识库/文档空间中的独立文件节点;folder 与 node、workspace 与 space-id 分别互斥 |
| 管理普通文件全局评论 | `dws drive comment list-v2/create-v2/reply/update/delete/batch-query/list-replies/resolve/restore/react-reply` | 复用 Doc/Sheet 新评论链路;旧 `list/create` 已 deprecated;固定全文 `global`,不支持划词、单元格或 mention | | 管理普通文件全局评论 | `dws drive comment list-v2/create-v2/reply/update/delete/batch-query/list-replies/resolve/restore/react-reply` | 复用 Doc/Sheet 新评论链路;旧 `list/create` 已 deprecated;固定全文 `global`,不支持划词、单元格或 mention |
| 创建文件夹 | `dws drive +create-folder --name <名称> [--folder <ID>]` | Shortcut 已提交并读回 | | 创建文件夹 | `dws drive +create-folder --name <名称> [--folder <ID>]` | Shortcut 已提交并读回 |
| 创建普通文件的独立副本 | `dws drive +download --node <源ID> --output <相对路径>` → `dws drive +upload --file <同一相对路径> [--folder <目标ID>]` | 经用户授权后执行;已知是普通文件时不试 `+copy`;新建上传不传 `--node`,避免覆盖;完成证据见下方副本规则 |
| 复制在线文档节点 | `dws drive +copy --node <ID> [--folder <目标ID>]` | 普通钉盘文件会被拒绝;Base 结构复制走 AITable `+base-copy --base-id <ID> --target-folder-id <真实ID> --only-struct` | | 复制在线文档节点 | `dws drive +copy --node <ID> [--folder <目标ID>]` | 普通钉盘文件会被拒绝;Base 结构复制走 AITable `+base-copy --base-id <ID> --target-folder-id <真实ID> --only-struct` |
| 移动节点 | `dws drive +move --node <ID> --folder <目标ID>` | 破坏性变更,按 Runtime confirmation | | 移动节点 | `dws drive +move --node <ID> --folder <目标ID>` | 破坏性变更,按 Runtime confirmation |
| 重命名节点 | `dws drive +rename --node <ID> --name <新名称>` | 写后检查最终名称 | | 重命名节点 | `dws drive +rename --node <ID> --name <新名称>` | 写后检查最终名称 |
@@ -55,6 +56,8 @@ metadata:
| 本地文件夹推到钉盘 | `dws drive push --local-folder <绝对路径> --remote-folder <folderId> --if-exists skip` | 安全默认不覆盖;先 dry-run;不会删除远端多余文件 | | 本地文件夹推到钉盘 | `dws drive push --local-folder <绝对路径> --remote-folder <folderId> --if-exists skip` | 安全默认不覆盖;先 dry-run;不会删除远端多余文件 |
| 双向补齐文件夹 | `dws drive sync --local-folder <绝对路径> --remote-folder <folderId> --on-conflict skip` | 先 dry-run;冲突策略必须显式保留 | | 双向补齐文件夹 | `dws drive sync --local-folder <绝对路径> --remote-folder <folderId> --on-conflict skip` | 先 dry-run;冲突策略必须显式保留 |
普通文件副本规则:复用已有可靠回执中的节点类型和源 ID;类型不明时先 inspect,不用失败的 copy 探测类型。下载成功后,按用户授权的目标位置新建上传;确认返回的新节点 ID 与源 ID 不同,并按任务要求核对名称、大小或内容。只给出操作说明不算已创建副本;该路径不承诺复制权限、版本历史或其他在线协作元数据。
### 低频入口 ### 低频入口
- 删除已确认节点:`dws drive +delete --node <dentryUuid>`;恢复:`+recycle-list/+recycle-restore`;版本:`+version-history/+version-get/+version-download/+version-revert`。 - 删除已确认节点:`dws drive +delete --node <dentryUuid>`;恢复:`+recycle-list/+recycle-restore`;版本:`+version-history/+version-get/+version-download/+version-revert`。
@@ -67,7 +70,7 @@ metadata:
- 已知 dentryUuid:直接执行 inspect/download/list/move/rename,禁止先 search;仅确认是受支持的在线文档节点后才执行 copy。 - 已知 dentryUuid:直接执行 inspect/download/list/move/rename,禁止先 search;仅确认是受支持的在线文档节点后才执行 copy。
- 任务若明确要求“在新知识库用本地文件建在线文档,再移到我的文档”,不属于 Drive 根目录整理:禁止预查 `mySpace/rootFolderId`,禁止 `doc +create` 后 `drive +move`;应由 Wiki 创建空间,Doc `+import --workspace <新workspaceId>`,再 Wiki `+move-to-drive --workspace <新workspaceId>`。 - 任务若明确要求“在新知识库用本地文件建在线文档,再移到我的文档”,不属于 Drive 根目录整理:禁止预查 `mySpace/rootFolderId`,禁止 `doc +create` 后 `drive +move`;应由 Wiki 创建空间,Doc `+import --workspace <新workspaceId>`,再 Wiki `+move-to-drive --workspace <新workspaceId>`。
- 目标 Drive 空间未知:先明确企业空间 `orgSpace` 或“我的文件”`mySpace`,用 `dws wiki space list --type <类型> --format json` 发现空间;`orgSpace` 在 `nextToken` 非空时以 `--cursor <nextToken>` 续页,`mySpace` 固定单条且不分页。按后续命令取真实 spaceId 或 rootFolderId 后立即回到 Drive;已知这些 ID 时不做空间发现。 - 当前命令需要定位空间且缺少目标 ID 时:先明确企业空间 `orgSpace` 或“我的文件”`mySpace`,用 `dws wiki space list --type <类型> --format json` 发现空间;`orgSpace` 在 `nextToken` 非空时以 `--cursor <nextToken>` 续页,`mySpace` 固定单条且不分页。按后续命令取真实 spaceId 或 rootFolderId 后立即回到 Drive;已知这些 ID 时不做空间发现。
- 只有名称:`+search` → 唯一候选的 nodeId → 目标命令;不得自动选择第一项。 - 只有名称:`+search` → 唯一候选的 nodeId → 目标命令;不得自动选择第一项。
- 只有文件夹层级:从最近的已知 folder ID 开始 `+list`,不要从根目录无界递归。 - 只有文件夹层级:从最近的已知 folder ID 开始 `+list`,不要从根目录无界递归。
- 上传普通文件:单条 `+upload`;知识库/文档空间目标显式加 `--workspace`,不要退回 upload-info + 手写 HTTP + commit。转换为在线文档走 Doc `+import`,插入正文附件走 Doc `+media-insert`。 - 上传普通文件:单条 `+upload`;知识库/文档空间目标显式加 `--workspace`,不要退回 upload-info + 手写 HTTP + commit。转换为在线文档走 Doc `+import`,插入正文附件走 Doc `+media-insert`。
@@ -76,7 +79,8 @@ metadata:
- 已知 nodeId 的重命名直接 `+rename`,不先 Catalog、Help 或 search;ALIDOC 的逻辑标题由 shortcut 内部文档读回验证。 - 已知 nodeId 的重命名直接 `+rename`,不先 Catalog、Help 或 search;ALIDOC 的逻辑标题由 shortcut 内部文档读回验证。
- 文件夹方向已明确时直接 `status/pull/push/sync`,不先 status;写操作先用完全相同参数 dry-run,再正式执行。 - 文件夹方向已明确时直接 `status/pull/push/sync`,不先 status;写操作先用完全相同参数 dry-run,再正式执行。
- 搜索结果 `type=able` 后按业务动词重路由:结构复制/删除/Base 内操作走 AITable。结构复制按当前 leaf 提供源 Base ID 和真实 `--target-folder-id`;缺少目标 ID 时停止,不猜根 ID或发明 `--target-root`。 - 搜索结果 `type=able` 后按业务动词重路由:结构复制/删除/Base 内操作走 AITable。结构复制按当前 leaf 提供源 Base ID 和真实 `--target-folder-id`;缺少目标 ID 时停止,不猜根 ID或发明 `--target-root`。
- `+inspect/+download/+list` 只保证 dentryUuid;只有 URL 时先用 `dws drive info --node <URL> --format json` 解析并核对 nodeId。 - `+inspect/+download/+list` 只保证 dentryUuid;只有 URL 时先用 `dws drive info --node <URL> --format json` 解析并核对 `result.fileId`(即 dentryUuid)。
- `drive info` 返回 `extension=dlink` 时,将 `result.fileId` 保存为快捷方式入口 ID,先用 `dws doc info --node <result.fileId> --format json` 读取 `linkSourceInfo`。内容读取、编辑、导出和类型路由改用目标 `linkSourceInfo.nodeId`;目标仍为 dlink 时逐跳解析并记录已访问 ID,解析失败、字段缺失或 ID 重复即停。用户明确移动、重命名或删除快捷方式入口本身时仍使用最初的 `result.fileId`。
最短路径不省略类型检查、确认、传输验证或写后校验。 最短路径不省略类型检查、确认、传输验证或写后校验。
@@ -87,6 +91,7 @@ metadata:
- copy/move/rename/create-folder 检查 `ok/outcome` 和读回;`partial_success` 不是完成。 - copy/move/rename/create-folder 检查 `ok/outcome` 和读回;`partial_success` 不是完成。
- status 检查分类集合;pull/push/sync 检查 summary 和逐项结果,failed/unknown 必须保留。 - status 检查分类集合;pull/push/sync 检查 summary 和逐项结果,failed/unknown 必须保留。
- 分页未结束时返回 continuation;目录树或大列表必须有最大深度、页数和条目数。 - 分页未结束时返回 continuation;目录树或大列表必须有最大深度、页数和条目数。
- 同一查询续页时,若 nextCursor 重复且没有新增节点,停止继续翻页,保留已取得结果并报告分页未完成,不将重复结果计作新增。
- 未知写入效果先 inspect/list 回读,不盲目重放写操作。 - 未知写入效果先 inspect/list 回读,不盲目重放写操作。
## 参数与安全边界 ## 参数与安全边界
@@ -112,12 +117,12 @@ Golden Route 参数足够时禁止读取 reference。其余最多读取一个精
## 错误最短路径 ## 错误最短路径
1. 零/多候选、类型不明或分页不完整:停止写入,返回候选或 continuation。 1. 零/多候选、类型不明或分页不完整:停止写入,返回候选或 continuation;分页读取可在有效游标和预算内继续。
2. `unknown flag`:只查一次当前 leaf Help;`unknown command`:只查一次 Drive shortcut 清单。 2. `unknown flag`:只查一次当前 leaf Help;`unknown command`:只查一次 Drive shortcut 清单。
3. 普通下载遇到在线文档类型:切 `doc +export`,不重复尝试 Drive download。 3. 普通下载遇到在线文档类型:切 `doc +export`,不重复尝试 Drive download。
4. 传输中断:保留本地临时状态或 checkpoint;先判断能否续传。 4. 传输中断:保留本地临时状态或 checkpoint;先判断能否续传。
5. 写入效果未知:按 nodeId 回读;无法证明时报告 unknown。 5. 写入效果未知:按 nodeId 回读;无法证明时报告 unknown。
6. 普通文件 `+copy` 被拒绝时不要重试或伪装成功;独立副本改走经用户授权的 download→upload。AITable 结构复制缺少或无法验证目标文件夹时停止,不猜 ID或创建测试文件夹。 6. 已知是普通文件且需要独立副本时,直接按 Golden Route 的 download→upload 执行,不以 `+copy` 失败作为前置探测。若仍收到普通文件 copy 不支持的错误,不重试或宣称已完成;确认授权与目标后按副本规则继续。AITable 结构复制缺少或无法验证目标文件夹时停止,不猜 ID 或创建测试文件夹。
## 跨产品边界 ## 跨产品边界
@@ -51,7 +51,13 @@ dws drive +recycle-list --limit 20
dws drive +recycle-restore --id <recycleItemId> dws drive +recycle-restore --id <recycleItemId>
``` ```
删除前核对名称、类型和 ID;恢复从列表真实返回取 `id`。恢复后使用返回的新 nodeId,不沿用旧 ID。 删除前核对名称、类型和 ID;恢复从回收站列表真实返回取 `id`,不要使用删除前的 nodeId 发起恢复。
- `+delete` 将节点移入回收站,不是永久删除;本节提供回收项列表与恢复入口,当前没有已验证的回收项永久删除入口。用户要求彻底删除时,如实说明能力限制和已知资源状态,不将软删除报告为彻底删除,也不猜测替代写命令。
- 已知列表或恢复任务直接使用上方入口;仅当现有路由无法定位其他低频能力时,才执行一次 `dws shortcut list --service drive --format json`。命令发现入口在顶层 `shortcut`,不是 `drive shortcut`;不要为上述已明确的能力限制反复查 Help 或清单。
- 恢复成功并读回确认后,使用返回的新 nodeId,不沿用旧 ID。
- 若错误回执表明 `accepted=true`、`readbackComplete=false`,表示恢复已受理但尚未确认完成,不得宣称恢复成功,也不得重复执行 restore。
- 保留回执中的回收项 ID、原路径及其他定位信息;有确切父目录 ID 时,可在原位置有界列表核对。不得仅凭同名结果认定恢复节点;无法确认身份或达到读取预算时,报告“恢复已受理,尚未确认完成”,并保留回执供后续核查。
## 普通文件历史版本 ## 普通文件历史版本
@@ -121,6 +127,6 @@ dws drive +publish-unset --node <dentryUuid>
3. 写入超时或响应丢失先按 nodeId、名称、路径和大小回读,不能盲目重放。 3. 写入超时或响应丢失先按 nodeId、名称、路径和大小回读,不能盲目重放。
4. 在线文档误入普通下载/覆盖时切 Doc、Sheet 或 AITable;不要用 Drive 重试改变内容。 4. 在线文档误入普通下载/覆盖时切 Doc、Sheet 或 AITable;不要用 Drive 重试改变内容。
## 辅助脚本 ## 目录树入口
- [drive_tree_list.py](../scripts/drive_tree_list.py):递归列出钉盘目录树;普通浏览仍优先使用 `dws drive +list`。 递归目录列表与过滤见[高级目录列表](#高级目录列表);普通浏览仍优先使用 `dws drive +list`。结果含 `truncated=true` 或目录读取错误时,不得声称已完整列出。
@@ -1,6 +1,6 @@
--- ---
name: dingtalk-event name: dingtalk-event
description: 钉钉个人 IM、OA 审批、VoIP 通话邀请与待办事件长连接监听。Use when 用户说监听消息/@我/某人/某群/全部消息、已读/撤回/reaction、群成员加入/群成员退出/群状态变化,监听审批任务创建/完成/转交、审批实例发起/抄送/终止/完成、VoIP 通话邀请,或监听待办创建/更新/删除。命令前缀:dws event。 description: 钉钉个人 IM、OA 审批、VoIP 通话邀请、待办与互动卡片回调事件长连接监听。Use when 用户说监听消息/@我/某人/某群/全部消息、已读/撤回/reaction、群成员加入/群成员退出/群状态变化,监听审批任务创建/完成/转交、审批实例发起/抄送/终止/完成、VoIP 通话邀请、待办创建/更新/删除,或互动卡片回调。命令前缀:dws event。
metadata: metadata:
cli_version: ">=0.2.14" cli_version: ">=0.2.14"
category: product category: product
@@ -9,13 +9,11 @@ metadata:
- dws - dws
--- ---
# 钉钉个人 IM、OA 审批、VoIP 与待办事件 # 钉钉个人 IM、OA 审批、VoIP、待办与互动卡片事件
> **前置:执行 `dws` 前必须完整读取 [`dingtalk-shared`](../dingtalk-shared/SKILL.md)。**Shared references 仅按需加载。 > **前置:执行 `dws` 前必须完整读取 [`dingtalk-shared`](../dingtalk-shared/SKILL.md)。**Shared references 仅按需加载。
本 Skill 只负责未来个人 IM/OA/VoIP/Todo 事件;发送和历史消息走 `dingtalk-chat`,审批处理走 `dingtalk-misc`,待办操作走 `dingtalk-todo`。 本 Skill 只负责实时个人事件;发送/历史消息、审批处理、待办操作分别走 `dingtalk-chat`、`dingtalk-misc`、`dingtalk-todo`。IM 优先 `+listen-im`;OA、VoIP、Todo、互动卡片使用 `event consume`,不用列表轮询模拟事件。
实时监听使用长连接,不用历史消息、审批列表、通话记录或待办列表轮询。IM 优先使用 `dws event +listen-im`;OA、VoIP 与 Todo 使用 `dws event consume`。
<!-- dws-intent: event.listen.im -->消息、reaction、已读和撤回的默认监听入口是 `dws event +listen-im`; <!-- dws-intent: event.listen.im -->消息、reaction、已读和撤回的默认监听入口是 `dws event +listen-im`;
只有群生命周期、Filter DSL、原始 envelope 或底层订阅控制才使用 只有群生命周期、Filter DSL、原始 envelope 或底层订阅控制才使用
@@ -40,6 +38,8 @@ metadata:
| VoIP 通话邀请 | 读取 [VoIP 事件参考](references/event-voip.md),使用精确 `event consume` EventKey | | VoIP 通话邀请 | 读取 [VoIP 事件参考](references/event-voip.md),使用精确 `event consume` EventKey |
| 待办创建、更新或删除事件 | 读取 [Todo 事件参考](references/event-todo.md),使用精确 `event consume` EventKey 与 `--role-types` | | 待办创建、更新或删除事件 | 读取 [Todo 事件参考](references/event-todo.md),使用精确 `event consume` EventKey 与 `--role-types` |
| 查看 Todo 事件目录 | `dws event list --category todo` | | 查看 Todo 事件目录 | `dws event list --category todo` |
| 互动卡片回调 | 读取 [互动卡片事件参考](references/event-card.md),使用 `dws event consume user_card_action_triggered --flatten -f ndjson` |
| 查看互动卡片事件目录 | `dws event list --category card` |
| 已知 EventKey 或需要底层订阅控制 | `dws event consume`;参数与约束以 leaf Schema 为准 | | 已知 EventKey 或需要底层订阅控制 | `dws event consume`;参数与约束以 leaf Schema 为准 |
| 查看状态 / 停止 | `dws event status` / `dws event stop <subscribe_id> --dry-run`,确认后再 `--yes` | | 查看状态 / 停止 | `dws event status` / `dws event stop <subscribe_id> --dry-run`,确认后再 `--yes` |
@@ -50,35 +50,36 @@ metadata:
- `group` 必须且只能传 `--chat-id` 或 `--chat-query` 之一。 - `group` 必须且只能传 `--chat-id` 或 `--chat-query` 之一。
- `--query` 只用于纯 `message` 监听;混入 reaction/read/recall 时不得使用。 - `--query` 只用于纯 `message` 监听;混入 reaction/read/recall 时不得使用。
OA 事件不进入 `+listen-im`。七个公开 OA EventKey 都订阅当前 OAuth 用户相关的全部审批事件,使用 `ruleType=all`、`filterRule={}`;不接受 `--user`、`--open-dingtalk-id`、`--group`、`--query` 或 `--filter-json`。七项可放入同一个 consume,每项建立独立订阅并共享 bus。 OA 七个 EventKey 使用 `ruleType=all`、`filterRule={}`,不接受目标或消息过滤;Todo 三个 EventKey 仅接受 `--role-types creator,executor,participant`,省略时取并集。每项独立订阅并共享 bus。
Todo 事件也不进入 `+listen-im`。三个公开 EventKey 使用 `--role-types creator,executor,participant` 控制当前用户作为创建者、执行者或参与者的范围;省略时默认三种角色。Todo 不接受用户、群或消息 Filter 参数,三项可共享同一角色范围和 bus。 <!-- dws-intent: event.listen.card -->互动卡片使用 `dws event consume user_card_action_triggered --flatten -f ndjson`、`ruleType=all`、`filterRule={}`;不接受目标、角色或消息过滤。结构化上下文在 `payload.body.actionData.context`,未知字段保留。
自然姓名和群名由 CLI 内部唯一解析:零命中或多候选返回结构化失败,在创建任何订阅前停止。`--dry-run` 走同一解析链。解析、监听、状态和停止必须使用同一个 `--profile`,不得跨组织搬运 ID。 姓名/群名必须唯一解析,零命中或多候选在创建订阅前停止。解析、监听、状态、停止使用同一 `--profile`,不得跨组织搬运 ID;`--dry-run` 走同一解析链。
### EventKey 索引 ### EventKey 索引
16 个 EventKey 的目标与组合约束见 [EventKey 索引](references/event-im-keys.md)。兼容键包括 `user_im_message_receive_o2o_all`、`user_im_message_receive_group_all`、`user_im_group_updated`、`user_im_group_member_added`、`user_im_group_member_exited`、`user_im_group_disbanded`;群输出可含 `operator_open_dingtalk_id`、`members[].open_dingtalk_id`。OA 见 [OA 事件参考](references/event-oa.md),Todo 见 [Todo 事件参考](references/event-todo.md)。 16 个 EventKey 及目标约束见 [索引](references/event-im-keys.md),含 `user_im_message_receive_o2o_all`、`user_im_message_receive_group_all`、`user_im_group_updated`、`user_im_group_member_added`、`user_im_group_member_exited`、`user_im_group_disbanded`;群输出可含 `operator_open_dingtalk_id`、`members[].open_dingtalk_id`。其它类别见下方 Reference。
## 运行与结果契约 ## 运行与结果契约
- 正常消费固定使用当前用户 OAuth 身份、`--flatten` 和 NDJSON;stdout 只输出事件,stderr 输出订阅、ready、退出和错误状态。 - 正常消费使用当前用户 OAuth、`--flatten` 和 NDJSON;stdout 仅事件,stderr 为生命周期状态。
- 单事件 ready:`[event] ready event_key=<key> bus_pid=<pid> subscribe_id=<id>`。 - 单事件 ready:`[event] ready event_key=<key> bus_pid=<pid> subscribe_id=<id>`。
- 多事件先逐条输出 subscription,全部就绪后输出 `[event] ready event_count=<n> bus_pid=<pid>`。必须等待 ready,不用 `sleep` 猜测。 - 多事件先逐条输出 subscription,全部就绪后输出 `[event] ready event_count=<n> bus_pid=<pid>`。必须等待 ready,不用 `sleep` 猜测。
- 有界任务使用 `--max-events N` 或 `--duration 10m`;无界任务需要宿主管理进程并持续读取 stdout。 - 有界任务用 `--max-events N`/`--duration 10m`;干净退出会取消本次新建订阅。无界任务由宿主管理并持续读 stdout;用 SIGTERM 或受控 stdin/bounded exit,不用 `kill -9`。
- 干净退出会取消本次新建的订阅;使用 SIGTERM、关闭符合条件的管道 stdin,或 Runtime 的 bounded exit。不要 `kill -9`。 - 自发消息会被 self-loop 过滤。回复时把真实 `conversation_id`/`sender_open_dingtalk_id` 交给 `dws chat +messages-send`,不从显示名猜 ID。
- 当前用户自己发送的消息会被 self-loop 过滤;自测事件应由另一用户或机器人发送。
- 事件只负责监听;需要回复时按 [输出与 Chat 交接](references/event-im-output.md) 把真实 `conversation_id` 或 `sender_open_dingtalk_id` 交给 `dws chat +messages-send`,不要从显示名猜 ID。
- 扁平消息/动作字段按事件类型读取:已读为 `reader_open_dingtalk_id`,撤回为 `recaller_open_dingtalk_id`,回应为 `reaction_name`、`operation_type`。媒体优先通过聊天读取命令加 `--download-resources`;已知消息 ID 的底层降级入口是 `dws chat message download-media`。 - 扁平消息/动作字段按事件类型读取:已读为 `reader_open_dingtalk_id`,撤回为 `recaller_open_dingtalk_id`,回应为 `reaction_name`、`operation_type`。媒体优先通过聊天读取命令加 `--download-resources`;已知消息 ID 的底层降级入口是 `dws chat message download-media`。
- OA 扁平事件提供审批实例、任务和状态字段;字段差异、原始回退条件及与 OA 命令的稳定 ID 交接以 [OA 事件参考](references/event-oa.md) 为准。 - OA 扁平事件提供审批实例、任务和状态字段;字段差异、原始回退条件及与 OA 命令的稳定 ID 交接以 [OA 事件参考](references/event-oa.md) 为准。
- Todo 扁平事件提供 `task_id`、标题、角色、状态阶段和时间字段;用真实 `task_id` 交给 `dws todo`,字段差异见 [Todo 事件参考](references/event-todo.md)。 - Todo 扁平事件提供 `task_id`、标题、角色、状态阶段和时间字段;用真实 `task_id` 交给 `dws todo`,字段差异见 [Todo 事件参考](references/event-todo.md)。
- 互动卡片输出 `type/event_id/timestamp/subscribe_id/payload`。在 `payload.body.actionData.context` 用 `questions[].id` 关联 `answers[question_id]`,按选项 ID 读 `selected`;空数组合法。操作者是 `operatorDTO.uid`;分别保留 `timestamp`、`event_time`、`triggerTimestamp`。其它字段与回退见 [互动卡片参考](references/event-card.md)。
## 安全与失败处理 ## 安全与失败处理
开源 normal 获取缺失 AppKey,无需 AppSecret;custom/定制版除外。[排障](references/event-im-operations.md)。
- `event stop` 会取消订阅并影响本地 consumer:先 `--dry-run`,用户确认后再加 `--yes`。 - `event stop` 会取消订阅并影响本地 consumer:先 `--dry-run`,用户确认后再加 `--yes`。
- 多事件属于一次原始操作;任一订阅启动失败时 Runtime 回滚本次已创建项,不拆成新命令绕过重试预算。 - 多事件属于一次原始操作;任一订阅启动失败时 Runtime 回滚本次已创建项,不拆成新命令绕过重试预算。
- 这套 `0/2/1` 是 **Agent/host** 编排预算,适用于全部 27 个公开个人 EventKey(16 个 IM + 7 个 OA + 1 个 VoIP + 3 个 Todo):`retryable=false` 对应 `max_additional_attempts=0`;`retryable=true` 对应 `max_additional_attempts=2`;`retryable=unknown` 对应 `max_additional_attempts=1`。它不是 CLI 持久化硬总次数上限;每次调用最多创建一次,进程内不会自动重试,CLI 也不持久化或计算跨调用的 Agent/host 尝试次数。 - 全部 28 个公开个人 EventKey(16 IM + 7 OA + 1 VoIP + 3 Todo + 1 卡片)遵循 Agent/host `0/2/1`:`retryable=false`→`max_additional_attempts=0`,`retryable=true`→`max_additional_attempts=2`,`retryable=unknown`→`max_additional_attempts=1`。它不是 CLI 持久化硬总次数上限;进程内不会自动重试,CLI 不持久化或计算跨调用的 Agent/host 尝试次数。
- 重试必须遵守 `retry_after_seconds` / `next_retry_at`。遇到 `in_flight`、`cooldown`、`terminal_hold` 不并发或递归重启同一逻辑订阅,也不换 `subscribe_id` / `trace_id` 绕过保护。 - 遵守 `retry_after_seconds`/`next_retry_at`;`in_flight`、`cooldown`、`terminal_hold` 时不并发或换 `subscribe_id`/`trace_id` 绕过。
- 认证、profile、订阅保护状态和 bus 排障按失败类型读取 [订阅运维](references/event-im-operations.md),不要在正常路径预加载完整运维手册。 - 认证、profile、订阅保护状态和 bus 排障按失败类型读取 [订阅运维](references/event-im-operations.md),不要在正常路径预加载完整运维手册。
### 本地订阅保护 ### 本地订阅保护
@@ -104,3 +105,4 @@ Todo 事件也不进入 `+listen-im`。三个公开 EventKey 使用 `--role-type
| OA 审批事件 | [event-oa.md](references/event-oa.md) | 选择七个 OA EventKey、组合消费或解析审批字段 | | OA 审批事件 | [event-oa.md](references/event-oa.md) | 选择七个 OA EventKey、组合消费或解析审批字段 |
| VoIP 通话邀请事件 | [event-voip.md](references/event-voip.md) | 选择 VoIP EventKey、解析邀请字段或检查敏感输出边界 | | VoIP 通话邀请事件 | [event-voip.md](references/event-voip.md) | 选择 VoIP EventKey、解析邀请字段或检查敏感输出边界 |
| Todo 待办事件 | [event-todo.md](references/event-todo.md) | 选择三个 Todo EventKey、设置角色范围或解析待办字段 | | Todo 待办事件 | [event-todo.md](references/event-todo.md) | 选择三个 Todo EventKey、设置角色范围或解析待办字段 |
| 互动卡片回调事件 | [event-card.md](references/event-card.md) | 订阅互动卡片回调、解析开放 payload 或检查空过滤规则 |
@@ -0,0 +1,95 @@
# 互动卡片回调事件
先读事件产品入口 [SKILL.md](../SKILL.md) 的命令规则、生命周期和失败处理。本参考覆盖公开个人事件 `user_card_action_triggered`。
<!-- dws-intent: event.listen.card -->互动卡片业务发生回调时,使用 `dws event consume` 长连接实时监听;不要轮询卡片状态模拟事件。
## 订阅规则
- 使用当前用户 OAuth 身份,非默认组织加全局 `--profile <corpId 或 profile 名>`。
- 注册复用 IM 的 `POST /dws/subscription/user`,取消复用 `POST /dws/subscription/cancel`。
- 事件使用 `ruleType=all`,并与 IM/OA 全量事件一致发送 `filterRule={}`。
- 不接受 `--user`、`--open-dingtalk-id`、`--group`、`--query`、`--filter-json` 或 `--role-types`。
## Golden Route
```bash
dws event list --category card
dws event schema user_card_action_triggered --flatten
dws event consume user_card_action_triggered --flatten -f ndjson
```
临时验证可加 `--max-events 1` 或 `--duration 10m`。等待 `[event] ready event_key=user_card_action_triggered ...` 后再触发卡片交互;干净退出会自动取消本次新建的订阅。复用既有订阅时使用 `--subscribe-id`,外部停止先运行 `dws event stop <subscribe_id> --dry-run`,确认后再加 `--yes`。
## 扁平输出
`--flatten` 保持以下顶层结构稳定,并为当前已评审的真实回调描述 `payload` 字段;各层仍允许服务端增加未知字段:
```json
{
"type": "user_card_action_triggered",
"event_id": "evt_xxx",
"timestamp": 1700000000123,
"subscribe_id": "subId_xxx",
"payload": {
"body": {
"actionData": {
"context": {
"answers": {
"q0": {"custom": "", "selected": ["o1"]},
"q1": {"selected": []}
},
"createUid": "10001",
"orgId": "20001",
"outcome": "answered",
"questions": [
{
"id": "q0",
"header": "会议标题",
"prompt": "请选择会议类型",
"selection": "single",
"allowCustom": true,
"options": [
{"id": "o1", "label": "项目讨论", "description": "项目相关讨论会议"}
]
},
{
"id": "q1",
"header": "参会人",
"prompt": "请选择参会人",
"selection": "multiple",
"allowCustom": false,
"inputKind": "person",
"options": []
}
],
"sourceProjectionVersion": "projection-v1",
"sourceTurnId": "turn_xxx"
}
},
"bizInfoDTO": {"appKey": "app_xxx", "bizId": "biz_xxx"},
"context": {"answers": "{...}", "questions": "[...]"},
"conversationContextDTO": {"cid": "cid_xxx"},
"extension": {"protocolProfile": "profile_xxx"},
"operatorDTO": {"operatorUserAgent": "client_xxx", "uid": 10001},
"spaceId": "space_xxx",
"spaceType": "im_single",
"triggerTimestamp": 1700000000100
},
"event_time": 1700000000100
}
}
```
读取互动结果时遵循这些规则:
- 结构化业务上下文优先读取 `payload.body.actionData.context`。通过 `questions[].id` 找到 `answers[question_id]`;`selected` 保存选项 ID,再通过同一问题的 `options[].id` 转换为标签,`custom` 独立保留。
- `selected: []` 是合法的未选择状态。`outcome`、`selection`、`inputKind` 和 `spaceType` 都是开放字符串,不据当前样本推断封闭枚举。
- `createUid`、`orgId` 是字符串;操作者读取整数 `operatorDTO.uid`,不要互相转换或用 `createUid` 替代操作者。
- `bizInfoDTO.bizId`、`sourceTurnId`、`spaceId` 和 `conversationContextDTO.cid` 可用于业务关联,但都按不透明 ID 原样保留,不拆解其格式。
- 顶层 `timestamp`、`payload.event_time`、`body.triggerTimestamp` 都是毫秒时间戳,分别保留,不假设三者始终相等。
- `body.context.answers/questions` 是 JSON 字符串形式的兼容副本,只在结构化字段缺失时回退;`extension` 中的 JSON 字符串和 `operatorUserAgent` 主要用于兼容或诊断,也不应优先于结构化字段。
`payload`、`body`、上下文和扩展对象都会保留服务端推送的未知字段,以兼容后续协议扩展。需要业务字段时以实时 payload 和 `dws event schema user_card_action_triggered --flatten` 为准。不传 `--flatten` 时保持兼容 transport envelope,业务数据仍在字符串 `.data` 内。
该事件与其它无目标个人事件可放入同一个 consume,共享 personal bus;每个 EventKey 仍创建独立订阅。启动中任一订阅失败时,现有多事件生命周期会回滚本次已创建项,并沿用同一重试保护。
@@ -1,5 +1,11 @@
# IM 订阅过滤、状态与排障 # IM 订阅过滤、状态与排障
## AppKey 与认证排障
开源版默认 `normal` 模式只需要用户 token 和 AppKey,不要求用户提供 AppSecret。已有 AppKey 沿用本地身份;本地元数据缺失时,CLI 从当前事件 MCP 的 `/cli/clientId` 自动获取,仅用于本次调用并传给后台 bus,不写入 token/profile/app 配置。`custom` 模式和定制版不使用此兜底;取得 AppKey 不代表已验证 token 的应用归属,仍由服务端校验。
自动获取失败时按结构化 `reason` 和 `retryable` 处理;网络暂时失败、429、5xx 可按预算重试,取消、拒绝或无效数据不盲目重试。不要引导默认模式用户提供 AppSecret。排查云端差异时只记录配置目录及字段存在性,不输出凭据;`auth status` 不能证明 AppKey 完整,`event list` 是本地目录,不能证明远端事件连接正常。
## Filter ## Filter
优先用订阅规则缩小范围:单聊/发送人用 user 身份,群用 `--group`。接收消息事件需要额外 优先用订阅规则缩小范围:单聊/发送人用 user 身份,群用 `--group`。接收消息事件需要额外
@@ -20,7 +20,7 @@ metadata:
<!-- VISIBLE_SHORTCUTS_START --> <!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先) ## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "mail +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws mail <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service mail --format json` 批量发现。 以下 shortcut 同时进入公开 catalog 与 Runtime Schema。按本 skill/recipe 路由,命中时 Shortcut 优先于原子命令。参数只查 `dws schema --cli-path "mail +<shortcut>" --compact --jq '{cli_path,parameters,constraints,confirmation}' -f json`;仅需且已发布 `result` 时查 `--jq '{cli_path,outcomes:.result.outcomes,pagination}'`,字段级再查 `data_schema`;缺失不以 Help/样例推断。Schema 不可用才读一次已知 leaf Help;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 禁 Help:错误 suggestion → 已加载 Skill/reference 明确入口;均无则报漂移。禁全 Catalog/root/parent/product Help;仅映射、接口或 provenance 审计省略 `--compact`。现有路由和 reference 均无法定位低频能力时,才用 `dws shortcut list --service mail --format json` 发现。
| Shortcut | 风险 | 适用场景 | | Shortcut | 风险 | 适用场景 |
|---|---|---| |---|---|---|
@@ -1,6 +1,6 @@
--- ---
name: dingtalk-minutes name: dingtalk-minutes
description: 钉钉 AI 听记。Use when 查询或修改听记摘要、完整逐字稿、关键词、行动项、录音、上传、思维导图、发言人洞察或分享权限。写文档走 dingtalk-doc;建待办走 dingtalk-todo;日程走 dingtalk-calendar。命令前缀:dws minutes。 description: 钉钉 AI 听记。Use when 查询或修改听记摘要、完整逐字稿、关键词、标签、行动项、录音、上传、思维导图、发言人洞察、ASR 热词/识别词配置或分享权限。写文档走 dingtalk-doc;建待办走 dingtalk-todo;日程走 dingtalk-calendar。命令前缀:dws minutes。
metadata: metadata:
cli_version: ">=0.2.14" cli_version: ">=0.2.14"
category: product category: product
@@ -14,37 +14,38 @@ metadata:
<!-- DWS_RUNTIME_CONTRACT_START --> <!-- DWS_RUNTIME_CONTRACT_START -->
## 最小 DWS 执行契约 ## 最小 DWS 执行契约
- 只通过 `dws` CLI 操作钉钉;结构化读取使用 `--format json`,按真实返回判断结果。 - 只用 `dws`;结构化读取加 `--format json`,按真实返回判断。
- 已知命令直接执行。只有 leaf 参数或安全语义不确定时读取精确 Schema,只有 Cobra flag 不确定时读取精确 leaf Help;不要加载产品级 Catalog 代替选路。 - 已知命令直调;参数/约束/安全不明查 leaf 窄 Schema。Schema 不可用才读已知 leaf Help 一次;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 不查 Help:优先错误中的明确 suggestion,其次已加载 Skill/reference 中的明确兼容入口;均无则报漂移并停,禁全 Catalog。低频 reference 不默认 Help,禁 root/parent/product Help。发现后必须执行或说明阻塞。
- 不猜命令、flag、字段、ID、账号或时间。后续 ID 必须来自真实返回;零命中、多候选或类型不明时停止并消歧。 - 不猜命令/flag/字段/ID/账号/业务事实;ID 来自真实返回。目标零命中/多候选/类型不明先消歧;仅可选时间/展示范围用契约默认,缺必需信息即停。
- 解析目标、读取上下文和最终执行必须使用同一 profile;不得跨组织复用 userId、openDingTalkId 或 openConversationId。多账号组织只使用明确的 `isOrgCurrent=true` 默认账号;没有默认账号时要求用户指定,禁止选择第一项、最近登录或最近使用账号。 - 解析/读/写同一 profile,ID 不跨组织。多账号只用唯一 `isOrgCurrent=true`;否则用户指定,禁止选择第一项、最近登录或最近使用账号。
- 不输出或记录 token、refresh token、appSecret、webhook token 等凭据;宿主已注入认证时不要索要凭据。 - 不输出/记录 token、refresh token、appSecret、webhook token;已注入认证时不索要。
- 写操作必须符合用户明确意图。是否需要确认以最终 Runtime gate 和 Schema 为准;需要确认时先说明对象、动作与影响,再追加 `--yes`。 - 写须符合明确意图;确认以最终 Runtime gate/Schema 为准,确认后才加 `--yes`。
- 写后按任务结果契约验证;不能仅凭退出码宣称成功。部分结果、未知投递状态和失败项必须如实保留。 - 写后验证结果,不凭退出码宣称成功。退出须最终答复,区分完成、部分、阻塞、待确认、失败;保留已有数据及 `complete/hasMore/stopReason/failures`。
- 时间戳面向用户展示时转换为带时区的可读时间;默认使用当前会话时区,必要时同时保留原值。 - 时间戳按会话时区展示,必要时保留原值。
- 遇到认证、权限、profile、confirmation 或未知错误时,只加载 `dingtalk-shared` 中对应 reference;不要连续猜测替代命令。 - 认证/权限/profile/confirmation/未知错误只读 `dingtalk-shared` 对应 reference,禁连续猜替代命令。
<!-- DWS_RUNTIME_CONTRACT_END --> <!-- DWS_RUNTIME_CONTRACT_END -->
<!-- VISIBLE_SHORTCUTS_START --> <!-- VISIBLE_SHORTCUTS_START -->
## Shortcut 发现(按需) ## Shortcut 发现(按需)
`minutes` 当前有 29 条公开 shortcut,完整清单保留在 Runtime Catalog 与 Schema,不在高频产品根 Skill 中重复展开。已知意图直接使用下方的优先路由、意图表或任务 reference;命令已选中时直接执行,只在参数/安全语义不确定时读取 leaf Schema,在当前 Cobra flags 不确定时读取 leaf Help。 `minutes` 当前有 29 条公开 shortcut,完整清单保留在 Runtime Catalog 与 Schema,不在高频产品根 Skill 中重复展开。已知意图按下方路由/reference 直调;参数/约束/安全不明时读一次 leaf 窄 Schema。仅需且已发布 `result` 时查 outcomes/pagination,字段级再查 `data_schema`;缺失不以 Help/样例推断。Schema 不可用才读一次已知 leaf Help;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 禁 Help:错误 suggestion → 已加载 Skill/reference 明确入口;均无则报漂移。禁全 Catalog/root/parent/product Help;低频 reference 不默认 Help。
仅当现有路由和 reference 都无法定位低频能力时,才执行 `dws shortcut list --service minutes --format json` 做最后回退;不要为已知高频意图加载完整 Shortcut Catalog 或产品级 Schema。 仅当现有路由和 reference 都无法定位低频能力时,才执行 `dws shortcut list --service minutes --format json` 做最后回退;不要为已知高频意图加载完整 Shortcut Catalog 或产品级 Schema。
<!-- VISIBLE_SHORTCUTS_END --> <!-- VISIBLE_SHORTCUTS_END -->
## Golden Route ## Golden Route
以下是当前 Minutes Case 支持的核心路径,用于减少 Agent 选路分叉;它不等同于生产使用频率统计。已有 `taskUuid` 直接使用;完整 `shanji.dingtalk.com` URL 先提取其中的真实 ID。只有标题或时间线索时先搜索,零命中停止,多候选或候选差异较大时让用户消歧,不默认取第一条。 以下是当前 Minutes Case 支持的核心路径,用于减少 Agent 选路分叉;它不等同于生产使用频率统计。已有 taskUuid 必须原样传入,禁止解码或重新编码;完整听记 URL 先提取 taskUuid,再原样使用。只有标题或时间线索时先搜索,零命中停止,多候选或候选差异较大时让用户消歧,不默认取第一条。
| 用户意图 | 唯一推荐入口 | 关键边界 | | 用户意图 | 唯一推荐入口 | 关键边界 |
|---|---|---| |---|---|---|
| 按标题或时间找听记 | `dws minutes +search --scope all --query "<关键词>" --page-all` | `scope` 可选 `mine/shared/all`;至少提供 query/start/end 之一。`all --page-all` 分别追完 mine/shared 后去重,不把单个 noLimit 端点当完整全集 | | 按标题或时间找听记 | `dws minutes +search --scope all --query "<关键词>" --page-all` | `scope` 可选 `mine/shared/all`;至少提供 query/start/end 之一。`all --page-all` 分别追完 mine/shared 后去重,不把单个 noLimit 端点当完整全集 |
| 浏览我创建、共享给我或全部可访问听记 | `+list-mine` / `+list-shared` / `+list-all` | 默认是可续拉预览;要声称完整必须加 `--page-all` 并检查 `complete=true`。用户只说“我的听记”不等于明确 `mine`,范围不清时用 `all` | | 浏览我创建、共享给我或全部可访问听记 | `+list-mine` / `+list-shared` / `+list-all` | 默认是可续拉预览;要声称完整必须加 `--page-all` 并检查 `complete=true`。用户只说“我的听记”不等于明确 `mine`,范围不清时用 `all` |
| 按真实标签找听记 | `dws minutes tag list --format json`,再 `tag query --tag-id <真实tagId>` | 标签不等于关键词;空标签即结束。按标签查找的分页细节见局部意图 |
| 看我最新创建的一条 | `dws minutes +latest [--keyword <关键词>]` | 只在用户明确说“最新”时用;不能用它替代具名目标搜索,也不能在录音 start 后拿 latest 猜新录音 ID | | 看我最新创建的一条 | `dws minutes +latest [--keyword <关键词>]` | 只在用户明确说“最新”时用;不能用它替代具名目标搜索,也不能在录音 start 后拿 latest 猜新录音 ID |
| 读取基础信息、摘要或关键词 | `dws minutes +detail --id <taskUuid> --artifacts basic,summary,keywords` | 已有 taskUuid 且只读取现有产物时直接使用,不要进入上传 workflow;任一产物失败都按 partial/非零处理,不把缺失项说成空内容 | | 读取基础信息、摘要或关键词 | `dws minutes +detail --id <taskUuid> --artifacts basic,summary,keywords` | 已有 taskUuid 且只读取现有产物时直接使用,不要进入上传 workflow;任一产物失败都按 partial/非零处理,不把缺失项说成空内容 |
| 读取逐字稿 | `dws minutes +transcript --id <taskUuid> [--direction 1] [--single-page]` | 已有 taskUuid 且只读取逐字稿时直接使用,不要借 `+upload-and-analyze --resume-id` 代读。默认正序并追完分页;倒序必须传 `--direction 1`,用户明确只要第一页时传 `--single-page`,不得随后自动续页。交付前检查 `data.direction/data.complete/data.pages` 与 `meta.pagination` | | 读取逐字稿 | `dws minutes +transcript --id <taskUuid> [--direction 1] [--single-page]` | 已有 taskUuid 且只读取逐字稿时直接使用,不要借 `+upload-and-analyze --resume-id` 代读。默认正序并追完分页;倒序必须传 `--direction 1`,用户明确只要第一页时传 `--single-page`,不得随后自动续页。交付前检查 `data.direction/data.complete/data.pages` 与 `meta.pagination` |
| 读取行动项 | `dws minutes +action-items --id <taskUuid>` | 只有受支持字段明确返回空数组才能说“没有待办”;`unsupported_shape`、字段解析失败或工具失败都不是空结果。需要创建钉钉待办时再切 `dingtalk-todo` | | 读取行动项 | 单条 `+action-items --id <taskUuid>`;批量 `+detail --ids <uuid1,uuid2> --artifacts todos` | 只在 `state=known_empty` 时说“没有待办”;`ready` 才能读取 `items`。`unsupported_shape/failed` 必须非零并保留脱敏 shape 证据,不能翻译为空结果。需要创建钉钉待办时再切 `dingtalk-todo` |
| 把摘要、关键词、完整逐字稿和行动项归档到本地 | `dws minutes +export-pack --id <taskUuid> --output <新相对目录>` | 要带媒体时加 `--include-media`;必须由 `published/path/manifest/files` 证明落盘,只有建目录、计划或文件名不能称已生成 | | 把摘要、关键词、完整逐字稿和行动项归档到本地 | `dws minutes +export-pack --id <taskUuid> --output <新相对目录>` | 要带媒体时加 `--include-media`;必须由 `published/path/manifest/files` 证明落盘,只有建目录、计划或文件名不能称已生成 |
| 修改或预览标题 | `dws minutes +update --id <taskUuid> --title "<新标题>"` | 真实修改按 Runtime confirmation 执行并读回;用户只要预览时先读 basic,再加 `--dry-run`,展示“当前值 → 目标值”后停止,不追加 `--yes` | | 修改或预览标题 | `dws minutes +update --id <taskUuid> --title "<新标题>"` | 真实修改按 Runtime confirmation 执行并读回;用户只要预览时先读 basic,再加 `--dry-run`,展示“当前值 → 目标值”后停止,不追加 `--yes` |
| 覆盖纪要正文 | `dws minutes +summary --id <taskUuid> --content @<相对文件>` | `content` 是完整目标正文,不是局部 patch;按 Runtime confirmation 执行,并保护图片引用、读回全文 | | 覆盖纪要正文 | `dws minutes +summary --id <taskUuid> --content @<相对文件>` | `content` 是完整目标正文,不是局部 patch;按 Runtime confirmation 执行,并保护图片引用、读回全文 |
@@ -52,7 +53,7 @@ metadata:
| 真实开始、暂停、继续或停止录音 | `+record-start` / `+record-pause` / `+record-resume` / `+record-stop` | 这组入口会真实执行。start 返回 `accepted=true, bound=false` 或 `controlReady=false` 时,报告“已受理但未绑定”并停止:不得重试 start,也不得用 `+latest`、列表第一条或时间最近项猜 ID。结束并等待产物用 `+record-wrap-up` | | 真实开始、暂停、继续或停止录音 | `+record-start` / `+record-pause` / `+record-resume` / `+record-stop` | 这组入口会真实执行。start 返回 `accepted=true, bound=false` 或 `controlReady=false` 时,报告“已受理但未绑定”并停止:不得重试 start,也不得用 `+latest`、列表第一条或时间最近项猜 ID。结束并等待产物用 `+record-wrap-up` |
| 只预览录音请求,不实际执行 | `dws minutes record start --dry-run --format json` | 使用对应的原子 `minutes record start|pause|resume|stop` leaf;start 的 `--session-id` 可选,pause/resume/stop 必须传真实 `--id`。不要把被拒绝的 Shortcut dry-run 描述成预览成功 | | 只预览录音请求,不实际执行 | `dws minutes record start --dry-run --format json` | 使用对应的原子 `minutes record start|pause|resume|stop` leaf;start 的 `--session-id` 可选,pause/resume/stop 必须传真实 `--id`。不要把被拒绝的 Shortcut dry-run 描述成预览成功 |
| 生成或继续思维导图 | `dws minutes +mindmap --id <taskUuid>` | 创建后有界轮询;超时或未知状态保留恢复信息,用 `--resume` 继续,不重复创建 | | 生成或继续思维导图 | `dws minutes +mindmap --id <taskUuid>` | 创建后有界轮询;超时或未知状态保留恢复信息,用 `--resume` 继续,不重复创建 |
| 生成或继续发言人洞察 | `dws minutes +speaker-insights --id <taskUuid>` | 有界轮询;保留 `taskId`,恢复时用 `--resume [--task-id <ID>]`,不重复创建 | | 生成或继续发言人洞察 | `dws minutes +speaker-insights --id <taskUuid>` | 只有 `state=ready/complete=true` 才完成;pending 保留 `taskId` 并同账号 `--resume [--task-id <ID>]`,不重复创建;用户给定等待时长传 `--timeout` |
| 当前用户申请查看/下载/编辑权限 | `dws minutes +apply-permission --id <taskUuid> --permission view|download|edit` | 这是“我申请访问”,不是所有者给别人授权;按 Runtime confirmation 执行 | | 当前用户申请查看/下载/编辑权限 | `dws minutes +apply-permission --id <taskUuid> --permission view|download|edit` | 这是“我申请访问”,不是所有者给别人授权;按 Runtime confirmation 执行 |
| 所有者给成员授权或撤权 | `dws minutes +share ...` / `dws minutes +unshare ...` | 先用通讯录把姓名解析为同组织稳定 UID;撤权是破坏性操作。批量结果必须保留逐成员 ledger 和失败项 | | 所有者给成员授权或撤权 | `dws minutes +share ...` / `dws minutes +unshare ...` | 先用通讯录把姓名解析为同组织稳定 UID;撤权是破坏性操作。批量结果必须保留逐成员 ledger 和失败项 |
@@ -68,22 +69,24 @@ dws minutes +list-shared --page-all --format json
dws minutes +list-all --page-all --format json dws minutes +list-all --page-all --format json
``` ```
只有用户明确要第一页、预览或样本时才省略 `--page-all`,并如实保留 `data.complete=false` 与 `meta.pagination.next_token`。有时间窗必须使用 `+search`,因为 `+list-*` 不接受 `--start/--end`。 只有用户明确要第一页、预览、样本或限定页数时才省略 `--page-all`,如实保留完整性与续页证据。每页 N 条须首轮及续页均传 `--limit N`,不能取默认数量后只展示 N 条冒充;限定页数用同一 leaf 的 `--cursor <真实 next_token>` 续拉,不扩大为全量。`+list-all` 的 `--cursor` 与 `--page-all` 互斥。有时间窗用 `+search`,因为 `+list-*` 不接受 `--start/--end`。
## 目标与完整性 ## 目标与完整性
- 时间展示与筛选:日期、时区偏移、星期及“本周”等边界必须由日期工具计算,不能心算或从标题日期推断。时间戳使用本地脚本 [format_timestamp.py](scripts/format_timestamp.py):`python3 <本Skill目录>/scripts/format_timestamp.py --unit ms --timezone Asia/Shanghai <时间戳1> [时间戳2 ...]`;示例单位/时区须替换为已确认值(优先用户指定时区,否则已知会话时区)。最终直接复用输出,不补算星期或二次换算;已有明确时区的可读返回可直接引用,单位/时区不明时保留原值。相对周边界另用日期库计算,该脚本只转换时间戳。`duration` 等时长不传给该脚本;仅按已确认单位用本地计算换算,单位未知保留原值,不凭时长判断内容无效或录音状态。
- 目标锁定优先级:用户给出的 `taskUuid`/URL > 精确标题 > 标题包含或语义相关候选。相似候选可展示,但候选差异明显或多个候选都合理时必须停下来消歧;分页未完成本身不是目标歧义。 - 目标锁定优先级:用户给出的 `taskUuid`/URL > 精确标题 > 标题包含或语义相关候选。相似候选可展示,但候选差异明显或多个候选都合理时必须停下来消歧;分页未完成本身不是目标歧义。
- 用户说“先确认/核对目标”默认要求用真实 basic 字段完成证据核对,不自动变成等待用户回复的会话门禁。目标唯一、分页完整且后续均为只读时,展示标题、时间、归属等核对证据后在同一轮继续;只有用户明确要求“等我确认后再继续”或仍有多个合理候选时才暂停。 - 用户说“先确认/核对目标”默认要求用真实基础信息字段完成证据核对,不自动变成等待用户回复的会话门禁。目标唯一、分页完整且后续均为只读时,展示标题、时间、归属等核对证据后在同一轮完成所需交付;只有用户明确要求“等我确认后再继续”或仍有多个合理候选时才暂停。
- 纯能力、规则或错误说明且没有唯一真实目标时直接解释,不猜对象;用户明确要求核对且目标唯一时必须真实读取,不能只展示命令。 - 纯能力、规则或错误说明且没有唯一真实目标时直接解释,不猜对象;用户明确要求核对且目标唯一时必须真实读取,不能只展示命令。
- `mine` 仅表示我创建的,`shared` 仅表示共享给我的;`all` 表示 accessible 聚合目标。不得声称后端单个 noLimit 端点天然等于 `mine + shared`。 - `mine` 仅表示我创建的,`shared` 仅表示共享给我的;`all` 表示 accessible 聚合目标。不得声称后端单个 noLimit 端点天然等于 `mine + shared`。
- 列表或逐字稿只有 `data.complete=true` 才能称为“全部/完整”。全量请求遇到 `meta.pagination.next_token` 时继续;只有 token 缺失、cursor 停滞/循环、达到 `page-limit` 或后页失败时才停止,并保留失败信封或不完整证据。 - 列表或逐字稿只有 `data.complete=true` 才能称为“全部/完整”。全量请求遇到 `meta.pagination.next_token` 时继续;只有 token 缺失、cursor 停滞/循环、达到 `page-limit` 或后页失败时才停止,并保留失败信封或不完整证据。
- 用户要求核对、汇总“这些/每条/全部”命中项时,必须覆盖完整命中集合;可用 `dws minutes +detail --ids <uuid1,uuid2> --artifacts basic --format json` 批量核对,并逐项保留失败。只检查第一条不能代表全体;响应没有逐条归属或组织字段时如实说明不可得,不能用当前 profile 的组织名代替每条听记的归属。 - 用户要求核对、汇总“这些/每条/全部”命中项时,必须覆盖完整命中集合;可用 `dws minutes +detail --ids <uuid1,uuid2> --artifacts basic --format json` 批量核对,并逐项保留失败。只检查第一条不能代表全体;响应没有逐条归属或组织字段时如实说明不可得,不能用当前 profile 的组织名代替每条听记的归属。
- 多听记、多来源或跨产品汇总按每个 `taskUuid`/来源 ID 保留 `requested/resolved/missing/artifacts/status`;缺输入或必需产物时整体按 partial,不用已找到项代表全部。 - 多听记、多来源或跨产品汇总按每个 `taskUuid`/来源 ID 保留 `requested/resolved/missing/artifacts/status`;缺输入或必需产物时整体按 partial,仍交付成功内容支持的独立结果并列缺项;不缩减原请求或把子集说成全部,依赖缺失来源的结论暂不生成。
- 内容归纳必须来自每条真实 `summary/transcript/keywords`;只有 `title/basic` 时只列元数据,不生成摘要、关键词或分类。 - 内容归纳必须来自每条真实 `summary/transcript/keywords`;只有 `title/basic` 时只列元数据;其他未满足用户要求的内容依据也只列元数据,分类任务列“待分类/证据不足”,不凭标题或时长判无效。按 taskUuid 去重后的已分类数+待分类数须等于目标总数。
- `partial_success`、异步 `pending`、超时和未知写入结果不是成功。按结果中的恢复句柄继续,不能重放已成功步骤。 - `partial_success`、异步 `pending`、超时和未知写入结果不是成功。按结果中的恢复句柄继续,不能重放已成功步骤。
## 安全边界 ## 安全边界
- `+export-pack` 会递归清理全部文本产物中的已识别签名链接/凭据,签名目标替换为 `[signed-url-removed]`;扫描失败不发布。交付时说明 `sanitized/redactionCount/sanitizationScope`,不要把 `complete=true` 解释为摘要图片已离线保存;当前 `offlineImagesComplete=false`。二进制媒体内容不属于文本扫描范围。
- 是否确认以 leaf Schema 与 Runtime gate 为准,不根据“看起来像写操作”自行推断。推荐 Golden Route 中的 `+update`、`+summary`、`+record-*`、`+share/+unshare/+apply-permission`、`+speaker-replace`、`+replace-batch` 等当前要求确认。 - 是否确认以 leaf Schema 与 Runtime gate 为准,不根据“看起来像写操作”自行推断。推荐 Golden Route 中的 `+update`、`+summary`、`+record-*`、`+share/+unshare/+apply-permission`、`+speaker-replace`、`+replace-batch` 等当前要求确认。
- 为兼容既有公开 Contract,对应的底层原子命令保留历史 `not_required`;它们只用于 Shortcut 无法表达的明确底层控制,不得为了绕过 Golden Route 的确认门禁而降级调用。 - 为兼容既有公开 Contract,对应的底层原子命令保留历史 `not_required`;它们只用于 Shortcut 无法表达的明确底层控制,不得为了绕过 Golden Route 的确认门禁而降级调用。
- `+upload` 与 `+upload-and-analyze` 即使不发送消息,仍会上传本地媒体并创建远端听记,真实执行必须按 Runtime confirmation;`--dry-run` 仍可在零远端调用下预览。上传并发送闪记卡片、精确同步并删除热词、撤权等副作用更大的入口继续单独处理。 - `+upload` 与 `+upload-and-analyze` 即使不发送消息,仍会上传本地媒体并创建远端听记,真实执行必须按 Runtime confirmation;`--dry-run` 仍可在零远端调用下预览。上传并发送闪记卡片、精确同步并删除热词、撤权等副作用更大的入口继续单独处理。
@@ -17,6 +17,8 @@
用户说“先核对识别词/词表”时,默认指当前 ASR 热词配置,必须实际执行 `hot-word list`,不能只展示命令。如果用户明确要核对某个音频最终识别出的文字,则必须真实上传并等待转写;upload dry-run 做不到这一点。用户同时要求“不实际创建听记”时,以不写入为最高边界,如实说明两项要求不能同时满足,不能通过真实 create 后 cancel 来伪造预览。 用户说“先核对识别词/词表”时,默认指当前 ASR 热词配置,必须实际执行 `hot-word list`,不能只展示命令。如果用户明确要核对某个音频最终识别出的文字,则必须真实上传并等待转写;upload dry-run 做不到这一点。用户同时要求“不实际创建听记”时,以不写入为最高边界,如实说明两项要求不能同时满足,不能通过真实 create 后 cancel 来伪造预览。
服务端拒绝目标热词的字面形式时,保留原词及失败事实,不静默删除或替换字符后重试;说明原因并请用户提供可接受词面。不要从个别失败样本推导完整字符白名单。录音依赖词表准备成功时,未准备完成就停止后续创建。
## 2. 上传、通知与恢复 ## 2. 上传、通知与恢复
### 2.1 直接上传 ### 2.1 直接上传
@@ -37,8 +39,8 @@ dws minutes +list-mine --page-all --format json
``` ```
- 热词查询、上传计划和前后列表是三份不同证据;命令示例不能代替真实查询结果。 - 热词查询、上传计划和前后列表是三份不同证据;命令示例不能代替真实查询结果。
- dry-run 只证明请求计划与 `executed=false`,不会创建 session、听记或 ASR 结果。前后列表按 `taskUuid` 集合比较;不能只比较数量或第一页。 - dry-run 展示实际设置的语言、模板、卡片和上传确认预算,不会创建 session、听记或 ASR 结果。前后列表按 `taskUuid` 集合比较;不能只比较数量或第一页。
- 没有真实文件、文件字节数或 sessionId 时,停止在相应前置门禁;不得调用 create/complete。用户要求确认“没有生成新听记”时,仍需用真实列表证据回答,不能仅由“我没有调用上传”推断列表事实。 - 没有真实文件或文件字节数时不 create;没有真实 sessionId 时不 complete。本次 dry-run 的零写入回执证明没有由本次操作创建听记;用户另要求比较列表时,用前后完整列表并说明读取时间,不把仅后验列表说成全局没有变化。
如果已有 `taskUuid`,并且只需要读取当前已经生成的摘要与逐字稿,直接使用只读入口: 如果已有 `taskUuid`,并且只需要读取当前已经生成的摘要与逐字稿,直接使用只读入口:
@@ -68,6 +70,8 @@ dws minutes +upload-and-analyze --resume-id <taskUuid> --artifacts summary,trans
上传状态未知时先根据真实 session/taskUuid 读回;不能重新 create 来“试一次”。预签名 URL 属于敏感临时数据,不写入日志、报告或长期 manifest。 上传状态未知时先根据真实 session/taskUuid 读回;不能重新 create 来“试一次”。预签名 URL 属于敏感临时数据,不写入日志、报告或长期 manifest。
`+export-pack` 对全部文本产物做递归签名凭据清理和发布前扫描,检查 `sanitized/redactionCount/sanitizationScope`。已识别签名链接目标替换为 `[signed-url-removed]`;不能将归档 `complete=true` 解释为图片离线完整,当前 `offlineImagesComplete=false`。二进制媒体不属于文本扫描范围,文件 hash/内容读回未实施。
## 3. 异步生成与录音收尾 ## 3. 异步生成与录音收尾
### 3.1 思维导图 ### 3.1 思维导图
@@ -90,6 +94,8 @@ dws minutes +speaker-insights --id <taskUuid>
- 首次执行保存 create 返回的异步 `taskId`。 - 首次执行保存 create 返回的异步 `taskId`。
- 真实执行遵循 `user_required`;`--resume` 沿用同一命令级门禁。 - 真实执行遵循 `user_required`;`--resume` 沿用同一命令级门禁。
- 超时后使用 `--resume [--task-id <taskId>]` 继续轮询。 - 超时后使用 `--resume [--task-id <taskId>]` 继续轮询。
- 只有 `state=ready/complete=true` 且有经过校验的总结正文才算完成;`createStatus` 只是创建时状态。`state=pending` 表示尚未取到可交付结果,可能是 processing 或暂不可读,并不证明后台仍在运行。超时非零,保留 `taskUuid/taskId/attempts/retryable/recovery`;`retryable` 只允许继续读,不能重新 create。
- 恢复使用返回的 `recovery.nextCommand` argv,沿用原 profile 并完成确认;例如 `dws minutes +speaker-insights --id <taskUuid> --resume --task-id <taskId> --timeout 180 --interval 3`。用户指定等待时间时原样传 `--timeout`。
- `taskId` 缺失、状态未知或结果不可解析时保留恢复信息并停止,不再次创建任务。 - `taskId` 缺失、状态未知或结果不可解析时保留恢复信息并停止,不再次创建任务。
### 3.3 结束录音并等待产物 ### 3.3 结束录音并等待产物
@@ -107,22 +113,23 @@ dws minutes +record-wrap-up --id <taskUuid> --artifacts summary,transcript
| 用户意图 | 推荐入口 | 目标身份 | | 用户意图 | 推荐入口 | 目标身份 |
|---|---|---| |---|---|---|
| “我打不开,帮我申请查看/下载/编辑” | `+apply-permission --id <taskUuid> --permission view|download|edit` | 当前登录用户 | | “我打不开,帮我申请查看/下载/编辑” | `+apply-permission --id <taskUuid> --permission view|download|edit` | 当前登录用户 |
| “把这条听记分享给张三” | `+share --id <taskUuid> --member-uids <UID> --permission view|download|edit` | 所有者给指定成员授权 | | “把这条听记分享给张三” | `+share --id <taskUuid> (--member-uids <UID> | --member-staff-ids <staffId>) --permission view|download|edit` | 所有者按真实 UID 或组织 staffId 给指定成员授权 |
| “撤销张三对这条听记的权限” | `+unshare --id <taskUuid> --member-uids <UID>` | 所有者移除指定成员权限 | | “撤销张三对这条听记的权限” | `+unshare --id <taskUuid> --member-uids <UID>` | 所有者移除指定成员权限 |
### 4.1 成员解析 ### 4.1 成员解析
1. 用户已给稳定成员 UID:直接复用,但必须保持同一 profile/组织。 1. 用户已给真实成员 UID:通过 `--member-uids` 直接复用。
2. 只有姓名、手机号或部门线索:切 `dingtalk-contact`/`dingtalk-aisearch` 解析;零或多候选时停止。 2. 用户已给当前组织内的稳定 staffId:通过 `--member-staff-ids` 原样传入,必须保留前导零并保持同一 profile/组织。
3. 不把姓名、手机号、userId、openId 或跨组织 UID 互相猜测转换。 3. 只有姓名、手机号或部门线索:切 `dingtalk-contact`/`dingtalk-aisearch` 解析;零或多候选时停止。
4. 不把姓名、手机号、userId、staffId、openId 或跨组织 UID 互相猜测转换。
### 4.2 批量执行 ### 4.2 批量执行
`+share` 的精确业务参数是 `--id|--ids`、`--member-uids`、必填的 `--permission view|download|edit`,以及可选的 `--cover`、`--sub-resources OrigContent|Summary|Analysis|Note`、`--failure-policy stop|continue`: `+share` 的精确业务参数是 `--id|--ids`、必须且只能选择一个的 `--member-uids|--member-staff-ids`、必填的 `--permission view|download|edit`,以及可选的 `--cover`、`--sub-resources OrigContent|Summary|Analysis|Note`、`--failure-policy stop|continue`。`--member-uids` 只接收真实钉钉 UID,`--member-staff-ids` 接收组织内 staffId 并保留前导零:
```text ```text
dws minutes +share --id <taskUuid> --member-uids <uid1,uid2> --permission view --failure-policy stop --format json dws minutes +share --id <taskUuid> --member-uids <uid1,uid2> --permission view --failure-policy stop --format json
dws minutes +share --ids <uuid1,uuid2> --member-uids <uid> --permission edit --cover --sub-resources OrigContent,Summary --failure-policy continue --format json dws minutes +share --ids <uuid1,uuid2> --member-staff-ids "074360" --permission edit --cover --sub-resources OrigContent,Summary --failure-policy continue --format json
``` ```
`+unshare` 的精确业务参数是 `--id|--ids`、`--member-uids` 和可选的 `--failure-policy stop|continue`;它没有 `--permission`、`--cover` 或 `--sub-resources`: `+unshare` 的精确业务参数是 `--id|--ids`、`--member-uids` 和可选的 `--failure-policy stop|continue`;它没有 `--permission`、`--cover` 或 `--sub-resources`:
@@ -146,4 +153,6 @@ dws minutes +unshare --ids <uuid1,uuid2> --member-uids <uid> --failure-policy co
### 4.4 dry-run ### 4.4 dry-run
这些权限 Shortcut 的 dry-run 只展示目标组合与将执行的动作,不调用远端,也不证明听记、成员或当前权限状态。真实执行后也只能声明“写调用已确认接收”,不能声明“已读回最终权限”。 这些权限 Shortcut 的 dry-run 展示目标组合、动作和失败处理策略;分享还展示权限、覆盖及子资源设置。不调用远端,也不证明听记、成员或当前权限状态。真实执行后也只能声明“写调用已确认接收”,不能声明“已读回最终权限”。
用户允许仅预览时,完成真实目标与成员解析、dry-run 和计划交付,不停在准备步骤。原权限基线未知时,不能临时授权后撤权并称为恢复原状;用户未允许预览替代且要求验证恢复时,说明能力缺口并确认是否接受仅写回执的执行方式。
@@ -22,8 +22,8 @@ dws minutes +speaker-replace --id <taskUuid> --from <当前昵称> --to <目标
- 这是写操作,按 leaf Schema 与 Runtime confirmation 执行;确认内容必须包含听记、源昵称、目标姓名和可选 UID。 - 这是写操作,按 leaf Schema 与 Runtime confirmation 执行;确认内容必须包含听记、源昵称、目标姓名和可选 UID。
- 多组映射逐项保留 ledger。某项失败后不能丢掉已成功项,也不能重放整个批次。 - 多组映射逐项保留 ledger。某项失败后不能丢掉已成功项,也不能重放整个批次。
- 每次写入后重新读取逐字稿,验证旧昵称的目标段落已经更新,且没有修改其他发言人。 - 每次写入后重新读取逐字稿,核对昵称和其他发言人;当前 Shortcut 只验证昵称数量,不证明 UID 关联。无对应证据时说明“UID 关联未证实”;同一源昵称覆盖多个人时不能靠该命令按段拆分。
- 验证失败或结果未知时停止,报告已执行动作和当前证据;禁止盲目重试。 - 验证失败或结果未知时停止,报告已执行动作和当前证据;禁止盲目重试,也不得改走原子命令重写。
## 听音识别边界 ## 听音识别边界
@@ -42,6 +42,14 @@ dws minutes tag query --tag-id <tagId> --limit 10 --cursor <nextToken> --format
- 不按标签名称猜 `tagId`;`tag list` 明确返回空数组时直接交付“当前无标签”,不再查 Help 或拿其他分组补位。 - 不按标签名称猜 `tagId`;`tag list` 明确返回空数组时直接交付“当前无标签”,不再查 Help 或拿其他分组补位。
- `tag query` 是单页原子查询;返回真实 `nextToken` 时继续续拉,或明确说明当前结果不完整。 - `tag query` 是单页原子查询;返回真实 `nextToken` 时继续续拉,或明确说明当前结果不完整。
## 逐字稿与行动项落盘
```text
dws minutes +detail --id <taskUuid> --artifacts transcript,todos --transcript-output file --output-dir <安全相对目录> --format json
```
`file` 只自动保存逐字稿,其他所需产物或失败说明按真实返回另存文件,不能说全部自动落盘。逐字稿优先复用命令返回的文件,不手抄终端片段;按真实 `direction/pages/complete` 和文件内容核对顺序与覆盖,不凭文件名判断。默认正序,只有用户要求倒序才加 `--direction 1`;待办未知不能保存成“0条”。额外要求的摘要/关键词按需加入 `--artifacts`。
## 本地归档 ## 本地归档
```text ```text
@@ -52,6 +60,8 @@ dws minutes +export-pack --id <taskUuid> --output ./minutes-export --include-med
- `--output` 必须是工作目录内尚不存在的安全相对目录;命令拒绝目录穿越和静默覆盖。 - `--output` 必须是工作目录内尚不存在的安全相对目录;命令拒绝目录穿越和静默覆盖。
- 默认归档 `basic,summary,keywords,transcript,todos`;如用 `--artifacts` 缩小集合,只能声称已交付实际选择并验证通过的产物。 - 默认归档 `basic,summary,keywords,transcript,todos`;如用 `--artifacts` 缩小集合,只能声称已交付实际选择并验证通过的产物。
- `--include-media` 只决定是否附带媒体,不降低逐字稿完整性要求;manifest 不保存短期签名 URL。 - `--include-media` 只决定是否附带媒体,不降低逐字稿完整性要求;manifest 不保存短期签名 URL。
- 全部文本产物(basic/summary/keywords/transcript/todos,含 JSON 内嵌字符串)清理已识别 OSS/AWS 签名 URL,目标替换为 `[signed-url-removed]`,独立凭据字段替换为 `[credential-removed]`;普通链接保持不变。发布前凭据扫描失败则不发布目录。
- manifest 和返回值包含 `sanitized/redactionCount/redactionKinds/sanitizationScope`;文本文件有各自清理次数。`complete=true` 不代表原始图片可离线访问,当前 `offlineImagesComplete=false`;清理范围为 `text_artifacts`,不扫描二进制媒体内容,也不提供文件 hash/内容一致性读回。
- 只有响应中的 `published=true`、真实 `path/manifest/files` 和所选产物均完整,才能称归档已生成;任一产物 unknown/pending/failed 时不得宣称成功。 - 只有响应中的 `published=true`、真实 `path/manifest/files` 和所选产物均完整,才能称归档已生成;任一产物 unknown/pending/failed 时不得宣称成功。
## 目标匹配 ## 目标匹配
@@ -19,13 +19,14 @@
### 辅助脚本 ### 辅助脚本
以下文件仍随 Skill 交付,所以必须可发现;但它不是 Golden Route。当前 Runtime 有对应 Shortcut 时优先 Shortcut,只有用户明确要求生成本地 Markdown 汇总文件、使用仓库脚本或兼容旧调用方时才运行脚本。 以下脚本随 Skill 交付。业务读取优先 Runtime Shortcut;摘要脚本只用于用户明确要求本地 Markdown 汇总或兼容旧调用方。时间转换脚本用于读取后的本地展示计算,可按根 Skill 规则直接使用。
| 文件 | 定位 | 当前边界 | | 文件 | 定位 | 当前边界 |
|---|---|---| |---|---|---|
| [minutes_recent_summary.py](../scripts/minutes_recent_summary.py) | 汇总最近若干条“我创建的”听记摘要 | 可直接运行;固定使用 `mine`,不能代替 `+search --scope all` 或具名目标定位 | | [minutes_recent_summary.py](../scripts/minutes_recent_summary.py) | 汇总最近若干条“我创建的”听记摘要 | 可直接运行;固定使用 `mine`,不能代替 `+search --scope all` 或具名目标定位 |
| [format_timestamp.py](../scripts/format_timestamp.py) | 本地批量转换时间戳、时区和星期 | 不调用 DWS;显式提供 `s/ms` 和 IANA 时区,不处理时长或相对周范围 |
脚本的 `--dry-run` 只打印计划且不得调用 DWS;DWS 失败、非法 JSON 与合法空结果必须分开。脚本没有覆盖完整分页、目标消歧与跨 scope 聚合,因此不能用脚本输出声称“全部听记”。行动项读取由 `+action-items`(单条)或 `+detail --ids ... --artifacts todos`(多条)承接,不再发布重复脚本。 摘要脚本的 `--dry-run` 只打印计划且不得调用 DWS;DWS 失败、非法 JSON 与合法空结果必须分开。摘要脚本没有覆盖完整分页、目标消歧与跨 scope 聚合,因此不能用它的输出声称“全部听记”。时间转换脚本始终纯本地,不提供或需要 `--dry-run`。行动项读取由 `+action-items`(单条)或 `+detail --ids ... --artifacts todos`(多条)承接,不再发布重复脚本。
命令前缀统一为 `dws minutes`。结构化读取加 `--format json`;参数不确定时查询精确 leaf: 命令前缀统一为 `dws minutes`。结构化读取加 `--format json`;参数不确定时查询精确 leaf:
@@ -63,9 +64,23 @@ dws minutes <group> <leaf> --help
1. 用户给真实 taskUuid/URL:直接使用。 1. 用户给真实 taskUuid/URL:直接使用。
2. 用户给标题/关键词/时间:先搜索。服务端过滤与 Agent 复核应使用同一时间范围和 profile。 2. 用户给标题/关键词/时间:先搜索。服务端过滤与 Agent 复核应使用同一时间范围和 profile。
3. 精确标题优先;标题包含或语义相关结果可作为候选。零命中停止,多候选、差异较大或分页未完成时消歧。 3. 精确标题优先;标题包含或语义相关结果可作为候选。零命中停止,多候选或差异较大时消歧;分页未完成本身不是歧义,续页与停止条件遵循根 Skill。
4. 锁定后所有 get/update 操作复用同一 taskUuid;某项内容为空或失败不能偷偷换对象。 4. 锁定后所有 get/update 操作复用同一 taskUuid;某项内容为空或失败不能偷偷换对象。
### 时间转换与展示
- 使用真实返回字段的时间值和已确认单位,不能把标题中的日期当作开始时间;缺失值不补零、不补当前时间。`duration` 是时长,不作为 Unix 时间戳转换。
- 日期、星期、UTC 偏移必须从同一个带时区的日期对象生成。优先用户指定的 IANA 时区,否则使用已知会话时区;不默认使用电脑时区。只有时区确认为 `Asia/Shanghai` 时才标注北京时间。秒/毫秒语义以字段 Contract 或来源说明为准,不单凭数字长度猜测。
- 使用 Python 3.9+ 运行本 Skill 的 [format_timestamp.py](../scripts/format_timestamp.py)。以下命令从本 Skill 目录执行;实际替换为真实时间戳、已确认的 `s`/`ms` 单位与 IANA 时区。可一次传入多个值,按输出 `index`(从 0 开始)和原始 `timestamp` 保留 taskUuid/字段对应关系。
```bash
python3 scripts/format_timestamp.py --unit ms --timezone Asia/Shanghai 0 1000
```
- 输出为 JSON:`items` 中含原值、单位、时区、带 UTC 偏移的 `datetime` 和 `weekday`。任一输入非法则整批非零退出,stdout 无部分成功结果,错误写 stderr;不把错误当作日期。负值放在 `--` 后。无 Python、时区数据库缺失或转换报错时,使用可用的等价日期工具;仍无法计算则展示原始值并说明未转换,不能猜日期或星期。
- “本周”等相对范围基于同一时区的真实当前时间,用日期库计算本周一和下周一边界;传给搜索前按其端点包含规则生成 RFC3339 参数,不能靠手算固定 UTC 偏移处理有夏令时的地区。
- 交付时直接引用计算输出,保留时区;不手动改日期、不补算星期。用户不需要星期时可省略;已有明确时区且无冲突的可读时间无需额外执行转换或重复查询 DWS。
## 3. 读取内容 ## 3. 读取内容
| 原子命令 | 返回内容 | 重要边界 | | 原子命令 | 返回内容 | 重要边界 |
@@ -74,19 +89,21 @@ dws minutes <group> <leaf> --help
| `minutes get summary --id <taskUuid>` | AI 摘要/纪要 | 合法空摘要与调用失败分开 | | `minutes get summary --id <taskUuid>` | AI 摘要/纪要 | 合法空摘要与调用失败分开 |
| `minutes get keywords --id <taskUuid>` | 关键词 | 不从空/未知字段编造关键词 | | `minutes get keywords --id <taskUuid>` | 关键词 | 不从空/未知字段编造关键词 |
| `minutes get transcription --id <taskUuid>` | 单页逐字稿 | 这是单页原子入口;存在下一页时继续传 cursor。需要完整结果优先 `+transcript` | | `minutes get transcription --id <taskUuid>` | 单页逐字稿 | 这是单页原子入口;存在下一页时继续传 cursor。需要完整结果优先 `+transcript` |
| `minutes get todos --id <taskUuid>` | 行动项 | 当前响应可能使用 `actions` 或 `dingtalkTodoList`;失败不能伪装成“暂无待办” | | `minutes get todos --id <taskUuid>` | 行动项 | 原子响应可能使用 `actions` 或 `dingtalkTodoList`;优先通过 `+action-items` 读取 typed state,失败不能伪装成“暂无待办” |
| `minutes get audio --id <taskUuid>` | 临时媒体 URL | URL 敏感且会过期,不长期记录 | | `minutes get audio --id <taskUuid>` | 临时媒体 URL | URL 敏感且会过期,不长期记录 |
| `minutes get batch --ids <uuid1,uuid2>` | 多条基础详情 | 批量结果逐项对应 ID,缺项不能算全成功 | | `minutes get batch --ids <uuid1,uuid2>` | 多条基础详情 | 批量结果逐项对应 ID,缺项不能算全成功 |
完整逐字稿优先 `+transcript`;它跨页去重,业务完整性位于 `data.complete/data.pages`,续页状态位于 `meta.pagination`,分页中断返回失败信封。`+detail` 适合一次读取多种产物;任何所选产物失败都属于 partial,不把 bundle 说成完整。 完整逐字稿优先 `+transcript`;它跨页去重,业务完整性位于 `data.complete/data.pages`,续页状态位于 `meta.pagination`,分页中断返回失败信封。`+detail` 适合一次读取多种产物;任何所选产物失败都属于 partial,不把 bundle 说成完整。
`+action-items` 使用稳定状态:`ready` 表示取得明确非空集合,`known_empty` 表示服务端明确返回受支持的空数组;`unsupported_shape` 表示响应存在但不能安全解释,`failed` 表示调用或后端失败。只有前两种状态 `complete=true`;未知 shape 只返回字段名与 JSON 类型,不回显未知字段值,也不得自动重试或改写成空行动项。
需要核对多条命中的 basic 时,不要只抽查第一条: 需要核对多条命中的 basic 时,不要只抽查第一条:
```text ```text
dws minutes +detail --ids <uuid1,uuid2> --artifacts basic --format json dws minutes +detail --ids <uuid1,uuid2> --artifacts basic --format json
``` ```
结果必须逐项覆盖请求 ID;缺项或失败项如实保留。当前 basic 投影没有逐条 `orgName` 时,应说明归属字段不可得;当前 profile 的 `corpName` 只能证明执行上下文,不能证明每条听记的创建组织或归属。 结果必须逐项覆盖请求 ID;缺项或失败项如实保留。核对时复用列表已返回的同 ID 元信息;`orgName`、`flashUserInfo.name` 仅按组织显示名、闪记用户显示名展示,不冒称已验证所有者。缺字段标未知,不为同一缺字段反复读取不提供它的 basic;当前 profile 的 `corpName` 不能代替资源归属。
多听记、多来源或跨产品任务先建立逐来源证据台账:`requested` 记录用户要求的输入,`resolved` 记录已锁定的 `taskUuid`/来源 ID,`missing` 记录未找到的输入,`artifacts` 记录每条实际取得的内容,`status` 记录 `succeeded/partial/failed/unknown`。任一必需来源缺失时整体不能称完整;后续跨产品 Skill 只能接收有来源 ID 的真实产物,不能把已找到的子集写成全部。 多听记、多来源或跨产品任务先建立逐来源证据台账:`requested` 记录用户要求的输入,`resolved` 记录已锁定的 `taskUuid`/来源 ID,`missing` 记录未找到的输入,`artifacts` 记录每条实际取得的内容,`status` 记录 `succeeded/partial/failed/unknown`。任一必需来源缺失时整体不能称完整;后续跨产品 Skill 只能接收有来源 ID 的真实产物,不能把已找到的子集写成全部。
@@ -116,6 +133,8 @@ dws minutes +update --id <taskUuid> --title "<目标标题>" --dry-run --format
录音 start 的成功回执不一定含可控制的 taskUuid。只有响应明确提供 `taskUuid`,并由 Shortcut 返回 `controlReady=true`,才能执行 pause/resume/stop;不能通过“最新听记”或列表第一条猜测绑定。 录音 start 的成功回执不一定含可控制的 taskUuid。只有响应明确提供 `taskUuid`,并由 Shortcut 返回 `controlReady=true`,才能执行 pause/resume/stop;不能通过“最新听记”或列表第一条猜测绑定。
录音预览的 `executed=false` 只证明本次控制请求未执行,不证明远端实时状态;basic 的 start/endTime、duration 或读取成功也不是正在录制、暂停或结束的证据。用户另要求前后列表比较时须按同一范围完整读取,不能凭后验首页宣称全局不变;不得为验证预览执行真实控制。
## 5. 思维导图与发言人 ## 5. 思维导图与发言人
| 原子命令 | 效果 | 当前 confirmation | | 原子命令 | 效果 | 当前 confirmation |
@@ -0,0 +1,60 @@
"""Format Unix timestamps locally; requires Python 3.9+ and an IANA timezone database."""
import argparse
import json
import sys
from datetime import datetime, timedelta, timezone
from decimal import Decimal, InvalidOperation, localcontext
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
def format_timestamps(values, unit, zone):
if unit not in ('s', 'ms'):
raise ValueError('unit must be s or ms')
tz = ZoneInfo(zone)
items = []
for index, raw in enumerate(values):
if not isinstance(raw, str) or not raw.strip() or len(raw) > 100:
raise ValueError('timestamp must be a nonempty number of at most 100 characters')
with localcontext() as ctx:
ctx.prec = 120
number = Decimal(raw)
if not number.is_finite():
raise ValueError('timestamp must be finite')
seconds = number / (1000 if unit == 'ms' else 1)
if not Decimal('-62135596800') <= seconds < Decimal('253402300800'):
raise ValueError('timestamp is outside supported datetime range')
micros = seconds * 1000000
if micros != micros.to_integral_value():
raise ValueError('precision finer than one microsecond is unsupported')
dt = (datetime(1970, 1, 1, tzinfo=timezone.utc)
+ timedelta(microseconds=int(micros))).astimezone(tz)
items.append({
'index': index,
'timestamp': raw,
'unit': unit,
'timezone': zone,
'datetime': dt.isoformat(timespec='microseconds'),
'weekday': '星期' + '一二三四五六日'[dt.weekday()],
})
return items
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('timestamps', nargs='+', help='One or more original Unix timestamp values')
parser.add_argument('--unit', choices=('s', 'ms'), required=True)
parser.add_argument('--timezone', required=True, help='Explicit IANA timezone, e.g. Asia/Shanghai')
args = parser.parse_args(argv)
try:
items = format_timestamps(args.timestamps, args.unit, args.timezone)
except (ValueError, InvalidOperation, OverflowError, ZoneInfoNotFoundError) as exc:
print(json.dumps({'ok': False, 'error': str(exc)}, ensure_ascii=False), file=sys.stderr)
return 2
# Buffer the whole batch: invalid input never produces a partial success on stdout.
print(json.dumps({'ok': True, 'items': items}, ensure_ascii=False, indent=2))
return 0
if __name__ == '__main__':
sys.exit(main())
@@ -0,0 +1,61 @@
import json
import subprocess
import sys
import unittest
from pathlib import Path
SCRIPT = Path(__file__).with_name('format_timestamp.py')
class FormatTimestampTest(unittest.TestCase):
def run_script(self, *args):
return subprocess.run([sys.executable, str(SCRIPT), *args], capture_output=True, text=True)
def test_seconds_milliseconds_and_observed_date(self):
for raw, unit in [('1783906929', 's'), ('1783906929000', 'ms')]:
result = self.run_script('--unit', unit, '--timezone', 'Asia/Shanghai', raw)
self.assertEqual(result.returncode, 0, result.stderr)
item = json.loads(result.stdout)['items'][0]
self.assertEqual(item['datetime'], '2026-07-13T09:42:09.000000+08:00')
self.assertEqual(item['weekday'], '星期一')
def test_batch_order_and_weekday(self):
result = self.run_script('--unit', 'ms', '--timezone', 'Asia/Shanghai', '1788487433000', '1788422619000')
self.assertEqual(result.returncode, 0, result.stderr)
items = json.loads(result.stdout)['items']
self.assertEqual([x['index'] for x in items], [0, 1])
self.assertEqual([x['weekday'] for x in items], ['星期五', '星期四'])
self.assertEqual(items[0]['timestamp'], '1788487433000')
def test_cross_day_and_negative_fraction(self):
result = self.run_script('--unit', 's', '--timezone', 'America/New_York', '--', '-0.000001')
self.assertEqual(result.returncode, 0, result.stderr)
item = json.loads(result.stdout)['items'][0]
self.assertEqual(item['datetime'], '1969-12-31T18:59:59.999999-05:00')
self.assertEqual(item['weekday'], '星期三')
def test_dst_boundary(self):
result = self.run_script('--unit', 's', '--timezone', 'America/New_York', '1710053999', '1710054000')
self.assertEqual(result.returncode, 0, result.stderr)
items = json.loads(result.stdout)['items']
self.assertEqual(items[0]['datetime'], '2024-03-10T01:59:59.000000-05:00')
self.assertEqual(items[1]['datetime'], '2024-03-10T03:00:00.000000-04:00')
def test_bad_timestamp_fails_entire_batch(self):
for invalid in ['bad', '', 'NaN', 'Infinity', '1e1000', '253402300800', '0.0000001']:
with self.subTest(invalid=invalid):
result = self.run_script('--unit', 's', '--timezone', 'UTC', '0', invalid)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(result.stdout, '')
self.assertFalse(json.loads(result.stderr)['ok'])
def test_missing_unit_or_zone_and_invalid_zone(self):
for args in [('0',), ('--unit', 's', '0'), ('--timezone', 'UTC', '0'), ('--unit', 'seconds', '--timezone', 'UTC', '0'), ('--unit', 's', '--timezone', 'No/Such_Zone', '0')]:
result = self.run_script(*args)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(result.stdout, '')
if __name__ == '__main__':
unittest.main()
@@ -1,8 +1,8 @@
--- ---
name: dingtalk-misc name: dingtalk-misc
description: 长尾产品集合技能,覆盖低频钉钉产品:OA审批查询与处理/考勤/直播/DING紧急消息/开放平台应用管理/Agoal目标管理/日志日报周报/电子表格/开放平台文档搜索与OpenAPI逃生舱/文档内嵌白板/钉钉招聘/DWS技能市场安装/组织大脑Hrbrain/原生Markdown/PAT行为授权/多组织profile。Use when 用户提到上述任一产品,或查待审批/同意拒绝转交撤销审批/打卡/排班/OKR/日报周报/单元格读写/白板节点读写/招聘职位/JD/创建职位/搜索安装技能/开发者后台应用/未封装OpenAPI/llms.txt/dws api/人才池/员工档案/职业历程/绩效/原生.md文件/Markdown版本比较/本地草稿diff/Markdown评论/PAT授权/切换组织/跨组织/profile 等相关操作。未来审批任务或实例变化的实时监听不属于本 skill,应使用 dingtalk-event。命中后由本 skill 的「产品索引表」定位具体子产品和命令前缀,再按对应子产品说明执行。 description: 长尾产品集合技能,覆盖低频钉钉产品:OA审批查询与处理/法务智能合同/考勤/直播/DING紧急消息/开放平台应用与MCP开发/Agoal目标管理/日志日报周报/电子表格/开放平台文档搜索与OpenAPI逃生舱/独立及文档内嵌白板/钉钉招聘/DWS技能市场安装/组织大脑Hrbrain/原生Markdown/原生HTML/PAT行为授权/多组织profile。Use when 用户提到上述任一产品,尤其是 Agoal/目标管理/战略解码/经营合约及字段配置/计分卡/OKR/目标规则与周期/个人目标/目标模板/周月报规则提交统计/按时/迟交/未提交/跟催,或查询/创建合同台账、批量导入合同、按听记起草合同、发起合同审查、归档合同、管理合同项目/相对方/账款,或查待审批/同意拒绝转交撤销审批/打卡/排班/日报周报内容填报/独立白板带内容创建/白板节点读写/单元格读写/招聘职位/JD/创建职位/搜索安装技能/开发者后台应用/企业内部应用/查应用状态或能否删除/应用管理成员及角色汇总/应用版本历史/企业内部应用机器人配置、消息模式或在线状态/本机机器人连接器/Stream连接/MCP服务或工具/发布或调试MCP/获取或调用已发布MCP/把OpenAPI、Swagger、Postman、curl或HTTP接口做成MCP或给Agent使用/未封装OpenAPI/llms.txt/dws api/人才池/员工档案/职业历程/绩效/原生.md文件/Markdown版本比较/本地草稿diff/Markdown评论/原生.html文件/新建HTML页面/PAT授权/切换组织/跨组织/profile 等相关操作。周报/月报内容填报属于 report;规则级按时、迟交、未提交统计与人员跟催属于 Agoal;带审批人、抄送人或审批流的日报、周报、简报提交属于 OA 审批。未来审批任务或实例变化的实时监听不属于本 skill,应使用 dingtalk-event。命中后由本 skill 的「产品索引表」定位具体子产品和命令前缀,再按对应子产品说明执行。
metadata: metadata:
cli_version: ">=0.2.14" cli_version: ">=1.0.61"
category: product category: product
requires: requires:
bins: bins:
@@ -13,7 +13,9 @@ metadata:
## 执行前路由 ## 执行前路由
本文件只负责产品路由。先由下表确定唯一产品:单一、清晰的 Attendance 任务以及 Report/Sheet 任务直接读取对应 reference(内含该任务所需的最小执行契约);其它产品先读取 [`dingtalk-shared`](../dingtalk-shared/SKILL.md),再读取唯一产品 reference。仅在实际触发认证、profile、确认或错误恢复时补读一份精确 shared reference,不做冷启动预读。 本文件只负责产品路由。先由下表确定唯一产品:单一、清晰的 Attendance、Report、Sheet 或 Dev 任务直接读取对应 reference(内含该任务所需的最小执行契约);其它产品先读取 [`dingtalk-shared`](../dingtalk-shared/SKILL.md),再读取唯一产品 reference。仅在实际触发认证、profile、确认或错误恢复时补读一份精确 shared reference,不做冷启动预读。
同一请求同时出现日报、周报或简报名称与审批人、抄送人、审批路径、审批单等审批意图时,审批意图优先,统一路由 OA 并读取 `oa.md`;本次任务不得执行 `dws report`。OA 中没有同名模板也不能降级提交 Report,只能交付 OA 搜索结果或请求消歧。只有用户明确要求提交钉钉日志,且没有审批流语义时,才路由 Report。
Attendance 任务直接按产品索引读取一份最匹配的 `attendance*.md`,不要重复预读 `dingtalk-shared`。只有出现跨产品编排、profile/认证问题、未知全局错误或 Reference 明确指向 shared 时,才按需读取 shared 对应内容。 Attendance 任务直接按产品索引读取一份最匹配的 `attendance*.md`,不要重复预读 `dingtalk-shared`。只有出现跨产品编排、profile/认证问题、未知全局错误或 Reference 明确指向 shared 时,才按需读取 shared 对应内容。
@@ -21,21 +23,23 @@ Attendance 任务直接按产品索引读取一份最匹配的 `attendance*.md`
| 触发关键词 | 一句话范围 | 命令前缀 | 详细参考 | | 触发关键词 | 一句话范围 | 命令前缀 | 详细参考 |
|---|---|---|---| |---|---|---|---|
| OA / 审批 / 待处理审批 / 同意 / 拒绝 / 撤销 / 已发起审批 | OA 审批:待处理/详情/同意/拒绝/撤销/已发起/批量审批 | `dws oa` | [oa.md](references/oa.md) | | OA / 审批 / 待处理审批 / 同意 / 拒绝 / 撤销 / 已发起审批 / 发起审批 / 审批附件 | OA 审批查询与处理;创建和附件按需加载专项说明 | `dws oa` | 先读核心 [oa.md](references/oa.md);发起审批再读 [oa-create.md](references/oa-create.md);附件操作再读 [oa-attachments.md](references/oa-attachments.md) |
| 法务 / 智能合同 / 合同台账 / 合同审查 / 合同归档 / 合同项目 / 相对方 / 合同账款 | 智能合同台账、导入、起草、审查、归档、项目、相对方与账款管理 | `dws contract` | [contract.md](references/contract.md) |
| 考勤 / 打卡 / 班次 / 考勤组 / 排班 / 考勤报表 / 假期余额 | 考勤记录、规则与配置、排班、报表、假期 | `dws attendance` | 日常查询/规则/设置:[attendance.md](references/attendance.md);排班导入或排班表导出:[attendance-schedule.md](references/attendance-schedule.md);考勤 Excel/报表导出:[attendance-report.md](references/attendance-report.md);假期/余额:[attendance-vacation.md](references/attendance-vacation.md) | | 考勤 / 打卡 / 班次 / 考勤组 / 排班 / 考勤报表 / 假期余额 | 考勤记录、规则与配置、排班、报表、假期 | `dws attendance` | 日常查询/规则/设置:[attendance.md](references/attendance.md);排班导入或排班表导出:[attendance-schedule.md](references/attendance-schedule.md);考勤 Excel/报表导出:[attendance-report.md](references/attendance-report.md);假期/余额:[attendance-vacation.md](references/attendance-vacation.md) |
| 直播 / 我的直播 / 直播列表 | 直播列表与直播记录查询 | `dws live` | [live.md](references/live.md) | | 直播 / 我的直播 / 直播列表 | 直播列表与直播记录查询 | `dws live` | [live.md](references/live.md) |
| DING / 紧急通知 / 电话DING / 短信DING / 必达消息 | DING 紧急消息(应用内/短信/电话),个人DING | `dws ding` | [ding.md](references/ding.md) | | DING / 紧急通知 / 电话DING / 短信DING / 必达消息 | DING 紧急消息(应用内/短信/电话),个人DING | `dws ding` | [ding.md](references/ding.md) |
| 开放平台应用 / 企业内部应用 / 应用成员 / 应用权限 / 应用版本 / agentId / clientId / 机器人配置 / 版本发布 / connect / MCP 服务开发 / MCP 工具发布 / MCP 凭证 | 开放平台企业内部应用,以及 MCP 服务、工具、鉴权、凭证和协作者的开发管理 | `dws dev` / `dws devapp` / `dws dev mcp` | [devapp.md](references/devapp.md),MCP 细节见 [dev/mcp.md](references/dev/mcp.md) | | 开放平台应用 / 企业内部应用 / 应用状态或删除条件 / 应用管理成员 / 应用权限 / 应用版本或版本历史 / agentId / clientId / 应用机器人配置 / 机器人消息模式或在线状态 / 本机机器人连接器 / Stream连接 / 版本发布 / connect / MCP 服务开发 / MCP 工具发布 / MCP 凭证 / OpenAPI或Swagger或Postman或curl转MCP / 接口给Agent用 / 已发布MCP调用 | 开放平台企业内部应用,以及 MCP 服务、工具、鉴权、凭证、协作者和发布后动态调用 | `dws dev` / `dws dev mcp` / `dws mcp published` | 按下方「Dev 直达规则」读取一个 `references/dev/*.md`;意图不明时才读 [dev-index.md](references/dev/dev-index.md) |
| 目标管理 / 战略解码 / 经营合约 / 计分卡 / OKR / 周月报统计 | Agoal 目标管理与经营目标跟进 | `dws agoal` | [agoal.md](references/agoal.md) | | 目标管理 / 战略解码 / 经营合约字段 / 计分卡 / OKR / 目标规则周期 / 个人目标 / 目标模板 / 周月报规则统计 / 按时迟交未提交 / 跟催 | Agoal 目标管理与经营目标跟进 | `dws agoal` | [agoal.md](references/agoal.md) |
| 日报 / 周报 / 月报 / 写日志 / 收件箱日志 / 发件箱日志 | 日志(日报/周报/月报)查询与按模版提交 | `dws report`(别名 `dws log`) | [report.md](references/report.md) | | 日报 / 周报 / 月报 / 写日志 / 收件箱日志 / 发件箱日志 | 日志(日报/周报/月报)查询与按模版提交 | `dws report`(别名 `dws log`) | [report.md](references/report.md) |
| 电子表格 / 工作表 / 单元格读写 / 公式 / 超链接 / 浮动图片 | 电子表格创建/读写/公式/超链接/浮动图片/导出 | `dws sheet` | [sheet.md](references/sheet.md) | | 电子表格 / 工作表 / 单元格读写 / 公式 / 超链接 / 浮动图片 | 电子表格创建/读写/公式/超链接/浮动图片/导出 | `dws sheet` | [sheet.md](references/sheet.md) |
| 开放平台文档 / API文档 / 接口文档 / 接口报错 | 开放平台开发文档搜索 | `dws devdoc` | [devdoc.md](references/devdoc.md) | | 开放平台文档 / API文档 / 接口文档 / 接口报错 | 开放平台开发文档搜索 | `dws devdoc` | [devdoc.md](references/devdoc.md) |
| 未封装 OpenAPI / llms.txt / dws api / Raw API / API 逃生舱 | 官方 llms.txt 分层发现,仅对企业内部应用 App Token 服务端 API 生成并确认 Raw 调用 | `dws api` | [openapi-explorer.md](references/openapi-explorer.md) | | 未封装 OpenAPI / llms.txt / dws api / Raw API / API 逃生舱 | 官方 llms.txt 分层发现,仅对企业内部应用 App Token 服务端 API 生成并确认 Raw 调用 | `dws api` | [openapi-explorer.md](references/openapi-explorer.md) |
| 白板 / 画布 / OpenNodes / 白板节点 | 读取和更新钉钉文档中的内嵌白板 | `dws whiteboard` | [whiteboard.md](references/whiteboard.md) | | 白板 / 独立白板 / 文档内嵌白板 / 画布 / OpenNodes / 白板节点 | 带内容创建、读取和更新独立或文档内嵌白板;没有 `partId` 时默认独立白板 | `dws whiteboard` | [whiteboard.md](references/whiteboard.md) |
| 招聘 / 职位 / JD / 在招职位 / 创建职位 / 职位详情 | 钉钉招聘职位的查询、详情与创建 | `dws recruit` | [recruit.md](references/recruit.md) | | 招聘 / 职位 / JD / 在招职位 / 创建职位 / 职位详情 | 钉钉招聘职位的查询、详情与创建 | `dws recruit` | [recruit.md](references/recruit.md) |
| 搜索技能 / 找技能 / 安装技能 / 技能市场 / 安装 DWS mono 或 multi skill | DWS 技能市场搜索、下载、安装与内置技能部署 | `dws skill` | [skill.md](references/skill.md) | | 搜索技能 / 找技能 / 安装技能 / 技能市场 / 安装 DWS mono 或 multi skill | DWS 技能市场搜索、下载、安装与内置技能部署 | `dws skill` | [skill.md](references/skill.md) |
| 人才池 / 储备干部池 / 员工档案 / 职业历程 / 绩效记录 / 员工标签 / 组织大脑 / 人才搜索 | 组织大脑:人才池、员工档案专项模块与结构化人才搜索 | `dws hrbrain` | [hrbrain.md](references/hrbrain.md) | | 人才池 / 储备干部池 / 员工档案 / 职业历程 / 绩效记录 / 员工标签 / 组织大脑 / 人才搜索 | 组织大脑:人才池、员工档案专项模块与结构化人才搜索 | `dws hrbrain` | [hrbrain.md](references/hrbrain.md) |
| 原生 Markdown / `.md` 原文 / Markdown 版本比较 / 本地草稿 diff / 覆盖 Markdown / 局部替换 Markdown / Markdown 评论 | 原生 `.md` 文件读取、创建、版本或草稿对比、全量覆盖、局部替换与评论列表 | `dws markdown` | [markdown.md](references/markdown.md) | | 原生 Markdown / `.md` 原文 / Markdown 版本比较 / 本地草稿 diff / 覆盖 Markdown / 局部替换 Markdown / Markdown 评论 | 原生 `.md` 文件读取、创建、版本或草稿对比、全量覆盖、局部替换与评论列表 | `dws markdown` | [markdown.md](references/markdown.md) |
| 原生 HTML / `.html` 文件 / 新建 HTML 页面 / 上传 HTML 文件 / 读取 HTML 原文 / 覆盖 HTML / 局部替换 HTML | 原生 `.html`/`.htm` 文件读取、创建、全量覆盖与局部替换(钉盘或文档空间) | `dws html` | [html.md](references/html.md) |
| PAT 授权 / 行为权限 / scope 授权 / 一次性授权 / 会话授权 / 永久授权 / 授权浏览器策略 | PAT 行为授权与本地浏览器策略 | `dws pat` | [pat.md](references/pat.md) | | PAT 授权 / 行为权限 / scope 授权 / 一次性授权 / 会话授权 / 永久授权 / 授权浏览器策略 | PAT 行为授权与本地浏览器策略 | `dws pat` | [pat.md](references/pat.md) |
| 切换组织 / 换组织 / 跨组织 / 多组织 / profile / 看登录了哪些组织 | 多组织 / profile 管理与跨组织取数 | `dws profile` / `dws auth` / `--profile` | [profile.md](references/profile.md) | | 切换组织 / 换组织 / 跨组织 / 多组织 / profile / 看登录了哪些组织 | 多组织 / profile 管理与跨组织取数 | `dws profile` / `dws auth` / `--profile` | [profile.md](references/profile.md) |
| 宜搭 / AI应用脚本 / 财务辅助脚本(未产品化) | **无**稳定命令面;仅仓库内辅助脚本 | (非默认路由) | [unsupported-scripts.md](references/unsupported-scripts.md) | | 宜搭 / AI应用脚本 / 财务辅助脚本(未产品化) | **无**稳定命令面;仅仓库内辅助脚本 | (非默认路由) | [unsupported-scripts.md](references/unsupported-scripts.md) |
@@ -43,13 +47,18 @@ Attendance 任务直接按产品索引读取一份最匹配的 `attendance*.md`
## 说明 ## 说明
- 命中产品后必须读取其 `references/<product>.md`,不要只凭索引推测命令。Report 只读 [report.md](references/report.md);Sheet 常见闭环只读 [sheet.md](references/sheet.md),复杂任务按进入阶段顺序加载子 reference,每阶段最多一份、常规任务最多三份,禁止批量预读或重复读取。 - 命中产品后必须读取其 `references/<product>.md`,不要只凭索引推测命令。Report 只读 [report.md](references/report.md);Sheet 常见闭环只读 [sheet.md](references/sheet.md),复杂任务按进入阶段顺序加载子 reference,每阶段最多一份、常规任务最多三份,禁止批量预读或重复读取。
- **Agoal / Report 边界**:填写、提交或查询普通日报/周报/月报正文走 Report;规则级提交统计、按时/迟交/未提交人数、人员清单和跟催走 Agoal。出现“月报”不能单独判给 Report,必须结合“规则/统计/迟交/未提交/跟催”等业务词判断。
- **考勤例外**:按索引表直接读取一份最匹配的 `attendance*.md`;不要先读 `attendance.md` 再读排班、报表或假期专项 Reference。只有一个任务确实跨两个独立考勤域时,才加载第二份。 - **考勤例外**:按索引表直接读取一份最匹配的 `attendance*.md`;不要先读 `attendance.md` 再读排班、报表或假期专项 Reference。只有一个任务确实跨两个独立考勤域时,才加载第二份。
- 考勤性能优化只允许减少重复的 shared/Reference/Help/Schema 加载和重复远程查询;禁止为节省 token 省略用户要求的记录、业务字段、分页、失败项、warning 或写后验证。需要“全部/完整”结论时必须保留并检查 endpoint exhaustion/总数等完整性证据。 - 考勤性能优化只允许减少重复的 shared/Reference/Help/Schema 加载和重复远程查询;禁止为节省 token 省略用户要求的记录、业务字段、分页、失败项、warning 或写后验证。需要“全部/完整”结论时必须保留并检查 endpoint exhaustion/总数等完整性证据。
- 考勤命令路径、flag、约束和返回声明以当前 live Cobra/Contract/精确 leaf Schema 为权威;attendance Reference 只维护业务路由、能力边界和工作流。已知命令直接执行,确有不确定性时最多读取一次精确 compact leaf,避免复制命令全集造成信息漂移。 - 考勤命令路径、flag、约束和返回声明以当前 live Cobra/Contract/精确 leaf Schema 为权威;attendance Reference 只维护业务路由、能力边界和工作流。已知命令直接执行,确有不确定性时最多读取一次精确 compact leaf,避免复制命令全集造成信息漂移。
- 产品自己的局部意图消歧文档命名为 `references/<product>-intent-guide.md`,不是共享的 `references/intent-guide.md`。 - 产品自己的局部意图消歧文档命名为 `references/<product>-intent-guide.md`,不是共享的 `references/intent-guide.md`。
- 各产品之间跨产品协作若指向本包内的其它产品,已在对应 `references/<product>.md` 里写成"见本包 references/X.md",无需切换 skill;若指向 top10 独立产品(如 `chat`/`aisearch`/`doc`),仍按 `dingtalk-<product>` 切换 skill。 - 各产品之间跨产品协作若指向本包内的其它产品,已在对应 `references/<product>.md` 里写成"见本包 references/X.md",无需切换 skill;若指向 top10 独立产品(如 `chat`/`aisearch`/`doc`),仍按 `dingtalk-<product>` 切换 skill。
- `scripts/` 下 yida / finance / `aiapp_create_and_poll.py` 等见 [unsupported-scripts.md](references/unsupported-scripts.md);默认不要当正式能力调用。 - `scripts/` 下 yida / finance / `aiapp_create_and_poll.py` 等见 [unsupported-scripts.md](references/unsupported-scripts.md);默认不要当正式能力调用。
- 开放平台应用的命令组细文档在 [references/dev/](references/dev/);命中后先读 [devapp.md](references/devapp.md),再按需加载对应子文件。 - **Dev 直达规则**:应用状态、增删改查、能否删除→[app.md](references/dev/app.md);凭证→[credentials.md](references/dev/credentials.md);开放平台应用的管理成员/角色汇总→[member.md](references/dev/member.md),不是 Chat 群成员;权限→[permission.md](references/dev/permission.md);安全配置→[security.md](references/dev/security.md);网页应用→[webapp.md](references/dev/webapp.md);机器人线上配置→[robot.md](references/dev/robot.md);版本历史/待发布版本/发布→[version.md](references/dev/version.md);事件订阅→[event.md](references/dev/event.md);本机机器人连接器/Stream连接→[connect.md](references/dev/connect.md);MCP→先读[mcp.md](references/dev/mcp.md),仅在进入对应阶段时再增量读取其 API 转换、映射、鉴权、表达式或故障定位子 reference,禁止一次性预读全集;明确跨两个以上 Dev 域→只先读[recipes.md](references/dev/recipes.md)。单域任务只读一份;跨域也不要预读各专题,仅当真实响应出现本页未覆盖的特殊状态或字段时补读一份。无需先读 `dingtalk-shared`、`devapp.md` 或索引,禁止重复读取 Reference。
- 查询、同意、拒绝、转交或撤销审批走 [oa.md](references/oa.md);要求未来审批任务或实例发生变化时实时通知,切换独立的 [`dingtalk-event`](../dingtalk-event/SKILL.md)。开放平台应用事件配置仍属于 DevApp,按 [dev/event.md](references/dev/event.md) 执行,不要与个人实时事件混淆。 - **Dev 命令纪律**:默认使用可审计的 `dws dev ...` 原子路径,不为发现 shortcut 调用 `dws dev help`、子组 help 或 `dws devapp +...`。应用按名称定位只有 `dws dev app list --name`,没有 `dws dev app search`;现有应用的机器人名称、模式、状态用 `dws dev app robot get`,不走 `dws chat bot`;连接器是 `dws dev connect ...`,不在 `dws dev app` 下。已知 leaf 直接执行;只有当前参数确有不确定性时才读取一次该 leaf 的 compact Schema,Schema 不可用时才调用一次精确 leaf `--help`。不要用 group help 探路。
- 原生 `.md` 与在线富文本 `adoc`、通用文件存储的边界见 [markdown.md](references/markdown.md);跨组织 / profile 规则见 [profile.md](references/profile.md)。 - **Dev JSON 与分页**:所有命令带 `--format json`;`connect list/status` 还必须带它们自己的 `--json`,否则实际输出是表格。`app list`、`permission list`、`event list`、`version list` 只接受游标分页:读取返回的 `meta.pagination.endpoint_exhausted/next_token`,续页传 `--cursor <next_token>`;禁止猜 `--page`、`--page-num`,也不要从 `data` 中找旧版 `hasMore/nextCursor`。
- PAT 行为授权不是开放平台应用权限;后者见 [devapp.md](references/devapp.md)。 - **Dev 完成纪律**:执行前记下用户要求的每个交付项;按依赖顺序执行,删除/停用等清理放到最后。用户最初请求写操作只授权 dry-run,不是看过预检后的正式确认。必须先展示 dry-run 的准确对象、动作、业务参数和影响,再取得用户对该预览的明确确认;随后只把同一命令的 `--dry-run` 换成 `--yes`,目标或业务参数有任何变化都重新 dry-run、展示并确认。确认前不得发出非 dry-run 写调用;`--dry-run` 不是完成。写后只做一次必要回读。相同业务错误且参数、状态均未改变时不要重试;仅在新证据实际改变命令、参数或状态后重试一次。最终逐项回答,空列表也明确写“暂无”,不要用大段原始 JSON 挤掉其它结果。
- **DevDoc 搜索预算**:开放平台文档任务对每个用户主题先用原短语精确搜索一次;某主题无结果时最多再用一个由结果证据支持的同义词,随后如实报告未命中。禁止用 10 轮以上近义词、help 或跨产品搜索碰运气。
- OA 任务先读核心 [oa.md](references/oa.md)。只有进入真实提单阶段才增量读取 [oa-create.md](references/oa-create.md),只有处理审批附件才增量读取 [oa-attachments.md](references/oa-attachments.md);不要为普通查询预读创建、控件、节点和附件全集。要求未来审批任务或实例发生变化时实时通知,切换独立的 [`dingtalk-event`](../dingtalk-event/SKILL.md)。开放平台应用事件配置仍属于 DevApp,按 [dev/event.md](references/dev/event.md) 执行,不要与个人实时事件混淆。
- 原生 `.md` 与在线富文本 `adoc`、通用文件存储的边界见 [markdown.md](references/markdown.md);原生 `.html`/`.htm` 文件读写见 [html.md](references/html.md);跨组织 / profile 规则见 [profile.md](references/profile.md)。
- PAT 行为授权不是开放平台应用权限;后者见 [dev/permission.md](references/dev/permission.md)。
@@ -14,6 +14,7 @@
注: 注:
- 用户明确指定回复、本地文件或禁止创建/发送时优先遵守,该约束覆盖下方 recipe 的默认交付步骤。
- "钉钉日志"在用户口语中多指 OA 周报应用,但偶有泛指日志/记录,必要时反问澄清。 - "钉钉日志"在用户口语中多指 OA 周报应用,但偶有泛指日志/记录,必要时反问澄清。
- 仅当用户**明确**说"钉钉日志(OA 应用)"或类似强信号时才切到 `submit-report`;否则不要主动推荐 OA 日志路径——多数用户的"周报"实际期望是文档(可分享、可编辑、长文本)。 - 仅当用户**明确**说"钉钉日志(OA 应用)"或类似强信号时才切到 `submit-report`;否则不要主动推荐 OA 日志路径——多数用户的"周报"实际期望是文档(可分享、可编辑、长文本)。
@@ -23,7 +24,7 @@
|--------|-------------------| |--------|-------------------|
| query-report | **0. 前置判定**:query 含「查日志 / 看日志 / 我发过的日志 / 收到的日志 / 日志详情」且语义指向钉钉日志 OA 应用?是 → 直接走 `dws report`;CSV 附件、群聊导出日志、系统日志或聊天记录核验不属于 OA 日志,应转到文件/群聊/表格相关 skill;其余歧义先按 doc/report 分歧澄清<br>1. 第一条有效查询必须按视角选择新命令:用户说「我发过 / 我创建 / 已发送」→ `report outbox list --cursor 0 --size 20 --format json`;用户说「收到 / 收件箱 / 别人发给我 / 最近收到」→ `report inbox list --start "<YYYY-MM-DDT00:00:00+08:00>" --end "<YYYY-MM-DDT23:59:59+08:00>" --cursor 0 --size 20 --format json`;不要先生成 `report list` / `report sent` 等 deprecated alias,也不要用 inbox 代替 outbox<br>2. 时间 flag 只允许 `--start` / `--end`;裸日期必须展开完整 ISO + `+08:00`;禁止 `--start-date` / `--end-date` / `--date`、UTC `Z`、`date -u`;用户只说「最近 / 近期 / 最近收到 / 最近一周」默认最近 7 天;`--size` 最大 20,更多结果按 `cursor` 分页,禁止传 50/100<br>3. 按发件人查收件箱时,先 `aisearch person --query "<姓名>" --dimension name --format json` 取 `userId/staffId`,再给 `inbox list` 加 `--sender-user-ids <id>`;如果列表中找不到目标发件人或目标标记日志,必须说明不可见 / 未找到,不得改选其他发件人或其他日志<br>4. 从列表返回中取 `reportId` 留给内部后续调用;如果用户已直接提供 `reportId`,跳过列表;面向用户展示列表时基于 `result[]` 拼 Markdown 表:`日期 | 标题 | 发送人 | 状态 | 钉钉链接`,不要把日志 ID 作为主列,缺失字段不编造<br>5. 用户要正文、详情、汇总、总结多篇日志或检查内容时,必须对选中的每篇日志逐条执行 `report entry get --report-id <reportId> --format json`;例如“总结最近收到的 5 篇”应先 list 取前 5 篇(不足 5 篇按实际数量说明),再执行相同数量的 `entry get`;用户要统计 / 已读情况时执行 `report entry stats --report-id <reportId> --format json`<br>**不要把 inbox list/outbox list 当正文接口**;查询正文必须补 `entry get`<br>**不要再生成** `report list` / `report sent` / `report detail` / `report stats`(deprecated alias,仍能跑但会打 stderr 警告) | | query-report | **0. 前置判定**:query 含「查日志 / 看日志 / 我发过的日志 / 收到的日志 / 日志详情」且语义指向钉钉日志 OA 应用?是 → 直接走 `dws report`;CSV 附件、群聊导出日志、系统日志或聊天记录核验不属于 OA 日志,应转到文件/群聊/表格相关 skill;其余歧义先按 doc/report 分歧澄清<br>1. 第一条有效查询必须按视角选择新命令:用户说「我发过 / 我创建 / 已发送」→ `report outbox list --cursor 0 --size 20 --format json`;用户说「收到 / 收件箱 / 别人发给我 / 最近收到」→ `report inbox list --start "<YYYY-MM-DDT00:00:00+08:00>" --end "<YYYY-MM-DDT23:59:59+08:00>" --cursor 0 --size 20 --format json`;不要先生成 `report list` / `report sent` 等 deprecated alias,也不要用 inbox 代替 outbox<br>2. 时间 flag 只允许 `--start` / `--end`;裸日期必须展开完整 ISO + `+08:00`;禁止 `--start-date` / `--end-date` / `--date`、UTC `Z`、`date -u`;用户只说「最近 / 近期 / 最近收到 / 最近一周」默认最近 7 天;`--size` 最大 20,更多结果按 `cursor` 分页,禁止传 50/100<br>3. 按发件人查收件箱时,先 `aisearch person --query "<姓名>" --dimension name --format json` 取 `userId/staffId`,再给 `inbox list` 加 `--sender-user-ids <id>`;如果列表中找不到目标发件人或目标标记日志,必须说明不可见 / 未找到,不得改选其他发件人或其他日志<br>4. 从列表返回中取 `reportId` 留给内部后续调用;如果用户已直接提供 `reportId`,跳过列表;面向用户展示列表时基于 `result[]` 拼 Markdown 表:`日期 | 标题 | 发送人 | 状态 | 钉钉链接`,不要把日志 ID 作为主列,缺失字段不编造<br>5. 用户要正文、详情、汇总、总结多篇日志或检查内容时,必须对选中的每篇日志逐条执行 `report entry get --report-id <reportId> --format json`;例如“总结最近收到的 5 篇”应先 list 取前 5 篇(不足 5 篇按实际数量说明),再执行相同数量的 `entry get`;用户要统计 / 已读情况时执行 `report entry stats --report-id <reportId> --format json`<br>**不要把 inbox list/outbox list 当正文接口**;查询正文必须补 `entry get`<br>**不要再生成** `report list` / `report sent` / `report detail` / `report stats`(deprecated alias,仍能跑但会打 stderr 警告) |
| generate-daily-report | 1. 按[「多源并行采集」](./report-conventions.md#多源并行采集公共模式)执行(时间=今日)<br>2. 交叉汇总并把日报内容写入临时文件 `<tmp>.md`(UTF-8,真实换行)<br>3. **创建文档**:`doc create --name "<日报名>" --content-file <tmp>.md`(> 200KB 按 write-doc 兜底(见 `dingtalk-doc/references/04-document.md`) 走 create 空 → 循环 update) | | generate-daily-report | 1. 按[「多源并行采集」](./report-conventions.md#多源并行采集公共模式)执行(时间=今日)<br>2. 交叉汇总并把日报内容写入临时文件 `<tmp>.md`(UTF-8,真实换行)<br>3. **创建文档**:`doc create --name "<日报名>" --content-file <tmp>.md`(> 200KB 按 write-doc 兜底(见 `dingtalk-doc/references/04-document.md`) 走 create 空 → 循环 update) |
| generate-weekly-report | 1. 按[「多源并行采集」](./report-conventions.md#多源并行采集公共模式)执行(时间=本周)<br>2. 交叉对比并把周报内容写入临时文件 `<tmp>.md`<br>3. **创建文档**:`doc create --name "<周报名>" --content-file <tmp>.md`(兜底同上) | | generate-weekly-report | 1. 按[「多源并行采集」](./report-conventions.md#多源并行采集公共模式)执行,优先用户给定时间范围,未给才用本周<br>2. 按真实来源及时间依据归纳,列出缺失或不完整来源<br>3. 按用户要求交付:仅回复则直接输出,不创建或发送;要求本地文档则保存并验证文件;要求在线文档则切 dingtalk-doc 创建及验证 |
| submit-report | **0. 前置判定**:query 含「钉钉日志 / OA 周报模板 / 我的钉钉日志」等强信号?是 → 继续;否 → 切换到 `generate-weekly-report` 或 `generate-daily-report`(走 dws doc)<br>1. 按[「多源并行采集」](./report-conventions.md#多源并行采集公共模式)执行(时间=当日)<br>2. `report template list --format json` → 取 `report_template_id`<br>3. `report template get --name "<模版名>" --format json` → 取 `result.report_template_fields[]`,每项含 `field_name`/`field_sort`/`field_type`<br>4. **把 contents 写入临时文件**(避免 shell 引号问题):每项含 `key`/`sort`/`content`/`contentType`/`type` 五个字段,**严格映射** `field_name → key`、`field_sort → sort`、`field_type → type`,再填 `content` 与 `contentType`<br>5. `report entry submit --template-id <id> --contents-file <tmp>.json --to-user-ids <userId1>,<userId2> --format json` → `--to-user-ids` 必填:无接收人的提交服务端仍返回成功但日志对任何人都不可见;CLI 会在提交成功后自动反查详情并追加 `dingtalkOpenMarkdownLink` / `dingtalkOpenUrl` / `dingtalkOpenLink` 字段;取返回的 `reportId` 与钉钉打开链接<br>6. final reply 优先直接使用 `dingtalkOpenMarkdownLink`,让用户点击跳转钉钉客户端查看 / 修改;仅当 submit 返回中缺少 `dingtalkOpenUrl` 时,才手动执行 `report entry get --report-id <reportId> --format json` 补取 `result.url`,再包装成 `[在钉钉中查看日志](result.url)`<br>**不要走 doc 写文档**;**禁止跳过 2/3 步**直接 submit;**禁止把 raw `dingtalk://...` URL 直接粘到回复**,必须包成 markdown link<br>**不要再生成** `report template detail` / `report create`(deprecated alias,仍能跑但会打 stderr 警告) | | submit-report | **0. 前置判定**:query 含「钉钉日志 / OA 周报模板 / 我的钉钉日志」等强信号?是 → 继续;否 → 切换到 `generate-weekly-report` 或 `generate-daily-report`(走 dws doc)<br>1. 按[「多源并行采集」](./report-conventions.md#多源并行采集公共模式)执行(时间=当日)<br>2. `report template list --format json` → 取 `report_template_id`<br>3. `report template get --name "<模版名>" --format json` → 取 `result.report_template_fields[]`,每项含 `field_name`/`field_sort`/`field_type`<br>4. **把 contents 写入临时文件**(避免 shell 引号问题):每项含 `key`/`sort`/`content`/`contentType`/`type` 五个字段,**严格映射** `field_name → key`、`field_sort → sort`、`field_type → type`,再填 `content` 与 `contentType`<br>5. `report entry submit --template-id <id> --contents-file <tmp>.json --to-user-ids <userId1>,<userId2> --format json` → `--to-user-ids` 必填:无接收人的提交服务端仍返回成功但日志对任何人都不可见;CLI 会在提交成功后自动反查详情并追加 `dingtalkOpenMarkdownLink` / `dingtalkOpenUrl` / `dingtalkOpenLink` 字段;取返回的 `reportId` 与钉钉打开链接<br>6. final reply 优先直接使用 `dingtalkOpenMarkdownLink`,让用户点击跳转钉钉客户端查看 / 修改;仅当 submit 返回中缺少 `dingtalkOpenUrl` 时,才手动执行 `report entry get --report-id <reportId> --format json` 补取 `result.url`,再包装成 `[在钉钉中查看日志](result.url)`<br>**不要走 doc 写文档**;**禁止跳过 2/3 步**直接 submit;**禁止把 raw `dingtalk://...` URL 直接粘到回复**,必须包成 markdown link<br>**不要再生成** `report template detail` / `report create`(deprecated alias,仍能跑但会打 stderr 警告) |
| generate-monthly-report | 1. 按[「多源并行采集」](./report-conventions.md#多源并行采集公共模式)执行(时间=当月)<br>2. `report outbox list --start "<月初ISO>" --end "<月末ISO>"` → 取当月已提交日志<br>3. 按周分段归纳并把月报内容写入临时文件 `<tmp>.md`<br>4. **创建文档**:`doc create --name "<月报名>" --content-file <tmp>.md`(兜底同上) | | generate-monthly-report | 1. 按[「多源并行采集」](./report-conventions.md#多源并行采集公共模式)执行(时间=当月)<br>2. `report outbox list --start "<月初ISO>" --end "<月末ISO>"` → 取当月已提交日志<br>3. 按周分段归纳并把月报内容写入临时文件 `<tmp>.md`<br>4. **创建文档**:`doc create --name "<月报名>" --content-file <tmp>.md`(兜底同上) |
| generate-topic-report | 1. 提取主题关键词;推断时间范围("最近"默认近 30 天)<br>2. 按[「多源并行采集」](./report-conventions.md#多源并行采集公共模式)执行<br>3. 按时间线排列,交叉归纳核心结论/决策/行动项/未解决问题/演进脉络,并把内容写入临时文件 `<tmp>.md`<br>4. **创建文档**:`doc create --name "<报告名>" --content-file <tmp>.md`(兜底同上) | | generate-topic-report | 1. 提取主题关键词;推断时间范围("最近"默认近 30 天)<br>2. 按[「多源并行采集」](./report-conventions.md#多源并行采集公共模式)执行<br>3. 按时间线排列,交叉归纳核心结论/决策/行动项/未解决问题/演进脉络,并把内容写入临时文件 `<tmp>.md`<br>4. **创建文档**:`doc create --name "<报告名>" --content-file <tmp>.md`(兜底同上) |
@@ -1,302 +1,141 @@
# Agoal(目标管理) # Agoal(目标管理)
## 产品说明 Agoal 覆盖战略解码、经营合约、计分卡、用户目标、目标模板和周月报规则跟催。CLI 前缀为 `dws agoal`。
Agoal 是钉钉目标管理工具,支持战略解码、经营合约、计分卡、用户目标、目标模板、周月报六大模块,帮助组织将战略目标从顶层分解到个人并持续跟踪。 所有结构化查询都带 `--format json`。已知路径直接执行;只有当前二进制的叶子命令或参数不确定时才读一次精确 `--help`,不要用 Profile、Auth、搜索其它产品或本地脚本猜命令和业务字段。
**CLI 前缀**: `dws agoal` ## 产品边界
## 命令总览 - 日报、周报、月报正文的填写、提交、收件箱和已发送记录走 Report。
- 周月报规则级按时、迟交、未提交统计,人员清单和跟催走 Agoal。
- 普通待办走 Todo;Agoal 目标、规则、周期、战略解码和经营合约不转成待办查询。
### strategy (战略解码管理) ## Golden Route 优先级
| 命令 | 用途 | 必填参数 | 备注 | 当前公开且经过稳定返回校验的 Agoal Shortcut 只有以下 5 条。意图能够满足时优先使用,不要手写等价的原子调用或解析脚本。
|------|------|----------|------|
| `strategy list` | 获取战略解码列表 | `--scope-type` `--scope-id` | scopeType: DEPT/PERSONAL;scope-id 为 scope-type 对应的部门 id 或用户 id |
| `strategy detail` | 获取战略解码详情 | `--profile-id` | 根据战略解码 id 查询 |
| `strategy update` | 更新战略解码 | `--profile-id` `--content` | **覆盖逻辑,必须基于查询返回的老数据修改后再传入**;`--content` 为 JSON 数组 |
> **`strategy update` 是覆盖式更新**:一定要先 `strategy detail` 获取完整数据,在原数据基础上修改后再传入,会根据战略解码id进行对应的修改。 | 意图 | 首选命令 | 稳定输出 |
|---|---|---|
| 经营合约字段配置 | `dws agoal +contract-fields --format json` | `count`、`fields` |
| 规则及周期列表 | `dws agoal +user-rules --format json` | `count`、`rules` |
| 周月报规则统计 | `dws agoal +report-statistics-list --format json` | `count`、`statistics` |
| 周月报人员详情 | `dws agoal +report-submit-detail ... --format json` | `count`、`submissions`、`page`、`pageSize`、`totalCount` |
| 目标模板分页 | `dws agoal +obj-template-list ... --format json` | `count`、`templates`、`page`、`pageSize`、`totalCount` |
#### strategy update --content 实体字段说明 以下带 `+` 的 Shortcut 当前明确 `unavailable`,不要尝试:`+strategy-list`、`+strategy-get`、`+strategy-update`、`+contract-list`、`+contract-get`、`+contract-update`、`+scorecard-get`、`+scorecard-entity-get`、`+scorecard-update`、`+user-objectives`、`+obj-template-upsert`。这不表示同名原子能力一定不可用;例如当前叶子 Help 存在时仍可执行 `dws agoal strategy list ...`。确需对应能力时,只使用当前叶子 Help 存在的原子命令,并按本文件的安全规则解释结果。
| 字段 | 说明 | ## 意图路由
|------|------|
| `id` | 实体 id |
| `title` | 标题对象,如 `{"title":"标题文本"}` |
| `linkEntityId` | 所属实体 ID(查询接口中有值时必须回传) |
| `entityType` | 类型枚举:`OGSM_OBJECTIVE`(目的)、`OGSM_GOAL`(目标)、`OGSM_STRATEGY`(策略)、`OGSM_MEASUREMENT`(衡量标准)、`OGSM_TACTICS`(行动方案) |
| `status` | 状态枚举:`NORMAL`(正常)、`PRE_PUBLISH_THEN_UPDATE`(预发布更新)、`PRE_PUBLISH_THEN_CREATE`(预发布新增)、`PRE_PUBLISH_THEN_DELETE`(预发布删除) |
| `supporters` | 承接人数组 `[{type, dingId, staffId}]`;type: `USER`/`DEPARTMENT`;staffId 在 type=USER 时必填 |
| `indicators` | 关键指标 id 字符串数组 |
| `linkSources` | 资源关联数组 `[{id, linkType, linkId, source, objectId, keyResultId}]`;linkType: project/task/campaign/product/doc/standard;source: teambition |
| `executors` | 人员 dingId 字符串数组 |
| `teams` | 部门 dingId 字符串数组 |
### contract (经营合约管理) | 用户意图 | 路由 |
|---|---|
| 战略解码 / 战略目标 / OGSM 列表 | `strategy list` |
| 战略解码详情或更新 | `strategy detail` → 必要时 `strategy update` |
| 经营合约列表或详情 | `contract list` / `contract detail` |
| 经营合约字段、必填或强制字段 | `+contract-fields` |
| 计分卡详情、实体或搜索 | `scorecard detail` / `scorecard entity-detail` / `scorecard search-entities` |
| 目标规则、周期 | `+user-rules`;需要默认偏好时见下方 SOP |
| 个人目标正文 | `user rules` → `user objectives` |
| 周月报规则统计、迟交、未提交、跟催 | `+report-statistics-list` → 按需 `+report-submit-detail` |
| 目标模板查询 | `+obj-template-list` |
| 新增或更新目标模板 | `obj-template create-or-update`,按覆盖写安全流程 |
| 命令 | 用途 | 必填参数 | 备注 | ## 高频只读 SOP
|------|------|----------|------|
| `contract list` | 获取经营合约列表 | `--scope-type` `--scope-id` | scopeType: DEPT/PERSONAL;scope-id 为 scope-type 对应的部门 id 或用户 id |
| `contract fields` | 获取经营合约字段列表 | — | 获取组织下经营合约的字段配置 |
| `contract detail` | 获取经营合约详情 | `--contract-id` | 根据合约 id 查询 |
| `contract update` | 更新经营合约 | `--contract-id` `--dimensions` | **覆盖逻辑**;可选 `--audit-config`、`--objective-template` |
> **`contract update` 同样是覆盖式更新**:必须基于 `contract detail` 返回的数据修改后再传入。 ### 本人与主部门范围
#### contract update --dimensions 维度字段说明 涉及“我本人”“个人与主部门分别查询”时,切换到 Contact skill 解析稳定身份并执行:
| 字段 | 说明 |
|------|------|
| `id` | 维度 id |
| `title` | 维度名称 |
| `description` | 维度描述 |
| `weight` | 维度权重 |
| `objectives` | 目标列表 |
| `dimensionConfig` | 维度配置 |
| `children` | 子维度列表 |
#### contract update 可选参数
| 参数 | 说明 | 示例 |
|------|------|------|
| `--audit-config` | 审批配置 JSON | `{"needAudit":true,"processTemplateId":"TPL_ID"}` |
| `--objective-template` | 合约模板 JSON | `{"id":"TPL_ID","title":"模板名称"}` |
### scorecard (计分卡管理)
| 命令 | 用途 | 必填参数 | 备注 |
|------|------|----------|------|
| `scorecard detail` | 获取计分卡详情 | `--selected-time` `--dept-id` | selectedTime 为 ISO-8601 字符串,如 `"2026-01-01T00:00:00+08:00"` |
| `scorecard entity-detail` | 获取计分卡实体详情 | `--sc-id` `--entity-id` | 根据计分卡 id 和实体 id 查询 |
| `scorecard update` | 更新计分卡 | `--dept-id` `--selected-time` `--id` `--tracking-period-type` `--content` | trackingPeriodType: MONTHLY/QUARTERLY |
| `scorecard search-entities` | 搜索计分卡指标与关键事项 | `--keyword` | 可选 `--page`、`--page-size`;keyword 为标题模糊匹配关键词 |
#### selectedTime 时间说明
`--selected-time` 接受 ISO-8601 字符串,传入对应周期起始时刻:
- **2026年** → `"2026-01-01T00:00:00+08:00"`
- **2026年5月** → `"2026-05-01T00:00:00+08:00"`
#### scorecard update --content 维度字段说明
| 字段 | 说明 |
|------|------|
| `id` | 维度 id |
| `title` | 维度名称 |
| `items` | 指标或关键事项列表 |
items 每项包含:
| 字段 | 说明 |
|------|------|
| `id` | 实体 id |
| `title` | 名称 |
| `reference` | 参考信息 |
| `start` | 起始值 |
| `target` | 目标值 |
| `executors` | 负责人列表,每项包含 `openId` |
#### trackingPeriodType 枚举
| 值 | 说明 |
|------|------|
| `MONTHLY` | 月度追踪 |
| `QUARTERLY` | 季度追踪 |
### user (用户目标管理)
| 命令 | 用途 | 必填参数 | 备注 |
|------|------|----------|------|
| `user rules` | 获取用户规则周期列表 | — | 可选 `--user-id`,不传则默认取操作人自己 |
| `user objectives` | 查询用户目标列表 | `--user-id` `--rule-id` `--period-ids` | `--period-ids` 为逗号分隔的周期 id 列表 |
### obj-template (目标模板管理)
| 命令 | 用途 | 必填参数 | 备注 |
|------|------|----------|------|
| `obj-template list` | 获取目标模板列表 | — | 可选 `--keyword` 搜索关键词、`--page` 页码、`--page-size` 每页数量 |
| `obj-template create-or-update` | 新增或更新目标模板 | `--dimensions` | **覆盖逻辑**;新增时 `--title` 必填;更新时 `--template-id` 必填,dimensions 必须基于老数据修改 |
> **`obj-template create-or-update` 同样是覆盖式更新**:更新时必须基于 `obj-template list` 返回的数据修改后再传入。新增时建议先参考已存在的模板数据再构建 dimensions。
#### obj-template create-or-update 参数说明
| 参数 | 说明 | 类型 |
|------|------|------|
| `--template-id` | 模板 id(更新时必填) | string |
| `--title` | 模板标题(新增时必填) | string |
| `--objective-weight` | 是否启用目标权重 | bool |
| `--dimension-weight` | 是否启用维度权重 | bool |
| `--compute-by-weight` | 维度是否参与计算 | bool |
| `--dimensions` | 模板关联的维度 JSON 字符串 | string |
### report (周月报管理)
| 命令 | 用途 | 必填参数 | 备注 |
|------|------|----------|------|
| `report list-statistics` | 获取周月报数据跟催列表 | — | 返回各规则的人员提交情况统计(按时/迟交/未提交人数);可选 `--keyword` 搜索规则名称 |
| `report submit-detail` | 获取周月报规则提交详情 | `--template-id` `--submit-state` | submitState: `ON_TIME`(按时)/`LATE`(迟交)/`NOT_SUBMITTED`(未提交);可选 `--query-date`(ISO-8601)、`--page`、`--page-size`、`--keyword`(搜索员工名称) |
#### report submit-detail --submit-state 枚举
| 值 | 说明 |
|------|------|
| `ON_TIME` | 按时提交 |
| `LATE` | 迟交 |
| `NOT_SUBMITTED` | 未提交 |
## 意图判断
用户说"战略解码/战略目标/OGSM":
- 查看/列表 → `strategy list`
- 详情 → `strategy detail`
- 修改/更新 → `strategy update`(先查后改)
用户说"经营合约/合约/KPI合约":
- 查看/列表 → `contract list`
- 字段配置 → `contract fields`
- 详情 → `contract detail`
- 修改/更新 → `contract update`(先查后改)
用户说"计分卡/scorecard/绩效看板":
- 查看详情 → `scorecard detail`
- 实体详情 → `scorecard entity-detail`
- 修改/更新 → `scorecard update`
- 搜索计分卡指标与关键事项 → `scorecard search-entities --keyword "关键词"`
用户说"目标/OKR/我的目标/个人目标":
- 规则周期 → `user rules`
- 目标列表 → `user objectives`
用户说"目标模板/模板管理":
- 查看模板列表 → `obj-template list`
- 新增模板 → `obj-template create-or-update --title "模板名称"`
- 更新模板 → `obj-template create-or-update --template-id TPL_ID`
用户说"周月报/周报统计/提交情况/跟催/迟交/未提交":
- 查看提交统计列表 → `report list-statistics`
- 查看某规则的提交详情(按时/迟交/未提交人员明细) → `report submit-detail`
## 核心工作流
```bash ```bash
# 1. 查看战略解码列表(按部门) dws contact user get-self --format json
dws agoal strategy list --scope-type DEPT --scope-id DEPT_ID
# 1.1 查看战略解码列表(按个人)
dws agoal strategy list --scope-type PERSONAL --scope-id USER_ID
# 2. 查看战略解码详情
dws agoal strategy detail --profile-id PROFILE_ID
# 3. 更新战略解码(基于详情返回的数据修改后传入)
dws agoal strategy update --profile-id PROFILE_ID \
--content '[{"id":"entity1","title":{"title":"新目标"},"entityType":"OGSM_OBJECTIVE","status":"NORMAL","executors":["dingId1"]}]'
# 4. 查看经营合约列表(按个人)
dws agoal contract list --scope-type PERSONAL --scope-id USER_ID
# 4.1 查看经营合约列表(按部门)
dws agoal contract list --scope-type DEPT --scope-id DEPT_ID
# 5. 查看经营合约字段列表
dws agoal contract fields
# 6. 查看经营合约详情
dws agoal contract detail --contract-id CONTRACT_ID
# 7 更新经营合约(基于详情返回的数据修改后传入)
dws agoal contract update --contract-id CONTRACT_ID \
--dimensions '[{"id":"dim1","title":"维度名称","objectives":[...]}]'
# 8. 查看计分卡详情
dws agoal scorecard detail --selected-time "2025-01-01T00:00:00+08:00" --dept-id DEPT_ID
# 9. 查看计分卡实体详情
dws agoal scorecard entity-detail --sc-id SC_ID --entity-id ENTITY_ID
# 10. 更新计分卡
dws agoal scorecard update --dept-id DEPT_ID --selected-time "2025-01-01T00:00:00+08:00" --id SC_ID --tracking-period-type MONTHLY --content '[{"id":"dim1","title":"业绩","items":[{"id":"item1","title":"收入","target":"100"}]}]'
# 10.1 搜索计分卡指标与关键事项
dws agoal scorecard search-entities --keyword "业绩"
dws agoal scorecard search-entities --keyword "业绩" --page 1 --page-size 20
# 11. 查看用户规则 → 提取 ruleId 和 periodId
dws agoal user rules --user-id USER_ID
# 12. 查看用户目标
dws agoal user objectives --user-id USER_ID --rule-id RULE_ID --period-ids "period1,period2"
# 13. 查看周月报提交统计列表
dws agoal report list-statistics
# 13.1 按关键词搜索规则
dws agoal report list-statistics --keyword "周报规则"
# 14. 查看某规则的按时提交详情
dws agoal report submit-detail --template-id TPL_ID --submit-state ON_TIME
# 14.1 查看迟交详情(带分页和日期)
dws agoal report submit-detail --template-id TPL_ID --submit-state LATE --query-date "2026-06-18T00:00:00+08:00" --page 1 --page-size 20
# 15. 获取目标模板列表
dws agoal obj-template list
# 15.1 搜索目标模板
dws agoal obj-template list --keyword "业绩"
# 16. 新增目标模板
dws agoal obj-template create-or-update --title "业绩模板" --objective-weight --dimension-weight --dimensions '[...]'
# 16.1 更新目标模板
dws agoal obj-template create-or-update --template-id TPL_ID --title "业绩模板" --dimensions '[...]'
``` ```
## 上下文传递表 从真实返回提取 `orgEmployeeModel.userId` 和主部门 ID。禁止把 `me`、`self`、空字符串、用户名或 Profile 当作 `--scope-id` / `--user-id`。如果返回多个部门但没有唯一主部门标记,先让用户选择,不默认取第一项。
| 操作 | 从返回中提取 | 用于 | 分别查询个人和部门范围,不用另一范围补空结果:
|------|-------------|------|
| `strategy list` | `profileId` | `strategy detail` / `strategy update` 的 `--profile-id` |
| `strategy detail` | 完整实体数据 | `strategy update` 的 `--content`(基于此修改) |
| `contract list` | `contractId` | `contract detail` / `contract update` 的 `--contract-id` |
| `contract detail` | 完整维度数据 | `contract update` 的 `--dimensions`(基于此修改) |
| `scorecard detail` | `scId`、`entityId` | `scorecard entity-detail` / `scorecard update` 的 `--id` |
| `user rules` | `ruleId`、`periodIds` | `user objectives` 的 `--rule-id` `--period-ids` |
| `report list-statistics` | `templateId`(列表项中) | `report submit-detail` 的 `--template-id` |
| `obj-template list` | `templateId` | `obj-template create-or-update` 的 `--template-id`(更新时) |
## 注意事项 ```bash
dws agoal strategy list --scope-type PERSONAL --scope-id <USER_ID> --format json
dws agoal strategy list --scope-type DEPT --scope-id <DEPT_ID> --format json
dws agoal contract list --scope-type PERSONAL --scope-id <USER_ID> --format json
dws agoal contract list --scope-type DEPT --scope-id <DEPT_ID> --format json
```
- **所有 update 命令都是覆盖逻辑**:必须先用对应的 detail/list 查询到完整数据,在原数据基础上修改后再传入,否则未传入的数据会被删除 原子查询返回 `success=true` 且 `content=[]` 时,结论是该精确范围为空;不要换上级部门、其它人员或搜索结果补数。
- 所有命令支持可选参数 `--request-id`
- `--scope-type` 仅支持 `DEPT`(按部门)和 `PERSONAL`(按个人)两种
- `--selected-time` 接受 ISO-8601 字符串(如 `"2026-01-01T00:00:00+08:00"`)
- `--period-ids` 为逗号分隔的字符串,如 `"period1,period2"`
- `report submit-detail` 的 `--query-date` 接受 ISO-8601 字符串(如 `"2026-06-18T00:00:00+08:00"`),不传则默认当天
- `report submit-detail` 的 `--page` 和 `--page-size` 默认为 1 和 10,不传时由服务端使用默认值
--- ### 经营合约字段
## SKILL 摘要(原 dingtalk-agoal/SKILL.md 正文) ```bash
dws agoal +contract-fields --format json
dws agoal +contract-fields --keyword <KEYWORD> --format json
```
## 意图表 按返回字段解释,不互相推断:`required` 决定必填/可选分组,`active` 表示是否启用,`forceActive` 和 `forceRequired` 只在其真实为 `true` 时标为强制。若 `required=false` 与 `forceRequired=true`,或 `active=false` 与 `forceActive=true` 同时出现,保留原值并标记服务端字段冲突,不自行改组或选择优先级。字段编码取 `code`,类型取 `type`,格式补充可取 `scheme.format`。
| 用户说 | 命令 | ### 默认规则、周期与个人目标
|--------|------|
| "查战略解码 / 战略列表" | `dws agoal strategy list` |
| "查战略详情" | `dws agoal strategy detail --profile-id <id>` |
| "更新战略解码" | 先 `strategy detail` 获取完整内容,再 `strategy update` 覆盖更新 |
| "查经营合约" | `dws agoal contract list` / `contract detail` |
| "更新经营合约" | 先 `contract detail` 获取完整内容,再 `contract update` 覆盖更新 |
| "查计分卡" | `dws agoal scorecard detail` / `scorecard entity-detail` |
| "搜索计分卡指标与关键事项" | `dws agoal scorecard search-entities --keyword <KW>` |
| "更新计分卡" | 先查详情,再按 [agoal.md](./agoal.md) 覆盖更新 |
| "查周月报统计/提交情况/跟催" | `dws agoal report list-statistics` / `report submit-detail` |
## 硬约束 只需规则和周期列表时使用 `+user-rules`。当前 Shortcut 不投影顶层 `preference`;用户问“默认/偏好规则或周期”时,改用原子读取:
- `strategy update`、`contract update`、`scorecard update` 都是覆盖式更新,必须先查询详情,在返回数据基础上修改后再提交。 ```bash
- 所有命令带 `--format json`;涉及写操作时回查确认。 dws agoal user rules --format json
```
从 `content.preference.ruleId` 和 `content.preference.periodId` 精确定位默认规则与周期,不把第一条规则、`defaultPeriodIds` 或最近开始的周期冒充偏好。
查询本人目标前,用 `contact user get-self` 取得真实用户 ID。比较“当前偏好周期与前一期”时:
1. 以 `preference.periodId` 对应周期为当前期。
2. 用 `nameEN` / `nameCn` 中的季度 `Q1`~`Q4`、半年 `S1`~`S2`、`Annual` / `年度` 识别粒度。在 `currentPeriods + historyPeriods` 的同粒度候选中,选择 `endDate < 当前期 startDate` 且 `endDate` 最大的一项;没有唯一候选时先让用户选择。
3. 每个周期单独查询以保留空周期,不为回答两期而扩展查询其它粒度。
```bash
dws agoal user objectives --user-id <USER_ID> --rule-id <RULE_ID> --period-ids <PERIOD_ID> --format json
```
秒或毫秒时间戳统一转换为 `Asia/Shanghai` 的可读时间;不要根据 FY 名称猜日期。
### 周月报统计与人员明细
```bash
dws agoal +report-statistics-list --keyword <RULE_KEYWORD> --format json
dws agoal +report-submit-detail --template-id <TEMPLATE_ID> --submit-state <ON_TIME|LATE|NOT_SUBMITTED> --query-date <ISO_DATE> --page 1 --page-size 10 --format json
```
- `enableStatistic=false` 时,列表里的 `onTime` / `late` / `notSubmitted` 零值不能证明无人提交;明确标为“统计未启用/列表值不可据此判断”。使用 `--keyword` 后仍按真实返回规则名核对匹配结果,不仅凭请求参数声称全部名称匹配。
- 用户要求找人数最多的规则,而候选统计关闭或并列时,对所有候选用同一 `query-date` 查询目标状态的第一页,以 `totalCount` 排名;并列再按用户指定规则处理,未指定时可按真实 `lastModified` 最近者展开并说明。
- 选中规则后,再分别查询 `ON_TIME`、`LATE`、`NOT_SUBMITTED`。把列表统计值和明细 `totalCount` 并排展示,差异单列。
- “今天”必须先按当前会话时区确定一个日期,并在同一任务的所有明细调用中显式传入同一个 `--query-date`,避免跨午夜漂移。
- 人员详情属于敏感数据,只返回用户要求的最小字段,不把原始人员结果写入无关文档或日志。
### 目标模板分页与去重
```bash
dws agoal +obj-template-list --keyword <KEYWORD> --page 1 --page-size 10 --format json
dws agoal +obj-template-list --keyword <KEYWORD> --page 2 --page-size 10 --format json
```
用户明确要求前两页时必须真实查询两页,不因第一页 `totalCount` 较小就声称第二页为空。逐页报告 `count`,用服务端 `totalCount` 表示系统总数;合并时优先用稳定 `id`,缺失时用 `templateId` 去重,标题相同但 ID 不同仍是不同模板。Golden Route 会在两者都缺失时失败;不要回退为按标题去重。权重只按 `objectiveWeight`、`dimensionWeight`、`computeByWeight` 的真实布尔值解释。
## 其它原子命令
仅在上述 Golden Route 不覆盖且当前叶子 Help 存在时使用:
```bash
dws agoal strategy detail --profile-id <PROFILE_ID> --format json
dws agoal contract detail --contract-id <CONTRACT_ID> --format json
dws agoal scorecard detail --selected-time <ISO_TIME> --dept-id <DEPT_ID> --format json
dws agoal scorecard entity-detail --sc-id <SC_ID> --entity-id <ENTITY_ID> --format json
dws agoal scorecard search-entities --keyword <KEYWORD> --page 1 --page-size 20 --format json
```
后续命令使用的 `profileId`、`contractId`、`scId`、`entityId`、`ruleId`、`periodId` 和 `templateId` 必须来自前一步真实返回。
## 写操作硬约束
当前没有可用的 Agoal 写 Golden Route。`strategy update`、`contract update`、`scorecard update` 和 `obj-template create-or-update` 都是覆盖写:
1. 先读取目标对象的完整详情;模板更新先读取稳定模板 ID 对应的完整数据。
2. 只在完整旧数据上修改用户指定字段,保留其余字段;禁止构造局部 JSON 覆盖。
3. 执行前展示对象、变更字段、覆盖范围和最终参数摘要,等待用户明确确认;确认后才允许追加 `--yes`。
4. 执行后回读同一稳定 ID,逐字段验证终态;读回不一致或不可用时不得宣称成功。
新增模板同样属于写操作。没有删除或恢复闭环时,不把试写当作验证手段。
@@ -47,9 +47,16 @@
|---|---|---| |---|---|---|
| 查已提交的请假/加班/出差外出/补卡记录 | `dws attendance +list-approve` | `--users <ids> --types overtime,trip,leave,patch --start YYYY-MM-DD --end YYYY-MM-DD` | | 查已提交的请假/加班/出差外出/补卡记录 | `dws attendance +list-approve` | `--users <ids> --types overtime,trip,leave,patch --start YYYY-MM-DD --end YYYY-MM-DD` |
| 获取可提交表单入口 | `dws attendance +get-approve-template` | `--type leave\|overtime\|repair-check\|travel\|out`;只返回入口,不代填提交 | | 获取可提交表单入口 | `dws attendance +get-approve-template` | `--type leave\|overtime\|repair-check\|travel\|out`;只返回入口,不代填提交 |
| 查可用假期类型与余额(请假套件前置) | `dws attendance approve leave-types` | `--user <userId>` 可选;返回 `leaveCode`/`leaveViewUnit`/余额;`bizType` 不可靠,哺乳假等自定义类型按 `leaveName` 识别 |
| 计算请假时长(服务端口径) | `dws attendance approve leave-duration` | `--leave-code --start --end` 必填;时长禁止本地估算 |
| 请假提交前资格校验 | `dws attendance approve leave-check` | 时长必须取自 leave-duration 输出;`success=false` 非零退出并转告 `errorMsg` 后终止 |
| 匹配补卡目标异常班次 | `dws attendance approve supply-plans` | `--time "YYYY-MM-DD HH:mm"` 必填;`plans` 空数组属正常结果;多班次须用户选择 |
| 补卡提交前资格校验 | `dws attendance approve supply-check` | `--timestamp` 必须取自 supply-plans 输出的 `supplyDate`;`qualify=false` 非零退出并转告 `title`/`desc` 后终止 |
查询接口把出差和外出合并为 `trip`;表单入口必须区分外出 `travel` 与出差 `out`。多个模板全部返回,将名称最匹配者置前,并用 `[表单名称](submitUrl)` 展示,禁止裸露 URL 或只返回一个模板。 查询接口把出差和外出合并为 `trip`;表单入口必须区分外出 `travel` 与出差 `out`。多个模板全部返回,将名称最匹配者置前,并用 `[表单名称](submitUrl)` 展示,禁止裸露 URL 或只返回一个模板。
请假与补卡的**提交**意图路由到 oa 域套件工作流(见 [oa.md](oa.md)「发起请假审批」/「发起补卡审批」章节);上表 `approve` 命令为其前置只读查询/校验。`+get-approve-template` 的 `submitUrl` 入口用于加班/外出/出差,以及不支持 CLI 发起的请假/补卡模板场景。
### 补卡规则、加班规则与班次定义 ### 补卡规则、加班规则与班次定义
| 用户意图 | 推荐入口 | 关键参数/边界 | | 用户意图 | 推荐入口 | 关键参数/边界 |
@@ -104,7 +111,7 @@
<!-- VISIBLE_SHORTCUTS_START --> <!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先) ## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "attendance +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws attendance <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service attendance --format json` 批量发现。 以下 shortcut 同时进入公开 catalog 与 Runtime Schema。按本 skill/recipe 路由,命中时 Shortcut 优先于原子命令。参数只查 `dws schema --cli-path "attendance +<shortcut>" --compact --jq '{cli_path,parameters,constraints,confirmation}' -f json`;仅需且已发布 `result` 时查 `--jq '{cli_path,outcomes:.result.outcomes,pagination}'`,字段级再查 `data_schema`;缺失不以 Help/样例推断。Schema 不可用才读一次已知 leaf Help;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 禁 Help:错误 suggestion → 已加载 Skill/reference 明确入口;均无则报漂移。禁全 Catalog/root/parent/product Help;仅映射、接口或 provenance 审计省略 `--compact`。现有路由和 reference 均无法定位低频能力时,才用 `dws shortcut list --service attendance --format json` 发现。
| Shortcut | 风险 | 适用场景 | | Shortcut | 风险 | 适用场景 |
|---|---|---| |---|---|---|
@@ -0,0 +1,331 @@
# 智能合同命令参考
命令前缀统一为 `dws contract`。所有调用追加 `--format json`。命令树已发布到 Agent Schema;参数、约束或确认语义不确定时,优先读取精确 compact leaf Schema,仅在 Schema 不可用或与 Cobra 实际行为发生漂移时读取同一 leaf 的 `--help`。不要使用旧的 dingtalk 二级入口,不要直接调用 MCP、HTTP API 或自行猜测工具名。
## 能力范围与边界
Use when:用户要查询或创建合同台账、批量导入合同、按听记起草合同、发起合同审查、归档合同,或管理合同项目、相对方与收付款账款。
Avoid when:
- 查询、同意、拒绝、转交或撤销已有合同 OA 审批实例走 `dws oa`,不要与合同审批模板 `process-templates` 混淆。
- 经营合约、目标、计分卡和 OKR 跟进走 `dws agoal`。
- 合同文件的搜索、上传、下载及钉盘元数据走 `dws drive`;合同台账、审查和归档仍走 `dws contract`。
- AI 听记内容查询走 `dws minutes`;取得真实 `taskUuid` 后再调用 `dws contract draft`。
- 花名册中的劳动合同等员工基础字段走 `dws contact`。
## 意图路由
| 用户意图 | 命令族 |
|---|---|
| 查合同列表、详情、状态数量、创建台账 | `dws contract record ...` |
| 从钉盘模板批量导入合同、查导入结果 | `dws contract import ...` |
| 查审批模板或台账分类 | `dws contract process-templates` / `dws contract file-directories` |
| 根据 AI 听记和模板起草合同 | `dws contract draft` |
| 解析合同、创建审查任务、查审查结果或权益 | `dws contract review ...` |
| 管理合同项目、导入导出项目 | `dws contract project ...` |
| 管理相对方、工商信息、风险、导入导出 | `dws contract subject ...` |
| 管理合同收付款账款 | `dws contract account ...` |
| 将合同文件归档 | `dws contract archive` |
## 执行规则
1. 先定位真实对象。更新、删除、导出、归档前,先通过对应 `list` / `get` / `detail` 获取真实 ID 和当前状态;禁止根据名称猜 ID。
2. 复杂请求只通过 `--file <json>` 或 `--file -` 传递。提交前检查 JSON 是对象,字段名、必填项和枚举符合本文及当前 leaf Schema;不要把整个请求 JSON 拼成未声明的 flags。
3. 时间单位不得混用:台账、项目及账款列表的筛选日期使用 ISO-8601;账款 JSON 的 `executionDate` 与归档 JSON 的 `archiveTime` 使用 Unix 毫秒时间戳。
4. 异步操作保存创建响应中的真实 `taskId`,再调用配套结果命令。结果未完成时只报告处理中;仅在返回明确可重试状态时轮询,并遵守服务端重试间隔。
5. 删除项目、相对方、账款以及归档等不可逆或高影响操作,在执行前说明对象和影响并获得用户确认;确认后才在 Runtime gate 要求时添加 `--yes`。
6. 创建或更新后优先使用详情查询回读。没有对应详情接口时,保留原始回执并明确说明未能独立回读验证。
7. 列表存在分页参数时按返回继续翻页,直到满足用户范围或服务端表明结束;不要把第一页当成完整结果。
## 标准流程
### 查询合同
1. 用 `record list` 按时间、状态或查询维度缩小范围。
2. 零结果直接说明;多候选先列出名称、状态和合同 ID,让用户消歧。
3. 需要完整字段时,用真实 `contractId` 调用 `record get`。
### 创建台账或归档
1. 查 `file-directories`、`process-templates` 或钉盘文件信息,取得真实目录、模板和文件 ID。
2. 按本文构造 JSON 文件,向用户确认提交对象与关键字段。
3. 创建使用 `record create`;归档使用 `archive`。执行后根据返回的真实 ID 回读或报告状态。
### 合同审查
1. 可先执行 `review benefit` 确认权益。
2. 不确定审查类型或推荐模型时,先用 `review analysis` 解析文件。
3. 用 `review create` 创建任务,保存真实 `taskId` 和 `reviewType`。
4. 用 `review result` 查询结果;未完成不得宣称审查已完成。
### 批量导入
1. 从模板命令或已有钉盘文件取得真实 `fileId` / `spaceId`。
2. 创建导入任务并保存真实 `taskId`。
3. 使用同一命令族的 `*-result` 查询结果,逐项报告成功、失败和处理中状态。
## 命令目录
- [台账](#台账)
- [批量导入](#批量导入)
- [基础资料](#基础资料)
- [合同起草](#合同起草)
- [合同审查](#合同审查)
- [项目管理](#项目管理)
- [相对方管理](#相对方管理)
- [账款管理](#账款管理)
- [合同归档](#合同归档)
## 台账
| 命令 | 必填参数 | 用途 |
|---|---|---|
| `dws contract record list` | 无 | 按创建时间、状态和查询维度筛选合同 |
| `dws contract record get` | `--contract-id` | 查询单份合同详情;别名 `record detail` |
| `dws contract record quantity-by-type` | 无 | 按查询维度统计各状态合同数量 |
| `dws contract record create` | `--file` | 从 JSON 创建合同台账 |
### 查询与统计
`record list` 支持:
- `--start` / `--end`:ISO-8601 合同创建时间;两者同时存在时结束时间不得早于开始时间。
- `--status`:逗号分隔,可选 `approving`、`signing`、`canceled`、`withdraw`、`refused`、`not-archive`、`archive-confirming`、`archived`。
- `--type`:`self`、`participation`、`department`、`all`、`unassigned`,默认 `all`。
`record quantity-by-type` 的 `--type` 与列表相同。
```bash
dws contract record list --type participation --status approving,signing --format json
dws contract record get --contract-id <CONTRACT_ID> --format json
dws contract record quantity-by-type --type department --format json
```
### 创建台账 JSON
`record create --file` 接受 `ImportContractInfoRequest` JSON 对象,必填字段为 `contentFiles`、`name`、`effectiveStatus`、`signStatus`、`ownerDeptNo`。
关键枚举:
- `effectiveStatus`:`not-effective`、`pre-effective`、`effective`、`expired`、`ineffective`、`canceled`。
- `signStatus`:`signing`、`not-archive`、`archived`。
- `amountType`:`payment_party_other`、`payment_party_our`、`none`。
- `signType`:`entity_seal`、`electronic_seal`。
- `termType`:`accurate_end_date`、`perform_finished`。
- `sealTypes`:`contract_seal`、`common_seal`、`legal_seal`。
```bash
dws contract record create --file ./contract.json --format json
```
## 批量导入
| 命令 | 必填参数 | 用途 |
|---|---|---|
| `dws contract import batch` | `--file-id`, `--space-id` | 从钉盘模板文件创建异步合同导入任务 |
| `dws contract import batch-result` | `--task-id` | 查询合同导入结果 |
`--space-id` 可缩写为 `-s`;`--file-id` 不得缩写为 `-f`,因为 `-f` 是全局输出格式。
```bash
dws contract import batch --file-id <FILE_ID> --space-id <SPACE_ID> --format json
dws contract import batch-result --task-id <TASK_ID> --format json
```
## 基础资料
| 命令 | 用途 |
|---|---|
| `dws contract process-templates` | 查询当前用户可见的合同审批模板/流程内容 |
| `dws contract file-directories` | 查询全部合同台账目录;别名 `directories` |
两条命令都没有产品级必填参数。
## 合同起草
`dws contract draft` 根据一条或多条 AI 听记和合同模板起草合同:
- `--task-uuids`:必填,听记任务 ID,多个值用英文逗号分隔。
- `--template-url` / `--template-content`:至少提供一个。
听记 ID 必须来自 `dingtalk-minutes` 的真实返回。模板正文较长时,应从可信本地文件读取后传入,不要让 shell 对内容做意外展开。
```bash
dws contract draft --task-uuids <TASK_UUIDS> --template-url <TEMPLATE_URL> --format json
```
## 合同审查
| 命令 | 必填参数 | 用途 |
|---|---|---|
| `dws contract review benefit` | 无 | 查询组织的合同审查权益 |
| `dws contract review analysis` | `--file` | 解析合同文件并返回摘要及推荐模型 |
| `dws contract review create` | `--file` | 创建合同审查任务 |
| `dws contract review result` | `--task-id`, `--review-type` | 查询审查结果 |
### analysis 请求
JSON 可包含 `fileInfo` 对象,常用字段为 `fileId`、`spaceId`、`fileName`、`fileSize`、`fileType`;可选 `source`。文件 ID 必须来自钉盘真实返回。
### create 请求
`IntelligentContractReviewClientRequest` 常用字段:
- `source`
- `fileInfo`:`fileId`、`spaceId`、`fileName`、`fileSize`、`fileType`
- `reviewType`
- `companyList[].reviewPosition`
- `reviewPosition`
- `reviewResultType`
- `customReviewRules`
```bash
dws contract review benefit --format json
dws contract review analysis --file ./analysis_request.json --format json
dws contract review create --file ./review_request.json --format json
dws contract review result --task-id <TASK_ID> --review-type <REVIEW_TYPE> --format json
```
## 项目管理
| 命令 | 必填参数 | 用途 |
|---|---|---|
| `dws contract project add` | `--name` | 创建合同项目 |
| `dws contract project update` | `--project-id`, `--name` | 更新项目 |
| `dws contract project set-status` | `--project-id`, `--status` | 更新项目状态 |
| `dws contract project list` | `--current-page`, `--page-size`, `--scope` | 分页查询项目 |
| `dws contract project digests` | `--current-page`, `--page-size`, `--scope` | 分页查询项目摘要 |
| `dws contract project detail` | `--project-id` | 查询项目详情 |
| `dws contract project delete` | `--project-ids` | 删除一个或多个项目 |
| `dws contract project export` | `--project-ids` | 导出项目到 Excel |
| `dws contract project import-template` | 无 | 获取项目导入模板链接 |
| `dws contract project import` | `--file-id` | 从钉盘文件创建项目导入任务 |
| `dws contract project import-result` | `--task-id` | 查询项目导入结果 |
### 项目字段
- 新增/更新:`--code`、`--owners`、`--start-date`、`--end-date`、`--remark`、`--contract-ids`;新增还支持 `--source`。
- 日期使用 ISO-8601;开始和结束同时提供时,结束日期不得早于开始日期。
- 列表/摘要:`--scope` 取 `self` 或 `all`;筛选支持 `--name`、`--code`、`--owners`、`--status`、四个日期区间参数。
- 导出可选 `--process-code`。
- 导入可选 `--space-id`、`--file-name`、`--file-type`、`--file-size`。
```bash
dws contract project list --current-page 1 --page-size 20 --scope all --format json
dws contract project detail --project-id <PROJECT_ID> --format json
dws contract project add --name <PROJECT_NAME> --format json
dws contract project import --file-id <FILE_ID> --space-id <SPACE_ID> --format json
dws contract project import-result --task-id <TASK_ID> --format json
```
## 相对方管理
| 命令 | 必填参数 | 用途 |
|---|---|---|
| `dws contract subject add` | `--file` | 添加相对方 |
| `dws contract subject update` | `--file` | 更新相对方 |
| `dws contract subject list` | `--current-page`, `--page-size` | 分页查询相对方 |
| `dws contract subject detail` | `--subject-id` | 查询相对方详情 |
| `dws contract subject delete` | `--subject-id` | 删除单个相对方 |
| `dws contract subject batch-delete` | `--subject-ids` | 批量删除相对方,最多 1000 个 |
| `dws contract subject sort` | `--subject-ids` | 按传入顺序排列己方主体 |
| `dws contract subject detect-risk` | `--subject-name` | 检测相对方风险;可选 `--subject-id` |
| `dws contract subject base-info` | `--subject-name` | 查询工商基本信息;可选 `--subject-id` |
| `dws contract subject auto-fill` | `--subject-name` | 智能填充相对方信息;可选 `--subject-id` |
| `dws contract subject export` | `--subject-ids` | 导出相对方到 Excel |
| `dws contract subject import-template` | 无 | 获取相对方导入模板;可选 `--type` |
| `dws contract subject import` | `--file-id` | 从钉盘文件创建相对方导入任务 |
| `dws contract subject import-result` | `--task-id` | 查询相对方导入结果 |
### 相对方 JSON
`subject add` 接受 `AddSubjectOpenRequest`,至少包含 `partyType` 和 `name`。`partyType` 取 `other`(对方)或 `our`(己方);`source` 常用 `contract` 或 `oa`;`bankAccountType` 取 `BUSINESS_ACCOUNT` 或 `PERSONAL_ACCOUNT`。
`subject update` 接受 `UpdateSubjectOpenRequest`,至少包含真实 `subjectId`、`partyType` 和 `name`。更新前先执行 `subject detail`,在原数据基础上修改,避免覆盖未知字段。
### 查询、导入与导出
- 列表可选 `--party-type`、`--name`、`--code`、`--source`。
- 导出可选 `--process-code`。
- 导入模板 `--type` 可取 `other` 或 `our`。
- 导入可选 `--space-id`、`--file-name`、`--file-type`、`--file-size`。
```bash
dws contract subject list --current-page 1 --page-size 20 --party-type other --format json
dws contract subject detail --subject-id <SUBJECT_ID> --format json
dws contract subject add --file ./subject.json --format json
dws contract subject detect-risk --subject-name <SUBJECT_NAME> --format json
dws contract subject import --file-id <FILE_ID> --space-id <SPACE_ID> --format json
dws contract subject import-result --task-id <TASK_ID> --format json
```
删除前必须先用 `subject detail` 展示真实相对方名称与 ID;批量删除还要展示总数和全部目标 ID,获得确认后再执行。
## 账款管理
| 命令 | 必填参数 | 用途 |
|---|---|---|
| `dws contract account create` | `--file` | 创建账款信息 |
| `dws contract account update` | `--file` | 更新账款信息 |
| `dws contract account get` | `--account-id` | 查询单条账款 |
| `dws contract account list` | 无 | 按条件分页查询账款 |
| `dws contract account delete` | `--account-id` | 删除账款 |
### 账款 JSON
创建请求至少包含:
- `contractId`:真实合同 ID。
- `amount`:以元为单位的字符串金额,例如 `1234.56`。
- `transactionNo`:不可重复的单据号。
- `executionDate`:Unix 毫秒时间戳。
- `status`:`approving`、`withdraw`、`refused`、`confirming`、`canceled`、`finished` 之一。
可选字段包括 `reimbursementNo`、`currencyCode`(默认 `CNY`)、`source`、`remark`。更新请求还必须包含真实 `accountEntryId`;应先执行 `account get`,修改原数据后再提交。
### 账款列表
- `--scope`:`self`、`department`、`all`。
- `--query-status`:`all`、`pay`、`receive`。
- `--amount-type`:`payment_party_other`、`payment_party_our`、`none`。
- 其他筛选:`--status`、`--source`、`--contract-code`、`--contract-name`、`--transaction-no`。
- `--exec-start` / `--exec-end`:ISO-8601 时间字符串;CLI 转换为 MCP 所需的 Unix 毫秒时间戳。
- 分页:`--page`、`--page-size`。
```bash
dws contract account list --scope self --page 1 --page-size 20 --format json
dws contract account get --account-id <ACCOUNT_ID> --format json
dws contract account create --file ./account.json --format json
```
删除账款前必须先执行 `account get`,向用户展示账款 ID、合同 ID、金额、单据号和状态并获得确认。
## 合同归档
`dws contract archive --file` 接受 `ContractOpenArchiveRequest` JSON 对象:
- `bizId`:合同唯一标识,必填。
- `archiveTime`:Unix 毫秒时间戳,必填。
- `archiveFiles`:必填数组;每项可包含 `spaceId`、`fileId`、`fileName`、`fileType`、`fileSize`。
- `archiveCode`、`archiveComment`、`fileDirectoryId`:可选。
归档前通过 `record get` 核对合同,通过钉盘能力核对每个文件 ID,并在确认信息中列出合同、文件数量、目录和归档时间。
```bash
dws contract archive --file ./archive_request.json --format json
```
## 命令与 MCP 能力映射
仅用于诊断和维护,不应绕过 CLI 直接调用:
| CLI 命令 | MCP 工具 |
|---|---|
| `record list` / `get` / `quantity-by-type` / `create` | `queryContracts` / `queryContractDetails` / `queryContractQuantityByType` / `createContract` |
| `import batch` / `batch-result` | `batchImportContractAsync` / `getBatchImportContractResult` |
| `process-templates` / `file-directories` / `draft` | `queryContractProcessContent` / `getAllFileDirectory` / `draft_contract_by_minutes` |
| `review benefit` / `create` / `analysis` / `result` | `queryContractReviewBenefit` / `createContractReviewTask` / `contractAnalysis` / `queryContractReviewResult` |
| `project ...` | `addProject`, `deleteProject`, `updateProject`, `setProjectStatus`, `queryProjects`, `queryProjectDigests`, `queryProjectDetail`, `exportProject`, `getImportProjectTemplate`, `importProject`, `getImportProjectResult` |
| `subject ...` | `addSubject`, `querySubjects`, `querySubjectDetail`, `updateSubject`, `deleteSubject`, `batchDeleteSubject`, `sortSubjects`, `detectSubjectRisk`, `querySubjectBaseInfo`, `autoFillSubjectInfo`, `exportSubject`, `getImportSubjectTemplate`, `importSubject`, `getImportSubjectResult` |
| `account ...` / `archive` | `createAccountInfo`, `updateAccountInfo`, `getAccountEntryInfo`, `listAccountInfo`, `deleteAccountEntryInfo`, `contractOpenArchive` |
@@ -1,42 +1,39 @@
# 应用基础操作 # 应用基础操作
> 操作的是「应用」容器本体(见 [devapp.md](../devapp.md) 概念地图);启停/删除改的是应用 appStatus,不是版本 versionStatus。 管理应用容器本体:列表、详情、创建、更新、启停和删除。应用状态 `appStatus` 与版本状态 `versionStatus` 是两套状态,不要混用。
应用列表查询、详情、创建、修改、生命周期启停和删除。参数用对应命令的 `--help` 查询。 ## 最短命令路径
## 应用定位 ```bash
# 按名称定位;唯一命中后保存 unifiedAppId,后续全部复用
dws dev app list --name <应用名> --format json
dws dev app get --unified-app-id <id> --format json
写操作与多数单应用命令统一用 `--unified-app-id`(全树主键)定位。`dev app get` 额外支持只读按 `--app-key`(=clientId)查详情;`--name` 仍只在 `dev app list` 作列表过滤。拿到 appKey 时可先 `app get --app-key` 核验并拿回 `unifiedAppId`;写操作必须由用户或上游结果提供明确 `unifiedAppId`。 # 已知 AppKey/clientId 时可直接查详情并取得 unifiedAppId
dws dev app get --app-key <appKey> --format json
## 应用状态 appStatus # 写操作:同一条命令先预检,再正式执行
dws dev app create --name <名称> --desc <描述> --dry-run --format json
`app get` 的 `appStatus` 是字符串,取值如 `normal`、`published`。`app list` 不回这个字段(恒 `null`),看应用状态以 `app get` 为准。应用状态 `appStatus` 和版本状态 `versionStatus` 是两套,别混。遇到没见过的 `appStatus` 值原样展示。 dws dev app update --unified-app-id <id> --desc <描述> --dry-run --format json
dws dev app disable --unified-app-id <id> --dry-run --format json
`app create`、`app update` 不返回状态字段;版本状态由 `version create` 返回的 `status`(值如 INIT)表达,见 version.md。 dws dev app enable --unified-app-id <id> --dry-run --format json
dws dev app delete --unified-app-id <id> --confirm-name <应用名> --dry-run --format json
## 要点
- `get` 主要用于定位核验;若返回里带 `appSecret`,脱敏处理,不复制到回答;主动读凭证走 `credentials get`。
- `disable/enable` 成功返回 `{disabled:true}` / `{enabled:true}` + `message`,不回 `appStatus`;以这个布尔判操作成败。要确认最终生效态再 `get` 看 `appStatus` 字符串值。
- `delete` 前必须展示应用摘要;删除是异步,成功后延迟从列表消失。
## 错误处理
| 情况 | 处理 |
|------|------|
| 多应用命中 | 展示候选,停止写操作 |
| `ServiceResult.success=false` | 透传 `errorCode/errorMsg` |
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
``` ```
按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 最初请求只允许执行 dry-run,不能代替预检后的确认。展示 dry-run 返回的准确应用名称/ID、动作、业务参数和影响,等用户对该预览明确确认后,才把同一命令仅由 `--dry-run` 换成 `--yes`;定位符或业务参数变化就重新预检并确认,确认前不得发出真实写调用。创建后用返回的 `unifiedAppId`;更新/启停后回读一次 `app get`。删除前还必须核对名称与 ID,删除放在全部依赖步骤之后;成功只按真实返回说明,未回读到消失时不要声称已删除。
应用只有 `list/get/create/update/disable/enable/delete` 及下属能力组;按名称查找使用 `app list --name`,不存在 `dev app search`。列表续页只传返回的 `meta.pagination.next_token` 到 `--cursor`;`endpoint_exhausted=true` 才是到底,禁止使用 `--page/--page-num`。
## 状态与结果
- `app list` 用于定位,不能用其空的 `appStatus` 判断状态;状态以 `app get` 为准。
- `app get` 若带 `appSecret`,只内部使用并脱敏,不写入回答。
- `disable/enable` 先看返回的 `disabled/enabled`,需要最终状态时再看 `app get.appStatus`。
- 创建结果可能没有版本状态,更新结果可能带 `versionStatus`;两者都不等于已上线。需要上线时继续走 [`version.md`](./version.md),不要仅凭某次写结果推断已发布。
- 多应用命中时列出候选并停止写操作;`ServiceResult.success=false` 原样报告 `errorCode/errorMsg`。
## 多步骤顺序
若一句话同时要求“创建、配置/发布、最后删除”,依赖顺序固定为:创建 → 配置 → 版本生效或明确阻塞 → 收集用户要求的状态/详情 → 删除。中文位置不改变依赖关系,不要因“办完后删除”出现在中间就提前删除或停下来反问。
已知上述路径时直接执行;仅 flag 确有疑问时查一次该 leaf compact Schema,Schema 不可用才查一次精确 leaf help。
@@ -1,108 +1,34 @@
# 本地建联(把机器人接到本地 agent) # 本地机器人连接器
> `dws dev connect` 是 dev 顶层命令,把一个现成机器人接到当前渠道的本地 agent CLI 做调试/值守——只建联、不建号。缺机器人时优先先创建应用并用 `robot config` 配置;若走无绑定的 `robot submit/result`,缺 `unifiedAppId` 时不能续写版本发布。 `dws dev connect` 只管理本地 Stream 调试/值守,不创建应用、不发布版本。状态类任务只看 DWS 返回,不扫描 Docker、pm2、launchctl、系统进程、源码目录或 `~/.dws`。
## 起连接 ## 状态、重启、停止
```bash ```bash
# 用现成机器人凭证起 Stream,接到当前渠道的本地 agent(前台运行,Ctrl-C 退出) dws dev connect list --json --format json
dws dev connect --channel auto --robot-client-id <clientId> --robot-client-secret <clientSecret> dws dev connect status --robot-client-id <list返回的clientId> --json --format json
dws dev connect restart --robot-client-id <list返回的clientId> --dry-run --format json
# 用统一应用 ID,复用 credentials get 自动取凭证 dws dev connect stop --robot-client-id <list返回的clientId> --dry-run --format json
dws dev connect --unified-app-id <unifiedAppId> --channel qoderwork
# 预览建联方案不实际起连接
dws dev connect --robot-client-id <clientId> --robot-client-secret <clientSecret> --dry-run --format json
``` ```
正式 connect 是前台长驻进程:在对话里跑必须后台运行并告诉用户如何停止,或引导用户自己开终端跑。 执行顺序:
`dev connect` 只做本地 Stream 调试/值守,不会创建版本、提交审批或发布应用。dry-run JSON 的 `invocation` 会声明 `scope=local_debug_only`、`doesNotPublish=true`、`completionState=LOCAL_DEBUG_ONLY`,真实前台/daemon 启动也会打印“本地调试,不代表线上发布完成”。完成态判定(建联成功不等于线上可用)以 [devapp.md](../devapp.md)「核心规则」为准。 1. 先执行一次 `connect list --json`。按 `appName`、明确的 `unifiedAppId` 或 `clientId` 唯一选择记录;名称缺失时才用一次 `app list --name` 取得 `unifiedAppId` 后与记录匹配。`connect list` 本身不接受定位 flag。
2. 管理现有记录时优先把该记录的 `clientId` 传给 `--robot-client-id`;只有记录没有 clientId 时才用它自己的 `unifiedAppId`。当前实现的守护进程目录优先按 clientId 建立,把 AppKey/clientId 塞进 `--unified-app-id` 会误报 `not_running`。
3. 只对唯一目标做一次 `status --json`。用户说“如果 down 就重启”时,仅当状态为 `down/degraded` 才先 dry-run、再执行一次 restart,随后 status 回读一次;`healthy` 时不重启。
4. 用户要求停止时,仅当实例明确存在才先 dry-run、再执行 stop,随后 status 回读一次。
5. restart/stop 支持 `--dry-run` 且正式执行不需要 `--yes`;status 不使用 dry-run。实例不存在时直接说明,不替用户新建。不存在 `dws dev app connect ...`。
| flag | 说明 | ## 新建连接
|------|------|
| `--channel` | `auto`(默认,运行时信号自动识别) / openclaw / qoder / qoderwork / hermes / workbuddy / claudecode / codebuddy / codex / gemini / opencode |
| `--robot-client-id` / `--robot-client-secret` | 现成机器人凭证(clientId=AppKey, clientSecret=AppSecret)。命名带 `robot-` 前缀以避开全局 OAuth `--client-id` flag |
| `--unified-app-id` | 统一应用 ID,内部复用 `credentials get` 自动取凭证,替代手填 robot-client-id/secret。注意 clientSecret 仅建号时返回一次、未必可取,取不到时回退手填 |
| `--agent-memory` | 按会话续聊(默认开):同一群/单聊共享 agent 会话,追问保留上下文。codex 走 app-server thread;opencode 走 `opencode serve` 的 HTTP session/message API;qoder/qoderwork 走常驻 `qodercli --input-format stream-json` 并传 `session_id`;claudecode/codebuddy/workbuddy 走 CLI `--session-id`/`--resume`。会话映射按机器人落盘,重启后可继续;gemini 保持无状态。`--agent-memory=false` 关闭 |
| `--agent-model` | 覆盖本地 agent 模型(如 claudecode 默认锁 haiku 求快,可改 `claude-sonnet-4-6` 换聪明)。env: `DWS_AGENT_MODEL` |
| `--agent-workdir` | agent 运行目录:放知识文件(如 CLAUDE.md)可给机器人企业上下文。默认空白临时目录(冷启动快 ~4s vs 大目录 ~29s,慢了会错过钉钉响应窗口)。env: `DWS_AGENT_WORKDIR` |
| `--reply-card` | 富回复(默认开):🤔Thinking/🥳Done 表态永远生效;**卡片需配 `--card-template` 才启用**(同 hermes:没配模板=纯文字回复),失败自动回退文字;env `DWS_REPLY_CARD=0` 全关 |
| `--card-template` | AI 卡片模板 ID。**模板按应用授权**:去开发者后台→你的应用→AI 卡片设置注册/获取模板 ID,可去掉公共模板的第三方角标;默认用公共模板 best-effort。env `DWS_CARD_TEMPLATE` |
| `--allowed-groups` | 群白名单 openConversationId(逗号分隔),配置后只有名单内的群能触发机器人。env `DWS_ALLOWED_GROUPS` |
| `--allowed-users` | 用户白名单 staffId(逗号分隔),配置后只有名单内的用户能触发。env `DWS_ALLOWED_USERS` |
| `--knowledge-dir` | 答疑知识目录(.md/.txt):每条消息本地检索 top-k 片段拼进 prompt,agent 仍在空目录跑、不拖慢回复。env `DWS_KNOWLEDGE_DIR` |
| `--user-rate-limit` | 单用户每分钟消息上限(防刷,每条消息都是一次 LLM 调用),0 关闭,默认 20。env `DWS_USER_RATE_LIMIT` |
## 建联前的依赖预检(agent 必做)
渠道背后是本地 agent CLI,用户可能没装。先 `--dry-run` 看出参 `cli` 字段再决定下一步:
```bash ```bash
dws dev connect --channel <ch> --robot-client-id x --robot-client-secret y --dry-run --format json # 先预检渠道、凭证来源与本地 CLI 依赖
# 输出里的 cli 字段: dws dev connect --unified-app-id <id> --channel <channel> --dry-run --format json
# "cli": {"required":"Claude Code","installed":false,"autoInstall":true,"installHint":"npm i -g @anthropic-ai/claude-code"}
# 用户明确要后台值守且预检通过时启动 daemon
dws dev connect --daemon --unified-app-id <id> --channel <channel> --format json
``` ```
| cli 状态 | agent 应该做什么 | 前台 connect 是长驻进程,不要在评测/对话 shell 里阻塞运行;需要可管理实例时用 daemon。daemon 启动后重新执行 `list --json`,用新记录的 `clientId` 做 status/restart/stop 定位。dry-run 的 `completionState=LOCAL_DEBUG_ONLY`、`doesNotPublish=true` 表示只完成本地调试准备。凭证优先由 `unifiedAppId` 自动取得,密钥不落盘、不回显;缺本地渠道 CLI 时按 dry-run 的 `installed/autoInstall/installHint` 报告,未经用户要求不扩展成系统安装排障。
|----------|----------------|
| `installed: true` | 直接建联 |
| `installed: false, autoInstall: true` | 告知用户缺哪个 CLI,说明启动建联时会自动 `npm` 安装(或先手动执行 installHint 里的命令再连);`DWS_CONNECT_NO_INSTALL=1` 可禁自动安装 |
| `installed: false, autoInstall: false` | **不要直接起连接**——桌面 App 渠道(qoder/qoderwork/workbuddy)需要用户先安装对应 App(installHint 是下载地址),装好后 CLI 随 App 自带;openclaw/hermes 引导用户走官方 onboarding |
dry-run 出参的完整建联预检结构(channel/detectedBy/credentialSource/agent/cli/connect)见 [devapp.md](../devapp.md)「通用出参约定」。 同一 restart/connect 错误且记录、参数、状态没变化时停止,不重复 help、环境探测或重启。已知路径直接执行;仅当前 leaf flag 确有疑问时查一次 compact Schema,Schema 不可用才查一次精确 leaf help。
必须检查 dry-run 顶层:
```json
{
"invocation": {
"completionState": "LOCAL_DEBUG_ONLY",
"doesNotPublish": true,
"scope": "local_debug_only",
"terminal": false
}
}
```
这几个字段表示:连接器可以起本地调试,但版本发布闭环仍由 `robot result` 的 blocking `nextSteps` 或后续 `version status` 决定(完整门禁规则见 [devapp.md](../devapp.md)「核心规则」)。
## Codex 渠道注意
```bash
dws dev connect --unified-app-id <unifiedAppId> --channel codex --format json
```
- `--channel codex` 只走 Codex app-server 的 thread/turn 协议,不再降级到 `codex exec`。
- `DWS_AGENT_CMD` 不覆盖 Codex 渠道;自研或未支持的 AI 工具请用 `--channel custom --agent-cmd "<命令>"`。
- 给 Codex 固定知识/项目上下文:使用 `--agent-workdir /path/to/repo`。
## 机制与环境覆盖
- **stream-bridge 渠道**:Go 原生进程内 Stream 转发器,订阅 `TOPIC_ROBOT`。Claude/CodeBuddy/WorkBuddy/custom 每条 @机器人消息起一个无头 CLI 实例 → stdout 回钉钉;Qoder/QoderWork 会在 connect 生命周期内复用一个常驻 `qodercli --print --output-format stream-json --input-format stream-json` 子进程。
- **会话记忆**:Codex 记录 `conversationId -> threadId`;opencode 记录 `conversationId -> sessionId` 并通过本地 `opencode serve` 的 HTTP API 续聊;Qoder/QoderWork 记录 `conversationId -> Qoder session_id` 并在常驻 stream-json 子进程里续聊;Claude/CodeBuddy/WorkBuddy 使用 `--session-id`/`--resume`。映射按机器人落盘,重启后可继续。
- **会话指令 `/new` vs `/clear`**(对齐各渠道真实能力):`/new`(含 `/start`、`/reset`)开新会话——丢掉当前映射、下一条消息起新 session,**旧 session 保留**(opencode/Codex/Claude 侧仍可按 id 续);`/clear` 则**真正清掉当前会话**——能删的渠道调 agent 原生删除原语(opencode `DELETE /session/:id`),不暴露删除接口的渠道(Codex/Qoder/Claude 系)降级为与 `/new` 相同的重置。两者都只回 ack、不消耗 agent turn。
- **官方渠道**(openclaw/hermes):dws 不代建机器人,输出官方 onboarding 指引。
- 环境覆盖:`DWS_AGENT_CMD`(整条命令覆盖,覆盖时不再注入模型/会话参数) / `DWS_AGENT_MODEL` / `DWS_AGENT_WORKDIR` / `DWS_CONNECT_CMD` / `DWS_CONNECT_NO_INSTALL=1` / `DWS_AGENT_TIMEOUT_MS`。
## 错误处理
| 情况 | 处理 |
|------|------|
| 缺凭证 | 优先用明确 `unifiedAppId` 走 `credentials get`;若只有 `robot submit/result` 的一次性 clientId/clientSecret,按敏感信息使用,缺 `unifiedAppId` 时不能续写版本发布 |
| Codex app-server 调用失败 | 检查本机 `codex` 是否可执行、是否已登录,以及 `--agent-workdir` 指向的目录是否可用;Codex 渠道不会降级到 `codex exec` |
| 桌面 App 渠道 `installed:false, autoInstall:false` | 引导用户先装对应 App(installHint 是下载地址),不要直接起连接 |
## 发现命令
起连接前先查清楚再敲:
```
# 浏览 connect 的子命令与 flag
dws dev connect --help
# 查 connect 的必填参数、类型、默认值
dws dev <command-path> --help
```
按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。
@@ -1,26 +1,18 @@
# 应用凭证读取 # 应用凭证读取
> 凭证=应用调 OpenAPI 的身份(appKey=clientId / appSecret=clientSecret);见 [devapp.md](../devapp.md) 概念地图。 凭证是应用调用 OpenAPI 的身份:`appKey=clientId`,`appSecret=clientSecret`。
`dws dev app credentials get --unified-app-id <id>` 读取应用凭证。参数用对应命令的 `--help` 查询。 ## 最短流程
返回字段:`clientId`/`appKey`(同值)、`clientSecret`/`appSecret`(同值)、`currentSecretStatus`、`hasPendingExpireTask`、`unifiedAppId` 等。 ```bash
dws dev app list --name <应用名> --format json
规则: dws dev app credentials get --unified-app-id <唯一命中的id> --format json
- 该命令只需 `--unified-app-id`。
- 返回里 `clientSecret/appSecret` 是明文密钥,按敏感凭证处理,不写进回答文本。
- 不能用 `dev app get` 代替;`dev app get` 也会带密钥,同样只用于内部判断并脱敏,不向用户展开。
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app credentials --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
``` ```
按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 - 已有明确 `unifiedAppId` 时只执行第二条;不要扫描本地目录、环境变量或配置文件找凭证。
- 只需要 `--unified-app-id`,不要改用 `app get` 绕路。
- 非空 `clientSecret/appSecret` 可证明凭证已生成,但其值属于敏感证据:不得在最终答复、后续命令或文件中复述。只返回实际 `data` 中存在的公开字段,如 `unifiedAppId`、`clientId/appKey`、`agentId`;CLI 不承诺额外的“凭证状态”字段,缺失时不要编造。
- 应准确表述为“本次答复未展示密钥值”或“答复中已隐藏”,不得声称 API、CLI 或既有执行日志已经脱敏;若源响应返回过明文,只做最小处理,不再打印或传递该值。
- 名称多匹配时展示候选,不读取任何候选的密钥。
已知路径直接执行;仅当前 leaf flag 确有疑问时查一次 compact Schema,Schema 不可用才查一次 leaf help。
@@ -1,13 +1,27 @@
# 开发者能力索引 # Dev 能力索引(只在意图不明时读取)
| 主题 | 详见 | 意图清楚时不要读本索引,直接从根 Skill 进入唯一专题 Reference。默认走 `dws dev` 原子命令,不先读 `devapp.md`,也不调用 help 发现 shortcut。
|---|---|
| 应用管理 | [`app.md`](./app.md) | | 用户意图 | 标准命令路径 | 只读此文件 |
| 凭证/鉴权 | [`credentials.md`](./credentials.md) | |---|---|---|
| 权限管理 | [`permission.md`](./permission.md) | | 应用创建、列表、详情、状态、更新、停启、能否删除 | `dws dev app ...` | [`app.md`](./app.md) |
| 机器人 | [`robot.md`](./robot.md) | | AppKey、AppSecret、clientId、agentId | `dws dev app credentials get` | [`credentials.md`](./credentials.md) |
| 长连接 | [`connect.md`](./connect.md) | | 开放平台应用的管理成员、成员角色与角色汇总 | `dws dev app member ...` | [`member.md`](./member.md) |
| 事件订阅 | [`event.md`](./event.md) | | 应用权限 | `dws dev app permission ...` | [`permission.md`](./permission.md) |
| 成员管理 | [`member.md`](./member.md) | | IP 白名单、回调或 SSO 地址 | `dws dev app security config` | [`security.md`](./security.md) |
| MCP 服务与工具开发 | [`mcp.md`](./mcp.md) | | H5、PC 主页与 OMP 地址 | `dws dev app webapp ...` | [`webapp.md`](./webapp.md) |
| 最佳实践 | [`recipes.md`](./recipes.md) | | 机器人配置、启停、提交审核 | `dws dev app robot ...` | [`robot.md`](./robot.md) |
| 版本历史、待发布版本、创建、审批、发布与状态 | `dws dev app version ...` | [`version.md`](./version.md) |
| 应用事件订阅 | `dws dev app event ...` | [`event.md`](./event.md) |
| 本机机器人连接器、Stream/长连接实例与健康状态 | `dws dev connect ...` | [`connect.md`](./connect.md) |
| MCP 服务、工具、鉴权、凭证与协作者 | `dws dev mcp ...` | [`mcp.md`](./mcp.md) |
| 同一任务明确跨两个以上 Dev 域 | 按依赖顺序组合原子命令;先不预读专题 | [`recipes.md`](./recipes.md) |
## 共同执行约束
1. 先列交付项,再按依赖顺序做;删除、停用等清理最后执行。
2. 应用名先用一次 `app list --name` 定位,再始终复用同一个 `unifiedAppId`,不要反复查找。
3. 应用定位没有 `dev app search`;连接器没有 `dev app connect`,只能走顶层 `dev connect`。已知命令直接调用;flag 不确定时只查一次精确 leaf compact Schema,Schema 不可用才查一次 leaf help。
4. 最初请求只允许写操作 dry-run;展示准确对象、动作、业务参数和影响并取得预览后的明确确认,才把同一命令仅由 `--dry-run` 换成 `--yes`。参数变化就重新预检并确认;确认前不得真实写入。回读按专题 Reference 完成;相同业务错误无状态变化时停止,不用 debug/verbose/help 循环重试。
5. Dev 的应用、权限、事件、版本列表使用 `--cursor`,没有 `--page/--page-num`。以 `meta.pagination.endpoint_exhausted=true` 为翻完;为 false 时只把原样 `next_token` 传给下一次 `--cursor`。
6. 最终逐项覆盖用户要求;列表很大时给总数、相关项和分页完整性,空结果明确说明。
@@ -1,47 +1,25 @@
# 事件订阅 # 应用事件订阅
> 把应用关心的事件推到回调地址;见 [devapp.md](../devapp.md) 概念地图。 事件配置走 `dws dev app event`;它不等于个人 IM/OA 实时监听。
`dws dev app event list/subscribe/unsubscribe`,按 `--unified-app-id` 定位,订阅/退订用 `--event-codes`(逗号分隔,一次多个)。参数用对应命令的 `--help` 查询。 ## 最短命令路径
规则: ```bash
- 写操作先 `--dry-run` 预览,确认后 `--yes`。 # 按事件名称或事件码搜候选;只有用户要求全部时才翻全量
- 一次可订阅多个事件码,共用同一回调。 dws dev app event list --unified-app-id <id> --keyword <关键词> --page-size 50 --format json
- **事件码定位优先用 `event list --keyword <关键词>` 搜索**(按事件码或事件名称模糊匹配);只有用户明确要「全部事件」时才不带 `--keyword` 翻全量。
- 可订阅的事件码通过 `event list` 查询:返回 `events[]` 列出 `eventCode/eventName/subscribed`,不用查文档。
- 退订前先 `event list` 确认当前订阅,避免退不存在的。
- 翻全量时用 `--cursor/--page-size` 逐页处理;返回 `events/hasMore/nextCursor/pageSize`,翻页继续传 `nextCursor`。
- 批量或全量订阅前,先把候选 `eventCode` 列给用户确认,再 `--dry-run` → `--yes`。
- 返回看 `events[].subscribed` 和 `pushType=STREAM`(事件走 Stream 长连推送;connect 与事件订阅的关系见下方「Stream 长连」)。
- `subscribe/unsubscribe` 的 `--event-codes` 必填,返回 `success/operation/unifiedAppId/eventCodes/needsPublish/versionRequiredAction`;失败时补 `errorCode/errorMsg/reason/retryable/action`。
- `subscribe/unsubscribe` 返回 `needsPublish=true` 或非空 `versionRequiredAction` 时,按 [version.md](version.md) 继续 `version create → check-approval → publish → status`;进入 `RELEASE` 后重新 `event list`,确认目标 `events[].subscribed` 与本次操作一致。进入审核态则报告待审批;需要选择审批人时停下让用户选择。
- 如果订阅失败、返回提示长链接未在线(是泛化错误:`reason=business_error`、`message` 含「长链接未在线」、`server_error_code=-1`;没有 STREAM_NOT_CONNECTED 这类结构化错误码,也没有 action 字段),先执行 `dev connect` 建联,再重试订阅。
## Stream 长连:connect 与事件订阅的关系 dws dev app event subscribe --unified-app-id <id> --event-codes <code1,code2> --dry-run --format json
dws dev app event unsubscribe --unified-app-id <id> --event-codes <code1,code2> --dry-run --format json
钉钉一个应用就一条 Stream 长连(WebSocket),上面同时承载机器人消息、事件、卡片回调等多种 topic。connect 和 event 在这条长连上分工不同:
- `dev connect`:用应用凭证把这条长连建起来并保活,但只注册了机器人消息处理(收 @机器人 → 转发本地 agent),**不消费事件**。
- `event subscribe/unsubscribe`:只是**配置**操作(配应用订阅哪些事件码),自己不建长连、不收事件;服务端要求应用的 Stream 长连已在线,否则报错「长链接未在线」(泛化 business_error,不是结构化错误码)。
- 两者唯一关联:先 `dev connect` 把长连建在线,`event subscribe` 才能成功——connect 负责「让长连在线」,subscribe 负责「配置订阅」。
- 当前限制:connect 的长连不处理事件,dws 也没有「收/消费事件」的运行时命令,event 只到「配置订阅」为止。订阅成功后真正的事件推送 dws 暂不消费——要消费得用注册了事件 handler 的 SDK 自接(事件结构走 `dws devdoc article search --query` 查)。
## Stream 长连怎么建的
`dev connect` 内部用 dingtalk-stream-sdk-go,凭 `clientId/clientSecret` 建一条 WebSocket 长连并保活——底层网关握手、ticket、加解密都由 SDK 封装,agent 跑 `dev connect` 即可,不用碰这些。
dws 当前只消费机器人消息、不消费事件。要自己写事件消费程序补这个 gap 时,SDK 用法以官方文档为准(走 `dws devdoc article search --query`,或看 github.com/open-dingtalk/dingtalk-stream-sdk-go)——注意事件用 `RegisterAllEventHandler`、connect 用的机器人是 `RegisterChatBotCallbackRouter`,两套别混;版本/接口会变,不在这里固化。
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app event --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
``` ```
按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 ## 闭环规则
1. 从 `event list` 实际返回的 `data.events[]` 中读取 `eventCode/eventName/subscribed`,名称多匹配时让用户确认;不要按 Schema 的通用列表描述猜成 `items[]`,也不要查 help 或文档猜事件码。
2. 退订前确认目标当前 `subscribed=true`。最初请求只允许 dry-run;展示预检返回的应用 ID、subscribe/unsubscribe 动作、完整事件码及影响并取得用户对该预览的明确确认后,才把同一命令仅由 `--dry-run` 换成 `--yes`。参数变化就重新预检并确认,确认前不得真实订阅或退订。
3. 每种不同动作(subscribe 与 unsubscribe)各只做一次 dry-run 和至多一次正式执行;一次动作失败不阻止另一项独立动作按相同预算尝试。看正式写结果的 `success/needsPublish/versionRequiredAction`;只有写入明确成功且要求发布,或回读已证明配置改变时,才按 [`version.md`](./version.md) 做版本闭环。
4. 到 `RELEASE` 后只回读目标事件,确认 `subscribed` 与请求一致;进入审批态就如实报告待审批,不重复发布。
5. 全量列表读取 `meta.pagination`:`endpoint_exhausted=false` 时把原样 `next_token` 传给下一次 `--cursor`,到 true 停止;CLI 不支持 `--page/--page-num`。普通关键词查询不要翻无关事件。最终给匹配数和相关事件,不倾倒整个列表。
正式写入若返回 `66117` 或泛化 `BUSINESS_ERROR`,做一次目标事件回读;`subscribed` 未变化就原样报告并停止该动作,不重复正式写入。此类错误本身不证明连接器异常:不得因此读取 connect/security/permission/recipes/openapi、重启连接器、搜索文档或创建/发布版本。只有服务端文本明确指出“长链接未在线”时,才按 [`connect.md`](./connect.md) 查询一次;实例存在且 down 才重启一次,并以一次新状态为证决定是否重试。没有记录则说明需先建立连接,不扫描本机进程或目录。
已知路径直接执行;仅 flag 确有疑问时查一次精确 leaf compact Schema,Schema 不可用才查一次 leaf help。
@@ -7,14 +7,29 @@
远端 `serverName` 和工具名不会注册成新的顶层命令,也不写入跨身份缓存。消费端每次按当前登录 profile、用户和组织身份解析服务地址;输出只展示脱敏地址。 远端 `serverName` 和工具名不会注册成新的顶层命令,也不写入跨身份缓存。消费端每次按当前登录 profile、用户和组织身份解析服务地址;输出只展示脱敏地址。
## 渐进参考
先使用本页确定阶段,只在进入对应步骤时加载一份深层参考:
| 任务阶段 | 参考 |
|---|---|
| OpenAPI/Swagger/Postman/curl/接口文档转工具 | [API 材料转工具](mcp/api-to-tool.md) |
| 编写 input/output mappings | [字段与映射规则](mcp/mapping-rules.md) |
| 配置 API key、token、签名或凭证 | [鉴权与凭证](mcp/auth-and-credentials.md) |
| 使用复杂 express 变换 | [7 组 82 个表达式函数](mcp/expression-functions.md) |
| debug、发布或调用异常 | [故障定位](mcp/troubleshooting.md) |
不要在普通服务查询或已发布工具调用时预读全部参考。
## MUST DO ## MUST DO
1. 先用对应分组或叶子的 `--help` 确认当前参数。 1. 下列已知 leaf 直接执行,不先读分组 help。只有创建/更新等复杂字段或确认语义确有不确定性时,读取一次 `dws schema --cli-path "dev mcp <leaf>" --compact --format json`;Schema 与 Cobra 不一致时才读取同一 leaf 的一次 help。
2. 开发写操作先 `--dry-run --format json`,检查 invocation,再改为 `--yes`。 2. 开发写操作的最初请求只允许 `--dry-run --format json`。展示 invocation 中的准确对象、动作、业务参数和影响,等用户对该预览明确确认后,才把同一命令仅由 `--dry-run` 换成 `--yes`;目标或业务参数变化就重新预检并确认,确认前不得真实写入。
3. 发布前执行 `tool get` 和 `tool debug`,核对输入、输出与映射。 3. 发布前执行 `tool get` 和 `tool debug`,核对输入、输出与映射。
4. 调用已发布工具前先执行 `mcp published tools <mcpId>`,按返回的 `inputSchema` 构造 `--params`。 4. 调用已发布工具前先执行 `mcp published tools <mcpId>`,按返回的 `inputSchema` 构造 `--params`;真实 invoke 还会在本次调用中重新发现工具并按实时 Schema 校验。
5. `mcp published invoke` 无法静态判断远端工具副作用,真实调用一律需要 `--yes`;未明确影响时只做 dry-run。 5. `mcp published invoke` 无法静态判断远端工具副作用,真实调用一律需要 `--yes`;未明确影响时只做 dry-run。
6. MCP URL、凭证内容、token 和密钥不得写入回答、日志、文档或代码仓库。 6. MCP URL、凭证内容、token 和密钥不得写入回答、日志、文档或代码仓库。
7. 若真实命令返回 `endpoint_not_resolved`,表示当前 CLI 的 `mcpdev` 运行端点不可用,不是 flag 错误;立即报告并停止,不用 help、改参数、Raw API 或 dry-run 冒充业务完成。
## 服务 ## 服务
@@ -34,6 +49,8 @@ dws dev mcp service delete --mcp-id <mcpId> --dry-run --format json
## HTTP 工具 ## HTTP 工具
用户给 API 材料要求转换为 MCP 时,先按 [API 材料转工具](mcp/api-to-tool.md) 完成业务拆分和设计评审,再执行创建命令。任何 input/output mapping 写入前必须读取 [字段与映射规则](mcp/mapping-rules.md)。
创建和更新前先准备: 创建和更新前先准备:
- `http-info`:HTTP method、URL 与 auth。 - `http-info`:HTTP method、URL 与 auth。
@@ -65,14 +82,16 @@ HTTP `tool update` 是全量提交:先 `tool get` 读回现状,再在完整
```bash ```bash
dws dev mcp hsf method-list --interface-name <fully.qualified.Interface> --format json dws dev mcp hsf method-list --interface-name <fully.qualified.Interface> --format json
dws dev mcp tool create-hsf --help dws schema --cli-path "dev mcp tool create-hsf" --compact --format json
dws dev mcp tool update-hsf --help # 更新时同理,只在本次确需 update-hsf 且字段不确定时读取它的 compact leaf Schema
``` ```
HSF 的 `apiInputs/apiOutputs` 由服务端按方法 Schema 生成。映射 target 使用 `$.<DTO简名>.<字段>`;输出 source 使用 `$.node_service_activator.<字段>`,不带 HTTP 的 `.Body`。 HSF 的 `apiInputs/apiOutputs` 由服务端按方法 Schema 生成。映射 target 使用 `$.<DTO简名>.<字段>`;输出 source 使用 `$.node_service_activator.<字段>`,不带 HTTP 的 `.Body`。
## 鉴权与凭证 ## 鉴权与凭证
鉴权类型选择、静态 API key 和动态 token 模板见 [鉴权与凭证](mcp/auth-and-credentials.md)。
```bash ```bash
dws dev mcp auth get --mcp-id <mcpId> --format json dws dev mcp auth get --mcp-id <mcpId> --format json
dws dev mcp auth save --mcp-id <mcpId> --auth-type NO_AUTH --dry-run --format json dws dev mcp auth save --mcp-id <mcpId> --auth-type NO_AUTH --dry-run --format json
@@ -83,7 +102,7 @@ dws dev mcp credential bind --mcp-id <mcpId> --credential-id <credentialId> --dr
dws dev mcp credential unbind --mcp-id <mcpId> --dry-run --format json dws dev mcp credential unbind --mcp-id <mcpId> --dry-run --format json
``` ```
敏感内容优先用 `--content-file` 或 stdin,不要放进 shell history。`credential debug` 会真实访问下游;`tool debug` 必须明确二选一:`--credential-id` 或 `--no-credential`。 敏感内容优先用 `--content-file <path>`;需要 stdin 时精确写成 `--content-file -`,不要把 JSON 放进 shell history。`credential debug` 会真实访问下游;`tool debug` 必须明确二选一:`--credential-id` 或 `--no-credential`。
## 协作者 ## 协作者
@@ -95,12 +114,47 @@ dws dev mcp member remove --mcp-id <mcpId> --user-ids <staffId> --dry-run --form
## 调用已发布工具 ## 调用已发布工具
本 Skill 保持 `cli_version >=1.0.61`,但 stock 1.0.61 不足以证明已安装下面的新门禁。真实调用前先做具体能力检测,不按版本号猜测:
```bash
dws mcp published invoke --help
```
确认帮助中包含 `inputSchemaDigest (fresh_core_subset_snapshot)`。若没有,先运行 `dws upgrade` 升级到最新**稳定版**并重新检测。不要安装/建议 beta;能力仍缺失时停止真实调用,只可展示本地 dry-run。
```bash ```bash
dws mcp published tools <mcpId> --format json dws mcp published tools <mcpId> --format json
dws mcp published invoke <mcpId> <toolName> \ dws mcp published invoke <mcpId> <toolName> \
--params '{"query":"example"}' --dry-run --format json --params '{"query":"example"}' --dry-run --format json
``` ```
检查 dry-run 后,只有用户明确同意本次真实调用,调用方才可在执行时追加确认标志;不要把确认标志固化进模板、脚本或可复制示例。 展示 dry-run 的准确对象、参数和潜在影响后,只有用户对该预览明确同意本次真实调用,调用方才可在执行时把同一命令仅由 `--dry-run` 换成确认标志;最初请求不算该确认,参数变化需重新预检。不要把确认标志固化进模板、脚本或可复制示例。
`tools` 返回当前身份看到的实时工具列表。`invoke` 不接受动态命令别名,不根据工具名猜读写属性,也不持久化含凭据的 endpoint。 ### 精确时序
| 路径 | 本地解析/确认 | endpoint 解析 | 发现 | 调用 |
|---|---|---:|---|---|
| `mcp published tools` | 校验 mcpId | 1 次 | 在该 endpoint 拉取全部 `tools/list` 分页 | 不调用 |
| `invoke --dry-run` | 只解析并展示 mcpId、精确工具名和参数 | 0 | 0 | 0 |
| 未带确认的 invoke | 本地参数 JSON 校验后停在确认门 | 0 | 0 | 0 |
| 已确认 invoke | 确认完成后开始一个总操作 | 1 次 | 紧邻调用前在该 endpoint 拉取全部分页,精确且唯一匹配工具名,要求非空 `inputSchema` 并校验 | 在同一 endpoint 发出恰好 1 次 `tools/call` |
根 `--timeout` 是 `tools` 或已确认 invoke 的一个**总时限**,覆盖 endpoint 解析、鉴权/客户端构造、所有分页、Schema 校验和调用;它不是每页重新计时。短于或长于默认值的自定义时限同时配置上下文 deadline 和发布端 HTTP client timeout。
发现限制为最多 100 页、累计 20 MiB 完整 JSON-RPC 响应、单 cursor 64 KiB、累计 10000 个工具;重复 cursor 也会失败。发现和调用都拒绝 HTTP 重定向,避免跨 endpoint 校验/执行;`tools/list` 可按传输策略重试,`tools/call` 的幂等性未知,绝不自动重试。若调用发送后出现超时、断连或其他传输失败,服务端可能已经执行,结果必须标记为**未知**;先从业务侧核实,不能自动或盲目重放。
### Schema 快照与 TOCTOU
真实 invoke 拒绝不存在/同名重复的工具、缺失 `inputSchema` 的工具、不符合 `required/type/enum/properties/items/additionalProperties` 核心约束的参数,以及包含客户端不能完整解释之约束的 Schema。命令已声明统一 Result contract 并在兼容阶段影子校验,因此仍使用原有顶层对象而不切换统一 envelope。本安全迁移有意不再返回 endpoint,将 `inputSchemaValidation` 升级为新快照证据,并新增 digest;依赖旧字段集合的消费端需按此调整。
- `inputSchemaValidation=fresh_core_subset_snapshot`:本次参数通过了紧邻调用前新获取快照的受限核心子集校验。
- `inputSchemaDigest`:该快照确定性 JSON 编码的 SHA-256 十六进制摘要;它绑定收到的数字词法表示,因此 `1`、`1.0` 和 `1e0` 可产生不同摘要。
- `result`:无损保留的远端 `tools/call` result;不回显 endpoint。
这不是“调用时当前 Schema”的原子保证。即使 endpoint 只解析一次并复用,服务端仍可能在 `tools/list` 返回后、`tools/call` 处理前改 Schema。完整关闭该 TOCTOU 窗口需要服务端提供 Schema revision/etag,并允许 `tools/call` 携带 revision precondition;当前协议端没有该能力时,客户端不能宣称原子固定。
支持的 `$schema` URI 仅用于识别 JSON Schema **解析方言**,不表示接受该 draft 的完整词汇。允许集合比方言窄:当前执行上述核心约束,并只接受明确支持的 `$id`、`$anchor`、`$comment`、`title`、`description`、`default`、`examples`、`deprecated`、`readOnly`、`writeOnly` 元数据形状。其他 assertion 或 annotation,包括 `$ref`、组合、pattern、范围和未识别词汇,一律失败关闭且不发送 `tools/call`。
消费端不接受动态命令别名,不根据工具名猜读写属性,也不持久化含凭据的 endpoint。
当前内置调用面适用于可直接接受 JSON-RPC `tools/list`/`tools/call` 的服务,不宣称覆盖必须显式 initialize、SSE 或 `Mcp-Session-Id` 的严格会话型端点。异常处理见 [故障定位](mcp/troubleshooting.md)。
@@ -0,0 +1,91 @@
# 从 API 材料生成 MCP 工具
适用于用户提供 OpenAPI/Swagger、Postman Collection、curl 或接口文档,并要求“做成 MCP”“让 Agent 调用”或“把接口变成工具”。本文只覆盖 HTTP 工具;HSF 工具先执行 `dws dev mcp hsf method-list`,再使用 `tool create-hsf`。
## 开始前对齐
缺少以下任一项时先询问,不猜字段或鉴权:
1. API 材料:OpenAPI、Postman、curl 或完整接口文档至少一种。
2. 业务目标:Agent 要完成什么动作,用于决定工具拆分和描述。
3. 鉴权方式:无鉴权、Basic、静态 API key、动态 token 或自定义签名。
4. 调试资源:写接口必须由用户指定可安全操作的测试对象。
密钥不能进入工具定义。静态 API key、动态 token 和签名均使用服务级鉴权与凭证,见 [auth-and-credentials.md](auth-and-credentials.md)。
## 材料提取
| 材料 | 提取规则 |
|---|---|
| OpenAPI / Swagger | `paths.{path}.{method}` 形成候选动作;`operationId` 作为工具名素材;`parameters` 按 query/path/header 分组;`requestBody` 进入 body;成功响应 Schema 进入 `apiOutputs.body`;`servers[].url + path` 形成 URL |
| Postman Collection | `item[].request` 提取 method、URL、headers 和 body;item 名称形成标题;example/response 形成调试输入和 `apiOutputs` 素材 |
| curl | `-X` 提取 method;URL query 拆入 `apiInputs.query`;`-H` 拆入 headers;`-d`、`--data`、`--form` 拆入 body |
| 文档文本 | 逐接口提取;字段类型、必填性或响应层级不明确时询问用户 |
只读接口可先直接取一次样本,用真实响应反推 `apiOutputs`,但不得把响应中的 token、cookie、签名 URL 或个人数据写入文档。
## 三段式定义
HTTP `tool create/update` 使用三组相互对应的事实:
| 层 | 作用 |
|---|---|
| `apiInputs` / `apiOutputs` | 下游 HTTP 接口的真实字段树 |
| `toolInputs` / `toolOutputs` | Agent 可见的稳定字段树,可裁剪、改名并补充防呆描述 |
| `inputMappings` / `outputMappings` | 工具字段、系统上下文与 HTTP 字段之间的映射 |
字段节点使用 `{key,title,type,required,description,children}`。支持 `string/number/integer/boolean/object/array`;array 的 `children` 必须且只能有一个 `key=items` 节点。
`--api-inputs` 的每个位置是字段数组,不是字段对象:
```json
{
"query": [{"key":"name","type":"string","description":"城市名"}],
"body": [],
"path": [],
"headers": []
}
```
不要写成 `{"query":{"name":{...}}}`,CLI 会以“必须是字段数组”拒绝。
## Agent 侧加工
- 裁剪分页游标、固定控制位和内部参数;固定值使用 `fixed` 映射。
- 把不友好的 API 字段名改成稳定语义名,再用 `reference` 映射连接。
- 平台字段模型不承载 enum/default/example 时,把枚举、默认值和正反例写进 `description`。
- `corpId/userId` 等身份字段使用 `$.system_node.*`,不要暴露给 Agent 输入。
- 一个语义动作一个工具,不要机械地让一个 endpoint 永远等于一个工具。
- 有前后依赖时,在工具描述中说明前置工具以及后续参数的来源。
## 设计评审表
执行写命令前,先向用户展示每个工具的完整设计:
| 项 | 要求 |
|---|---|
| `name` | snake_case,包含明确动作词,最多 32 字符 |
| `title` | 简短中文标题 |
| `description` | 功能、参数来源、输出、适用场景和副作用 |
| `httpInfo` | method、URL、内联 NO_AUTH/BASIC 或服务级鉴权关系 |
| `apiInputs` / `toolInputs` | 真实字段树与 Agent 投影 |
| `inputMappings` | 每个工具输入、固定值和系统变量的去向 |
| `apiOutputs` / `toolOutputs` | 真实响应字段树与 Agent 投影;不精修时 `toolOutputs=[]` |
| `outputMappings` | 整体透传或字段级精修,二选一 |
| 调试输入 | 来自材料或用户提供的安全样本,不能用空 `{}` 走过场 |
映射写法见 [mapping-rules.md](mapping-rules.md)。
## 创建与验证闭环
1. `service create --dry-run`,展示预览并取得用户明确确认后执行同命令 `--yes`。
2. 先创建最简单的一个工具,随后 `tool get` 回读字段树和映射。
3. 首个工具结构正确后再创建其余工具,每个工具都回读。
4. 草稿调试必须使用 `tool get` 返回的 `versionId`;无鉴权传 `--no-credential`,有鉴权传 `--credential-id`。
5. `tool debug` 必须返回符合业务预期的真实数据,不能只看“未报错”。
6. HTTP `tool update` 是全量提交,必须先 `tool get`,在完整定义上修改后整包提交。
7. 所有工具调试通过后,列出待发布清单并取得发布确认,再逐个发布。
8. 发布后执行 `dws mcp published tools <mcpId>`,确认实时工具名和 `inputSchema`。
9. 使用 `published invoke ... --dry-run` 展示调用,用户明确确认后再真实调用。
任何写操作的目标或参数发生变化,都必须重新 dry-run 和确认。
@@ -0,0 +1,101 @@
# MCP 鉴权与凭证
鉴权配置是“如何取值和注入”的说明书,凭证账号保存真实密钥。二者必须分离,密钥不得放进工具定义、回答、日志、文档或代码仓库。
## 类型选择
| 下游要求 | `auth-type` | 配置方式 |
|---|---|---|
| 无鉴权 | `NO_AUTH` | 不保存密钥 |
| HTTP Basic | `BASIC` | `--basic-auth-config` |
| 静态 API key | `SIGNATURE` | 自定义 `authFields`,通过 `authQuery` 或 `authHeaders` 直引 |
| 动态 access token | `TOKEN` | 配置换 token 请求、注入位置、失效规则和刷新策略 |
| 自定义摘要或签名 | `SIGNATURE` | 自定义字段和表达式 |
不确定鉴权方式时先询问用户,不要默认 NO_AUTH。
## 安全执行顺序
带鉴权服务按以下顺序执行:
1. `auth save` 保存鉴权说明书。
2. `credential save --content-file` 保存实际凭证。
3. `credential debug` 验证下游鉴权。
4. `credential bind` 选择发布实例使用的凭证。
5. 创建并调试工具;工具 debug 仍须显式传 `--credential-id`。
6. 发布工具。
凭证应在发布前绑定。已发布后才绑定但实例仍停留在草稿态时,不要循环重试取址,按 [troubleshooting.md](troubleshooting.md) 核对实例状态。
所有写命令先 `--dry-run --format json`,展示脱敏预览并获得用户明确确认后,将同一命令仅改为 `--yes`。
## 静态 API Key
query 参数示例:
```json
{
"authFields": [
{"dataId":"apiKey","description":"API Key","type":"password","required":true}
],
"authQuery": [
{"key":"api_key","type":"authField","value":"#(\"apiKey\")"}
],
"testRequest": {"method":"GET","url":"https://api.example.com/health"}
}
```
```bash
dws dev mcp auth save --mcp-id <mcpId> --auth-type SIGNATURE \
--signature-auth-config '<上方JSON>' --dry-run --format json
```
API key 放 header 时使用 `authHeaders`,`key` 改为真实 header 名。凭证文件只含 dataId 对应值,例如 `{"apiKey":"<secret>"}`。
## 动态 Token
```json
{
"authFields": [
{"dataId":"appKey","type":"string","required":true},
{"dataId":"appSecret","type":"password","required":true}
],
"fetchTokenRequest": {
"method":"POST",
"url":"https://api.example.com/token",
"body":[
{"key":"app_key","type":"authField","value":"#(\"appKey\")"},
{"key":"app_secret","type":"authField","value":"#(\"appSecret\")"}
]
},
"authHeaders": [
{"key":"Authorization","type":"text","value":"$.Body.access_token"}
],
"tokenExpireRules": [
{"func":"EQ(${@(\"Body/errorCode\")},'TOKEN_EXPIRED')"}
],
"refreshToken": true,
"testRequest": {"method":"GET","url":"https://api.example.com/health"}
}
```
按真实下游协议选择 `authHeaders`、`authQuery` 或 `authBody`,不能照抄占位字段。换 token 响应引用使用 `$.Body.<字段>`;若下游值需要 `Bearer ` 前缀,按平台当前支持的表达式显式拼接并通过 credential debug 验证,不能假设 text 引用会自动补前缀。失效规则可基于 HTTP status 或业务错误码。
## 凭证命令
```bash
dws dev mcp credential list --mcp-id <mcpId> --format json
dws dev mcp credential save --mcp-id <mcpId> --name <账号名> \
--content-file <local-json-file> --dry-run --format json
dws dev mcp credential debug --mcp-id <mcpId> --credential-id <id> \
--dry-run --format json
dws dev mcp credential bind --mcp-id <mcpId> --credential-id <id> \
--dry-run --format json
dws dev mcp credential unbind --mcp-id <mcpId> --dry-run --format json
```
优先使用 `--content-file`;需要 stdin 时精确传 `--content-file -`。不要把真实 JSON 密钥放入 shell history。
`credential debug` 会真实访问下游。外层流程成功不等于鉴权通过,必须检查响应状态和业务错误。`credential bind` 只影响正式实例;`tool debug` 不使用绑定凭证,必须在本次调试显式指定 `--credential-id`,无鉴权工具显式指定 `--no-credential`。
删除已绑定凭证前先 unbind。任何删除均须回读对象、dry-run 并取得明确确认。
@@ -0,0 +1,153 @@
# MCP express 表达式函数
仅在 reference、fixed 和系统变量不能完成转换时使用本页。平台函数目录共 7 组 82 个函数。
表达式写入映射的 `expression`,不要写入 `source`:
```json
{
"type":"express",
"expression":"COALESCE(${@(\"node_start/$/name\")},'unknown')",
"displayText":"为空时使用 unknown",
"target":"$.Query.name"
}
```
变量引用:工具输入 `${@("node_start/$/<key>")}`,系统参数对象 `${@("system_node/$")}`。字符串字面量使用单引号。
## 序列化注意
- boolean 映射到 string 时是 `true/false`;double 运算结果可能带 `.0`,需要整数文本时组合 `INT` 或 `TEXT`。
- date 直接映射通常是毫秒时间戳;可读文本使用 `DATEFORMAT`。
- collection 直接映射可能只保留首项;映射到 string 前使用 `JOIN`。
- `deapRunId` 等会话参数在 tool debug 环境中可能为空。
- 平台推荐映射中出现但不在本目录的函数属于内部函数,不要凭空手写。
## 集合函数(10)
| 函数 | 参数 | 返回 | 作用 |
|---|---|---|---|
| `IN` | any, collection | boolean | 判断元素是否在集合中 |
| `INTER` | collection, collection... | collection | 集合交集 |
| `SUPPLE` | collection, collection... | collection | 集合补集 |
| `LIST` | any... | collection | 构造集合 |
| `UNION` | collection, collection... | collection | 集合并集 |
| `COALESCE` | any... | any | 返回首个非空值 |
| `LISTITEM` | collection, number | any | 按从 1 开始的下标取元素 |
| `SIZE` | collection | number | 集合大小 |
| `FOREACH` | collection, expression | collection | 对每项执行表达式,当前项用 `_` |
| `REPEAT` | number, any | collection | 重复元素构造集合 |
## 日期函数(13)
| 函数 | 参数 | 返回 | 作用 |
|---|---|---|---|
| `DATEDELTA` | date, number, unit? | date | 日期加减 |
| `DATEDIF` | date, date, unit? | number | 计算日期差;单位支持 y/M/d/h/m/s |
| `DATE` | string or number | date | 从日期文本或时间戳构造日期 |
| `DAY` | date | number | 日 |
| `HOUR` | date | number | 小时 |
| `MINUTE` | date | number | 分钟 |
| `MONTH` | date | number | 月 |
| `NETWORKDAYS` | date, date, holidays... | number | 计算工作日数量 |
| `NOW` | none | date | 当前时间 |
| `SECOND` | date | number | 秒 |
| `TIMESTAMP` | date | number | 转毫秒时间戳 |
| `YEAR` | date | number | 年 |
| `DATEFORMAT` | date, string | string | 按格式输出日期文本 |
## 逻辑函数(14)
| 函数 | 参数 | 返回 | 作用 |
|---|---|---|---|
| `AND` | boolean or collection, boolean... | boolean | 全部为真 |
| `EQ` | any, any | boolean | 相等 |
| `FALSE` | none | boolean | false |
| `GE` | number/date, number/date | boolean | 大于等于 |
| `GT` | number/date, number/date | boolean | 大于 |
| `IF` | boolean, any, any | any | 条件分支 |
| `ISEMPTY` | any | boolean | 判空;纯空格先 `TRIM` |
| `LE` | number/date, number/date | boolean | 小于等于 |
| `LT` | number/date, number/date | boolean | 小于 |
| `NE` | any, any | boolean | 不相等 |
| `NOT` | boolean | boolean | 逻辑取反 |
| `OR` | boolean or collection, boolean... | boolean | 任一为真 |
| `TRUE` | none | boolean | true |
| `XOR` | boolean, boolean | boolean | 异或 |
## 数学函数(15)
| 函数 | 参数 | 返回 | 作用 |
|---|---|---|---|
| `ABS` | number | number | 绝对值 |
| `AVERAGE` | number... | number | 平均值 |
| `CEILING` | number, significance | number | 向上舍入到指定基数倍数 |
| `FIXED` | number, digits | number | 向下保留指定位数 |
| `INT` | number | number | 向下取整 |
| `MAX` | number or collection, number... | number | 最大值 |
| `MIN` | number or collection, number... | number | 最小值 |
| `MOD` | number, divisor | number | 余数 |
| `PI` | none | number | 圆周率 |
| `POWER` | number, power | number | 幂 |
| `PRODUCT` | number... | number | 乘积 |
| `RAND` | none | number | `[0,1)` 随机数 |
| `ROUND` | number, digits | number | 四舍五入 |
| `SUM` | number or collection, number... | number | 求和 |
| `NUMRANGE` | number, start, end, mode? | boolean | 区间判断;mode 为 closed/open/leftOpen/rightOpen |
## 字符串函数(21)
| 函数 | 参数 | 返回 | 作用 |
|---|---|---|---|
| `CONCATENATE` | any... | string | 拼接 |
| `CONTAIN` | string, string | boolean | 包含判断 |
| `EXACT` | string, string | boolean | 区分大小写精确比较 |
| `LEFT` | string, number | string | 左侧字符 |
| `LEN` | string | number | 长度 |
| `LOWER` | string | string | 转小写 |
| `MID` | string, start, length | string | 从 1 开始截取中间字符 |
| `REPLACE` | string, start, count, string | string | 按位置替换 |
| `REPT` | string, number | string | 重复文本 |
| `RIGHT` | string, number | string | 右侧字符 |
| `SPLIT` | string, separator | collection | 分割文本 |
| `STARTWITH` | string, string | boolean | 前缀判断 |
| `TEXT` | any, format? | string | 转文本,可格式化日期 |
| `TRIM` | string | string | 去首尾空格 |
| `UPPER` | string | string | 转大写 |
| `VALUE` | string | number | 文本转数字 |
| `GETUUID` | none | string | 生成 UUID |
| `MD5` | string | string | MD5 小写十六进制摘要 |
| `JOIN` | collection, separator | string | 连接集合 |
| `TEXTREPLACE` | string, search, replacement | string | 按内容替换 |
| `UNESCAPEHTML` | string | string | 解码 HTML 实体 |
## JSON 函数(5)
| 函数 | 参数 | 返回 | 作用 |
|---|---|---|---|
| `JSONPARSE` | string | any | JSON 文本转对象 |
| `GET` | string, any | any | 按 key 取 JSON 值,适合特殊字符 key |
| `JACKSONJSONPATHEVAL` | any, string | any | 使用 JSONPath 取值 |
| `JSONTOSTRING` | any | string | JSON 对象序列化 |
| `SORTED_JSONSTRING` | any or string | string | 输出字段有序的 JSON 文本 |
## 系统函数(4)
| 函数 | 参数 | 返回 | 作用 |
|---|---|---|---|
| `USERID2UNIONID` | corpId, userId | string | userId 转 unionId |
| `UNIONID2USERID` | corpId, unionId | string | unionId 转 userId |
| `BATCHUSERID2UNIONID` | corpId, userId collection | collection | 批量 userId 转 unionId |
| `BATCHUNIONID2USERID` | corpId, unionId collection | collection | 批量 unionId 转 userId |
## 组合示例
```text
COALESCE(${@("node_start/$/nickname")},'anonymous')
DATEFORMAT(DATE(${@("node_start/$/timestamp")}), 'yyyy/MM/dd HH:mm:ss')
JOIN(SPLIT(${@("node_start/$/csv")},','),'-')
TEXT(INT(POWER(${@("node_start/$/base")},2)))
GET('operateUserId',${@("system_node/$")})
```
表达式配置后必须 `tool get` 回读,再用非空业务输入执行 `tool debug`。函数存在不代表下游目标类型会自动转换正确,最终以接口实际收到的值和 `toolOutput` 为准。
@@ -0,0 +1,137 @@
# MCP 字段与映射规则
本页是 `--input-mappings` 和 `--output-mappings` 的格式指南。HTTP 与 HSF 的路径不同,不能混用。
## 映射结构
```json
{"type":"reference","source":"$.node_start.city_name","target":"$.Query.name"}
```
| 类型 | 字段 | 用途 |
|---|---|---|
| `reference` | `source` + `target` | 引用工具输入、接口输出或系统变量 |
| `fixed` | `source` + `target` | 把固定常量写入接口参数,不暴露给 Agent |
| `express` | `expression` + `target`,可选 `displayText` | 对输入进行计算或格式转换 |
`express` 的表达式必须写在 `expression`,不能写在 `source`。CLI 会拒绝缺少 `expression` 的 express 映射,防止平台静默存成无效规则。
```json
{
"type":"express",
"expression":"GET('operateUserId',${@(\"system_node/$\")})",
"displayText":"读取当前调用用户",
"target":"$.Query.user_id"
}
```
复杂表达式才读取 [expression-functions.md](expression-functions.md)。普通 API 转换优先使用 reference、fixed 和系统变量。
## HTTP 入参路径
`apiInputs` 的分组键是小写 `query/body/path/headers`,但映射 target 的位置名必须是 Pascal:
| API 位置 | target 前缀 |
|---|---|
| body | `$.Body.` |
| query | `$.Query.` |
| headers | `$.Head.` |
| path | `$.Path.` |
`$.query.name` 和 `$.QUERY.name` 都会在平台侧静默失效;必须使用 `$.Query.name`。
```json
[
{"type":"reference","source":"$.node_start.city_name","target":"$.Query.name"},
{"type":"fixed","source":"zh","target":"$.Query.language"},
{"type":"reference","source":"$.system_node.ddDataCorpId","target":"$.Head.X-Corp-Id"}
]
```
`reference` 的 Agent 输入 source 使用 `$.node_start.<toolInput key>`。固定值的 source 直接写值,不加 `$.`。
## HTTP 出参路径
整体透传:
```json
[{"type":"reference","source":"$.node_service_activator.Body","target":"$"}]
```
整体透传仍必须在同批 `--api-outputs` 中如实声明 Body 字段。映射只会读取声明 Schema 内的值;漏声明会导致字段被裁剪或管理台显示“变量已失效”。
字段级精修:
```json
[
{
"type":"reference",
"source":"$.node_service_activator.Body.data.staff_id",
"target":"$.user.userId"
}
]
```
字段级 target 必须在同批 `toolOutputs` 字段树中存在。未映射字段会被裁剪。
## 数组
数组逐元素映射时 source 和 target 都使用 `[*]`,并确保两侧 array 字段都有唯一 `key=items` 子节点:
```json
{
"type":"reference",
"source":"$.node_service_activator.Body.items[*].staff_id",
"target":"$.members[*].userId"
}
```
平台配置页可能要求数组整体和元素级映射成对出现。创建后必须用 `tool get` 回读并通过 `tool debug` 验证实际形状。
## 系统上下文
常用安全系统变量:
| source | 含义 |
|---|---|
| `$.system_node.operateUserId` | 当前调用用户 userId |
| `$.system_node.ddDataCorpId` | 当前调用组织 corpId |
| `$.system_node.deapAgentCode` | Agent code |
| `$.system_node.deapAgentName` | Agent name |
| `$.system_node.deapRunId` | 当前运行 ID,debug 中可能为空 |
| `$.system_node.deapClientSessionId` | 当前会话 ID,debug 中可能为空 |
| `$.system_node.deapScenarioCode` | 场景 code |
| `$.system_node.deapParentAbilityCallSessionId` | 父能力调用会话 ID |
不要让 Agent 手工传 `corpId/userId` 冒充系统身份。服务配置鉴权后可能还提供 `$.system_node.AppKey/AppSecret`,这些值同样不得输出或写入仓库。
## HSF 差异
| 项 | HTTP | HSF |
|---|---|---|
| 入参 target | `$.Body/Query/Head/Path.<字段>` | `$.<DTO简名>.<字段>` |
| 出参 source | `$.node_service_activator.Body...` | `$.node_service_activator.result...`,具体根以方法 Schema 和读回结果为准 |
| 字段权威 | `apiInputs/apiOutputs` | `hsf method-list` 返回的 input/output Schema |
| 身份字段 | 按接口需要注入 | DTO 需要 corpId/userId 时必须显式注入 |
HSF 身份注入示例:
```json
[
{"type":"reference","source":"$.system_node.ddDataCorpId","target":"$.SearchRequest.corpId"},
{"type":"reference","source":"$.system_node.operateUserId","target":"$.SearchRequest.userId"}
]
```
HSF `update-hsf` 是部分更新,未传字段保持原值;HTTP `tool update` 是全量更新。`update-hsf --output-mappings '[]'` 会清空映射,不表示“不修改”。
## 发布前检查
1. 每条 reference/fixed 有非空 `source`,每条 express 有非空 `expression`。
2. HTTP target 使用 Pascal 位置名,并在 `apiInputs` 中可解析。
3. output source 在 `apiOutputs` 中声明到最深引用字段。
4. 字段级 output target 在 `toolOutputs` 中可解析。
5. `tool get` 回读的规则数量、类型和路径与设计一致。
6. `tool debug` 的 `toolOutput` 形状和业务值均符合预期。
映射问题常常不会在服务端报错,不能跳过 debug。
@@ -0,0 +1,57 @@
# MCP 故障定位
## 五步法
1. 对静态命令读取一次精确 leaf:`dws schema --cli-path "dev mcp <leaf>" --compact --format json`。
2. 仅当 Schema 与当前二进制表现冲突时,再读取同一 leaf 的 `--help`。
3. 写操作执行 `--dry-run --format json`,核对实际 helper、对象 ID 和参数。
4. 用 `service get`、`tool get`、`tool versions` 回读平台状态和草稿 `versionId`。
5. 发布后用 `dws mcp published tools <mcpId>` 做实时发现,再用 `published invoke ... --dry-run` 核对调用参数。
同一业务错误在参数、版本和平台状态都没有变化时不要重复调用。得到新证据并修正命令后最多重试一次。
## 常见错误
| 现象 | 判断与处理 |
|---|---|
| `endpoint_not_resolved` | 当前 CLI edition 未提供 `mcpdev` 管理端点,不是 flag 错误。停止真实管理调用并报告,不能用 dry-run 冒充完成 |
| `mcp_not_found` | 当前身份或组织下没有该 mcpId,或管理端点指向错误环境;重新查询 service list |
| MCP 数据初始化中 | 等待短时间后只重试一次 |
| `no_draft_to_publish` | 没有待发布草稿;先 tool get/versions 确认版本 |
| `business_error_invalid_params` | 常见于旧参数名;当前使用 `--tool-id` 和 `--http-info`,不要使用 actionId/http 旧键 |
| debug 成功但接口说缺参数或返回空 | 映射静默失效;检查 HTTP target 是否 Pascal、express 是否使用 `expression`、source/target 是否在字段树声明 |
| debug 通过但发布后仍是旧逻辑 | 调试时可能漏传草稿 versionId,实际命中了已发布版本 |
| tool output 丢失复杂字段 | `apiOutputs` 未声明到真实响应层级,或 output source 超出声明范围;按真实 debug 响应补全后全量 update |
| 管理台显示“变量已失效” | 映射 source/target 无法在同批 Schema 中解析;不要发布,修正字段树和映射 |
| publish 后取址成功但无 `mcpUrl` | 空成功不等于可用;回查服务、已发布工具数和凭证绑定时序 |
| published tools 为空 | 当前身份看不到工具,或服务没有已发布工具;不要尝试猜工具名调用 |
| published invoke 提示工具不存在 | 工具列表在本次调用前已刷新全部分页;重新执行 tools,使用当前返回的精确名称 |
| published invoke 提示 input schema validation failed | `--params` 缺 required 字段、类型/枚举错误,或包含被 `additionalProperties: false` 禁止的未声明字段;按本次发现的 `inputSchema` 快照修正 |
| published invoke 提示 unsupported JSON Schema keyword | 实时 Schema 含 oneOf/anyOf/allOf/$ref/pattern/范围等当前核心校验器不能完整执行的约束;为避免带着错误安全保证调用未知副作用工具,CLI 会失败关闭,不发送 `tools/call` |
| published invoke 调用阶段超时或断连 | `tools/call` 不自动重试,但请求可能已经到达服务端;把结果视为未知,先从业务侧核实,不能盲目重放 |
## HTTP 映射检查
- `apiInputs` 分组是小写数组,映射 target 是 `$.Body/Query/Head/Path`。
- reference/fixed 使用 `source`;express 使用 `expression`,不要放进 `source`。
- 整体输出透传仍需非空 `apiOutputs.body`。
- 字段级 output target 需在 `toolOutputs` 声明。
- array 需检查 `children.items` 和 `[*]` 路径。
完整规则见 [mapping-rules.md](mapping-rules.md)。
## HSF 检查
- 先用 `hsf method-list` 获取方法和 DTO 字段,不凭 Java 记忆猜字段。
- target 是 `$.<DTO简名>.<字段>`,不是 HTTP 位置路径。
- DTO 需要 corpId/userId 时显式注入系统变量。
- output source 不使用 HTTP 的 `.Body` 前缀。
- `update-hsf` 是部分更新;不要传空数组表达“不修改”。
## 发布后调用边界
`dws mcp published tools/invoke` 在运行期按当前 profile 和组织身份重新解析 endpoint,不缓存含凭据 URL,也不注册动态顶层 Cobra 命令。`tools` 和已确认 invoke 都在一个根 `--timeout` 总时限内完成 endpoint 解析及全部分页;dry-run 和未确认 invoke 不解析 endpoint、不发现工具。真实 invoke 在同一 endpoint 上刷新全部 `tools/list` 分页,精确且唯一匹配工具,使用本次 `inputSchema` 快照的 `required/type/enum/properties/items/additionalProperties` 核心约束校验 JSON 参数,再发出恰好一次、不自动重试的 `tools/call`。
成功结果中的 `inputSchemaDigest` 只绑定本次校验的快照。服务端仍可能在 `tools/list` 返回后、`tools/call` 处理前更新工具;没有服务端 revision/etag 与调用 precondition 时,客户端不能宣称快照和执行原子一致。支持的 `$schema` URI 也只标识解析方言,不代表支持该 draft 的全部词汇;未明确支持的 assertion 或 annotation 会失败关闭。
当前内置 published transport 面向可直接接受 JSON-RPC `tools/list`/`tools/call` 的端点。它不宣称覆盖必须显式 `initialize`、SSE 响应或 `Mcp-Session-Id` 的严格会话型服务;这类服务应使用 `dws mcp url get` 获得脱敏处理之外的本地连接配置,并交给完整 MCP 客户端,且不得传播 URL 中的凭据。
@@ -1,19 +1,21 @@
# 成员管理 # 应用成员管理
> 成员=谁能改这个应用(DEVELOPER 等角色);见 [devapp.md](../devapp.md) 概念地图。 应用成员表示谁能管理该应用,常用成员类型为 `DEVELOPER`。
`dws dev app member list/add/remove` 管理应用成员。参数用 `dws dev app member <method> --help` 查询。add/remove 需 `--user-ids` 列表 + `--member-type`,如 DEVELOPER;remove 也必须传 memberType,因为同一用户可能有多个成员身份。 “某开放平台应用的全部管理成员/按成员角色汇总”就是本域,不是聊天群成员:不要切到 `dws chat`、搜索同名群或用群角色替代应用角色。
## 发现命令 ```bash
dws dev app member list --unified-app-id <id> --format json
调用任何方法前先查清楚再敲: dws dev app member add --unified-app-id <id> --user-ids <staffId1,staffId2> --member-type DEVELOPER --dry-run --format json
dws dev app member remove --unified-app-id <id> --user-ids <staffId1,staffId2> --member-type DEVELOPER --dry-run --format json
```
# 浏览命令组下的子命令与 flag
dws dev app member --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
``` ```
按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 执行规则:
1. 应用名先用一次 `dws dev app list --name <名> --format json` 定位并复用 ID。
2. 只有姓名时,用 `dingtalk-aisearch` 一次解析到唯一 staffId/userId;重名时展示候选,不猜人。
3. 最初请求只允许执行 add/remove 的 dry-run。展示预检返回的应用 ID、动作、完整 userId 列表、成员类型及影响,等用户对该预览明确确认后,才把同一命令仅由 `--dry-run` 换成 `--yes`;目标或业务参数变化就重新预检并确认,确认前不得真实增删成员。remove 也必须带预检中的原成员类型。
4. 查询“全部”时以一次 `member list` 的 `data.members[]` 完整返回为准;按真实 `memberType` 分组计数并列出成员,字段缺失就标记“角色未返回”,不要猜。写后只回读一次,按目标人员是否存在判断结果。
5. 若后续还要删除应用,先完成并保存用户要求的成员名单与移除结果,再把删除放最后;最终说明该名单是删除前快照。
已知路径不调用 group help;仅 flag 确有疑问时查一次精确 leaf Schema,必要时再退回一次 leaf help。
@@ -1,56 +1,30 @@
# 权限管理 # 应用权限管理
> 权限点 scopeValue 是授权单元,一个权限点授权一组 OpenAPI;requiredApproval=true 的变更走版本通道生效(见 [devapp.md](../devapp.md) 生效模型)。 权限点以 `scopeValue` 为稳定标识。查询、申请和取消都走 `dws dev app permission`。
查询、申请、取消开放平台应用的 APP 应用权限和 SNS 个人权限。参数用对应命令的 `--help` 查询。 ## 最短命令路径
## 权限列表 ```bash
# 按名称/API/权限点定位;单权限可直接用 --scope-value
dws dev app permission list --unified-app-id <id> --keyword <关键词> --page-size 50 --format json
dws dev app permission list --unified-app-id <id> --scope-value <scopeValue> --format json
`--scope-value` 传入即进单权限详情模式;`--scope-type` 取 `APP`/`SNS`,留空返回两者;一个应用可能 150+ 权限点,游标分页续翻、`--page-size` 不超过 50。 dws dev app permission add --unified-app-id <id> --scope-values <scope1,scope2> --dry-run --format json
dws dev app permission remove --unified-app-id <id> --scope-values <scope1,scope2> --dry-run --format json
`--auth-status` 是查询过滤条件:
| authStatus | 含义 |
|------------|------|
| `ALL` | 不按授权状态过滤 |
| `AUTHED` | 只看已授权/已开通 |
| `UNAUTHED` | 只看未授权/未开通 |
单个权限项的状态看这几个字段:
- `authed`(布尔):是否已授权/已开通。true=已开通,不要重复申请。
- `allowedActions`(数组):本权限点当前允许的动作,如 `["view","detail","apply"]`。含 `apply` 才能申请,含 `remove` 才能取消。
- `authedStatusDesc`(中文文案):状态的中文说明,如"已开通"/"未开通",直接展示给用户。
- `apiStatus`:权限点本身的开放状态,如 `FULLY_OPEN`。
- `requiredApproval`(布尔):申请是否需审批。true 的变更走版本通道,审批在版本发布时处理。
- `displayMessage`(中文文案):服务端给的提示语,能否申请的原因看它。
list 默认同时返回 APP 和 SNS 权限;列表模式和 `--scope-value` 详情模式,权限项里的 API 信息字段都叫 `apiPreview`。`permission search` 是 `list` 的别名。
scopeValue 选择顺序:
1. 用户给了 `scopeValue`,精确匹配
2. 给了 API 名,用 `keyword` 搜,匹配 `apiPreview.name`
3. 给了权限名,匹配 `scopeName/scopeDesc`
4. 多个候选,展示列表让用户选,不自动取第一条
## 申请权限
`--scope-values` 传 `scopeValue`,多个逗号分隔,必须来自 `permission list` 返回。已开通跳过、不可编辑拒绝。`requiredApproval=true` 允许申请——写入版本变更,审批在版本发布时处理。不在此处选审批人。
## 取消权限
`--scope-values` 多个逗号分隔。返回:`removed`(布尔,整体成败)、`removedScopeValues`(成功取消的)、`rejectedScopeValues`(被拒的)、`message`。逐条看 `removedScopeValues`/`rejectedScopeValues` 判断每个权限点的结果。
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app permission --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
``` ```
按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 最初请求只允许执行 dry-run。展示预检返回的应用 ID、add/remove 动作、完整 `scopeValue` 列表及影响,等用户对该预览明确确认后,才把同一命令仅由 `--dry-run` 换成 `--yes`;目标或权限列表变化就重新预检并确认,确认前不得真实增删权限。写后只回读目标 `scopeValue`。
分页只有 `--cursor`:读取 `meta.pagination`,`endpoint_exhausted=false` 时把原样 `next_token` 传给下一次 `--cursor`,直到 true。CLI 不支持 `--page` 或 `--page-num`;权限记录在 `data.items[]`。
## 选择与状态
- 选择顺序:用户给的 `scopeValue` → API 名匹配 `apiPreview.name` → 权限名匹配 `scopeName/scopeDesc`。多候选时让用户选,不取第一条。
- `authed=true` 已开通,跳过重复 add;`allowedActions` 含 `apply/remove` 才能执行对应动作。
- `requiredApproval=true` 的变更会进入版本通道;权限写入成功后按 [`version.md`](./version.md) 创建并发布版本。
- remove 逐条核对 `removedScopeValues/rejectedScopeValues`,不要只看整体布尔值。
- 用户只说“去掉一个多余权限”却未给唯一标识时,不得为了猜目标拉取 150+ 全量:仅当本轮上下文已给出候选名称/scope 时做一次关键词定向查询,否则直接请求一个名称或 `scopeValue`。暂停 remove,但继续其它独立查询和可安全步骤;不要自行猜测并删除。
- 目标 add 已是 `authed=true` 时说明无需重复申请。没有任何权限写入成功时,不创建/发布版本;缺少 remove 选择也不能用发布空版本替代业务变更。
- 仅用户要求全部权限时才翻到 `meta.pagination.endpoint_exhausted=true`;普通关键词查询不要拉取 150+ 全量。最终给匹配数和相关项,避免整段 JSON。
已知路径不跑 group help;只有 flag 确有疑问时查一次精确 leaf compact Schema,Schema 不可用才查一次 leaf help。
@@ -1,58 +1,48 @@
# 端到端链路(recipes) # Dev 跨域任务闭环
dev 的端到端任务都是「定位应用,改容器某节点,按审批需要走版本生效,最后回读验证」。每步先 `--dry-run` 确认再 `--yes`,参数用对应命令的 `--help` 查询,细节进对应 reference。 仅当同一请求明确跨两个以上 Dev 域时读取本文件。先建立交付清单并复用一个 `unifiedAppId`;本页就是常见跨域任务的首轮执行契约,禁止先读 `app/robot/event/version/...`。只有真实响应出现本页未覆盖的特殊状态或字段时,才补读一份对应专题 Reference。
## 建一个钉钉里打开的网页应用 ## 通用顺序
1. `dev app create --name <名>` 建应用,拿 unifiedAppId ```text
2. `dev app webapp config` 配移动端/PC 首页(见 webapp.md) 定位/创建应用
3. `dev app version create` 建版本 → 读取用户要求的初始状态或名单
4. `dev app version check-approval` 预检是否需审批 → 配置成员/权限/安全/网页/机器人/事件
5. `dev app version publish` 发布(需审批时让用户选审批人) → 仅当用户明确要求或成功写结果要求发布时创建版本
6. 回读 `dev app version status` 到 `RELEASE` 才算生效 → 检查审批并发布(若需要)
→ 回读版本与用户要求的各项结果
→ 删除/停用等清理(若用户要求)
```
## 权限从申请到生效 句子里“办完后删除”即使出现在配置或发布之前,也按依赖关系把删除移到最后;这不是冲突,不要因此停止反问。只有两个最终状态确实无法同时成立且不能用“操作前快照 + 最终清理”满足时才澄清。
1. `dev app permission list` 选 `scopeValue`(选择顺序见 permission.md) ## 常用原子路径(首轮直接执行)
2. `dev app permission add --scope-values <值>` 申请
3. 若是 `requiredApproval` 的权限,走版本:`version create`,再 `check-approval`,再 `publish --approver-user-id <用户选的>`,最后 `version status`
4. 免审权限直接开通,不必发版本
## 做一个答疑机器人并接到本地调试 | 域 | 已知 leaf 与关键定位参数 |
|---|---|
| 应用 | `app list --name <name>`;详情/更新/删除复用 `--unified-app-id` |
| 成员 | `app member list/add/remove --unified-app-id <id>` |
| 权限 | `app permission list/add/remove --unified-app-id <id>` |
| 安全/网页 | `app security config --unified-app-id <id>`;`app webapp get/config --unified-app-id <id>`;安全配置无 get 命令 |
| 机器人/事件 | `app robot get/config/enable`;`app event list/subscribe/unsubscribe --unified-app-id <id>` |
| 版本 | `app version create/check-approval/publish/status --unified-app-id <id>`;后续命令复用 create 返回的 `versionId` |
| 本机连接器 | `dev connect list/status/restart`;list/status 另带 `--json`,不扫描系统进程 |
1. `dev app create --name <名>` 创建应用,拿明确 `unifiedAppId` 所有命令补 `dws dev` 前缀和 `--format json`;写操作先 `--dry-run`。最初请求不等于预检后的确认,只有展示准确对象、动作、业务参数和影响并取得用户对该预览的明确确认后,才可把同一命令仅由 `--dry-run` 换成 `--yes`。已知路径不要用 help 探路。
2. `dev app robot config --unified-app-id <unifiedAppId>` 配置机器人能力;需要启用时再 `robot enable`
3. 线上使用闭环:`version create` → `check-approval` → `publish` → `version status`;`SELECT_APPROVER` 时必须等用户选择审批人,不默认取第一个
4. 本地调试/值守:`dev connect --unified-app-id <unifiedAppId>` 把机器人接到本地 agent(见 connect.md);注意订阅事件前要先建联长连(见 event.md)
5. 若走无绑定的 `robot submit/result`,只有结果返回明确 `unifiedAppId` 才能继续版本发布
6. 完成态与缺 `unifiedAppId`、`SELECT_APPROVER` 等门禁判定见 [devapp.md](../devapp.md)「核心规则」:建联成功 + 版本进入 `RELEASE`/`AUDIT`/`UNDER_REVIEW` 才算完成
## 重启守护进程连接器(不存密钥) ## 常见闭环
守护进程被 stop / kill / 崩溃后,通过持久化的 `unifiedAppId` 重新拉取密钥并重启,无需本地保存 AppSecret。 - **网页应用**:`app create/list` → `webapp config` → 写结果要求发布时才 `version create` → `check-approval` → `publish` → `status` → `webapp get`。
- **权限治理**:按关键词 `permission list` → `add/remove` → 成功结果要求发布时才走版本闭环 → 回读目标权限。未指定“多余权限”时不拉全量猜目标,只暂停 remove,其它安全步骤继续完成。
- **机器人**:`app create/list` → `robot config/enable` → 版本闭环;用户另要本地调试时再 `connect`。建联和线上发布是两项独立状态。
- **事件订阅**:按关键词一次 `event list` → subscribe/unsubscribe 各至多一次正式执行 → 只有写成功且要求发布才走版本闭环 → 一次目标回读。
- **安全 + 网页**:先 `app get` 保存应用初始状态,并用 `webapp get` 保存网页配置。安全配置没有读取命令;需要保留旧安全项但没有上游可信的完整旧值时,说明整组覆盖风险并暂停 `security config`,继续不依赖它的网页步骤。用户提供完整目标列表或明确接受覆盖后,才执行 `security config` → `webapp config` → 一次版本闭环 → 回读可读取的应用/网页/版本结果。
- **成员临时变更**:解析唯一 userId → add → `member list` 保存名单 → remove → 回读;若还要删除应用,删除最后做。
1. `dev connect --daemon --unified-app-id <id> --channel <channel>` 首次启动(`unifiedAppId` 和 `channel` 会写入 `~/.dws/connect/<key>/daemon.pid`) ## 轮次与失败预算
2. `dev connect restart --unified-app-id <id>` 重启:自动 stop 旧进程 → 从 dev 平台拉取 AppKey/Secret → 重新建联
3. `dev connect status --unified-app-id <id>` 确认恢复 `healthy`
4. 若 daemon.pid 未持久化 `unifiedAppId`(如用 `--robot-client-id` 直接启动的),restart 会提示改用 `--unified-app-id` 启动
注意:密钥不落盘,每次 restart 动态从开发者平台获取;`daemon.pid` 只存 `unifiedAppId`、`channel`、`clientId`(公开值)。 - 每个写步骤:一次 dry-run,展示预检后等待明确确认;确认前不发出非 dry-run 写调用。正式命令的目标和全部业务参数必须与用户确认的预检完全一致,只允许把 `--dry-run` 换成 `--yes`;任何变化都重新 dry-run、展示并确认。一次必要回读即可。
- 相同业务错误不做无状态变化的重试;只在新的查询结果实际改变参数/状态后重试一次。发布错误按 [`version.md`](./version.md) 止损。
## 上传图片拿 mediaId(应用图标 / 机器人图标) - 某个破坏性选择缺失时,暂停该动作但继续所有不依赖它的步骤;最终分开写“已完成 / 等待选择 / 失败阻塞”,不要只留下一个反问。
- 大列表只保留总数、用户相关项和分页完整性;空列表写“暂无”。最终逐项核对交付清单,避免结果被截断。
应用图标、机器人图标都靠 mediaId 指定,但 dev 命令集不含上传——mediaId 要调钉钉 OpenAPI 拿到: - 清理对象只能使用本任务创建并返回的 ID;删除失败不得声称已清理。
1. 拿凭证:`dev app credentials get --unified-app-id <id>` 取 `appKey/appSecret`(secret 按敏感处理,不写进回答)
2. 换 access_token:`GET https://oapi.dingtalk.com/gettoken?appkey=<appKey>&appsecret=<appSecret>`,取返回的 `access_token`(有效期约 7200 秒、gettoken 有频控,缓存复用别每次上传都换)
3. 上传图片:`POST https://oapi.dingtalk.com/media/upload?access_token=<token>&type=image`,multipart 表单字段名 `media`,返回 `media_id`(形如 `@lA...`)。图标用方形图(如 256×256 的 jpg/png)
```
curl -F "media=@/path/to/img.png;type=image/png" \
"https://oapi.dingtalk.com/media/upload?access_token=<token>&type=image"
```
4. 用 mediaId 更新:机器人图标 `dev app robot config --unified-app-id <id> --icon-media-id <media_id>`;应用图标 `dev app update --unified-app-id <id> --icon-media-id <media_id>`。写操作先 `--dry-run` 再 `--yes`
5. 回读:`dev app robot get` 看 `iconMediaId` 变为新值(应用图标看 `app get` 的 `icon`)
## 查「为什么没生效 / 机器人搜不到 / 权限加了还报错」
先 `dev app version status`——改配置不等于生效,未发到 `RELEASE` 就不生效。
@@ -1,74 +1,34 @@
# 机器人能力 # 应用机器人
> 机器人是应用的能力扩展之一;建号/配置在此,接到本地 agent 调试用 `dws dev connect`(见 connect.md)。 分为两条互斥路径:已有应用用 `robot config/get/enable/disable`;新建独立智能体机器人用异步 `submit/result`。不要为已有应用误走建号。
为开放平台企业内部应用创建和配置机器人。参数用对应命令的 `--help` 查询。分两类场景: ## 已有应用配置
1. 新建智能体机器人:异步创建一个新的 Agent 应用 + 承载机器人(`submit` / `result`),当前不绑定已有开放平台应用。 ```bash
2. 现有应用配置机器人:在已存在的应用上配置/启用/停用机器人(`get` / `config`(upsert) / `enable` / `disable`),用 `--unified-app-id` 定位。 dws dev app robot get --unified-app-id <id> --format json
dws dev app robot config --unified-app-id <id> --name <机器人名> --desc <描述> --mode STREAM --dry-run --format json
> `corpId` / `userId` 由系统上下文自动注入,CLI 不传。所有写操作先 `--dry-run`,确认后再 `--yes`。 dws dev app robot enable --unified-app-id <id> --dry-run --format json
dws dev app robot disable --unified-app-id <id> --dry-run --format json
## 一、新建智能体机器人(异步建号)
`submit` 提交任务拿 `taskId`,`result --task-id <taskId>` 轮询。`submit` 返回 `taskId/status/expiresInSeconds/intervalSeconds/retryCount/bindsUnifiedApp`,提交成功通常是 `WAITING`,且 `bindsUnifiedApp=false` 表示异步建号任务不挂到现有应用。失败重试:把上次 `taskId` 通过 `--task-id` 传回 `submit`,避免重复创建。`result` 返回 `SUCCESS` 或 `APPROVAL_REQUIRED` 时可能带 `agentId/robotCode/clientId/clientSecret`;凭证可用于本地建联,但线上搜索、加群、路由消息必须等版本发布到 `RELEASE`。
异步任务状态:
| status | 含义 | 下一步 |
|--------|------|--------|
| `WAITING` | 创建中 | 按 `intervalSeconds` 轮询 `robot result` |
| `SUCCESS` | 创建完成 | 保存 `robotCode/clientId/clientSecret`,凭据按敏感处理;若结果含明确 `unifiedAppId` 才继续版本发布,否则要求用户提供 |
| `APPROVAL_REQUIRED` | 已建号但线上使用需审核 | 不要重复建号;若结果含明确 `unifiedAppId` 才提交版本发布审核,否则要求用户提供 |
| `FAIL` | 创建失败 | 读 `errorCode/errorMsg/failReason`;可带原 `taskId` 重新 `submit` |
| `EXPIRED` | `taskId` 不存在或过期 | 重新 `submit` |
`robot result` 的 JSON 会额外补 `lifecycle` 与 `nextSteps`,用于把链路闭环到版本发布:
- 顶层 `completionState=BLOCKED_BY_VERSION_PUBLISH`、`mustContinue=true`、`terminal=false` 是硬门禁;看到它就继续执行 blocking `nextSteps`,不能把后续 `dev connect` 当完成。
- 顶层 `completionState=BLOCKED_BY_MISSING_UNIFIED_APP_ID`、`actionRequired=provide_unified_app_id` 时,说明缺少可安全写版本的应用主键;必须要求用户提供明确的 `unifiedAppId`,不能用 `clientId/appKey` 自动反查后继续写版本。
- 后续顺序是 `create_version` → `check_approval` → `publish_version` → `wait_release`。所有写操作仍先 `--dry-run`,确认后再 `--yes`。
- `check-approval` 若返回 `approvalMode=SELECT_APPROVER`,展示候选审批人的 `name/userId/mainAdmin`,等待用户选择后再把该 `userId` 传给 `publish --approver-user-id`;不要默认取第一个。
- `connect_local` 的命令只用 `<clientSecret-from-result>` 占位,不能把真实 `clientSecret` 写进回答或脚本;它是 `optional=true` / `scope=local_debug_only`,不能抵消版本发布审核。
- `lifecycle.overallComplete=false` 或版本未进入 `RELEASE` / `AUDIT` / `UNDER_REVIEW` 时,不要总结“全部完成”“机器人已创建并成功连接”“可以在钉钉中 @机器人使用”。只能说“本地建联成功,线上发布/审批未完成”或继续执行阻塞步骤。
完成态门禁规则的完整说明见 [devapp.md](../devapp.md)「核心规则」。
## 二、现有应用的机器人配置
`robot get` 返回机器人基础信息、回调、模式、状态、技能列表;应用尚未配置机器人时返回空态 `robotStatus=UNCONFIGURED`,不是业务错误。
状态判断:
- `robotStatus=UNCONFIGURED`:应用未配置机器人,走 `robot config`。
- `robotStatus=OFFLINE`:配置存在但停用/下线,可走 `robot enable`。
- `robotStatus=ONLINE`:配置已启用;`robotCode` 可用于加群、机器人身份发消息或后续建联。
- `mode` 是字符串枚举:`HTTPS` / `STREAM` / `AISKILL`。
- `robot get` 正常返回是平铺字段(`configured`/`mode`/`robotStatus`/`robotCode`/`name`/`brief`/`desc`),没有 `success` 字段;拿到这组字段就是配置已落库,不是异步等待态。
- ONLINE 只代表能力已开启。要让机器人自动处理消息,还需配 `--outgoing-url`/`--event-callback-url`,或用 `dev connect` 接本地 Agent(见 connect.md)。
`config` 是 upsert:建或改都用它,不存在则建、存在则改,至少给一个配置字段。国际化字段(`--i18n-name` 等)传 JSON,如 `'{"en_US":"Bot"}'`。`enable` 是纯启用:只开启能力,不带配置字段(只传 `--unified-app-id`)。`config/enable/disable` 成功统一返回 `success/operation/unifiedAppId/robotCode/robotStatus/configured`;回读 `robot get` 看到 `robotStatus=ONLINE` 就别再误判"待生效"。
## 错误处理
| 情况 | 处理 |
|------|------|
| `robotStatus=UNCONFIGURED` | 应用未配置机器人,先用 `robot config` 创建 |
| 应用名重复 | `app-name` 企业内需唯一,换名 |
| `ServiceResult.success=false` | 透传 `errorCode/errorMsg` |
| 创建任务 `EXPIRED` | 任务过期,重新 `submit`(可带原 taskId) |
> 把机器人接到本地 agent 调试/值守见 [connect.md](connect.md)。
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app robot --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
``` ```
按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 `config` 是 upsert,至少传一个实际要改的字段;国际化字段传 JSON。最初请求只允许 dry-run;展示预检返回的应用/机器人、动作、业务参数和影响并取得用户对该预览的明确确认后,才把同一命令仅由 `--dry-run` 换成 `--yes`。参数变化就重新预检并确认,确认前不得真实配置、启停或提交。写后只回读一次 `robot get`:`UNCONFIGURED` 未配置,`OFFLINE` 已配置未启用,`ONLINE` 已启用。ONLINE 只代表机器人能力开启,不代表版本已发布或本地连接已建立。
按应用名称审计机器人时先 `dev app list --name` 取得唯一 `unifiedAppId`,随后直接 `dev app robot get`;不存在 `dev app search`,也不要切到 `dws chat bot` 猜命令。
## 异步建号
```bash
dws dev app robot submit --name <智能体名> --robot-name <机器人名> --desc <描述> --dry-run --format json
dws dev app robot result --task-id <submit返回的taskId> --format json
```
正式 submit 后按返回的 `intervalSeconds` 轮询同一个 taskId,直到 `SUCCESS`、`APPROVAL_REQUIRED`、失败或过期;不要重复创建新任务。只有结果返回明确 `unifiedAppId`,才能继续 get/enable、版本发布和最终删除。没有 ID 时报告建号终态及阻塞,禁止猜应用。
## 完成门禁
- 要求线上可用:配置/启用后继续 [`version.md`](./version.md),到 `RELEASE` 或明确审核态才算该阶段闭环。
- 要求本地调试:发布状态与 [`connect.md`](./connect.md) 的本地连接状态分别报告;connect 成功不代表发布完成。
- 要求最终删除:先保存机器人配置/状态和版本结果,再按 [`app.md`](./app.md) 最后删除。
- 相同业务错误无状态变化时不改名、不重提任务、不用 help/debug/verbose 循环;如实报告 `errorCode/errorMsg`。
已知路径直接执行;仅 flag 确有疑问时查一次精确 leaf compact Schema,Schema 不可用才查一次 leaf help。
@@ -1,21 +1,18 @@
# 安全配置 # 应用安全配置
> 安全配置=应用的 IP 白名单 / 登录重定向 / 端内免登 URL;见 [devapp.md](../devapp.md) 概念地图。 安全配置只有一个写入口;不存在 `dws dev app security get`,不要试探或调用它。
`dws dev app security config` 配 IP 白名单(`--ip-whitelist`)、登录重定向(`--redirect-urls`)、端内免登(`--sso-urls`)。参数用 `dws dev app security config --help` 查询;至少给一个配置字段。 ```bash
dws dev app security config --unified-app-id <id> \
覆盖语义:未提供的字段不动;显式提供的列表是整组覆盖(传入即全量替换该项,不是追加)——要保留旧值就把旧值一起带上。 --ip-whitelist <ip1,ip2> \
--redirect-urls <url1,url2> \
## 发现命令 --sso-urls <url1,url2> \
--dry-run --format json
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app security --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
``` ```
按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 - 至少提供一个配置字段;未提供的字段不变。
- 显式传入的列表是该字段整组覆盖,不是追加。若用户说“加上”且现值可从前序应用详情/配置结果获得,合并旧值后再提交;拿不到现值时说明覆盖风险,不编造旧值。
- 最初请求只允许 dry-run。展示预检返回的应用 ID、完整列表参数、整组覆盖影响并取得用户对该预览的明确确认后,才把同一命令仅由 `--dry-run` 换成 `--yes`;参数变化就重新预检并确认,确认前不得真实写入。以 config 的结构化成功结果为写入证据;需要说明应用总体状态时回读一次 `app get`,不要发明 security get。
- 需要配置生效时继续走 [`version.md`](./version.md)。
已知路径直接执行;仅 flag 不确定时查一次 `dev app security config` compact Schema,必要时一次 leaf help。
@@ -1,99 +1,42 @@
# 版本发布 # 应用版本创建与发布
> 版本是配置变更生效的唯一通道——改配置不等于上线,发布到 RELEASE 才生效(见 [devapp.md](../devapp.md) 生效模型)。 配置写入不等于上线;版本进入 `RELEASE` 才表示已发布生效。版本始终用 `unifiedAppId + versionId` 定位。
管理应用版本:基于当前配置建版本、查列表/详情、预检审批、发布、查状态。参数用对应命令的 `--help` 查询。版本用 `--unified-app-id` 定位,单个版本再加 `--version-id`;`corpId`/`userId` 系统注入,CLI 不传。 ## 标准闭环
## 典型流程 ```bash
dws dev app version list --unified-app-id <id> --page-size 50 --format json
```text dws dev app version create --unified-app-id <id> --desc <描述> --dry-run --format json
permission add(requiredApproval=true 写入版本变更) dws dev app version check-approval --unified-app-id <id> --version-id <create返回的versionId> --format json
→ version create 创建版本 dws dev app version publish --unified-app-id <id> --version-id <versionId> --dry-run --format json
→ version check-approval 预检是否需审批 / 审批人 dws dev app version status --unified-app-id <id> --version-id <versionId> --format json
→ version publish 发布(含高敏权限需 --confirmed-sensitive) dws dev app version get --unified-app-id <id> --version-id <versionId> --format json
→ version status 轮询发布/审批状态
``` ```
新应用如果 `version list` 返回空,先 `version create`,用返回的 `versionId` 继续 check-approval/publish;不要因列表空误判无可发布内容。 最初请求只允许执行 create/publish 的 dry-run,不能代替预检后的确认。每一步都先展示预检返回的应用 ID、动作、版本/审批参数及影响,等用户对该预览明确确认后,才把同一命令仅由 `--dry-run` 换成 `--yes`;目标或业务参数变化就重新预检并确认,确认前不得真实创建或发布。dry-run 不是业务完成。create 成功后只使用它返回的 `versionId`,不要再 list 猜“最新”;没返回 ID 就停止。默认不传 `--version`,服务端自动递增;用户明确指定时才传。
## 创建版本 `version list` 只有游标分页:`meta.pagination.endpoint_exhausted=false` 时把原样 `next_token` 传给下一次 `--cursor`,到 true 停止;不要使用 `--page/--page-num`,版本记录在 `data.items[]`。
默认不要传 `--version`,不传时服务端基于最新已发布版本自动递增。只有用户明确要指定时才填 `--version`,且必须大于最新 `RELEASE` 版本,否则服务端返回 `62018`(版本号需高于上个版本号)。 查询“发过哪些版本/全部版本历史”时翻到 endpoint exhaustion,再按真实状态汇总。查询“最新待发布版本”时只在完整列表的 `versionStatus=INIT` 中按 `gmtCreate` 选择最新一条;若没有 INIT,明确回答“暂无待发布版本”,不要对 RELEASE 版本调用 `check-approval`,也不要为了制造待发布项擅自 `version create`。
创建成功后,后续 `get`/`check-approval`/`publish` 必须用 `create` 返回的 `versionId`;不要通过 `list` 猜最新版本。如果创建没返回 `versionId`,停止并报错。 ## 审批决策
## check-approval 与 publish - `requiresApproval=false && publishable=true`:可以正式 publish。
- `approvalMode=SELECT_APPROVER`:原样展示 `approvalPromptText` 或 `approvalOptions[].label`,等待用户选人;不默认第一个。
- `approvalMode=ENTERPRISE_SELF_BUILT`:不传 approver,publish 后进入企业审批。
- 高敏权限错误明确要求确认时,只改变一次参数,加 `--confirmed-sensitive` 后重试;这属于有新证据的单次修复。
`check-approval` 只查审批要求和候选审批人,不发布。`publish` 是真实发布;含高敏权限要加 `--confirmed-sensitive`,灰度选人模式用 `--approver-user-id` 指定审批人。 ## 完成态与止损
区分两个"预检":`--dry-run` 是 CLI 层的预览不调上游;`check-approval` 是服务端查审批要求不发布。发布前建议先 `check-approval`。 | `versionStatus` | 结论 |
|---|---|
| `INIT` | 已创建但未发布,仍未生效 |
| `AUDIT` | 已提交审核;报告待审批,不重复 publish |
| `RELEASE` | 已发布生效 |
| `GRAY` | 灰度中,不等于全量发布 |
发布/预检不返回动作枚举 `result`,看结构化字段判断下一步: `processStatus=UNDER_REVIEW` 同样是等待外部审批;`FAIL/WITHDRAW/CANCEL/PUBLISH_FAILED` 原样报告真实状态与错误。未列出的状态不猜。
| 字段 | 含义 | 下一步 | 发布失败时最多做一次 `version get/status` 获取新状态;若错误含义仍不清楚,可用错误码做一次精确 `dws devdoc article search --query`。只有新证据改变 flag 或状态时才重试一次;同一 `errorCode` 下禁止追加 debug/verbose、重复 help、反复搜索或重复 publish。无法达到 `RELEASE/AUDIT` 时明确报告阻塞和已完成步骤,仍需回答用户要求的版本列表、应用状态等其它交付项。没有实际配置变更成功时不创建空版本;`version list` 为空时明确写“暂无版本”。
|------|------|--------|
| `requiresApproval=false` + `publishable=true` | 不需审批,`check-approval` 通过 | 可以执行 `version publish` |
| `requiresApproval=true` + `approvalMode=SELECT_APPROVER` | 需从候选人里选审批人 | 展示 `approvalOptions[].label`,让用户选后再 `publish --approver-user-id` |
| `requiresApproval=true` + `approvalMode=ENTERPRISE_SELF_BUILT` | 企业自建审核 | 不传 `--approver-user-id`,直接 `publish` 提交审批 |
| `published=true` | 本次 `publish` 已直接发布 | 回读 `version status/get` 验证 `versionStatus=RELEASE` |
| `approvalSubmitted=true` | 本次 `publish` 已提交审批 | 保存 `processId`,轮询 `version status` |
`SELECT_APPROVER` 时 CLI 会把原始 `approvalCandidates` 增强为更容易展示的字段: 已知路径不调用 group help;仅 flag 确有疑问时查一次精确 leaf compact Schema,Schema 不可用才查一次 leaf help。
- `approvalPromptText`:预渲染的成品选择文案(带 `A.`/`B.` 序号 + `姓名(userId: xxx)`);agent 原样展示这一段即可,无需自己遍历结构。
- `approvalOptions[]`:结构化选项数组,字段包括 `label/name/userId/mainAdmin/index/key`,供需要结构化数据时使用。
- `completionState=WAITING_FOR_APPROVER_SELECTION`、`actionRequired=select_approver`、`mustAskUser=true`:必须等待用户选择,不能默认取第一个。
展示审批人时,优先原样展示 `approvalPromptText`;需结构化时用 `approvalOptions[].label`(格式 `姓名(userId: xxx)`,`mainAdmin=true` 时标注“主管理员”,仅 `name` 为空才退回 `userId: xxx`)。发布时把用户选中的 `userId` 传给 `--approver-user-id`。
审批模式 `approvalMode`:
| approvalMode | 含义 | 下一步 |
|--------------|------|--------|
| `SELECT_APPROVER` | 灰度选人,需在候选审批人里选一个 | 展示候选,不自动取第一个 |
| `ENTERPRISE_SELF_BUILT` | 企业自建审核 | 不传 `--approver-user-id`,按企业自建流程等待 |
## 版本状态
`version create/list/get/status` 统一返回 `versionStatus`:
| versionStatus | 含义 | 下一步 |
|---------------|------|--------|
| `INIT` | 已创建或有待发布变更,未发布 | 可 `check-approval`/`publish` |
| `AUDIT` | 发布审核中 | 不要重复发布;即使没返回 `processStatus` 也按审核中处理 |
| `RELEASE` | 已发布生效 | 完成,可验证权限/机器人/网页等能力 |
| `GRAY` | 灰度 | 按灰度流程,不要当全量已发布 |
`version status` 的 `processStatus` 只在存在审批流程且后端透出时有值。`versionStatus=AUDIT` 但没 `processStatus/processInstanceId` 时不要判失败,仍是审核中。
| processStatus | 含义 | 下一步 |
|---------------|------|--------|
| `UNDER_REVIEW` | 审批中 | 等待,必要时把 `processInstanceId` 给用户去钉钉客户端看 |
| `PASS` | 审批通过 | 继续回读,确认是否进 `RELEASE` |
| `FAIL` | 审批拒绝 | 展示 `processComment`,改后重新建/发版本 |
| `WITHDRAW` / `CANCEL` | 撤回或取消 | 回到发布前,重新 `check-approval`/`publish` |
| `PUBLISH_FAILED` | 审批后发布失败 | 展示错误,重查版本状态和后端错误 |
遇到未列出的状态值,不要猜语义;原样展示,回读 `version get/status` 或查文档/后台。
## 错误处理
| 情况 | 处理 |
|------|------|
| `check-approval` 提示需审批 | 按返回选审批人,再 `publish --approver-user-id` |
| 发布报高敏权限未确认 | 加 `--confirmed-sensitive` 重新发布 |
| `ServiceResult.success=false` | 透传 `errorCode/errorMsg` |
## 发现命令
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app version --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
```
按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。
@@ -1,22 +1,18 @@
# 网页应用配置 # 网页应用配置
> 网页应用=应用的能力扩展之一,钉钉内打开的 H5;见 [devapp.md](../devapp.md) 概念地图。 ```bash
dws dev app webapp get --unified-app-id <id> --format json
`dws dev app webapp get` 查配置,`webapp config` 配移动端/PC 首页和管理后台地址。参数用 `dws dev app webapp get --help` 和 `dws dev app webapp config --help` 查询;config 至少给一个配置字段。 dws dev app webapp config --unified-app-id <id> \
--homepage-url <移动端主页> \
- 未配置网页应用前,`get` 返回空对象 `{}`。拿到 `{}` 就是还没配过,走 `webapp config` 首次配置。 --pc-homepage-url <PC端主页> \
- `h5PageType` 未显式传入时不要假设固定默认值;配置后以 `webapp get` 回读为准。 --omp-url <管理后台地址> \
--h5-page-type <类型> \
## 发现命令 --dry-run --format json
调用任何方法前先查清楚再敲:
```
# 浏览命令组下的子命令与 flag
dws dev app webapp --help
# 查某方法的必填参数、类型、默认值
dws dev <command-path> --help
``` ```
按 `--help` 输出构造 flag;不要凭旧 schema 名称猜参数。 - `config` 至少传一个需要修改的字段,未要求的字段不要猜。
- 以 `get` 实际返回的 `data.configured` 和 URL 字段为准;`configured=false` 或 URL 缺失表示尚未配置,不是命令错误。不要依赖旧版“空对象 `{}`”判断。
- 最初请求只允许 dry-run。展示预检返回的应用 ID、完整 URL/页面类型参数及影响并取得用户对该预览的明确确认后,才把同一命令仅由 `--dry-run` 换成 `--yes`;参数变化就重新预检并确认,确认前不得真实配置。随后只回读一次 `webapp get`,以实际 URL 与 `h5PageType` 为准。
- 需要上线生效时继续走 [`version.md`](./version.md)。
已知路径不调用 group help;仅 flag 确有疑问时查一次精确 leaf compact Schema,Schema 不可用才查一次 leaf help。
@@ -20,7 +20,7 @@
<!-- VISIBLE_SHORTCUTS_START --> <!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先) ## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "devapp +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws devapp <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service devapp --format json` 批量发现。 以下 shortcut 同时进入公开 catalog 与 Runtime Schema。按本 skill/recipe 路由,命中时 Shortcut 优先于原子命令。参数只查 `dws schema --cli-path "devapp +<shortcut>" --compact --jq '{cli_path,parameters,constraints,confirmation}' -f json`;仅需且已发布 `result` 时查 `--jq '{cli_path,outcomes:.result.outcomes,pagination}'`,字段级再查 `data_schema`;缺失不以 Help/样例推断。Schema 不可用才读一次已知 leaf Help;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 禁 Help:错误 suggestion → 已加载 Skill/reference 明确入口;均无则报漂移。禁全 Catalog/root/parent/product Help;仅映射、接口或 provenance 审计省略 `--compact`。现有路由和 reference 均无法定位低频能力时,才用 `dws shortcut list --service devapp --format json` 发现。
| Shortcut | 风险 | 适用场景 | | Shortcut | 风险 | 适用场景 |
|---|---|---| |---|---|---|
@@ -176,7 +176,7 @@ dws ding message recall --id <OPEN_DING_ID> --format json
<!-- VISIBLE_SHORTCUTS_START --> <!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先) ## Shortcuts(无专用脚本/recipe 时优先)
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "ding +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws ding <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service ding --format json` 批量发现。 以下 shortcut 同时进入公开 catalog 与 Runtime Schema。按本 skill/recipe 路由,命中时 Shortcut 优先于原子命令。参数只查 `dws schema --cli-path "ding +<shortcut>" --compact --jq '{cli_path,parameters,constraints,confirmation}' -f json`;仅需且已发布 `result` 时查 `--jq '{cli_path,outcomes:.result.outcomes,pagination}'`,字段级再查 `data_schema`;缺失不以 Help/样例推断。Schema 不可用才读一次已知 leaf Help;`unknown flag` 用同 leaf Help 修正一次。`unknown command` 禁 Help:错误 suggestion → 已加载 Skill/reference 明确入口;均无则报漂移。禁全 Catalog/root/parent/product Help;仅映射、接口或 provenance 审计省略 `--compact`。现有路由和 reference 均无法定位低频能力时,才用 `dws shortcut list --service ding --format json` 发现。
| Shortcut | 风险 | 适用场景 | | Shortcut | 风险 | 适用场景 |
|---|---|---| |---|---|---|
@@ -17,6 +17,8 @@ Hrbrain 是钉钉组织大脑,提供人才池管理、员工档案查询、人
| `talent-pool list` | 查询人才池列表 | - | 可选 `--keyword`、`--pool-type`、`--creator`、`--labels`(逗号分隔)、`--page`、`--page-size` | | `talent-pool list` | 查询人才池列表 | - | 可选 `--keyword`、`--pool-type`、`--creator`、`--labels`(逗号分隔)、`--page`、`--page-size` |
| `talent-pool detail` | 获取人才池详情 | `--pool-code` | - | | `talent-pool detail` | 获取人才池详情 | `--pool-code` | - |
| `talent-pool employees` | 查询人才池内人员列表 | `--pool-code` | 可选 `--page`、`--page-size` | | `talent-pool employees` | 查询人才池内人员列表 | `--pool-code` | 可选 `--page`、`--page-size` |
| `talent-pool save` | 创建或更新人才池 | `--pool-name` | 写操作,需 `--yes`。无 `--pool-code` 新建,有 `--pool-code` 更新;可选 `--pool-desc`、`--rule-json`(JSON 对象)、`--pool-tags`(JSON 数组) |
| `talent-pool move-members` | 人才池人员出入池 | `--pool-code` `--opt-type` `--staff-ids` | 写操作,需 `--yes`。`--opt-type`=`ENTERING`/`LEAVING`;`--staff-ids` 逗号分隔;可选 `--remark` |
### profile (员工档案管理) ### profile (员工档案管理)
@@ -39,6 +41,7 @@ Hrbrain 是钉钉组织大脑,提供人才池管理、员工档案查询、人
## 意图判断 ## 意图判断
- "人才池/储备干部池" → `talent-pool list/detail/employees` - "人才池/储备干部池" → `talent-pool list/detail/employees`
- "新建/修改人才池" → `talent-pool save`(写,需 `--yes`);"人员入池/出池" → `talent-pool move-members`(写,需 `--yes`)
- "员工档案/档案数据" → `profile metadata/query` - "员工档案/档案数据" → `profile metadata/query`
- "员工标签" → `profile labels` - "员工标签" → `profile labels`
- "职业历程/内部履历" → `profile career` - "职业历程/内部履历" → `profile career`
@@ -51,6 +54,8 @@ Hrbrain 是钉钉组织大脑,提供人才池管理、员工档案查询、人
dws hrbrain talent-pool list --page 1 --page-size 20 --format json dws hrbrain talent-pool list --page 1 --page-size 20 --format json
dws hrbrain talent-pool detail --pool-code POOL_CODE --format json dws hrbrain talent-pool detail --pool-code POOL_CODE --format json
dws hrbrain talent-pool employees --pool-code POOL_CODE --format json dws hrbrain talent-pool employees --pool-code POOL_CODE --format json
dws hrbrain talent-pool save --pool-name "储备干部池" --yes --format json
dws hrbrain talent-pool move-members --pool-code POOL_CODE --opt-type ENTERING --staff-ids WORK_NO1,WORK_NO2 --yes --format json
dws hrbrain profile metadata --work-no WORK_NO --format json dws hrbrain profile metadata --work-no WORK_NO --format json
dws hrbrain profile query --work-no WORK_NO --data-queries '[{"modelCode":"basic","fields":["name","dept"]}]' --format json dws hrbrain profile query --work-no WORK_NO --data-queries '[{"modelCode":"basic","fields":["name","dept"]}]' --format json
dws hrbrain profile labels --staff-ids WORK_NO1,WORK_NO2 --format json dws hrbrain profile labels --staff-ids WORK_NO1,WORK_NO2 --format json
@@ -64,4 +69,5 @@ dws hrbrain search employees-structured --origin-json '{"rules":[{"field":"name"
## 安全规则 ## 安全规则
- `--data-queries`、`--fields`、`--origin-json` 必须是合法 JSON;`--staff-ids`、`--labels`、`--order-by` 是逗号分隔字符串。 - `--data-queries`、`--fields`、`--origin-json` 必须是合法 JSON;`--staff-ids`、`--labels`、`--order-by` 是逗号分隔字符串。
- `talent-pool save` / `talent-pool move-members` 为写操作,`confirmation=user_required`,非交互环境须在用户明确授权后追加 `--yes`;`save --rule-json` 为 JSON 对象字符串,`save --pool-tags` 为非空 JSON 数组,`move-members --opt-type` 仅 `ENTERING`/`LEAVING`。
- `talent-pool list` 需要账号单独开通人才池查看权限(`errorCode=2002` 时提示用户联系管理员开通)。 - `talent-pool list` 需要账号单独开通人才池查看权限(`errorCode=2002` 时提示用户联系管理员开通)。
Loaded 100 of 185 files, more files were not shown because too many files have changed in this diff. Show more