diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e54347b3..f024c98a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -92,10 +92,18 @@ jobs:
node-version: "20"
registry-url: "https://registry.npmjs.org"
- - name: Publish to npm
+ - name: Publish stable to npm
# 只有官方仓库发 npm;fork(dev 预览)没有 NPM_TOKEN,跳过以免红叉
- if: ${{ github.repository_owner == 'DingTalk-Real-AI' }}
+ if: ${{ github.repository_owner == 'DingTalk-Real-AI' && !contains(github.ref_name, '-') }}
working-directory: dist/npm/dingtalk-workspace-cli
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+
+ - name: Publish prerelease to npm beta
+ # 预发布版本不能更新 npm latest,避免普通 npm 安装链路拿到 beta。
+ if: ${{ github.repository_owner == 'DingTalk-Real-AI' && contains(github.ref_name, '-') }}
+ working-directory: dist/npm/dingtalk-workspace-cli
+ run: npm publish --access public --tag beta
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7aab1e93..016cf466 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,16 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th
## [Unreleased]
+### Delivery Beta
+
+- **Remove-discovery delivery seal** — pre-release validation line for the static-endpoint delivery branch. It removes dynamic service discovery, keeps legacy CLI compatibility aliases, syncs the open command/help/skill surface with the dws-wukong baseline, and includes the `dev connect` default-yolo behavior plus the beta upgrade track.
+- **Official beta packages stay out of normal `dws upgrade`** — publish the sealed beta as an upstream GitHub pre-release after merge, using a low pre-release tag such as `v0.0.48-beta.1`. Old clients' plain `dws upgrade` / `dws upgrade --check` follows GitHub `releases/latest`, so it does not install pre-releases. New clients' plain `dws upgrade` stays on the stable track, and the low numeric version prevents the sealed beta from being treated as newer than `v1.0.47`. Testers install this sealed beta only by specifying the exact tag.
+- **Pinned curl install for the official beta package after the upstream pre-release exists**:
+
+ ```sh
+ curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.sh | DWS_VERSION=v0.0.48-beta.1 sh
+ ```
+
## [1.0.47] - 2026-07-05
This release adds **connector supervision & health monitoring** (`dev connect list/status/restart/stop`) and fixes **bot-to-bot @-mention** delivery end-to-end.
diff --git a/README.md b/README.md
index 1d40d0ca..f644f2d0 100644
--- a/README.md
+++ b/README.md
@@ -109,6 +109,10 @@ go build -o dws ./cmd # build to current directory
cp dws ~/.local/bin/ # install to PATH
```
+Static endpoint data is generated from the Wukong baseline and committed in this
+repository under `internal/syncdata`, so source builds do not require a sibling
+data checkout.
+
> Requires Go 1.25+. Use `make package` to cross-compile for all platforms (macOS / Linux / Windows x amd64 / arm64).
@@ -152,12 +156,18 @@ dws has built-in self-upgrade capability. Updates are pulled directly from [GitH
```bash
dws upgrade # interactive upgrade to latest version
dws upgrade --check # check for new versions without installing
-dws upgrade --list # list all available versions
+dws upgrade --list # list stable release versions
+dws upgrade --beta # upgrade to the latest beta pre-release
+dws upgrade --check --beta # check the beta track without installing
+dws upgrade --list --beta # list beta pre-release versions
dws upgrade --version v1.0.7 # upgrade to a specific version
+dws upgrade --version v1.0.8-beta.1 # upgrade to a specific beta version
dws upgrade --rollback # rollback to the previous version
dws upgrade -y # skip confirmation prompt
```
+By default, `dws upgrade` follows the stable release track. Use `--beta` only when you explicitly want the newest GitHub pre-release build.
+
How it works
@@ -171,8 +181,9 @@ A backup of the current version is automatically created before each upgrade. Us
| Flag | Description |
|------|-------------|
| `--check` | Check for updates without installing |
-| `--list` | List all available versions with changelogs |
-| `--version` | Upgrade to a specific version (e.g. `v1.0.7`) |
+| `--list` | List available stable release versions with changelogs |
+| `--beta` | Use the beta pre-release track for `upgrade`, `--check`, or `--list` |
+| `--version` | Upgrade to a specific version (e.g. `v1.0.7` or `v1.0.8-beta.1`) |
| `--rollback` | Rollback to the previous backed-up version |
| `--force` | Force reinstall even if already on the latest version |
| `--skip-skills` | Skip skill package update |
@@ -300,21 +311,18 @@ dws contact user search --query "engineering" --dry-run
dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserName, dept: .depts[0].deptName, userId}'
```
-### Schema Discovery
+### Command Help and Schema
-Agents don't need pre-built knowledge of every command. Use `dws schema` to dynamically discover capabilities:
+Product commands are compiled into the binary in static endpoint mode. Use `--help` and the bundled Agent Skills as the source of truth; `dws schema` is retained for helper-only schemas such as `dev.*`.
```bash
-# Step 1: Discover all available products
-dws schema --jq '.products[] | {id, tool_count: (.tools | length)}'
+# Inspect the current compiled command surface
+dws aitable record query --help
-# Step 2: Inspect target tool's parameter schema
-dws schema aitable.query_records --jq '.tool.parameters'
+# Helper-only schema introspection
+dws schema "dev app create"
-# Optional: inspect DingTalk authorization metadata for PAT planning
-dws schema aitable.query_records --jq '.tool.auth'
-
-# Step 3: Construct the correct call
+# Construct the call
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --limit 10
```
@@ -479,7 +487,7 @@ dws aitable record query --base-id BASE_ID --tabel-id TABLE_ID # --tabel-i
```bash
# Built-in jq expressions
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --jq '.invocation.params'
-dws schema --jq '.products[] | {id, tools: (.tools | length)}'
+dws schema "dev app create" --jq '.tool.required'
# Return only specific fields
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocation,response
@@ -488,14 +496,12 @@ dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocati
-Schema Introspection — query parameter schemas before making calls
+Schema Introspection — helper-only schemas in static endpoint mode
```bash
-dws schema # list all products and tools
-dws schema aitable.query_records # view parameter schema
-dws schema aitable.query_records --jq '.tool.required' # view required fields
-dws schema aitable.query_records --jq '.tool.auth' # view authorization metadata
-dws schema --jq '.products[].id' # extract all product IDs
+dws schema # static endpoint mode note
+dws schema "dev app create" # view helper-only schema
+dws schema "dev app create" --jq '.tool.required' # view required fields
```
@@ -626,7 +632,7 @@ See [`docs/robot-quickstart.md`](./docs/robot-quickstart.md) for the full 4-step
- [Command Index](./docs/command-index.md) — every runtime command with description and when-to-use guidance
- [Reference](./docs/reference.md) — environment variables, exit codes, output formats, shell completion
-- [Architecture](./docs/architecture.md) — discovery-driven pipeline, IR, transport layer
+- [Architecture](./docs/architecture.md) — static endpoint pipeline, command surface, transport layer
- [Open Platform App Command Routing](./docs/dev-yulan-command-routing.md) — yulan dev app command design, MCP overlay, permission flow, and Agent routing
- [Changelog](./CHANGELOG.md) — release history and migration notes
diff --git a/README_zh.md b/README_zh.md
index 7238988e..72f1ea22 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -110,6 +110,7 @@ cp dws ~/.local/bin/ # 安装到 PATH
```
> 需要 Go 1.25+。也可以用 `make package` 构建所有平台产物(macOS / Linux / Windows × amd64 / arm64)。
+> 静态端点数据由悟空基线生成并提交在本仓库 `internal/syncdata`,源码构建不需要额外 checkout 数据仓库。
@@ -152,12 +153,18 @@ dws 内置自升级能力,直接从 [GitHub Releases](https://github.com/DingT
```bash
dws upgrade # 交互式升级到最新版本
dws upgrade --check # 仅检查是否有新版本
-dws upgrade --list # 列出所有可用版本
+dws upgrade --list # 列出正式 release 版本
+dws upgrade --beta # 升级到最新 beta 预发布版本
+dws upgrade --check --beta # 仅检查 beta 轨道是否有新版本
+dws upgrade --list --beta # 列出 beta 预发布版本
dws upgrade --version v1.0.7 # 升级到指定版本
+dws upgrade --version v1.0.8-beta.1 # 升级到指定 beta 版本
dws upgrade --rollback # 回滚到上一版本
dws upgrade -y # 跳过确认直接升级
```
+默认情况下,`dws upgrade` 只跟随正式 release 轨道。只有显式传入 `--beta` 时,才会选择 GitHub pre-release 里的 beta 构建。
+
工作原理
@@ -171,8 +178,9 @@ dws upgrade -y # 跳过确认直接升级
| Flag | 说明 |
|------|------|
| `--check` | 仅检查更新,不安装 |
-| `--list` | 列出所有可用版本及更新日志 |
-| `--version` | 升级到指定版本(如 `v1.0.7`) |
+| `--list` | 列出正式 release 版本及更新日志 |
+| `--beta` | 对 `upgrade`、`--check`、`--list` 使用 beta 预发布轨道 |
+| `--version` | 升级到指定版本(如 `v1.0.7` 或 `v1.0.8-beta.1`) |
| `--rollback` | 回滚到上一个备份版本 |
| `--force` | 强制重新安装,即使已是最新版本 |
| `--skip-skills` | 跳过技能包更新 |
@@ -300,18 +308,18 @@ dws contact user search --query "张三" --dry-run
dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserName, dept: .depts[0].deptName, userId}'
```
-### Schema 发现
+### 命令帮助与 Schema
-Agent 无需预置所有命令知识,通过 `dws schema` 动态发现可用能力:
+产品命令在静态端点模式下已经编译进二进制。Agent 以 `--help` 和内置 Skill 为事实源;`dws schema` 仅保留给 `dev.*` 等 helper-only schema 查询。
```bash
-# 第一步:发现所有可用产品
-dws schema --jq '.products[] | {id, tool_count: (.tools | length)}'
+# 查看当前编译出的命令面
+dws aitable record query --help
-# 第二步:查看目标工具的参数结构
-dws schema aitable.query_records --jq '.tool.parameters'
+# helper-only schema 自省
+dws schema "dev app create"
-# 第三步:构造正确的调用
+# 构造正确的调用
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --limit 10
```
@@ -476,7 +484,7 @@ dws aitable record query --base-id BASE_ID --tabel-id TABLE_ID # --tabel-i
```bash
# 内置 jq 表达式
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --jq '.invocation.params'
-dws schema --jq '.products[] | {id, tools: (.tools | length)}'
+dws schema "dev app create" --jq '.tool.required'
# 只返回指定字段
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocation,response
@@ -485,13 +493,12 @@ dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocati
-Schema 自省 — 调用前查询任意工具的参数结构
+Schema 自省 — 静态端点模式下的 helper-only schema
```bash
-dws schema # 列出所有产品和工具
-dws schema aitable.query_records # 查看参数 Schema
-dws schema aitable.query_records --jq '.tool.required' # 查看必填字段
-dws schema --jq '.products[].id' # 提取所有产品 ID
+dws schema # 静态端点模式提示
+dws schema "dev app create" # 查看 helper-only schema
+dws schema "dev app create" --jq '.tool.required' # 查看必填字段
```
@@ -619,7 +626,7 @@ dws dev connect --channel auto --robot-client-id --robot-client-secret Auto-generated. Edit `pre/config.json`, not this file.
+> Auto-generated. Update plugin descriptors in `internal/plugin/`, not this file.
## Global flags
diff --git a/docs/connect-daemon-service.md b/docs/connect-daemon-service.md
index eb4bf0c0..8d5fce85 100644
--- a/docs/connect-daemon-service.md
+++ b/docs/connect-daemon-service.md
@@ -1,6 +1,6 @@
# Running the connector as a 7x24 service
-`dws devapp robot connect` keeps a DingTalk robot wired to a local agent over a
+`dws dev connect` keeps a DingTalk robot wired to a local agent over a
Stream long-connection. By default it runs in the foreground and dies when the
terminal closes. For an unattended "digital employee" you have two options.
@@ -15,14 +15,14 @@ terminal closes. For an unattended "digital employee" you have two options.
```bash
# Detach into a background supervisor that restarts the connector if it crashes.
-dws devapp robot connect --daemon \
+dws dev connect --daemon \
--channel claudecode \
--unified-app-id
# Inspect / stop / restart it (locate the daemon by unifiedAppId).
-dws devapp robot connect status --unified-app-id
-dws devapp robot connect stop --unified-app-id
-dws devapp robot connect restart --unified-app-id
+dws dev connect status --unified-app-id
+dws dev connect stop --unified-app-id
+dws dev connect restart --unified-app-id
```
- The parent prints the daemon pid and the log path, then exits.
@@ -60,8 +60,7 @@ and `REPLACE_UNIFIED_APP_ID`, then `launchctl load -w `.
ProgramArguments
/usr/local/bin/dws
- devapp
- robot
+ dev
connect
--channel
claudecode
@@ -110,7 +109,7 @@ Wants=network-online.target
[Service]
Type=simple
-ExecStart=/usr/local/bin/dws devapp robot connect \
+ExecStart=/usr/local/bin/dws dev connect \
--channel claudecode \
--unified-app-id REPLACE_UNIFIED_APP_ID
Restart=always
@@ -136,7 +135,7 @@ security warning to stderr. This form:
- exposes `clientSecret` to every user on the box via `ps -ef`;
- gets baked into launchd `ProgramArguments` / systemd `ExecStart`, which
makes rotation harder;
-- means `dws devapp robot connect restart` cannot re-fetch credentials — you
+- means `dws dev connect restart` cannot re-fetch credentials — you
must re-run the full command yourself.
Prefer `--unified-app-id`. Only fall back to the pair when you understand the
diff --git a/docs/reference.md b/docs/reference.md
index 5b3cdce8..76b60ba6 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -5,7 +5,7 @@
| Variable | Purpose / 用途 |
|---------|---------|
| `DWS_CONFIG_DIR` | Override default config directory / 覆盖默认配置目录 |
-| `DWS_SERVERS_URL` | Point discovery at a custom server registry endpoint / 将服务发现指向自定义端点 |
+| `DWS__MCP_URL` | Override a product MCP endpoint for local development / 本地开发时覆盖指定产品 MCP endpoint |
| `DWS_CLIENT_ID` | OAuth client ID (DingTalk AppKey) |
| `DWS_CLIENT_SECRET` | OAuth client secret (DingTalk AppSecret) |
| `DWS_TRUSTED_DOMAINS` | Comma-separated trusted domains for bearer token (default: `*.dingtalk.com`). `*` for dev only / Bearer token 允许发送的域名白名单,默认 `*.dingtalk.com`,仅开发环境可设为 `*` |
@@ -22,7 +22,7 @@
| 3 | Validation | Invalid input, flags, or parameter schema mismatch / 输入参数校验失败 |
| 4 | PAT | PAT authorization interception; stderr carries raw machine-readable PAT JSON / PAT 授权拦截;stderr 返回原始机器可解析 JSON |
| 5 | Internal | Unexpected internal error / 未预期的内部错误 |
-| 6 | Discovery | Server discovery, cache, or protocol negotiation failure / 服务发现、缓存或协议协商失败 |
+| 6 | Discovery | Static endpoint resolution or protocol negotiation failure / 静态端点解析或协议协商失败 |
With `-f json`, error responses include structured payloads: `category`, `reason`, `hint`, `actions`.
@@ -34,7 +34,7 @@ With `-f json`, error responses include structured payloads: `category`, `reason
dws contact user search --query "Alice" -f table # Table (default, human-friendly / 表格,默认)
dws contact user search --query "Alice" -f json # JSON (for agents and piping / 适合 agent)
dws contact user search --query "Alice" -f raw # Raw API response / 原始响应
-dws schema -f pretty ding.send_ding_message # Pretty (ANSI-colored, schema-aware / 彩色分区,专为 schema 设计)
+dws schema -f pretty "dev app create" # Pretty helper-only schema view / helper-only schema 彩色分区展示
```
## Dry Run / 试运行
@@ -51,26 +51,24 @@ dws contact user search --query "Alice" -o result.json
## Schema Introspection / Schema 查询
-`dws schema` 查询已发现的 MCP 产品和工具元数据。不带参数列出所有产品,带路径输出单个工具的完整 schema。
+静态端点模式下,产品命令和 flag 以当前二进制的 `--help` 与内置 Skill 为准。`dws schema` 仅保留 helper-only 子树(如 `dev.*`)的 schema 查询。
### 路径写法
```bash
-dws schema # 列出所有产品 + 工具名
-dws schema ding.send_ding_message # canonical: product.rpc_name
-dws schema ding.message.send # CLI 点路径: product.group.cli_name
-dws schema "ding message send" # CLI 空格路径(同上)
-dws schema --cli-path "ding message send" # 显式 flag(脚本友好,免转义)
-dws schema -f pretty ding.send_ding_message # ANSI 着色分区展示(人肉查看最舒服)
+dws schema # 静态端点模式提示
+dws schema "dev app create" # CLI 空格路径
+dws schema --cli-path "dev app create" # 显式 flag(脚本友好,免转义)
+dws schema -f pretty "dev app create" # ANSI 着色分区展示(人肉查看最舒服)
```
-Canonical 路径先匹配;落空后走 CLI 路径(product → group.. → cli_name)。
+helper-only schema 以 CLI 路径为准;普通产品命令请使用 `dws --help` 查看参数。
### 单工具输出字段
| 字段 | 说明 |
|------|------|
-| `name` / `cli_name` / `canonical_path` | MCP RPC 名 / CLI 叶子名 / `product.rpc_name` |
+| `name` / `cli_name` / `canonical_path` | MCP RPC 名 / CLI 叶子名 / helper-only canonical path |
| `group` | CLI 父级 group 路径(dot-separated) |
| `title` / `description` | 工具名/说明(overlay 优先) |
| `parameters` / `required` | MCP 输入 JSON Schema 的 properties / required |
@@ -85,10 +83,8 @@ Canonical 路径先匹配;落空后走 CLI 路径(product → group.. → cl
### 筛选输出
```bash
-dws schema ding.send_ding_message --jq '.tool.flag_overlay' # 只看 overlay
-dws schema calendar.create_event --jq '.tool.auth' # 只看授权元数据
-dws schema --jq '.products[] | {id, count: (.tools|length)}' # 各产品工具数
-dws schema aitable.delete_base --jq '.tool.annotations' # 敏感操作提示
+dws schema "dev app create" --jq '.tool.parameters' # 只看参数 schema
+dws schema "dev app create" --jq '.tool.required' # 只看必填字段
```
## Shell Completion / 自动补全
diff --git a/skills/mono/SKILL.md b/skills/mono/SKILL.md
index 4f8dc473..da1d2772 100644
--- a/skills/mono/SKILL.md
+++ b/skills/mono/SKILL.md
@@ -9,50 +9,35 @@ cli_version: ">=1.0.15"
通过 `dws` 命令管理钉钉产品能力。
-> ⚠️ **命令可用性可能因企业服务发现配置而异**。本文档列出的命令基于 dws envelope schema 与本仓库 v1.0.30 实测,但部分命令的 cobra 子命令暴露与否还取决于你的企业 MCP gateway 是否注册了对应 tool。如果跑某条命令报 `unknown command` 或 fall back 到父级 help,说明当前账号企业未开通该能力。实际调用前可用 `dws --help` 或 `--dry-run` 验证。
+> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws --help` 或 `--dry-run` 验证。
## 严格禁止 (NEVER DO)
- 不要使用 dws 命令以外的方式操作(禁止 curl、HTTP API、浏览器)
- 不要编造 UUID、ID 等标识符,必须从命令返回中提取
-- 不要编造 URL、Email、手机号等结构化信息,必须从命令返回中提取或由用户明确提供
- 不要猜测字段名/参数值,操作前必须先查询确认
-- 禁止编造命令路径、子命令或 flag;产品参考缺失、路径/flag 不确定,或报 `unknown command` / `unknown flag` 时,必须先运行对应层级的 `dws --help` 查证后再执行或重试
## 严格要求 (MUST DO)
-- DWS 命令合法性协议:执行 `dws` 前必须用当前 skill 资料确认命令;产品参考已覆盖时直接按参考执行,缺失或不确定时必须先用 `--help` 查证
- 所有命令必须加 `--format json` 以获取可解析输出
- 危险操作必须先向用户确认,用户同意后才加 `--yes` 执行
- 单次批量操作不超过 30 条记录
- 所有命令必须**严格遵循**对应产品参考文档里面规定的参数格式(如:如果有参数值,则参数和参数值之间至少用一个空格隔开)
- **脚本优先**:[scripts/](./scripts/) 下的 `python scripts/.py` 已封装翻页/轮询/批量逻辑,遇到对应场景(如 AI 表格批量导入导出、AI 应用创建轮询、文档创建后写内容、钉盘目录树等)**优先调用脚本**而非手写多步命令。脚本均支持 `--dry-run` 预览、`--format json` 输出,失败时回退到手动步骤
-- **业务域最佳实践优先**:文档类多步任务先读 [04-document.md](./references/best_practices/04-document.md);AI 表格读取/统计/写入/导入导出先读 [06-data-analytics.md](./references/best_practices/06-data-analytics.md)。本仓库只迁入这些业务域 best practices,不引入其它产品行动指南。
-- 知识库容器只用 `dws wiki space/member`;知识库内文件/文档的浏览、搜索、读取、创建、移动、复制统一切到 `dws doc`。`workspaceId` 只能传给 `wiki --workspace`、`doc --workspace` 或 `doc search --workspace-ids`,禁止传给 `doc list --folder`,也不要使用不存在的 `--space-id`。
-- 找群 / 找人 / 找数据在当前组织没命中、且 `dws profile list` 显示 ≥2 个组织时,对每个组织带一次性 `--profile ` 各搜一遍;命中即用,全部组织都没有才追问用户。禁止在当前组织搜不到就判定「不存在」或直接甩给用户选。
-
-## 开放平台文档 RAG / 错误码排查
-
-- 任何产品执行中,只要用户问开放平台 API、接口参数、字段含义、权限点、回调、SDK、配额、错误码,或命令返回上游 OpenAPI/SDK 错误,必须先用 `dws devdoc article search --query "<关键词>" --format json` 做官方文档 RAG。
-- 查询词优先保留原始 API 名、能力名、权限点、完整错误码和 message;首轮形如 `errcode `,无结果再换 `<产品/场景> <错误码>`、`<接口名> 参数`。
-- 本地 CLI 错误(如 `unknown command` / `unknown flag` / 认证 / recovery)仍按本文件「错误处理」执行;`devdoc` 用于开放平台业务错误码和接口语义排查。
-- `devdoc` 只查钉钉开放平台开发者文档,不查业务数据;排查结论必须基于命中条目的标题、摘要或链接,不能编造错误原因或不存在的命令。
## 产品总览
-> 若用户意图涉及多步操作、汇总/整理/归纳/分析、文档创建后写入内容、知识库内文档处理、AI 表格批量读写/统计/导入导出,**先匹配下方「行动指南」**;仅当行动指南无匹配且明确是单一产品单步操作时,按本表路由。
-
| 产品 | 用途 | 参考文件 |
|-------------------|------------------------------------------------------|----------------------------------------------------------------|
+| `aiapp` | AI应用:创建/查询/修改AI应用 | [aiapp.md](./references/products/aiapp.md) |
| `aisearch` | AI搜问(搜人首选):按姓名/部门/职位/职责/上级/下级/手机号/工号维度找人,"谁负责 XX/XX 的负责人/某事项/某项目的人"统一走本产品 | [aisearch.md](./references/products/aisearch.md) |
| `aitable` | AI表格:Base/数据表/字段/记录/视图/附件/图表/仪表盘/导入导出/模板搜索 | [aitable.md](./references/products/aitable.md) |
-| `attendance` | 考勤:考勤组与规则查询(rules)/个人打卡详情(record get)/批量班次查询(shift list)/考勤统计摘要(summary),仅此 4 个命令组 | [attendance.md](./references/products/attendance.md) |
+| `attendance` | 考勤:打卡结果/打卡流水/考勤组查询/考勤规则/汇总统计/假期类型/假期余额(P0 已落地,部分管理类命令仍属 P1) | [attendance.md](./references/products/attendance.md) |
| `calendar` | 日历:日历列表/日程/参与者/附件/响应/会议室/闲忙查询/时间建议 | [calendar.md](./references/products/calendar.md) |
-| `chat` | 群聊与机器人:搜索群/建群/群成员管理/改群名/消息发送(文本/Markdown/图片/文件)/拉取消息/@我/特别关注/机器人群发/单聊/撤回/转发/引用回复/Webhook/**查询**已有机器人 | [chat.md](./references/products/chat.md) |
-| `contact` | 通讯录:用户查询(当前用户/搜索/详情/手机号)/花名册档案(学历/家庭/银行卡/合同)/离职员工查询(姓名/时间范围/部门)/部门查询(搜索/详情/子部门/成员)/特别关注列表(按角色/职责找人请走 `aisearch`) | [contact.md](./references/products/contact.md) |
-| `dev` | 开放平台开发者:**新建/配置机器人(建号)**、建联调试(把机器人接到本地 agent 的 Stream)、应用生命周期(创建/更新/删除/凭证/权限/成员/事件订阅)、开放平台文档搜索 | [dev.md](./references/products/dev.md) |
+| `chat` | 群聊与机器人:搜索群/建群/群成员管理/改群名/消息发送(文本/Markdown/图片/文件)/拉取消息/@我/特别关注/机器人群发/单聊/撤回/转发/引用回复/Webhook/机器人搜索 | [chat.md](./references/products/chat.md) |
+| `contact` | 通讯录:用户查询(当前用户/搜索/详情/手机号)/花名册档案(学历/家庭/银行卡/合同)/离职员工查询(姓名/时间范围/部门)/部门查询(搜索/详情/子部门/成员)/角色查询(主管/管理员/财务/HR 等 label)/特别关注列表 | [contact.md](./references/products/contact.md) |
| `devdoc` | 开放平台文档:搜索开发文档 | [devdoc.md](./references/products/devdoc.md) |
| `ding` | DING消息:发送/撤回(应用内/短信/电话) | [ding.md](./references/products/ding.md) |
-| `doc` | 钉钉文档:搜索/浏览/读写/块级编辑/评论/文件创建/复制/移动/重命名/**删除/导出 docx/权限管理/媒体上传下载**;创建/编辑先按渐进式 doc 子文档与 JSONML 工作流决策 | [doc.md](./references/products/doc.md) |
+| `doc` | 钉钉文档:搜索/浏览/读写/块级编辑/评论/文件创建/复制/移动/重命名/**删除/导出 docx/权限管理/媒体上传下载** | [doc.md](./references/products/doc.md) |
| `drive` | 钉钉云盘:文件列表/元数据/文件夹/上传(两步)/下载 | [drive.md](./references/products/drive.md) |
| `minutes` | AI听记:听记列表/摘要/关键词/转写/待办/思维导图/发言人/发言人段落总结/热词/录音控制/成员权限/上传 | [minutes.md](./references/products/minutes.md) |
| `oa` | OA审批:待处理/详情/同意/拒绝/撤销/记录/已发起/任务/转交/评论/抄送 | [oa.md](./references/products/oa.md) |
@@ -62,73 +47,18 @@ cli_version: ">=1.0.15"
| `todo` | 待办:创建(含优先级/截止时间/循环)/查询/修改/标记完成/删除 | [todo.md](./references/products/todo.md) |
| `wiki` | 知识库:空间创建/详情/列表/搜索 + 成员管理 | [wiki.md](./references/products/wiki.md) |
-## 核心流程(每次请求必须执行,不得跳过)
-
-作为一个智能助手,你的首要任务是**理解用户的真实、完整意图**,不是简单执行第一条看起来相关的命令。在选择 `dws` 产品命令前,必须按以下流程执行:
-
-0. **URL 预检**:输入含 `alidocs.dingtalk.com` URL 时,必须先读取 [url-patterns.md](./references/url-patterns.md) 的「alidocs URL 分流决策」,识别 URL 是文档、文件夹、知识库、表格、分享短链还是其它格式,再选择对应产品。含 `shanji.dingtalk.com` URL 时直接路由到 `minutes`。
-1. **意图拆解**:判断用户请求是否包含多个时序步骤(如“创建文件夹,然后创建文档并写入内容”“读文档,然后总结并评估”“建 AI 表格,再加字段和记录”)。若是,拆成多个子意图,按顺序执行,前一步产出的 `workspaceId` / `nodeId` / `baseId` / `tableId` 必须作为后一步输入。
-2. **行动指南优先匹配**:将用户意图或拆解后的子意图与下方「行动指南」逐行做语义比对。命中文档知识或 AI 表格数据任一行时,必须先读取对应 best practice,再读取对应产品参考文件执行;文档知识场景还必须进入 [doc.md](./references/products/doc.md) 的渐进式文档索引按需加载子文件。
-3. **recipe 分级执行**:当前开源版只迁入业务域 full recipe(`04-document.md`、`06-data-analytics.md`),未迁入悟空完整 `lite-recipes.md`。因此命中下方任一业务域行动指南时,一律按 full recipe 处理:先读行动指南,再读产品参考,不要只凭主 skill 摘要直接执行。文档创建/更新/块级编辑按 `doc.md` 前置条件继续读取 `doc/style/doc-create-workflow.md`、`doc/style/doc-update-workflow.md`、`doc/format/doc-jsonml-schema.md` 或 `doc/format/doc-jsonml-cookbook.md`,优先保真使用 JSONML。
-4. **Fallback 单产品路由**:仅当行动指南未命中,且用户意图明确是单一产品单步操作时,才按「产品总览」和「意图判断决策树」选择产品,并读取对应 `references/products/*.md`。
-5. **追问**:以上步骤都无法判断时,主动追问用户澄清,严禁猜测命令、flag、URL、ID 或字段名。
-
-## 多组织处理
-dws 可同时登录多个钉钉组织,一个 profile = 一个已登录组织(corp)。当前 profile 决定本次命令用哪个组织的身份(corpId / userId 按当前 profile 自动注入,不是只支持单组织)。
-
-**触发条件(命中任一即进入本节)**:
-- 显式:用户提到 切换 / 换 / 跨组织、另一个钉钉、别的公司、看登录了哪些组织、当前是哪个组织、某人 / 某群 / 某数据在别的组织
-- 隐式(最常见、易漏):在当前组织读 / 搜没找到目标(群 / 人 / 数据),且 `dws profile list` 显示已登录 ≥2 个组织 —— 别急着判「不存在」,按下方跨组织铁律去其他组织找
-- 需要跨多个组织汇总 / 对比数据
-- 用户问认证状态 / 登录了哪些组织 / 主组织是哪个
-
-**不触发**:只登录 1 个组织时,按当前组织正常处理,不带 `--profile`,不进本节。
-
-命令:
-- `dws profile list` — 列出已登录组织(主 / 当前标记、状态、有效期),只读元数据
-- `dws profile switch <名称|corpId|->` — 持久切换当前组织;`-` 切回上一个;无参数在交互终端弹选择器(非交互须显式传参)。`dws profile use` 是其别名
-- 全局 `--profile <名称|corpId>` — 单次指定本命令用哪个组织,一次性、不改当前组织
-- `dws auth login` — 再登一个组织即新增 profile(自动从授权账号取 corpId / corpName);同组织重复 login = 刷新
-- `dws auth status [--profile <名称>]` — 查看认证状态
-
-多组织数据聚合步骤:`dws profile list` 拿到所有已登录组织,对每个组织带 `--profile ` 各取一次数,合并并标注来源组织;某组织失败则标「该组织暂不可用」并继续返回其余。
-安全护栏:
-- 只有 `dws profile list` 显示 ≥2 个组织才启用上面的跨组织逻辑;单组织直接按当前组织走,不带 `--profile`。
-- 自动跨组织只对「读 / 搜」。写 / 发 / 删 / 撤回等操作默认只在当前组织做;确需带 `--profile` 跨组织写时,必须先与用户确认目标组织。
-- 持久切换 `dws profile switch`(改默认组织)按写操作对待:未经用户明确要求不得执行。跨组织找数一律用一次性 `--profile`,不改当前组织。
-
-## 行动指南(优先匹配)
-
-> 将用户意图与下表做**语义比对**,不要求字面包含关键词。命中后必须读取该行动指南文件,并按其中固定路线执行;多个场景同时命中时,按下方「消歧规则」选择。
-
-| # | 场景 | 触发关键词 / 能力范围 | 行动指南 |
-|---|------|----------------------|----------|
-| 4 | 文档知识 | 搜索/浏览/读取/创建/更新/迁移/模板复用/导出钉钉文档;知识库内文档处理;文件夹下创建文档;块级编辑;JSONML 保真改写;图片/附件/PDF/Excel/PPT 嵌入正文;读文档后总结/评估/对照 | [04-document.md](./references/best_practices/04-document.md) |
-| 6 | AI 表格数据 | AI 表格读取/统计/写入记录/更新记录/字段/视图/导入导出/模板建表/主文档读取 | [06-data-analytics.md](./references/best_practices/06-data-analytics.md) |
-
-### 消歧规则
-
-- "工作台应用/app001/appXYZ/钉钉工作台上有哪些应用" → `workbench app`。
-- "开放平台接口文档/API 怎么调用/错误码/字段说明" → `devdoc`。
-- "MCP 服务/connector/创建工具/HSF 映射/上架 MCP" → MCP 平台配置流程。
-- 只说"应用"且缺少上下文时,先结合当前对话判断;仍不明确时追问是工作台应用、文档能力还是 MCP 工具。
-- "知识库空间/成员管理" → `wiki` 产品参考;"知识库里的文档/文件/内容" → #4 文档知识,再切 `doc`。
-- "创建表格/在线电子表格/单元格" → `sheet`;"AI 表格/多维表/base/记录/字段/视图" → #6 AI 表格数据。
-- "写文档/读文档/总结文档/插入图片附件/块级编辑/JSONML 保真改写" → #4 文档知识,不要停在 `doc create` 或 `doc block --help`;创建/编辑必须继续按 [doc.md](./references/products/doc.md) 加载渐进式子文档和 JSONML workflow。
-- "导出文档后归档/上传" → 先 #4 完成 `doc export`,再按产品路由继续 `drive upload` 等后续动作。
-
## 意图判断决策树
-用户提到"找人/搜人/谁负责 XX/某事项的负责人/某项目的人/某职责/某角色(主管/管理员/财务/HR 等)由谁担任/团队成员/上级/下级/按工号找人/按手机号找人" → `aisearch`(按角色或职责找人用 `aisearch person --dimension duty`)
+用户提到"AI应用/创建应用/生成系统/做工具/管理后台/低代码" → `aiapp`
+用户提到"找人/搜人/谁负责 XX/某事项的负责人/某项目的人/团队成员/上级/下级/按工号找人/按手机号找人" → `aisearch`
用户提到"表格/多维表/AI表格/记录/数据/视图/图表/仪表盘" → `aitable`
用户提到"考勤/打卡/排班" → `attendance`
用户提到"日程/日历/会议室/约会/时间建议" → `calendar`
-用户提到"群聊/建群/群成员/群管理/发消息/发图片消息/发文件消息/发 Markdown 消息/截图发钉钉/转发消息/引用回复/@我/特别关注消息/机器人发消息/Webhook/机器人群发/机器人单聊/通知" → `chat`(仅 IM 操作;创建/配置/建联机器人走 `dev`,见下)
-用户提到"创建机器人/新建机器人/建机器人/配置机器人/机器人建号/建联/把机器人连到/接入 agent/opencode/claude/qoder/连接 agent/开放平台应用/开发者应用/app 创建/应用凭证/事件订阅/dws dev" → `dev`
-用户提到"通讯录/同事/部门/组织架构/子部门/部门多少人/离职员工/离职名单/离职花名册/花名册/员工档案/学历/家庭/银行卡/紧急联系人/合同/特别关注/星标联系人" → `contact`(按角色/职责找人不在此,走 `aisearch`)
+用户提到"群聊/建群/群成员/群管理/发消息/发图片消息/发文件消息/发 Markdown 消息/截图发钉钉/转发消息/引用回复/@我/特别关注消息/机器人发消息/Webhook/机器人群发/机器人单聊/通知" → `chat`
+用户提到"通讯录/同事/部门/组织架构/子部门/部门多少人/离职员工/离职名单/离职花名册/花名册/员工档案/学历/家庭/银行卡/紧急联系人/合同/角色/主管角色/管理员角色/财务/HR/特别关注/星标联系人" → `contact`
用户提到"开发/API/调用错误 文档" → `devdoc`
用户提到"DING/紧急消息/电话提醒" → `ding`
-用户提到"钉钉文档/云文档/读写文档/知识库里的文档/浏览知识库内容/知识库内搜索文档/块级编辑/文档评论/文档复制移动" → `doc`
+用户提到"钉钉文档/云文档/知识库/读写文档/块级编辑/文档评论/文档复制移动" → `doc`
用户提到"云盘/文件存储/文件上传下载/文件夹" → `drive`
用户提到"听记/AI听记/会议纪要/转写/摘要/思维导图/发言人/热词" → `minutes`
用户提到"邮箱/邮件/发邮件/收邮件/搜邮件/查邮件/邮件草稿/转发邮件/回复邮件/邮件附件/抄送" → `mail`
@@ -136,15 +66,12 @@ dws 可同时登录多个钉钉组织,一个 profile = 一个已登录组织
用户提到"日志/日报/周报/日志统计/写日报/提交周报/发日志/填日志" → `report`
用户提到"在线电子表格/钉钉表格/axls/工作表/单元格读写/合并单元格/筛选视图/导出 xlsx" → `sheet`
用户提到"待办/TODO/任务提醒/循环待办" → `todo`
-用户提到"创建知识库/知识库列表/搜索知识库空间/wiki/团队空间/知识库成员管理/我的文档个人空间" → `wiki`
-用户提到"切换组织/换组织/跨组织/另一个钉钉/别的公司/多组织/看所有组织/profile/登录了哪些组织" → `profile`(见「多组织 / profile」节)
+用户提到"知识库/wiki/团队空间/知识库成员管理" → `wiki`
-关键区分: **dev(创建/配置/建联机器人)** vs **chat(查询/发消息已有机器人)**。`dws chat bot search/find` 只查询机器人;**建号**(创建钉钉智能体机器人)走 `dws dev app robot submit`;**建联**(把机器人接到本地 agent 的 Stream)走 `dws dev connect`。凡是"创建机器人""建机器人""接入 agent""建联"一律路由到 `dev`,禁止走 `chat`。
关键区分: aitable(数据表格) vs todo(待办任务)
关键区分: report(钉钉日志/日报周报) vs todo(待办任务)
关键区分: chat send-by-bot(机器人身份发消息) vs send-by-webhook(自定义机器人Webhook告警)
关键区分: doc(钉钉文档/富文本协同) vs drive(钉钉云盘/二进制文件)
-关键区分: wiki(知识库空间/成员管理) vs doc(知识库内文档内容读写)。用户要读/搜/列知识库里的文档时:先 `wiki space list/search` 拿 `workspaceId`,再 `doc list --workspace` / `doc search --workspace-ids` / `doc read --node`。
关键区分: oa tasks(审批 taskId,审批/拒绝用) vs oa list-pending(收件箱 processInstanceId,查看用)
@@ -175,7 +102,6 @@ dws 可同时登录多个钉钉组织,一个 profile = 一个已登录组织
| `oa` | `approval reject` | 拒绝待审批(需加明确理由) |
| `todo` | `task delete` | 删除待办 |
| `minutes` | `replace-text` | 全文批量替换转写与摘要 |
-| `auth` | `logout` | **默认退出所有已登录组织**;只退一个加 `--profile <名称\|corpId>`。注意:退主组织不会被拦,会静默把「主」改选为剩下第一个组织,退主前必须向用户确认 |
### 确认流程
```
@@ -184,40 +110,43 @@ Step 2 → 用户明确回复确认(如 "确认" / "好的")
Step 3 → 加 --yes 执行命令
```
+## 核心流程
+作为一个智能助手,你的首要任务是**理解用户的真实、完整的意图**,而不是简单地执行命令。在选择 `dws` 的产品命令前,必须严格遵循以下四步流程:
+
+0. **URL 预检**:输入含 `alidocs.dingtalk.com` URL 时,该域名下存在多种路径格式(`/i/nodes/...`、`/i/p/...`、`/spreadsheetv2/...`、`/document/edit|preview?dentryKey=...` 等),每种的处理流程不同。**必须先读取 [url-patterns.md](./references/url-patterns.md) 中的「alidocs URL 分流决策」**,按其中规则识别 URL 类型后再选择对应产品。含 `shanji.dingtalk.com` URL 时直接路由到 `minutes`。URL 已识别后直接进入对应产品流程,无需后续步骤。
+1. 意图分类:首先,判断用户指令的核心 动词/动作 属于哪一类。这比关注名词更重要。
+2. 歧义处理与信息追问:如果用户指令模糊或包含多个产品的关键字,严禁猜测。必须主动向用户追问以澄清意图。这是你作为智能助手而非命令执行器的核心价值。
+3. 精准产品映射:在完成前两步,意图已经清晰后,参考产品总览和意图判断决策树 来选择产品。
+4. 充分阅读产品参考文件,通过编写代码或直接调用指令实现用户意图。
+
## 命令发现(flag / 参数以 binary 为准)
-产品参考文档(`references/products/*.md`)里的 flag 列表是**便于理解用途的参考**,不是权威契约。参数名称、默认值、必填约束随服务发现动态变化,**以下两个命令的输出才是调用的事实源**:
+产品参考文档(`references/products/*.md`)里的 flag 列表是**便于理解用途的参考**,不是权威契约。参数名称、默认值、必填约束以当前二进制编译出的 Cobra 命令为准,**`--help` 是产品命令调用的事实源**:
```bash
-# 1) 人读视图:看 Usage / Example / Flags
+# 人读视图:看 Usage / Example / Flags
dws --help
# 例:dws calendar event list --help
-# 2) 机读视图:JSON Schema + flag 别名映射 + 必填字段
-dws schema # 列出所有产品及工具
-dws schema . # 规范路径(如 calendar.list_suggested_event_times)
-dws schema " " # CLI 路径(如 "calendar event list")
-dws schema --jq '.tool.flag_overlay' # 只看 flag 别名
-dws schema --jq '.tool.required' # 只看必填字段
+# helper-only schema 查询(如 dev.*),普通产品命令不要依赖 schema 推断参数
+dws schema "dev app create"
+dws schema "dev app create" --jq '.tool.required'
```
**何时用哪条路径:**
- 只需看某个命令怎么调用 → `dws --help`
-- 构造 `--params` / `--json` 时不确定字段类型、必填、别名 → `dws schema `
-- 参考文档和 `--help` 冲突时 → **以 `--help` / `dws schema` 为准**,文档视为过期
+- 构造 `--params` / `--json` 时不确定字段类型、必填、别名 → 先看 `dws --help`,helper-only 命令再看 `dws schema`
+- 参考文档和 `--help` 冲突时 → **以 `--help` 为准**,文档视为过期
-`dws schema` 输出的 `flag_overlay[key].alias` 就是实际生效的 flag 名(如 `attendeeUserIds → --attendee-user-ids`);`parameters[key]` 是原始 JSON Schema;`required` 是必填字段数组;`sensitive: true` 表示写/删操作,须先向用户确认再加 `--yes`。
+`dws schema` 在静态端点模式下只保留 helper-only 子树;普通产品命令和 flag 不再通过远程 schema 动态发现。写/删操作须先向用户确认再加 `--yes`。
## 错误处理
-1. 遇到错误,加 `--verbose` 重试**一次**
+1. 遇到错误,加 `--verbose` 重试一次
2. 若 stderr 出现 `RECOVERY_EVENT_ID=`,优先按 [recovery-guide.md](./references/recovery-guide.md) 执行 recovery 闭环
-3. 仍然失败,**立即停止**并报告完整错误信息,禁止自行尝试替代方案或反复变通
-4. **严禁**连续重试超过 3 次相同或类似的命令;如果 3 次仍失败,必须停止并报告
-5. 报 `unknown command` / `unknown flag` 时,先运行对应层级 `dws --help` 查证,再修正一次;不要把自然语言同义词直接当命令或 flag
-6. 逐条命令多次失败时,检查 [scripts/](./scripts/) 是否有对应业务域脚本可降级使用
-7. 认证失败时,参考 [global-reference.md](./references/global-reference.md) 中的认证章节处理
-8. 各产品高频错误及排查流程见 [error-codes.md](./references/error-codes.md)
-9. 遇到 [capability-limits.md](./references/capability-limits.md) 中列出的「已知不支持操作」时,**直接告知用户不支持并建议在钉钉客户端操作**,不要重试或变通
+3. 仍然失败,报告完整错误信息给用户,禁止自行尝试替代方案
+4. 认证失败时,参考 [global-reference.md](./references/global-reference.md) 中的认证章节处理
+5. 各产品高频错误及排查流程见 [error-codes.md](./references/error-codes.md)
+6. 遇到 [capability-limits.md](./references/capability-limits.md) 中列出的「已知不支持操作」时,**直接告知用户不支持并建议在钉钉客户端操作**,不要重试或变通
## 详细参考 (按需读取)
@@ -230,6 +159,21 @@ dws schema --jq '.tool.required' # 只看必填字段
- [references/error-codes.md](./references/error-codes.md) — 错误码 + 调试流程
- [references/recovery-guide.md](./references/recovery-guide.md) — recovery 闭环、`RECOVERY_EVENT_ID`、`execute/finalize` 规范
- [scripts/](./scripts/) — 各产品批量/复合操作脚本(AI表格批量导入导出、AI应用创建轮询、日历、机器人消息、通讯录、考勤、日志、待办、文档创建并写入、钉盘目录树等)
-- [references/products/aitable/](./references/products/aitable/) — AI表格细分章节(单元格值/字段属性/公式/筛选排序/导入导出/仪表盘/记录增删改查/错误恢复)
+- [references/products/aitable/](./references/products/aitable/) — AI表格细分章节(单元格值/字段属性/公式/筛选排序/导入导出/仪表盘/记录增删改查/错误恢复/最佳实践)
- [references/products/aitable-record-ops.md](./references/products/aitable-record-ops.md) — AI表格记录操作专项说明
- [references/capability-limits.md](./references/capability-limits.md) — 已知能力限制(doc/aitable/chat/minutes,遇到时直接告知用户不支持)
+- [references/best_practices/](./references/best_practices/) — 全场景 recipe 行动指南(11 个编号场景 + lite 速查)
+ - [01-messaging.md](./references/best_practices/01-messaging.md) — 消息沟通
+ - [02-task.md](./references/best_practices/02-task.md) — 任务管理(todo)
+ - [03-meeting.md](./references/best_practices/03-meeting.md) — 会议日程(日历 + 会议室)
+ - [04-document.md](./references/best_practices/04-document.md) — 文档场景(write-doc / search-docs / migrate-doc / update-doc-section / doc-to-message / delete-old-doc / export-doc-as-docx / grant-doc-access / insert-image-to-doc / template-based-generation 等)
+ - [05-reporting.md](./references/best_practices/05-reporting.md) — 工作汇报(钉钉日志 / 文档周报选路)
+ - [06-data-analytics.md](./references/best_practices/06-data-analytics.md) — AI表格数据分析(read-aitable / generate-data-report / create-aitable-record / update-aitable-record / export-aitable-to-xlsx / primary-doc-from-record 等)
+ - [07-minutes.md](./references/best_practices/07-minutes.md) — 听记与会后
+ - [08-directory.md](./references/best_practices/08-directory.md) — 通讯录(组织架构)
+ - [09-mail.md](./references/best_practices/09-mail.md) — 邮件
+ - [10-minutes-speaker-match.md](./references/best_practices/10-minutes-speaker-match.md) — 听记发言人智能匹配
+ - [11-minutes-speaker-correct.md](./references/best_practices/11-minutes-speaker-correct.md) — 听记发言人识别与标注
+ - [lite-recipes.md](./references/best_practices/lite-recipes.md) — Lite Recipe 速查(核心流程判定为 lite 后直接执行)
+ - [_common/conventions.md](./references/best_practices/_common/conventions.md) — 批量查询、多源并行采集、字段术语等通用规范
+ - [_common/recipe-conventions.md](./references/best_practices/_common/recipe-conventions.md) — recipe 元规范
diff --git a/skills/mono/references/best_practices/01-messaging.md b/skills/mono/references/best_practices/01-messaging.md
index 966a79ee..fb95e817 100644
--- a/skills/mono/references/best_practices/01-messaging.md
+++ b/skills/mono/references/best_practices/01-messaging.md
@@ -5,7 +5,7 @@
| Recipe | 行动指南(固定路线) |
|--------|-------------------|
| query-group-chat | **优先**:`chat_export_messages.py`(开源版未引入;可手动用 `dws chat message list` 翻页后写入文件)(自动搜群+翻页+导出)
备选:1. `chat search --query "<群名>"` → 取 `openConversationId`
2. `chat message list --group --time ""` → 取消息列表
3. **翻页**:`hasMore=true` 时取本页最后 `createTime` 作为下次 `--time`,重复至 `hasMore=false`
4. `--forward=false` 拉给定时间**之前**的消息
5. 合并全部消息后总结 |
-| query-private-chat | **优先**:`chat_history_with_user.py`(开源版未引入;可手动用 `aisearch person` + `dws chat message list-direct` 组合)(自动搜人+翻页+导出)
备选:1. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`
2. `chat message list-direct --user --time ""` → 取消息列表(单聊专用;旧版 `list --user` 已停用)
3. **翻页**:同 query-group-chat
4. 合并全部消息后总结 |
+| query-private-chat | **优先**:`chat_history_with_user.py`(开源版未引入;可手动用 `dws chat search` + `dws chat message list` 组合)(自动搜人+翻页+导出)
备选:1. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`
2. `chat message list --user --time ""` → 取消息列表
3. **翻页**:同 query-group-chat
4. 合并全部消息后总结 |
| escalate-ding | 三级升级:
1. `ding message send --robot-code --type app --users --content "<内容>"`(必填项见 [ding.md](../products/ding.md))
2. `chat message send --group --text "<内容>"` 群里提醒(可选 `--title` / `@` 见 [chat.md](../products/chat.md))
3. `todo task create --title "<标题>" --executors --priority 40` 建紧急待办
前置:`aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`;`chat search --query "<群名>"` → 取 `openConversationId` |
| send-by-bot | **多群批量优先**:`bot_broadcast.py`(开源版未引入;可手动用 `dws chat message send-by-bot` 多次调用)
单群:1. `chat bot search` → 取 `robotCode`
2. `chat search --query "<群名>"` → 取 `openConversationId`
3. `chat message send-by-bot --robot-code --group --title "<标题>" --text "<内容>"` |
| forward-message | 1. `chat search --query "<群名>"` → 取 `openConversationId` → `chat message list --group --time "<起始时间>"` 拉源消息
2. `contact user search --query "<姓名>"` → 取 `openDingTalkId`(推荐);或 `chat search --query "<群名>"` → 取目标 `openConversationId`
3. `chat message send --open-dingtalk-id --text "<内容>"`(推荐)或 `--group --text "<内容>"` 发送。仅当无法获取 openDingTalkId 时才用 `--user `(备选) |
diff --git a/skills/mono/references/best_practices/04-document.md b/skills/mono/references/best_practices/04-document.md
index f2cf2686..c884341e 100644
--- a/skills/mono/references/best_practices/04-document.md
+++ b/skills/mono/references/best_practices/04-document.md
@@ -1,18 +1,81 @@
# 文档知识
-> lite recipe 见 [SKILL.md 速查表](../../../../SKILL.md#常用-recipe-速查表lite-recipe--可直接执行无需读行动指南文件)。
+> 通用规范见 [_common/conventions.md](_common/conventions.md)。
| Recipe | 行动指南(固定路线) |
|--------|-------------------|
-| write-doc | 0. 阅读 [doc-create-workflow.md](../products/doc/style/doc-create-workflow.md) 的 §前置必读 + §关键词速查表,锁定文档类型和起稿路径:**决策型/含对比的知识沉淀型/用户要求美观 → JSONML 起稿**(`.json`);执行型/说明型 → Markdown 起稿(`.md`)
1. 按选定路径执行 doc-create-workflow.md(JSONML 路径有骨架范例可直接复制修改)
2. `doc create --content-file /tmp/.json --content-format jsonml`(或 `.md` + `--content-format markdown`)
3. 大内容默认依赖 DWS 自动分片;只有 `CONTENT_TRUNCATED`、部分写入失败或回读发现缺失时,才按 workflow 的恢复流程手工补片
4. **回读校验(必须)**:所有写入完成后,执行 `doc read --node `,校验关键标题/段落/表格是否完整写入 |
+| write-doc | 1. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行
2. **先把内容写入临时文件**(Linux/Mac `/tmp/.md`,Windows `%TEMP%\.md`)—— 含多行/表格/长文本必须走文件,不要把 markdown 直接作为命令行字符串
3. **单步创建**(< 200KB):`doc create --name "<文档名>" --content-file [--folder ] [--workspace ]`(`--folder` 只传文档文件夹 nodeId / alidocs 文件夹 URL,不传数字 dentryId)
4. **超长兜底**(> 200KB):**必须先向用户提示截断风险**(详见下方「分块 append 截断风险提示」),用户确认后再执行:`doc create --name "<文档名>" [--folder/--workspace]` → `nodeId` → 按段落切 ≤200KB 片段(不断表格) → 每片 `doc update --node --content-file --mode append`
5. **回读校验**(必须):所有写入完成后,执行 `doc read --node ` 回读文档,校验关键标题/段落是否完整写入(详见下方「doc update 回读校验规范」)
备选(仅短内容 <2KB 且无换行/表格):`doc create --name "..." --content "..."` |
| search-docs-and-share | 1. `doc search --query "<关键词>"` → 取 `nodeId` + 标题建索引(不读全文)
2. `doc read --node `(追问按需,最多 2 篇) |
| create-knowledge-base | 1. 创建知识库空间取 `WS_ID`
2. `doc create --name "<文档名>" --workspace ` → 取 `nodeId`
3. `doc list --workspace ` 确认 |
| migrate-doc | 1. `doc read --node <源nodeId>` → 取正文并写入临时文件 `.md`
2. `doc create --name "<文档名>" --folder --content-file .md` → 取新 `nodeId`(`--folder` 只传文档文件夹 nodeId / alidocs 文件夹 URL,不传数字 dentryId;正文 <200KB 单步到位)
2a. 若正文 >200KB:**必须先向用户提示截断风险**(详见下方「分块 append 截断风险提示」),用户确认后再执行:`doc create --name "<文档名>" --folder ` → `nodeId` → 按段落切片 → 每片 `doc update --node --content-file --mode append`
3. **回读校验**:`doc read --node ` 校验内容完整性 |
-| update-doc-section | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. **形态选择(按 [doc-update-workflow.md §1.3](../products/doc/style/doc-update-workflow.md) 优先级)**:目标段落含 callout / 分栏 / 颜色 / @人 / 附件 / 嵌套结构 → 走 `jsonml-node-edit`;纯文本替换且确认无富结构 → 继续本 recipe
3. `doc read --node ` 定位目标章节
4. `doc update --node --content "<替换内容>" --mode overwrite`
5. **回读校验**:`doc read --node ` 确认 overwrite 未被降级为 append、内容完整无截断
**overwrite 须用户确认**;完整改写流程见 [doc-update-workflow.md](../products/doc/style/doc-update-workflow.md) |
-| rewrite-doc | 1. 阅读并执行 [doc-update-workflow.md](../products/doc/style/doc-update-workflow.md):先看 §1.3 编辑形态优先级(**JSONML 首选**),再按 §3 速查表选路径,跳 §4 对应小节执行
2. 单块改写 / 含富结构 → §4.4 路径 B;多处保真改写或改 root → §4.4 路径 A;纯文本骨架重写 → §4.5 markdown
3. 整篇 overwrite 前必须按 workflow §4.5 向用户提示风险并等待确认
4. **回读校验(必须)**:按 workflow §6 的校验要点逐项核查;@人、附件、图片等保真要素必须原样保留
**适用场景**:用户提供已有 nodeId/链接,需要改写、润色、章节补充、段落形态转换、整篇重写 |
+| update-doc-section | **优先块级精修(保形)**:1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc block list --node --format json` 定位目标块取 `blockId`
3. `doc block update --node --block-id --text "<替换内容>" --format json` 局部修改(不破坏富格式)
4. **回读校验**:`doc read --node ` 确认目标章节已更新
**兜底**:仅当无法定位块且用户确认整篇替换时,先 `doc update --node --content-file .md --mode overwrite --dry-run` 预览,再加 `--yes` 执行;overwrite 会丢富格式(颜色/字号/合并单元格等),详见下方 template-based-generation §"为什么避免 overwrite" |
+| template-based-generation | **触发**:用户给已有 alidocs URL + "参照/按模板/复刻/同样模板生成 X 月份的" → **保形复制,禁止重写**。固定走下方 [template-based-generation](#template-based-generation) 章节:`doc copy` 保形复制 → `doc rename` → `doc block list/update` 局部改副本。禁止 `doc read + doc create` 重写链。 |
| doc-to-message | 1. `doc read --node ` → 取正文(大文档只摘要+链接)
2. `contact user search --query "<姓名>"` → 取 `openDingTalkId`(推荐);或 `chat search --query "<群名>"` → 取 `openConversationId`
3. `chat message send --open-dingtalk-id --text "<内容>"`(推荐)或 `--group --text "<内容>"` 发送。仅当无法获取 openDingTalkId 时才用 `--user `(备选) |
-| lossless-doc-edit | 1. `doc read --node --content-format jsonml --output /tmp/doc.json` → 获取完整 JSONML 结构(输出含 `revision`,并发敏感时记下来;默认改写不需要)
2. 解析 JSON 文件,修改 `jsonml` 数组中的目标节点(节点结构参见 [doc-jsonml-schema.md](../products/doc/format/doc-jsonml-schema.md))
3. 将修改后的内容写回临时文件 `/tmp/doc_modified.json`,格式为 `{"jsonml": [...]}`
4. `doc update --node --content-file /tmp/doc_modified.json --content-format jsonml --mode overwrite`(默认不做并发检查;担心多 agent 同时改时加 `--revision <第 1 步拿到的 N>` 触发并发检查,版本不一致返回 `VersionConflict` 时回到第 1 步重读重写)
5. **回读校验**:`doc read --node --content-format jsonml` 确认写入成功
**适用场景**:保留样式、精准插入特定节点类型、改属性不动文本;普通文本编辑仍优先用 markdown 模式。完整 JSONML 改写流程见 [doc-update-workflow.md §4.4](../products/doc/style/doc-update-workflow.md) |
-| jsonml-node-edit | 1. `doc block list --node --content-format jsonml` → 获取 JSONML 节点列表(含 uuid)
2. 根据 uuid 定位目标节点
3. `doc block list --node --content-format jsonml --block-id ` → 读取完整子树
4. 修改 JSONML 节点内容(节点结构参见 [doc-jsonml-schema.md](../products/doc/format/doc-jsonml-schema.md),可复制范例见 [doc-jsonml-cookbook.md](../products/doc/format/doc-jsonml-cookbook.md))
5. `doc block update --node --block-id --content-format jsonml --element '<修改后的 JSONML>'` → 写回
**适用场景**:只改一个 block 的结构/样式,无需全文回写;写入端默认 normalize(自动补 uuid、裸字符串包成 canonical 文本),可用 `--no-fix-jsonml` 关闭全部修复;`--fix-jsonml` 额外启用 JSON 语法修复(推荐 agent 调用) |
+| delete-old-doc | 1. `doc search --query "<关键词>"` 或 `doc list --folder ` 取 `nodeId`
2. **必须先向用户展示要删除的文档标题/路径**,等用户确认
3. `doc delete --node --yes`(注意:是 `doc delete`,不是 `doc block delete`;前者删整篇,后者删块)
4. 验证:`doc info --node ` 应返回 not-found |
+| export-doc-as-docx | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc export --node --output /tmp/.docx --timeout-sec 600`(CLI 内置渐进式退避轮询,**不要自己拼 GET downloadUrl**)
3. 落盘失败兜底:`doc export get --job-id --output /tmp/.docx` 续等
4. (可选)`doc-to-message` 把 docx 路径作为附件发给用户 |
+| grant-doc-access | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `contact user search --query "<姓名>"` → 取 `userId`(**注意**: doc permission 用 `userId`,不是 `openDingTalkId`)
3. **节点级**授权:`doc permission add --node --user --role EDITOR`
role 取值: MANAGER / EDITOR / DOWNLOADER / READER(**不要传 OWNER**)
单次最多 30 个 userId
4. 查权限:`doc permission list --node ` 确认
5. **不要混淆**: 给"知识库"加成员用 `wiki member add --workspace`(容器级);给"单个文档"加权限用 `doc permission add --node`(节点级)|
+| insert-image-to-doc | 1. `doc search --query "<关键词>"` → 取 `nodeId`
2. `doc media insert --node --file ./image.png`(3 步一体化:获取凭证 → PUT OSS → 插入块;图片 ≤20MB 自动作为内联图片,其他作为附件块)
3. (可选)`doc read --node ` 回读确认
**禁止**: 自己拿 uploadUrl 写 PUT —— 90%+ 会因 Content-Type 未清空触发 SignatureDoesNotMatch |
+| download-doc-attachment | 1. `doc block list --node ` → 找到 `blockType=attachment` 的块取 `resourceId`
2. `doc media download --node --resource-id ` → 返回 OSS 临时 URL(**注意 expirationSeconds**)
3. 调用方自行 GET 该 URL 落盘 |
+
+---
+
+## template-based-generation
+
+### 触发条件
+
+用户给已有 alidocs URL,并要求:
+- "参照这个生成同样的"
+- "按模板生成"
+- "复刻这个"
+- "同样的模板,X 月份的"
+- "仿照这个文档"
+
+### 核心原则
+
+**保形优于重写**。adoc 富格式(行高、单元格背景色、字号、渐变文字色、表格列宽、合并单元格)在 markdown 层无表达,`doc read → create_file → doc create` 会做两次 lossy projection(先 adoc→markdown,再 markdown→adoc),必然丢失这些属性。
+
+正确做法:**在 adoc 层保形复制(`doc copy`)**,再局部改写需要替换的块(`doc block update`),保留所有富格式。
+
+### 固定路线
+
+```bash
+# 1. probe 模板节点,确认是 ALIDOC(adoc 富文档)
+dws doc info --node <模板NODE或URL> --format json
+
+# 2. 在 adoc 层保形复制(不经过 markdown 投影)
+dws doc copy --node <模板NODE或URL> --folder <目标文件夹NODE或URL> --format json
+# → 取返回的副本 nodeId
+
+# 3. 重命名副本
+dws doc rename --node <副本nodeId> --name "<新文档名>" --format json
+
+# 4. 列出所有块定位要改的内容
+dws doc block list --node <副本nodeId> --format json
+# → 记录 blockId 和现有文本
+
+# 5. 逐块局部改写(只改月份/日期/姓名/指标等局部变量,不动结构)
+dws doc block update --node <副本nodeId> --block-id --text "<新内容>" --format json
+
+# 6. 回读确认
+dws doc read --node <副本nodeId>
+```
+
+### 为什么避免 overwrite
+
+| 场景 | overwrite 后果 |
+|------|---------------|
+| 模板带表格背景色 | 全部变白 |
+| 标题字号 24pt 加粗渐变 | 退化为普通 H1 |
+| 合并单元格的表格 | 拆成独立单元格 |
+| 嵌入图片 | 链接保留但样式重置 |
+
+**用 copy+block update 全部可保留**。
+
+### 禁止反模式
+
+- ❌ doc read + doc create 重写模板文档(两次 lossy projection)
+- ❌ 先 `dws doc copy`,再删除副本退回 create 重写链路
+- ❌ 用 `dws doc update --mode overwrite --yes` 把含富格式的 adoc 整篇覆盖成 markdown
---
@@ -43,15 +106,32 @@
## doc update 回读校验规范
-**所有 `doc update`(含 overwrite 和 append)执行后都必须回读校验**——返回 `success=true` 不等于内容真的写入完整。
+### 为什么需要回读校验
-完整规范(静默失败场景、校验流程、异常处理路径、"先清空再重建"命令样板)见 [doc-update-workflow.md §6「回读验收」](../products/doc/style/doc-update-workflow.md)。
+`doc update` 在以下场景可能产生**静默失败**,返回 `success=true` 但实际写入不完整:
-最小流程:
+- **overwrite 降级为 append**:大文档 overwrite 模式被后端静默降级,导致旧内容未清除、新内容追加在末尾
+- **分块 append 内容截断**:超长文档分片写入时,部分片段丢失或顺序错乱
+- **编码/通道问题**:PowerShell 等特殊终端下 UTF-8 内容传输乱码
+
+### 校验流程
+
+在 **每次 `doc update` 完成后**(无论 overwrite 还是 append 模式),必须执行以下步骤:
+
+1. **回读文档**:`doc read --node `
+2. **关键内容校验**:检查回读结果是否包含预期的关键标题、段落首句、表格标记等
+3. **异常处理**:
+ - 若内容缺失或截断 → 向用户报告具体差异,建议重试或手动修复
+ - 若 overwrite 后仍残留旧内容 → 提示用户 overwrite 可能被降级,建议先清空再 append
+ - **禁止**在未回读的情况下直接向用户报告"已完成"
+
+### 示例
-```bash
-dws doc update --node --content-file /tmp/new-content.md --mode overwrite
-dws doc read --node # 校验关键标题、段落首句、表格、@人、附件
```
+# 更新文档
+doc update --node abc123 --content-file /tmp/new-content.md --mode overwrite
-**禁止**在未回读的情况下向用户报告「已完成」。
+# 回读校验
+doc read --node abc123
+# → 检查输出是否包含预期内容的关键标题和段落
+```
diff --git a/skills/mono/references/best_practices/06-data-analytics.md b/skills/mono/references/best_practices/06-data-analytics.md
index 213cc7ae..6b089723 100644
--- a/skills/mono/references/best_practices/06-data-analytics.md
+++ b/skills/mono/references/best_practices/06-data-analytics.md
@@ -9,5 +9,5 @@
| create-aitable-record | **写入路径分流**(关键决策):
• **追加到已有表**(场景:把这批数据加到「成员表」)→ `python scripts/import_records.py data.csv\|data.json [batch_size]`(走 `record create`,需要列名匹配字段名)
• **新建表**(场景:把这个 Excel 变成多维表)→ `python scripts/aitable_import_via_task.py ` 或 `dws aitable import upload --base-id --file ./x.xlsx` + `dws aitable import data --import-id `(CLI 已内置 OSS PUT 清空 Content-Type + 同步轮询,**禁止自己写 PUT**)
• **单条/少量手动**:1. `aitable base search` → `baseId`/`tableId`;2. `aitable field get` → `fieldId` 与类型;3. `aitable record create --records '[{"cells":{"":"值"}}]'` |
| update-aitable-record | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`
2. **取目标 record**:
• 已知少量 recordId → `aitable record query --record-ids `
• 按条件批量改 → `aitable record query --base-id --table-id --filters '' --all`(**用 --all 防止漏改**)
3. **先展示让用户确认要改的 record 列表**
4. `aitable record update --base-id --table-id --records '[{"recordId":"","cells":{...}}]'`(单次 ≤30 条) |
| search-aitable-template | 1. `aitable template search --query "<关键词>"` → 取 `templateId`
2. 用户选定
3. `aitable base create --name "<表格名>" --template-id ` → 取 `baseId` |
-| export-aitable-to-xlsx | 1. `aitable base search --query "<表格名>"` → 取 `baseId`
2. **按场景选 scope**:
• 全表+附件 → `aitable export data --base-id --scope all --export-format excel_and_attachment --output ./.xlsx`
• 单表(仅 xlsx)→ `--scope table --table-id --export-format excel`
• 单视图 → `--scope view --table-id --view-id `
3. CLI 内置渐进式退避轮询 + 自动落盘,**不要自己写 GET downloadUrl**
4. 大表超时(默认 5 分钟):加 `--timeout-sec 900` 或拿到 `taskId` 后 `aitable export data --base-id --task-id --output ./.xlsx` 续等
5. 与悟空脚本路径并存:复杂场景(多 base 批量 / 按视图组合)请用 `python scripts/aitable_export_via_task.py --scope all\|table\|view` |
+| export-aitable-to-xlsx | 1. `aitable base search --query "<表格名>"` → 取 `baseId`
2. **按场景选 scope**:
• 全表+附件 → `aitable export data --base-id --scope all --export-format excel_and_attachment --output ./.xlsx`
• 单表(仅 xlsx)→ `--scope table --table-id --export-format excel`
• 单视图 → `--scope view --table-id --view-id `
3. CLI 内置渐进式退避轮询 + 自动落盘,**不要自己写 GET downloadUrl**
4. 大表超时(默认 5 分钟):加 `--timeout-sec 900` 或拿到 `taskId` 后 `aitable export data --task-id --output ./.xlsx` 续等
5. 与悟空脚本路径并存:复杂场景(多 base 批量 / 按视图组合)请用 `python scripts/aitable_export_via_task.py --scope all\|table\|view` |
| primary-doc-from-record | 当 AI 表格用文档作主键字段时,从 record 跳到其主文档:
1. `aitable record query` 取 `recordId`
2. `aitable base get-primary-doc-id --base-id --table-id --record-id ` → 取主键文档 `dentryUuid`
3. 凭 `dentryUuid` 调 `doc read --node ` 拿文档内容 |
diff --git a/skills/mono/references/best_practices/08-directory.md b/skills/mono/references/best_practices/08-directory.md
index 16ab9fc0..95bfc5d9 100644
--- a/skills/mono/references/best_practices/08-directory.md
+++ b/skills/mono/references/best_practices/08-directory.md
@@ -5,7 +5,7 @@
## 专用规则(#8 非 lite 步骤必守)
-- **角色/职责类查人优先 aisearch**:用户说"角色为XX的员工/XX角色的人员""所有主管/财务/HR/总经理""谁负责 XX"等角色/职责类查询时,**优先** `aisearch person --keyword "<角色或职责>" --dimension duty`(`contact label` 角色查询命令已下线,不再可用)。
+- **角色类查人优先 label**:用户说"角色为XX的员工/XX角色的员工/XX角色的人员""所有主管/主管理员/财务/HR/总经理"等角色类型人员时,**优先** `contact label list` 获取全部角色 → 匹配目标角色 → `contact label list-members --id `;若用户明确指定了角色名称(如"角色为总经理"),则先用 `contact label get --names ` 精确匹配,**若精确匹配无结果,降级 `label list` 模糊匹配**(如用户说"管理员"可匹配到"主管理员"和"子管理员")。
- **脚本优先**:按部门拉成员**优先** `python scripts/contact_dept_members.py --query "<部门名>"`(`--dry-run` / `--format json`);失败再 `dept search` → `dept list-members --depts`。
- **详情链路**:用户要子部门、职位、联系方式、汇报关系等,在 `user search` 之后**必须**再 `contact user get --ids `;禁止仅用 search 的浅表字段交差。
- **`user get` 后部门仍空**:不得过早结束或只建议用户去 App;须在 CLI 能力内尝试 **用户点名的部门** `dept search` + `dept list-members` 等与 `userId` 交叉核对,再结构化汇总「返回中有哪些字段 / 哪些为空及可能原因」。
@@ -20,7 +20,7 @@
## 与其他场景消歧
-- **按角色/职位类型查人(主管/管理员/财务等)** → 优先 `aisearch person --dimension duty`(按职责维度找人);`contact label` 角色查询命令已下线。
+- **按角色/职位类型查人(主管/管理员/财务等)** → 优先 `contact label list` + `label list-members`;label 精确命中角色维度,返回完整名单;aisearch 是语义模糊搜索不保证完整性。
- **搜人/找人/找同事/查工号/查手机号** → 首选 **`aisearch person`**(AI 语义搜索,支持姓名/部门/职责/上下级/手机号/工号维度),见 `aisearch`(开源版未引入,悟空内部产品)。
- **需要 userId 做后续操作 / 按手机号查 / 按 userId 查详情** → `contact`(精确查询)。
- **纯查部门与子部门成员 / 验证归属 / 组织关系** → `contact`。
@@ -31,7 +31,7 @@
| Recipe | 步骤 |
|--------|------|
-| `lookup-role-members` | 1. `aisearch person --keyword "<角色或职责>" --dimension duty` → 按职责维度找人(`contact label` 已下线) |
+| `lookup-label-members` | 1. `contact label list` → 浏览全部角色,匹配目标角色的 labelId
2. `contact label list-members --id ` → 该角色下的成员列表 |
| `search-user-by-mobile` | 1. `contact user search-mobile --mobile "<手机号>"` → 按需 `contact user get --ids ` |
| `lookup-dept-id` | 1. `contact dept search --query "<部门关键词>"` → 回显 `deptId`(多命中须消歧) |
| `list-subdepts` | 1. 已有父 `deptId` → `contact dept list-children --dept <父deptId>` 直接取直属子部门列表
2. 只有部门名 → 先 `lookup-dept-id` 取 `deptId`,再 `list-children` |
diff --git a/skills/mono/references/best_practices/_common/conventions.md b/skills/mono/references/best_practices/_common/conventions.md
index 8125a295..502df690 100644
--- a/skills/mono/references/best_practices/_common/conventions.md
+++ b/skills/mono/references/best_practices/_common/conventions.md
@@ -1,6 +1,7 @@
-# 业务域通用规范
+# 通用规范
-> 仅服务本仓库已迁入的文档与 AI 表格行动指南。安全门控、危险操作确认、`--format json` 等已在根 [SKILL.md](../../../SKILL.md) 中定义,此处不重复。
+> full recipe 执行时的共享规范。安全门控、危险操作确认、`--format json` 等已在根 [SKILL.md](../../../SKILL.md) 中定义,此处不重复。
+> Recipe 元规范(YAML frontmatter、命名、三层架构)见 [recipe-conventions.md](recipe-conventions.md)。
## 批量查询规范
@@ -9,7 +10,8 @@
| 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** |
| 2 | **翻页**:分页接口须拉全直至无更多 |
| 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 |
-| 4 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` |
+| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 |
+| 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` |
## 多源并行采集(公共模式)
@@ -18,7 +20,7 @@
- 同条 Shell:`&` 并行 + `wait`;分页须采全。
- 只保留与主题相关的数据,无关丢弃。
- 有批量详情接口优先;否则并行拉详情(见上表 #1)。
-- 具体采哪些产品列表由对应 **行动指南 recipe** 与 [SKILL 产品参考](../../../SKILL.md) 决定;不要引入本文档未覆盖的产品路线。
+- 具体采哪些产品列表由对应 **行动指南 recipe** 与 [SKILL 产品参考](../../../SKILL.md) 决定。
## 字段术语与 ID 传递
@@ -26,10 +28,18 @@
| 字段 | 来源 | 传递给 |
|------|------|--------|
+| `taskUuid` | `minutes list` | `minutes get summary/info/batch --id(s)` |
+| `userId` | `aisearch person` / `contact user search` / `contact dept list-members` | `contact user get --ids`、`todo --executors`、`calendar --users` |
+| `deptId` | `contact dept search` | `contact dept list-members --depts`;多子部门时对每个子部门分别 `dept search` 取 id |
| `nodeId` | `doc search` | `doc read/update/copy/move/rename --node` |
| `nodeId` | `doc list` 中的 folder 类型节点 / `doc folder create` | `doc list --folder`、`doc create --folder`、`doc upload --folder`、`doc copy/move --folder` |
+| `eventId` | `calendar event list` | `calendar event get/update --id` |
+| `processInstanceId` | `oa approval list-*` | `oa approval detail/approve --instance-id` |
+| `openConversationId` | `chat search` | `chat message list/send --group` |
+| `todoTaskId` | `todo task list` | `todo task update/done --task-id` |
+| `reportId` | `report inbox list` / `report outbox list` | `report entry get/stats --report-id` |
| `baseId` / `tableId` | `aitable base search` | `aitable record query --base-id --table-id` |
| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download --file-id`、`drive list/mkdir/upload --parent-id` |
-| `workspaceId` | `wiki space search/list/create` | `doc list/search/create --workspace`、`wiki member * --workspace` |
+| `dentryId` | `drive info` 的数字字段 | 仅用于 `chat message send --dentry-id` |
-**ID 边界硬约束**:遇到 `drive --parent-id`、`doc --folder`、`doc --node` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `doc list` / `doc search` 获取正确 ID,不能把该数字直接代入后续命令。
+**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --parent-id`、`doc --folder`、`doc --node` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `doc list` / `doc search` 获取正确 ID,不能把该数字直接代入后续命令。
diff --git a/skills/mono/references/best_practices/lite-recipes.md b/skills/mono/references/best_practices/lite-recipes.md
index 3ab28f22..49caffb0 100644
--- a/skills/mono/references/best_practices/lite-recipes.md
+++ b/skills/mono/references/best_practices/lite-recipes.md
@@ -146,6 +146,17 @@ dws minutes list shared --query "ROI" --format json
- **热词管理**:`minutes hot-word add --words "词1,词2"` / `minutes hot-word list`
- **思维导图**:`minutes mind-graph create --id ` → `mind-graph status --id ` 轮询至完成
+### minutes-tag(标签/分组查询)
+
+- 查询标签列表:`minutes tag list` → 返回用户在听记页面创建的所有标签/分组(含 tagId 和名称)
+- 按标签查听记:`minutes tag query --tag-id [--limit 20] [--cursor ]`
+ - tagId 来自 `tag list` 返回值,不可编造
+ - 支持分页,`--cursor` 传入上一次返回的 nextToken
+
+**典型链路**:用户说"帮我看看'周会'标签下的听记" →
+1. `dws minutes tag list --format json` → 按名称匹配找到 tagId
+2. `dws minutes tag query --tag-id --format json`
+
### minutes-permission(权限管理)
- 添加成员:`minutes permission add --ids --member-uids --policy 4`
@@ -176,6 +187,17 @@ dws minutes upload cancel --session-id --format json
| 案例 6 | 通讯录+部门+转写三路印证 | Step 3 画像 + Step 4 `contact user search` 并发 → 置信度 ≥70% → 确认 → 替换 |
| 案例 7 | grep 花名误判未参会 | **禁止**在转写文本里 grep 人名判参会;**必须**调 `contact user search` |
| 案例 8 | 听记类 query 不走 dws | **禁止**用 session_search/browser_use/activity:search 替代 dws;模糊请求先 `list mine` |
+| 案例 9 | 按标签筛选听记 | `tag list` → 按名称匹配 tagId → `tag query --tag-id `;**禁止**编造 tagId |
+
+### 听记取数深度约束(0609 点踩 case 提炼)
+
+> 详细说明见 [minutes.md](../products/minutes.md)。
+
+- **转写原文硬约束**:用户诉求含「聚焦原话/逐字/沟通细节/具体讨论了什么」等词时,**必须先调 `get transcription` 翻页拉全**,禁止仅凭 summary 出稿
+- **数据源下钻**:听记维度**必须 `get summary`(或 `get transcription`)读正文**,严禁只取标题列表;scope 用 `all`;空时换窗重试或标注
+- **听记链接解析**:聊天消息中遇到听记链接(`flash_minutes_detail`/`SHANJI`)→ 解析 `minutesId` → 调 `minutes get summary/transcription`,禁止把链接降级为关键词
+- **忠实性约束**:源数据无某要素(行动项/责任人/数字)时禁止生成;统计字段基于实际取数计数,不得编造
+- **多源全覆盖**:用户枚举多数据源时每个来源都必须调对应工具;瞬时错误重试;如实声明缺失来源,禁编无来源数字
### 间接意图识别铁律
diff --git a/skills/mono/references/global-reference.md b/skills/mono/references/global-reference.md
index 8089ba7f..edfc7374 100644
--- a/skills/mono/references/global-reference.md
+++ b/skills/mono/references/global-reference.md
@@ -9,10 +9,6 @@ dws auth login
# 查看状态
dws auth status
-# 沙箱间迁移登录态(Linux,含 refresh token)
-dws auth export -o dws-auth.tar.gz
-dws auth import -i dws-auth.tar.gz
-
# 退出
dws auth logout
@@ -101,7 +97,7 @@ dws recovery finalize --event-id --outcome recovered|failed|handoff -
| 变量 | 说明 |
|------|------|
| `DWS_CONFIG_DIR` | 覆盖默认配置目录 |
-| `DWS_SERVERS_URL` | 自定义服务发现端点 |
+| `DWS__MCP_URL` | 本地开发时覆盖指定产品 MCP endpoint |
| `DWS_CLIENT_ID` | 覆盖 OAuth Client ID (DingTalk AppKey) |
| `DWS_CLIENT_SECRET` | 覆盖 OAuth Client Secret (DingTalk AppSecret) |
diff --git a/skills/mono/references/intent-guide.md b/skills/mono/references/intent-guide.md
index a572736b..532434e0 100644
--- a/skills/mono/references/intent-guide.md
+++ b/skills/mono/references/intent-guide.md
@@ -6,29 +6,121 @@
| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 |
|-----------|----------|--------|--------|------|
-| "搜一下 OAuth2 接入文档" | 搜索开发文档 | `devdoc` | — | 搜索开放平台技术文档,不是钉钉内部内容 |
-| "查询开放平台 API 错误码/字段说明" | 搜索开放平台接口文档 | `devdoc` | — | 查 API 用法和错误码 |
-| "查工作台应用 appXYZ 的详情" | 查询钉钉工作台应用 | `workbench app` | — | `appXYZ/app001` 是工作台 app id |
-| "帮我创建一个应用" | 应用类型不明确 | 先追问 | — | 缺少工作台/AI/MCP 等上下文,不能默认 |
-| "做一个 AI 应用" | AI 应用/智能体需求不明确 | 先追问或转对应能力 | — | 需求不明确时不默认走任何创建命令 |
-| "创建 MCP 服务并配置 HSF tool" | MCP 平台工具配置 | OpenDev MCP 平台流程 | — | 这是 connector/tool 配置 |
-| "帮我建一个项目跟踪表" | 创建数据表格 | `aitable` | `todo` | 涉及结构化数据/行列操作,不是个人待办 |
-| "帮我记一下明天要做的事" | 创建个人待办 | `todo` | `aitable` | 个人待办提醒,非数据表 |
-| "帮我建一个明天下午的日程" | 日历日程 | `calendar` | — | 日历日程管理(可含参与者/会议室)|
-| "帮我看看收到的日报" | 日志收件箱 | `report` | `todo` | 钉钉日志系统(日报/周报),不是待办 |
-| "帮我创建一个待办提醒" | 个人待办 | `todo` | `report` | 个人任务提醒,不是日志汇报 |
-| "帮我提交请假审批" | 发起审批 | `oa` | — | 审批流程,不是待办或日志 |
+| "搜一下 OAuth2 接入文档" | 搜索开发文档 | `devdoc` | `doc search` | 搜索开放平台技术文档,不是钉钉内部内容 |
+| "帮我建一个项目跟踪表" | 创建数据表格 | `aitable` | `doc` / `sheet` | 涉及结构化数据/行列操作,不是富文本文档或电子表格 |
+| "帮我写个项目周报" | 创建钉钉文档 | `doc` | `aitable` | 富文本内容创作,不是数据表 |
+| "参照这个生成同样的 / 按模板生成 / 复刻 X / 同样的模板 X 月份的" + 已有 alidocs URL | 模板保形生成同形态变体 | `doc copy + doc rename + doc block update` → 见 [best_practices/04-document.md `template-based-generation`](./best_practices/04-document.md#template-based-generation) | `doc read + doc create`(重写链) | adoc → markdown 是有损投影,read+create 会丢行高/单元格背景色/字号;copy 在 adoc 层保形复制后只在副本上局部修改 |
+| "创建一个电子表格" | 创建表格文档 | `sheet` | `aitable` | Excel 式表格/单元格操作,不是多维表记录 |
+| "帮我读一下表格 A1:D10 的数据" | 读取单元格数据 | `sheet` | `aitable` | 按单元格区域读写,不是按记录查询 |
+| "这个 alidocs 表格链接帮我看下"(粘贴原始 URL) | 先 probe 节点类型 | `dws doc info --node` → 按 `extension` 路由 | 直接调 `sheet` | `alidocs/i/nodes/{id}` 可能是文档/axls/able/xlsx 等,禁止凭 URL 猜类型 |
+| "读一下这个 xlsx 的数据" / xlsx 节点链接 | 下载本地表格文件 | `dws drive download --node` | `sheet range read` | xlsx / xls / xlsm / csv 是上传的本地文件(`contentType=DOCUMENT`),sheet 命令只支持在线表格,必须下载后本地解析 |
+| "把这个在线表格导出为 xlsx 文件" | 在线表格格式转换 | `dws sheet export` | `dws drive download` | `export` 是 axls → xlsx 的导出转换;`download` 只能下载已有的 xlsx 节点 |
+| "帮我记一下明天要做的事" | 创建个人待办 | `todo` | `doc` / `aitable` | 个人待办提醒,非文档内容或数据表 |
+| "给自己留一个明天下午的时间块/建个个人日程" | 创建个人日程 | `calendar event create` | `todo` | 个人 schedule 仍属于日历事件,不是待办 |
+| "帮我把这个文件传到网盘" | 钉盘上传 | `drive upload` | — | 文件上传是存储层操作,归 drive |
+| "上传文件到钉盘/我的文件" | 钉盘上传 | `drive upload` | — | 提到"钉盘/网盘/我的文件"→ drive |
+| "上传文件"(未指定目标) | 默认钉盘 | `drive upload` | — | 未明确目标时默认上传到钉盘 |
+| "帮我看看知识库里的文件" | 知识库节点列表 | `wiki node list --workspace` | `drive list` | 明确"知识库"上下文 → wiki node list |
+| "列出钉盘团队空间" | 列出钉盘空间 | `wiki space list --type orgSpace` | `drive list-spaces` | 空间管理归 wiki,drive list-spaces 已 deprecated |
+| "在知识库里搜方案" | 空间内搜索 | `wiki node search --workspace` | `drive search` | 指定了空间上下文 → wiki node search |
+| "搜一下有没有叫XX的文件" | 全局搜索 | `drive search` | `wiki node search` | 未指定空间 → drive search 全局聚合搜索 |
+| "在知识库里创建一个文档" | 创建空文件实体 | `wiki node create --type adoc` | `doc create` | 空间内创建节点归 wiki;doc create 是向已有文档写入内容,不是创建文件节点 |
+| "帮我建一个明天下午的日程" | 日历日程 | `calendar` | — | 日历日程管理(可含参与者/会议室);视频会议(conference)当前开源 CLI 不支持 |
+| "明早 9 点提醒我提交周报" | 创建个人待办,但需先声明 reminder 边界 | `todo` | `calendar` | todo 当前只支持 dueTime 截止时间,不支持独立精确 reminder |
| "帮我建一个项目群" | 创建群聊 | `chat group create` | — | 群聊管理,不是日历日程 |
| "把张三拉进群" | 添加群成员 | `chat group members add` | — | 先查 userId,再添加 |
-| "让机器人在群里发个通知" | 机器人群发 | `chat message send-by-bot` | `chat message send-by-webhook` | 企业内部机器人发消息,需 robotCode |
-| "通过 Webhook 发告警到群里" | Webhook 告警 | `chat message send-by-webhook` | `chat message send-by-bot` | 自定义机器人 Webhook,需 token |
+| "通知群里的人都来开会" | 个人身份群发 | `chat message send` | `chat message send-by-bot` | 以个人身份向群发消息 |
+| "让机器人每天推送日报" | 机器人定时推送 | `chat message send-by-bot` | `chat message send` | 需要机器人身份定期发送 |
| "给张三发一条机器人单聊消息" | 机器人单聊 | `chat message send-by-bot --users` | — | 机器人批量单聊,先查 userId |
+| "CPU 超过 90% 自动告警" | Webhook 告警 | `chat message send-by-webhook` | `chat message send-by-bot` | 系统告警场景,需自定义 Webhook |
+| "帮我看看收到的日报" | 收到的日志 | `report` | `doc` | 钉钉日志系统(日报/周报),不是文档 |
+| "帮我创建一个待办提醒" | 个人待办 | `todo` | `report` | 个人任务提醒,不是日志汇报 |
+| "拉取一下上周项目群的聊天记录" | 拉取会话消息 | `chat message list` | — | 拉取指定群聊的消息列表 |
+| "看看张三发给我的消息" | 按发送者查询消息 | `chat message list-by-sender` | `chat message list --user` | 用户未明确说"单聊"时优先用 list-by-sender(跨单聊/群聊) |
+| "拉取和张三的单聊记录" | 拉取单聊消息 | `chat message list --user` | `chat message list-by-sender` | 用户明确说"单聊"时用 list --user |
+| "谁@了我/查看提及我的消息" | 查询@我的消息 | `chat message list-mentions` | `chat message list-all` | 都是跨会话时间范围查询,但 list-mentions 只返回@我的消息 |
+| "查看我今天的所有消息" | 全量会话消息 | `chat message list-all` | `chat message list` | 用户未指定具体会话时用 list-all(跨所有会话),指定了具体群或人时用 list |
+| "搜一下消息里的changefree链接" | 消息搜索 | `chat message search-advanced`(首选) | `chat search` | 推荐首选 search-advanced,它是 search 的严格超集(keyword 可选、支持多群、可叠加发送者/at 维度) |
+| "按发送者搜索/指定多个群搜索/多维度搜消息" | 多维度搜索消息 | `chat message search-advanced`(首选) | `chat message search` | 推荐首选,支持关键词、发送者、@我、多个会话等维度组合 |
+| "消息发没发成功/查询消息发送状态" | 查询消息发送状态 | `chat message query-send-status` | — | 需要 send 返回的 openTaskId |
+| "撤回我发的消息/撤回消息" | 撤回个人消息 | `chat message recall` | `chat message recall-by-bot` | recall 撤回个人消息,recall-by-bot 撤回机器人消息 |
+| "未读消息会话/未读会话列表/我的未读会话" | 未读会话列表 | `chat message list-unread-conversations` | `chat message read-status` | list-unread-conversations 查哪些会话有未读;read-status 查具体消息的已读状态 |
+| "谁看了这条消息/消息已读未读/查读状态" | 查询消息已读状态 | `chat message read-status` | `chat message list-unread-conversations` | read-status 查具体消息的已读人员;list-unread-conversations 查未读会话列表 |
+| "我和风雷的共同群/我们都在哪些群" | 搜索共同群 | `chat search-common` | `chat search` | search-common 按人员搜共同群,chat search 按群名搜索 |
+| "查看会话分组/自定义分组" | 获取会话分组 | `chat category list` | — | 获取用户自定义的会话分组列表 |
+| "某个分组下有哪些会话" | 分组下会话列表 | `chat category list-conversations` | — | 需先通过 category list 获取分组 ID |
+| "创建会话分组/新建分组" | 创建自定义分组 | `chat category create` | — | 需指定 --title 分组名称 |
+| "删除会话分组/移除分组" | 删除自定义分组 | `chat category delete` | — | 需指定 --category-id |
+| "重命名分组/修改分组名称" | 更新分组名称 | `chat category rename` | — | 需指定 --category-id 和 --title |
+| "把会话加到分组/将群移入分组" | 会话加入分组 | `chat category add-conv` | `chat category remove-conv` | add-conv 加入;remove-conv 移出 |
+| "把会话从分组移出/从分组中删除" | 会话移出分组 | `chat category remove-conv` | `chat category add-conv` | remove-conv 移出;add-conv 加入 |
+| "根据群号查群信息/群号转openConversationId" | 群号查群聊信息 | `chat group get-by-group-id` | `chat search` | 已知数字群号时直接查;用户发消息只给了群号时,先用此工具将群号转为 openConversationId |
+| "我创建的群/我管理的群/我是群主的群/我当管理员的群" | 拉取我创建/管理的群 | `chat group list-my-groups` | `chat search` | list-my-groups 按角色(群主/管理员)过滤当前用户的群;chat search 按关键词搜全部群 |
+| "引用消息回复/回复那条消息" | 引用回复消息 | `chat message reply` | `chat message send` | reply 引用指定消息回复;send 普通发消息不引用 |
+| "转发消息/把消息转到另一个群" | 转发单条消息 | `chat message forward` | `chat message send` | forward 转发已有消息到目标会话;send 是发新消息 |
+| "置顶会话/取消置顶" | 设置/取消会话置顶 | `chat set-top` | `chat list-top-conversations` | set-top 设置或取消置顶;list-top-conversations 查看置顶列表 |
+| "全员禁言/群禁言" | 全员禁言或解除 | `chat group-mute` | `chat group-mute-member` | group-mute 全员禁言或解除;group-mute-member 指定成员禁言 |
+| "禁言某人/指定成员禁言" | 指定成员禁言 | `chat group-mute-member` | `chat group-mute` | group-mute-member 指定成员禁言/解禁;group-mute 全员禁言 |
+| "设管理员/取消管理员" | 设置群管理员 | `chat group set-admin` | `chat group members add` | set-admin 设置或取消管理员角色;加人入群用 members add |
+| "退群/退出群聊/离开群" | 退出群聊 | `chat group quit` | `chat group members remove` | quit 是当前用户自己退群;members remove 是管理员踢别人 |
+| "改群头像/更新群头像" | 更新群头像 | `chat group update-icon` | `chat group rename` | update-icon 改群头像;rename 改群名称 |
+| "改群设置/群设置开关/群权限/入群许可/禁止私聊" | 更新群设置 | `chat group update-settings` | `chat group set-admin` | update-settings 改群功能开关;set-admin 是管理员角色操作 |
+| "改群昵称/设置群昵称/我在群里的名字" | 设置个人群昵称 | `chat group update-nick` | `chat group rename` | update-nick 改自己的群昵称;rename 改群名称 |
+| "群备注/给群加备注/修改群备注" | 设置群备注 | `chat group update-alias` | `chat group rename` | update-alias 设置仅自己可见的备注;rename 改群名称全员可见 |
+| "隐藏会话/隐藏群聊/隐藏对话" | 隐藏会话 | `chat hide` | `chat mute` | hide 隐藏会话不显示;mute 是免打扰但仍显示 |
+| "关闭@所有人通知/屏蔽@all/不接收@所有人" | 关闭 @所有人提醒 | `chat mute-at-all` | `chat mute` | mute-at-all 仅屏蔽 @所有人;mute 是整个会话免打扰 |
+| "关闭红包通知/屏蔽红包/不接收红包提醒" | 关闭红包提醒 | `chat mute-red-envelope` | `chat mute` | mute-red-envelope 仅屏蔽红包;mute 是整个会话免打扰 |
+| "解散群/解散群聊" | 解散群聊 | `chat group dismiss` | `chat group quit` | dismiss 是群主解散整个群(不可逆);quit 是当前用户自己退群 |
+| "新成员看历史/历史消息可见范围" | 设置新成员可见历史消息 | `chat group set-history` | `chat group update-settings` | set-history 控制新成员入群后可见历史消息范围;update-settings 是其他群功能开关 |
+| "群里有哪些机器人/查看群机器人/列出群机器人" | 查看群内机器人列表 | `chat group bots` | `chat group members` | bots 只列机器人;members 列普通群成员 |
+| "从群里移除机器人/踢机器人" | 移除群内机器人 | `chat group members remove-bot` | `chat group members add-bot` | remove-bot 通过 openBotId 移除;add-bot 通过 robotCode 添加 |
+| "批量查群成员详情/按ID查群成员/查看指定成员信息" | 批量查询群成员详情 | `chat group members list-by-ids` | `chat group members` | list-by-ids 根据 openDingTalkId 列表查询指定成员详情;members 分页查询全部成员列表 |
+| "标记未读/设为未读/会话标未读" | 标记会话为未读 | `chat mark-unread` | `chat clear-red-point` | mark-unread 标记为未读;clear-red-point 清除红点(相反操作) |
+| "清除红点/已读/取消未读" | 清除会话红点 | `chat clear-red-point` | `chat mark-unread` | clear-red-point 清除单个会话红点;mark-unread 标记未读(相反操作) |
+| "全部已读/一键清除所有未读/红点清零" | 清除所有会话红点 | `chat clear-all-red-point` | `chat clear-red-point` | clear-all-red-point 清除所有会话红点;clear-red-point 只清单个会话 |
+| "所有会话/全部会话列表/我的会话" | 分页获取全部会话 | `chat list-all-conversations` | `chat list-top-conversations` | list-all-conversations 返回所有会话;list-top-conversations 仅返回置顶会话 |
+| "清空聊天记录/删除会话消息/清空消息" | 清空会话聊天记录 | `chat clear-messages` | `chat clear-red-point` | clear-messages 清空聊天记录;clear-red-point 仅清除未读红点 |
+| "标记已读/消息已读/读了" | 标记消息已读 | `chat mark-read` | `chat clear-red-point` | mark-read 标记指定消息及之前的消息为已读;clear-red-point 仅清除红点不标记消息 |
+| "我加入的所有群/拉取全部群列表/我的群" | 分页拉取所有群 | `chat group list-all` | `chat group list-my-groups` | list-all 返回用户加入的所有群;list-my-groups 仅返回用户作为群主/管理员的群 |
+| "入群申请记录/谁申请入群/群申请列表/查看入群审批" | 拉取入群验证记录 | `chat group list-join-validations` | `chat group list-all` | list-join-validations 拉取入群验证/审批记录;list-all 拉取群列表 |
+| "通过入群申请/拒绝入群/审批入群/同意加群" | 审批入群验证 | `chat group audit-join-validation` | `chat group list-join-validations` | audit-join-validation 执行审批操作;list-join-validations 仅查询记录 |
+| "搜索机器人/找机器人/查机器人/帮我找XXX机器人" | 搜索全部可用机器人 | `chat bot find` | `chat bot search` | find 返回全部可用机器人(含他人/官方),额外返回 openDingTalkId(可用于给机器人发单聊消息);search 仅返回我创建的 |
+| "给机器人发单聊/给机器人发消息/跟机器人聊天" | 给机器人发单聊消息 | `chat bot find` → `chat message send --open-dingtalk-id` | `chat bot search` | 必须先用 find 拿 openDingTalkId(search 没有此字段),再用 send --open-dingtalk-id 发单聊 |
+| "我创建的机器人/我的机器人/我自己的机器人/查看我的机器人" | 搜索我创建的机器人 | `chat bot search` | `chat bot find` | search 仅返回当前用户自己创建的机器人(返回 robotCode + robotName,无 openDingTalkId);find 返回全部可用机器人 |
+| "合并转发/批量转发/合并转发多条消息" | 合并转发多条消息 | `chat message combine-forward` | `chat message forward` | combine-forward 合并多条为一条转发;forward 转发单条消息 |
+| "转发话题/转发话题消息/话题转发到另一个群" | 转发话题消息 | `chat message forward-topic` | `chat message forward` | forward-topic 专用于转发话题消息(需要话题ID);forward 转发普通单条消息 |
+| "发卡片消息/推送流式卡片" | 创建并推送流式卡片 | `chat message send-card` | `chat message send` | send-card 发流式卡片;send 发普通文本/Markdown 消息 |
+| "更新卡片/流式更新卡片" | 流式更新卡片内容 | `chat message update-card` | `chat message send-card` | update-card 更新已有卡片;send-card 创建新卡片 |
+| "钉住消息/Pin消息/置顶消息到会话" | 钉住消息 | `chat message set-pin-msg` | `chat set-top` | set-pin-msg 钉住单条消息(Pin);set-top 置顶整个会话 |
+| "取消钉住/Unpin消息" | 取消钉住消息 | `chat message unset-pin-msg` | `chat message set-pin-msg` | unset-pin-msg 取消钉住;set-pin-msg 设置钉住 |
+| "查看钉住的消息/Pin列表/钉住消息列表" | 拉取钉住消息列表 | `chat message list-pin-msg` | `chat message list` | list-pin-msg 只返回被钉住的消息;list 拉取全部消息 |
+| "置顶某条消息/把这条消息置顶" | 置顶消息 | `chat message set-top-msg` | `chat set-top` | set-top-msg 置顶会话内某条消息;set-top 置顶整个会话在列表中 |
+| "取消置顶消息/取消消息置顶" | 取消置顶消息 | `chat message unset-top-msg` | `chat message set-top-msg` | unset-top-msg 取消置顶;set-top-msg 设置置顶 |
+| "DING消息/查DING/DING历史" | 查询 DING 消息列表 | `ding message list` | `chat message list` | ding 是独立顶层命令;ding message list 查 DING 消息;chat message list 查普通聊天消息 |
+| "DING接收状态/谁收到了DING" | DING 接收状态 | `ding message receiver-status` | `chat message read-status` | ding 是独立顶层命令;receiver-status 查 DING 接收;chat message read-status 查普通消息已读 |
+| "发DING/DING通知" | 发送 DING 消息 | `ding message send` | `chat message send` | DING 是钉钉的强提醒(应用内/短信/电话),独立顶层命令;普通群消息用 chat |
+| "撤回DING" | 撤回 DING 消息 | `ding message recall` | `chat message recall` | DING 撤回独立命令;chat recall 是撤回普通聊天消息 |
+| "以我的名义发DING/个人发DING/用户身份DING" | 以用户身份发 DING | `ding message send-personal` | `ding message send` | send-personal 以用户身份发送,无需 robot-code;send 以机器人身份发送 |
+| "以我的名义撤回DING/个人撤回DING" | 以用户身份撤回 DING | `ding message recall-personal` | `ding message recall` | recall-personal 以用户身份撤回;recall 以机器人身份撤回 |
+| "消息转DING/把这条消息DING给某人/转发为DING" | 消息转 DING | `ding message send-by-message` | `ding message send-personal` | send-by-message 是将已有消息转为 DING,需指定原消息;send-personal 是直接发新 DING |
+| "把最近几次关于XX的会议汇总成报告" | 按主题汇总多次听记 | [05-reporting.md `generate-topic-report`](./best_practices/05-reporting.md) | [07-minutes.md `meeting-followup`](./best_practices/07-minutes.md) | meeting-followup 是单次会议听记跟进;多次会议按主题汇总属于工作汇报 |
+| "整理一下XX项目的所有讨论" | 跨源主题归档 | [05-reporting.md `generate-topic-report`](./best_practices/05-reporting.md) | [04-document.md `write-doc`](./best_practices/04-document.md) | write-doc 侧重单篇文档创作;按主题跨听记/群消息汇总属于工作汇报 |
+| "张三在哪个部门/查一下同事工号" | 通讯录精确查询 | `contact`(行动指南 [08-directory.md](./best_practices/08-directory.md)) | 汇报 / 文档 | 需要 userId、手机号、部门 ID 等精确信息时用 contact |
+| "研发部的详细信息/部门信息" | 查部门详情 | `contact dept get-info` | `contact dept list-members` | 查部门属性(ID、名称、人数)用 get-info;查成员列表用 list-members |
+| "研发部有多少人" | 查部门人数 | `contact dept get-info` | `contact dept list-members` | 问人数用 get-info(返回 memberCount);问有哪些人用 list-members |
+| "找一下张三/搜同事/找人" | AI搜人(首选) | `aisearch person` | `contact user search` | 搜人首选 aisearch,支持姓名/部门/职责/上下级维度;精确查 userId/手机号用 contact |
+| "五道的上级是谁/谁负责XX/XX的下属有谁" | AI语义搜人 | `aisearch person` | `contact` | 涉及上下级、职责、负责人等语义维度搜索,用 aisearch |
+| "222020这个工号是谁/查工号" | 按工号搜人 | `aisearch person --dimension jobNumber` | `contact` | 工号查人走 aisearch,dimension=jobNumber |
+| "13800138000是谁/查手机号" | 按手机号搜人 | `aisearch person --dimension phone` | `contact` | 手机号查人走 aisearch,dimension=phone |
+| "搜一下智能化方案/最近 OKR 相关邮件/最近发版相关消息" | 搜企业知识内容 | `aisearch enterprise` | `doc search` / `mail search` / `chat message search` | 跨文档、消息、日程、听记、邮件等企业内容语义检索走 enterprise;具体 `queries/types/time-range` 抽槽见 `aisearch.md` |
+| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注"谁对什么做过什么",走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` |
+| "查/提交 请假/加班/补卡 审批单" | 考勤业务审批单 | `attendance approve`(查询走 `attendance approve list`;提交走 `attendance approve templates --type leave\|overtime\|repair-check`) | `oa approval list-pending` / `oa approval records` | 请假/加班/补卡/出差/外出 属于考勤业务审批单,按业务类型查询;`oa approval` 是通用 OA 审批中心,覆盖范围不同 |
---
## 典型场景详解
-### 1. aitable vs todo — 表格数据 vs 待办任务
+### 1. aitable vs doc vs sheet — 数据表格 vs 文档内容 vs 电子表格
**用 `aitable` 的场景**:
- "创建一个表格记录团队成员信息" — 结构化数据,有行列
@@ -37,106 +129,344 @@
- "用项目管理模板建一个表" — 模板创建
- 用户提到"多维表"、"Base"、"数据表"、"记录"
-**用 `todo` 的场景**:
-- "帮我记一下这周要做的事" — 个人任务管理
-- "创建一个待办提醒" — 任务提醒
+**用 `doc` 的场景**:
+- "帮我写个会议纪要" — 富文本内容创作
+- "看一下这个文档链接的内容" — 阅读文档
+- 用户提到"文档"、"知识库"、"写文档"
-**判断关键**:有没有行列/字段/记录概念?有→ `aitable`;个人任务清单 → `todo`
+**用 `sheet` 的场景**:
+- "创建一个电子表格" — 创建 Excel 式在线表格
+- "帮我读一下这个表格 A1 到 D10 的数据" — 按单元格区域读取
+- "在 B2 写入一个 SUM 公式" — 写入公式/值到单元格
+- "帮我看看这个表格有哪些工作表" — 工作表管理
+- 用户提到"电子表格"、"Excel"、"工作表"、"Sheet"、"单元格"、"公式"
+
+**三者判断关键**:
+- 有字段定义/记录增删改查/数据筛选 → `aitable`
+- 纯文本/Markdown/富文本编辑 → `doc`
+- 单元格区域读写/公式/多工作表 → `sheet`
+- 个人任务清单(无行列/字段概念)→ `todo`
+
+**易误判场景**:
+- "在知识库中新建一个表格" — 指在钉钉文档空间创建表格类型节点 → `wiki node create --type axls`(不是 `aitable`)
+- "帮我建个表记录项目进度" — 指创建结构化数据表 → `aitable`
---
-### 2. devdoc vs workbench — 应用泛词消歧
+### 1.1 xlsx vs axls — 本地表格文件 vs 在线电子表格
-`应用` 是泛词,不能直接等价为某个产品。
+alidocs 链接表面长得一样(`https://alidocs.dingtalk.com/i/nodes/{id}`),但节点类型完全不同。sheet 产品线只服务 axls(在线电子表格),xlsx / xls / xlsm / csv 等本地表格文件必须走 `dws drive download`,严禁错路由。
-**用 `devdoc` 的场景**:
-- "开放平台 API 怎么调用" — 接口文档
-- "错误码/字段说明/接口参数" — 文档检索
+用 `sheet` 的场景(axls,钉钉在线电子表格):
+- `dws doc info --node ` 返回 `contentType=ALIDOC` + `extension=axls`
+- 用户在钉钉文档空间直接"新建电子表格"得到的节点
+- 所有 sheet 子命令(`list` / `range read` / `range write` / `export` 等)仅服务这类节点
-**用 `workbench app` 的场景**:
-- "工作台应用/钉钉工作台上的应用"
-- "应用 app001/appXYZ 的详情"
+用 `dws drive download` 的场景(xlsx / xls / xlsm / csv 本地表格文件):
+- `dws doc info --node ` 返回 `contentType=DOCUMENT` + `extension=xlsx` / `xls` / `xlsm` / `csv`
+- 用户把本地 Excel 文件上传到文档空间得到的节点,本质是"文件 + 预览",非在线表格
+- sheet 命令直接调用会报错,必须先 `dws drive download --node ` 下载到本地再解析处理
-**先追问的场景**:
-- "创建应用/做个应用/查应用" 但没有工作台、文档、AI、MCP 等上下文
-- "AI 应用/智能体" 但没有说明是哪种产品能力
-- "MCP 服务/connector/HSF tool" 属于平台配置流程,不要转成任何创建命令
+判断关键:
+- 未知 alidocs URL → 必须先 `dws doc info --node --format json` 探测 `contentType` 与 `extension`
+- `contentType=ALIDOC` + `extension=axls` → `sheet`
+- `contentType=DOCUMENT` + `extension=xlsx` / `xls` / `xlsm` / `csv` → `dws drive download`
+- 用户说"把在线表格导出为 xlsx 文件" → `dws sheet export`(axls → xlsx 的格式转换,不是读取 xlsx)
-**判断关键**:是否在查 API 文档?是 → `devdoc`。是否在查钉钉工作台入口应用?是 → `workbench app`。仍不明确 → 追问,不调用写命令。
+易误判场景:
+- 用户粘贴一个 alidocs 链接说"读一下这个表格" — 不能直接调 `sheet range read`,必须先 probe 再按 `extension` 路由
+- 用户说"读一下这个 xlsx 文件里的数据" — 走 `dws drive download` 下载后本地解析,不要走 `sheet`
+- 用户说"把这个在线表格导出为 xlsx" — 走 `dws sheet export`,不要走 `dws drive download`(后者只能下载已有的 xlsx 节点,无法从 axls 生成)
+
+详见 [url-patterns.md](./url-patterns.md) 和 [sheet.md 适用范围](./products/sheet.md)。
---
-### 3. devdoc — 开发文档搜索
+### 2. devdoc vs drive search / wiki node search — 两种搜索
**用 `devdoc` 的场景**:
-- "API 调用报错 403 怎么解决" — 走 `devdoc error diagnose`
-- "requestId 15r6h45w0muec 为什么失败" — 走 `devdoc error diagnose --request-id ...`
+- "API 调用报错 403 怎么解决" — 开发调试问题
- "搜一下 OAuth2 接入文档" — 开放平台技术文档
- "CLI 命令出错了怎么办" — CLI 使用错误
-- 用户提到"开发"、"API"、"接口文档" → `devdoc article search`
-- 用户提到"调用错误"、"错误码"、"requestId"、"traceId" → `devdoc error diagnose`
+- 用户提到"开发"、"API"、"调用错误"
+
+**用 `drive search` / `wiki node search` 的场景**:
+- "在我的文档里搜一下'项目方案'" — 全局搜索用 `drive search`
+- 用户明确说"某个知识库里搜" — 空间内搜索用 `wiki node search --workspace`
+
+**判断关键**:搜开发文档→ `devdoc`;搜用户自己的文档→ `drive search`(全局)或 `wiki node search`(空间内)
---
-### 4. report vs todo — 日志 vs 待办
+### 3. drive vs doc vs wiki — 存储层 vs 内容层 vs 空间管理层
-**用 `report` 的场景**:
-- "帮我看看收到的日报" — 日志收件箱
-- "帮我写/提交今天的日报(钉钉日志模版)" — 先 `report template list` / `template get`,再 `report entry submit --contents-file .json`
-- "有什么日志模版" — 查看模版
-- "看看这个日志的已读统计" — 阅读状态
-- "我发过的日志有哪些" — 已发送列表 (`report outbox list`)
-- 用户提到"日报"、"周报"、"日志"
+> **两层模型判定口诀**:如果操作换一种文件类型还能成立,就是存储层(→ drive);操作只对特定格式有意义,就是内容层(→ doc/sheet);操作是对空间/节点的组织管理,就是空间管理层(→ wiki)。
-**用 `todo` 的场景**:
-- "记一下这周要做的事" — 个人任务管理
+**用 `drive`(存储层)的场景**:
+- "把这个 PDF 传到钉盘" — 上传文件(不关心格式)
+- "下载那个 Excel 附件" — 下载文件(不关心格式)
+- "看一下钉盘根目录有什么文件" — 浏览文件列表
+- "搜一下有没有叫季度汇报的文件" — 全局搜索文件实体 (`drive search`)
+- "把这个文档复制一份" — 复制文件实体
+- "把文件移到另一个文件夹" — 移动文件实体
+- "改一下文件名" — 重命名文件实体
+- "给张三加个编辑权限" — 权限管理
+- 用户提到"钉盘"、"网盘"、"上传"、"下载"、"搜文件"、"找文件"、"复制"、"移动"、"重命名"、"权限"
-**判断关键**:钉钉日志系统(日报/周报模版,含按模版创建汇报)→ `report`;任务清单→ `todo`
+**用 `doc`(内容层)的场景**:
+- "读一下这个文档的内容" — 读取文档 Markdown(仅 adoc 有意义)
+- "帮我写入一段话到文档里" — 编辑文档内容(仅 adoc 有意义)
+- "在第三段后面插入一个表格" — 块级编辑(仅 adoc 有意义)
+- "给这段内容加个评论" — 文档评论(仅 adoc 有意义)
+- "把这个文档导出为 docx" — 文档导出(当前仅 adoc 支持)
+- 用户提到"读文档内容"、"写文档"、"编辑文档"、"块级编辑"、"文档评论"、"导出文档"
+
+**用 `wiki`(空间管理层)的场景**:
+- "列出所有知识库" — 空间列表 (`wiki space list`)
+- "列出钉盘团队空间" — 钉盘空间列表 (`wiki space list --type orgSpace`)
+- "在产品知识库里搜一下方案" — 空间内搜索 (`wiki node search --workspace`)
+- "在知识库里创建一个空白文档" — 创建节点 (`wiki node create --type adoc`)
+- "把这个文件移到另一个知识库" — 跨知识库移动 (`drive move`)
+- "给知识库加个成员" — 成员管理 (`wiki member add`)
+- 用户提到"知识库"、"团队空间"、"空间成员"、"空间内搜索"、"列出空间"
+
+**判断关键(两层模型)**:
+- 操作不关心文件格式 → `drive`(存储层)
+- 操作仅对特定文档格式有意义 → `doc` / `sheet`(内容层)
+- 操作是对空间/节点的组织管理 → `wiki`(空间管理层)
+
+**搜索场景路由**:
+- "搜文件" / "找文件"(不指定空间) → `drive search`(全局聚合搜索)
+- "在某个知识库里搜" → `wiki node search --workspace `(空间内搜索)
+
+**创建场景路由**:
+- "在知识库里创建一个文档" → `wiki node create --type adoc`(创建空文件实体)
+- "帮我写一篇项目周报" → `doc create`(创建并写入内容)
+- "创建一篇文档并写入内容" → 先 `wiki node create` 再 `doc update`(先创建实体,再写内容)
+
+**列表场景路由**:
+- "列出钉盘文件" → `drive list`
+- "列出知识库里的文件" → `drive list --workspace ` 或 `wiki node list --workspace `
+- "列出所有空间" → `wiki space list`
---
-### 5. chat 内部 — 两种消息发送方式
+### 4. 视频会议 vs 日历日程 — conference 不在开源 CLI
+
+当前开源 CLI **不提供**视频会议(conference)命令。"预约视频会议/开个会/发起会议/会中控制(静音/共享屏幕/录制)/呼叫入会"等诉求,直接告知用户请在钉钉客户端完成,不要试探 `dws conference` 或用其他命令变通。
+
+**用 `calendar` 的场景**(这些都不是视频会议):
+- "明天下午安排个会" — 日程管理(可含会议室)
+- "给自己留两个小时写方案/建个个人日程" — 个人日历事件,仍用 `calendar event create`
+- "帮我约几个人开会" — 创建日程 + 添加参会人
+- "看看下午有没有空闲会议室" — 会议室管理
+- "帮我查一下同事有空吗" — 闲忙查询
+- 用户提到"日程"、"会议室"、"约会"
+
+---
+
+### 5. chat 内部 — 消息发送与撤回
+
+**用 `chat message send` 的场景**:
+- "帮我在群里发个消息提醒大家" — **个人身份**发群消息
+- "发个单聊消息给某人" — 个人身份发单聊:
+ - 已有 userId 时直接使用 `--user`;已有 openDingTalkId 时使用 `--open-dingtalk-id`
+ - 纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力,不需要先手动查询 openDingTalkId
+ - 富媒体消息(image/file)单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析为 openDingTalkId 后发送
+- "发张图片/截图/语音/视频/文件到群里" / "发张图给某某" — **统一一条命令**:`dws chat message send ... --msg-type file --file-path <本地路径>`,CLI 内部自动上传并发送,**任意扩展名(png/jpg/pdf/mp4/zip…)都走这条**
+- "发图片+文字说明" — 不要硬塞进一条命令;先发文件消息再补一条 `--text "..."` 即可
+
+```bash
+dws chat message send --group --msg-type file --file-path ./screenshot.png --format json
+dws chat message send --open-dingtalk-id --msg-type file --file-path ./report.pdf --format json
+```
+
+> ❌ 反模式:调 `dt_media_upload` / `extract_media_id.py` / `drive upload` / `drive download` 等前置工具再 `--msg-type image --media-id`。这是**旧链路**,仅当上游已持有 mediaId 才用;新场景一律 `--file-path` 直发,避免长链路与"空白图"现象。
+> 富媒体消息单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析为 openDingTalkId 后发送。
**用 `chat message send-by-bot` 的场景**:
-- "让机器人在群里发一条通知" — **机器人身份**发群消息
-- "给张三发一条机器人单聊消息" — 机器人批量单聊
+- "让机器人在群里发一条通知" — **机器人身份**发消息
+- "给张三发一条机器人单聊消息" — 机器人单聊
**用 `chat message send-by-webhook` 的场景**:
- "通过 Webhook 发告警到群里" — 自定义机器人 Webhook
- 用户有 Webhook Token
-**判断关键**:企业内部机器人→ `send-by-bot`(需 robotCode);有 Webhook Token→ `send-by-webhook`
+**用 `chat message recall-by-bot` 的场景**:
+- "撤回刚才机器人发的消息" — 需要 robot-code + processQueryKey
+
+用 `chat message recall` 的场景:
+- "撤回我刚发的消息" — 撤回以个人身份发送的消息,需要 openConversationId + openMessageId
+
+用 `chat message query-send-status` 的场景:
+- "消息发没发成功/查询消息发送状态" — 查询个人发送消息的状态,需要 send 返回的 openTaskId
+
+用 `chat message search-advanced` 的场景(推荐首选):
+- "按发送者搜索消息/指定多个群搜索/@我的消息多维度搜" — 支持关键词、发送者、@我、@指定人、多个会话等维度组合搜索
+- 替代关系:完全替代 `chat message search`(严格超集:keyword 可选 vs 必填,支持多群 vs 单群);大部分替代 `chat message list-by-sender`(--user/--users 覆盖按 userId 搜索发送者,--sender-ids 覆盖按 openDingTalkId 搜索)和 `chat message list-mentions`(--at-me 覆盖核心功能)
+- 不能替代:`chat message list-focused`(「特别关注人」是独立维度)
+- 默认使用 search-advanced,仅在上述不适用场景才降级到具体命令
+
+**不支持的场景**:
+- "撤回我刚发的消息"(但不知道消息 ID) — 需先通过消息拉取或搜索接口(如 `chat message list`、`chat message search-advanced` 等)获取 openMessageId,再调用 `chat message recall`
+
+判断关键:个人发→ `send`;机器人发→ `send-by-bot`;有 Webhook Token→ `send-by-webhook`;个人撤回→ `recall`;机器人撤回→ `recall-by-bot`;查发送状态→ `query-send-status`;消息搜索类意图优先路由到 `search-advanced`(推荐首选),仅在不适用时降级到具体命令
+
+---
+
+### 6. report vs doc vs todo — 日志 vs 文档 vs 待办
+
+**用 `report` 的场景**:
+- "帮我看看收到的日报" — 收件箱列表 (`report inbox list`)
+- "帮我写/提交今天的日报(钉钉日志模版)" — 先 `report template list` / `template get`,再 `report entry submit`
+- "有什么日志模版" — 查看模版 (`report template list`)
+- "看看这个日志的已读统计" — 阅读状态 (`report entry stats`)
+- "我发过的日志有哪些" — 发件箱列表 (`report outbox list`)
+- 用户提到"日报"、"周报"、"日志"
+
+**用 `doc` 的场景**:
+- "帮我写个项目总结文档" — 长文本创作(钉钉在线文档,非日志模版)
+
+**用 `todo` 的场景**:
+- "记一下这周要做的事" — 个人任务管理
+- "创建一个待办提醒" — 仍归 `todo`,但要先说明当前只有 dueTime 截止时间,没有独立 reminder schedule
+
+**判断关键**:钉钉日志系统(日报/周报模版,含按模版创建汇报)→ `report`;文档/知识库长文→ `doc`;任务清单→ `todo`
+
+---
+
+### 6.1 attendance approve vs oa approval — 考勤业务审批 vs 通用 OA 审批
+
+**用 `attendance approve` 的场景**(考勤业务审批单):
+- "上周谁请假了 / 某人近期的加班记录 / 外出出差单 / 补卡审批单" — `attendance approve list --types overtime,leave,trip,patch`(`trip` 同时覆盖出差与外出,两者合并为同一类,查询不再细分)
+- "查看考勤审批模板 / 帮我提交考勤审批" — `attendance approve templates --type leave|overtime|repair-check`(repair-check/patch=补卡)
+- 用户提到"请假单 / 加班单 / 出差单 / 补卡单 / 考勤审批"等考勤上下文
+
+**用 `oa approval` 的场景**(通用 OA 审批中心):
+- "我的待审批 / 待办审批 / 已审批记录" — `oa approval list-pending`
+- "某审批单详情 / 同意或驳回审批 / 撤销我发起的审批" — `oa approval detail/approve/reject/revoke`
+- "查业务审批记录 / 审批转交 / 添加评论与抄送" — `oa approval records/transfer/comment/cc`
+- 用户提到"报销 / 采购 / 用印 / 合同 等非考勤类审批"
+
+**判断关键**:
+- 审批主题明确是【请假 / 加班 / 外出 / 出差 / 补卡】 → `attendance approve`
+- 不限于考勤业务、面向"我上下游的审批任务"表述 → `oa approval`
+
+**提交审批单的边界**:
+- 提交考勤审批单走 `attendance approve templates --type leave|overtime|repair-check`(当前仅支持请假/加班/补卡三类模板;出差/外出的提交模板暂不支持,查询仍可用 `approve list --types trip` 覆盖),命令会返回审批表单的 submitUrl 跳转链接,由用户点击链接跳转到钉钉客户端的提交页面完成填写与提交。**展示链接时必须用 Markdown 可点击格式 `[表单名称](submitUrl)`,不要裸露 URL**。
+- 提交诉求的辅助查询:可用假期余额走 `attendance vacation balance`、历史已提交记录走 `attendance approve list`。
+- 任何场景下都**不要误用 `oa approval` 代替** —— 该命令组只能查/审/撤已存在的审批单,考勤业务审批单走考勤自己的逻辑便于区分。
+
+---
+
+### 7. 纯通讯录查询 vs 跨产品
+
+**仅查人/部门/成员/归属/组织关系**(没有「发消息、写文档、建待办」等第二动作)→ 属通讯录场景(行动指南 [08-directory.md](./best_practices/08-directory.md)),不要走汇报或文档场景。
+
+**多轮对话**:用户先说「搜 userId」再说「要详细资料」→ 仍属通讯录场景;第二步 **必须** 执行 `contact user get --ids`,禁止只用第一次 `user search` 的浅表字段交差。
+
+**与消息场景区分**:终点是「把消息发给某人」→ 消息场景([01-messaging.md](./best_practices/01-messaging.md));终点是「某人 userId/部门是什么」→ 通讯录。可先通讯录解析 ID 再发消息。
+
+**与发邮件、待办、日程混排**:先后顺序与口径见 [08-directory.md](./best_practices/08-directory.md#与其他场景消歧)。
+
+**特别关注列表查询**:用户说"我关注了谁/我的特别关注列表/我的星标联系人/特别关注的人有哪些" → `dws contact relation list-my-followings`(无入参)。
+
+- 与 `chat message list-focused` 区分:前者拉"人员列表",后者拉"这些人发的消息"。
+- **可执行判断口径(按顺序扫描)**:
+ 1. 扫描 query 是否含动词【发/说/聊/讲】或名词【消息/聊天/动态/最新内容】 → 含则**强制**路由到 `chat message list-focused`,**忽略**主语中的"关注/特别关注/星标"。
+ 2. 仅含"关注/特别关注/星标"+"人/列表/谁/有哪些/多少" → 路由到 `dws contact relation list-my-followings`。
+- **反例 query(绝不路由到 list-my-followings)**:
+ - "我特别关注的人最近发了什么消息" → `chat message list-focused`
+ - "关注的人最近都说了啥" → `chat message list-focused`
+ - "星标联系人发的群消息" → `chat message list-focused`
+- **正例 query(路由到 list-my-followings)**:
+ - "我特别关注的人有哪些"、"我关注了谁"、"我的星标联系人"
---
## 跨产品工作流路由
-以下场景需要多个产品配合完成,注意上下文传递顺序。
+以下场景需要多个产品配合完成,注意上下文传递顺序。多步骤操作有现成脚本时优先使用脚本。
-### 创建日程并邀请同事(contact → calendar)
+### 发邮件给同事(aisearch → contact → mail)
+
+用户说"给张三发封邮件",但只知道名字不知道邮箱地址:
+
+```bash
+# 1. 搜人获取 userId(多人同名须 contact user get 消歧,禁止默认选第一个,详见 08-directory.md「多命中」)
+dws aisearch person --keyword "张三" --dimension name --format json
+
+# 2. 用 userId 查详情获取 email
+dws contact user get --ids --format json
+
+# 3. 用搜索到的邮箱地址作为收件人发送邮件
+dws mail mailbox list --format json # 获取发件人邮箱
+dws mail message send --from my@company.com --to zhangsan@company.com \
+ --subject "周报" --body "内容" --format json
+```
+
+### 创建日程并邀请同事(aisearch → calendar)
用户说"约张三明天下午开会":
+> 有脚本: `python scripts/calendar_schedule_meeting.py --title "会议" --start "..." --end "..." --users userId1 --book-room`
+
```bash
-# 1. 搜索同事 userId
-dws contact user search --query "张三" --format json
+# 手动流程(脚本不可用时):
+# 1. 搜人获取 userId(多人同名须 contact user get 消歧,禁止默认选第一个,详见 08-directory.md「多命中」)
+dws aisearch person --keyword "张三" --dimension name --format json
# 2. 创建日程
dws calendar event create --title "会议" \
--start "2026-03-15T14:00:00+08:00" --end "2026-03-15T15:00:00+08:00" --format json
-# 3. 添加参与者
-dws calendar participant add --event --users --format json
+# 3. 添加参会人
+dws calendar attendee add --event --attendees --format json
```
-### 创建待办并指派(contact → todo)
+### 创建待办并指派(aisearch → todo)
用户说"给张三建个待办":
```bash
-# 1. 搜索同事 userId
-dws contact user search --query "张三" --format json
+# 1. 搜人获取 userId(多人同名须 contact user get 消歧,禁止默认选第一个,详见 08-directory.md「多命中」)
+dws aisearch person --keyword "张三" --dimension name --format json
# 2. 创建待办
dws todo task create --title "任务内容" --executors --format json
```
+
+---
+
+## 图片 / 文件发送
+
+### 发送图片 / 本地文件(统一一条命令)
+
+用户说"发张图/把这张图发给某某/发个 PDF/发个语音/发个视频/发个文件"等任何场景,**统一一条命令**:
+
+```bash
+# 群聊
+dws chat message send --group --msg-type file --file-path <本地路径> --format json
+
+# 单聊(推荐 --open-dingtalk-id;--user 也支持)
+dws chat message send --open-dingtalk-id --msg-type file --file-path <本地路径> --format json
+```
+
+支持任意扩展名(`.png/.jpg/.gif/.bmp/.webp/.pdf/.doc/.xls/.zip/.mp3/.wav/.mp4/.avi` …),CLI 自动识别并处理。**无需** `dt_media_upload` / `extract_media_id.py` / `drive upload` / `drive download` / `chat conversation-info` / `chat file upload` 等任何前置工具调用。
+
+### 图片/文件 + 文字说明
+
+不要把文字塞进 `--msg-type file` 命令(该命令不读 `--text`)。先发文件再补一条文本消息即可:
+
+```bash
+dws chat message send --open-dingtalk-id --msg-type file --file-path ./screenshot.png --format json
+dws chat message send --open-dingtalk-id --text "这是本周数据汇总" --format json
+```
+
+### 旧链路(mediaId)— 仅兼容场景
+
+仅当上游已经通过 `dt_media_upload` 拿到 `@lQL...` 形式的 mediaId 时使用:
+
+```bash
+dws chat message send --group --msg-type image --media-id "@lQLPD4JNnliqBq3NBQDNA8Cw" --format json
+```
diff --git a/skills/mono/references/products/aitable.md b/skills/mono/references/products/aitable.md
index a2dc9eee..66945081 100644
--- a/skills/mono/references/products/aitable.md
+++ b/skills/mono/references/products/aitable.md
@@ -7,15 +7,9 @@
| 资源 | URI 格式 |
|------|----------|
| Base 文档 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` |
-| 指定数据表 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` |
-| 指定数据表+视图 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` |
| 模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` |
-> **操作后请返回文档 URI**:返回链接时必须带上当前操作的数据表 tableId,让用户点击后直接看到目标数据表,而不是落在空白的默认表。
-> - 已知 tableId + viewId 时(view create 返回、view get 中提取):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}`
-> - 已知 tableId 时(table create 返回、base get 中提取、record 操作所用的 tableId):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}`
-> - 仅有 baseId、无明确 tableId 时(如 base list/search):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}`
->
+> **操作后请返回文档 URI**:每次执行 base list/search/create/get 操作后,从返回数据中提取 `baseId`,拼接为 `https://alidocs.dingtalk.com/i/nodes/{baseId}` 返回给用户。
> 补充:如果 URL 不是来自 `aitable` 命令返回,而是用户直接贴的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe,确认是 `able` 后再按 AI 表格处理。
## 命令索引表
@@ -27,7 +21,7 @@
| `base list` | 列出最近访问的 Base | — | 仅返回最近访问过的,优先用 `base search` |
| `base search` | 按名称搜索 Base | `--query` | 关键词 ≥2 字符 |
| `base get` | 获取 Base 信息(含 tables 列表) | `--base-id` | 用户给 URL 时提取末尾 ID |
-| `base create` | 创建 Base | `--name` | 创建后直接用返回的 baseId;**默认新建的 base 自带一个空白「数据表」(含 3 行空记录)和一个空白仪表盘**,如需干净的空 base,传 `--template-id 1743` |
+| `base create` | 创建 Base | `--name` | 创建后直接用返回的 baseId |
| `base update` | 更新 Base 名称 | `--base-id` `--name` | — |
| `base delete` | 删除 Base | `--base-id` | 不可逆 |
@@ -37,7 +31,7 @@
|------|------|----------|----------|
| `table get` | 获取表结构(字段+视图目录) | `--base-id` | 不传 `--table-ids` 返回全部表 |
| `table create` | 创建数据表 | `--base-id` `--name` `--fields` | fields 为 JSON 数组,至少 1 个 |
-| `table update` | 修改表名 / 备注 / 行命名规则 | `--base-id` `--table-id` + 三选一(`--name` / `--description` / `--record-name-key`) | `--record-name-key` 是固定枚举(如 task/project/event/customer/ji_lu 等),非字段 ID |
+| `table update` | 重命名表 | `--base-id` `--table-id` `--name` | — |
| `table delete` | 删除表 | `--base-id` `--table-id` | 不可逆 |
### field (字段管理) → 详见 [aitable-field.md](./aitable/aitable-field.md)、[field-properties](./aitable/aitable-field-properties.md)
@@ -49,30 +43,6 @@
| `field update` | 更新字段名/配置 | `--base-id` `--table-id` `--field-id` | 不可变更字段类型 |
| `field delete` | 删除字段 | `--base-id` `--table-id` `--field-id` | 不可逆 |
-#### 搜索字段选项
-```
-Usage:
- dws aitable field search-options [flags]
-Example:
- dws aitable field search-options --base-id --table-id --field-id
- dws aitable field search-options --base-id --table-id --field-id --keyword 已完成
- dws aitable field search-options --base-id --table-id --field-id --limit 100
-Flags:
- --base-id string Base ID (必填)
- --field-id string 目标字段 ID,必须是 singleSelect / multipleSelect 类型 (必填)
- --keyword string 模糊搜索关键词,大小写不敏感、contains 匹配 option name;不传返回全部
- --limit int 返回的最大 option 数量,默认 3000(全量),最大 3000
- --table-id string Table ID (必填)
-```
-
-仅适用于 **singleSelect / multipleSelect** 字段。其他类型(text/number/date/...)调用会返回错误。
-
-适用场景:
-- options 较多,只想要含某关键词的子集(避免 `field get` 拉取整个字段配置带回所有 options)。
-- 写入 record 前预览选项 id ↔ name 的映射,确认要使用的选项确实存在。
-
-> **写 record 时**:`record create / update` 对 singleSelect/multipleSelect 可直接传 option **name**,不需要用本命令。本命令主要用于 **filter** 写法(filters 优先用 option **id**)或选项较多需要精确定位时。
-
### record (记录管理)
| 命令 | 用途 | 必读 reference | 路由提醒 |
@@ -80,40 +50,28 @@ Flags:
| `record query` | 查询/搜索记录 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 先 `table get` 拿 fieldId;`--all` 自动翻页;filters 结构见 reference |
| `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;只传需改字段;`--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 update` | 更新记录 | [aitable-record-update.md](./aitable/aitable-record-update.md) | 需先 query 拿 recordId;只传需改字段;**没有** `--record-id` `--cells` flag |
| `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 |
### view (视图管理)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
-| `view get` | 获取视图配置(不传子命令) | `--base-id` `--table-id` | 不传 `--view-ids` 返回全部视图 |
-| `view get ` | 获取视图某个属性 | `--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;**Gantt 创建后必须 `view update timebar` 绑定日期字段** |
-| `view update` | 整体更新视图 / 多属性合并更新 | `--base-id` `--table-id` `--view-id` | 可传 `--name --desc --config '{...}'`,**`--config` 路径继续保留** |
-| `view update ` | 按属性局部更新(推荐)| `--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 get` | 获取视图配置 | `--base-id` `--table-id` | 不传 `--view-ids` 返回全部视图 |
+| `view create` | 创建视图 | `--base-id` `--table-id` `--view-type` | 类型: Grid/Kanban/Gantt/Calendar/Gallery/FormDesigner |
+| `view update` | 更新视图(**调整字段顺序的入口**) | `--base-id` `--table-id` `--view-id` | `visibleFieldIds` 重排字段顺序 |
| `view delete` | 删除视图 | `--base-id` `--table-id` `--view-id` | 不可删最后一个/锁定视图 |
-> **优先用 `view get ` / `view update ` 子命令**:每个属性独立命令,typed flag 友好,agent 不必拼 JSON。**`view update --config '{...}'` 仍可用**,适合一次性多属性更新或脚本场景。
+> **"移动字段/调整字段顺序"** 在 AI 表格里没有 `field reorder` 命令,必须通过 `view update --config '{"visibleFieldIds":[...]}'` 完成。
-> **属性按 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。
+> **view update --config 支持的 key 白名单**(传入其他 key 会报错):
+> - `visibleFieldIds` — 视图可见字段列表及顺序(首列字段必须保留在第一位)
+> - `filter` — 筛选规则列表
+> - `sort` — 排序规则列表
+> - `group` — 分组规则列表
+> - `fieldWidths` — 列宽映射(仅 Grid 视图有效)
+>
+> 不支持 `formInfo`、`requiredFields`、`conditionalRules` 等 FormDesigner 高级配置,这些 key 会被服务端忽略。
### form (表单管理) → 详见 [aitable-form.md](./aitable/aitable-form.md)
@@ -147,7 +105,6 @@ Flags:
|------|------|
| `dashboard get/create/update/delete` | 仪表盘管理 |
| `dashboard config-example` | 查看仪表盘配置模板 |
-| `dashboard arrange` | 自动重排仪表盘图表布局(智能填满网格,避免空缺) |
| `chart get/create/update/delete` | 图表管理 |
| `chart widgets-example` | 查看图表 widgets 配置模板 |
@@ -316,7 +273,7 @@ dws aitable chart get --base-id --dashboard-id --chart-
```bash
# 第一步:创建任务(按 scope 传必要参数)
-dws aitable export data --base-id --scope table --table-id --export-format excel --timeout-ms 1000
+dws aitable export data --base-id --scope table --table-id --format excel --timeout-ms 1000
# 第二步:拿 taskId 继续轮询,直到返回 downloadUrl
dws aitable export data --base-id --task-id --timeout-ms 3000
@@ -340,8 +297,7 @@ dws aitable export data --base-id --task-id --timeout-ms 300
用户说"数据表/子表/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 update`
- 删除 → `table delete`
用户说"字段/列/column":
@@ -352,34 +308,10 @@ dws aitable export data --base-id --task-id --timeout-ms 300
用户说"记录/行/数据/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 update`(读 [aitable-record-update.md](./aitable/aitable-record-update.md))
- 删除 → `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 `
-- 创建 → `view create`
-- 修改(含"调整字段顺序/隐藏字段") → `view update --config '{"visibleFieldIds":[...]}'`
-- 修改某一项配置(filter/sort/group/card/timebar/aggregate 等)→ `view update `(读 [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)
用户说"筛选/过滤/filter" → 读 [aitable-filter-sort.md](./aitable/aitable-filter-sort.md)
@@ -389,33 +321,14 @@ dws aitable export data --base-id --task-id --timeout-ms 300
用户说"查找引用/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)
-- 看 Base 里有哪些流程 / 哪些在跑 → `workflow list`(看 `recordCount` / `runningCount`)
-- 看某个流程具体配置(触发条件、动作步骤) → `workflow get`
-- 启用流程 → `workflow enable`
-- 临时停掉流程(调试 / 数据迁移)→ `workflow disable --yes`
-- **新建 / 修改 / 删除流程**:当前不支持,引导用户到 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=行
@@ -446,8 +359,7 @@ dws aitable record create --base-id --table-id \
|------|-------------|------|
| `base list/search` | `baseId` | 所有后续命令的 --base-id,拼接文档 URI |
| `base create` | `baseId` | 后续命令 + 文档 URI |
-| `base get` | `tables[].tableId` | --table-id,拼接指定数据表 URI |
-| `table create` | `tableId` | 后续命令 + 拼接指定数据表 URI |
+| `base get` | `tables[].tableId` | --table-id |
| `table get` | `fields[].fieldId` | record 操作的 cells key, field get/update/delete |
| `record query` | `recordId` | record update/delete;按 ID 反查字段值用 `record get` |
| `template search` | `templateId` | base create --template-id,拼接模板预览 URI |
diff --git a/skills/mono/references/products/aitable/aitable-attachment.md b/skills/mono/references/products/aitable/aitable-attachment.md
index 45e9c2ae..1648fc87 100644
--- a/skills/mono/references/products/aitable/aitable-attachment.md
+++ b/skills/mono/references/products/aitable/aitable-attachment.md
@@ -1,8 +1,6 @@
# attachment — 附件上传
> **STOP — 不要使用钉盘 (drive) 上传!** 钉盘 fileId 无法写入 attachment 字段。必须使用以下流程。
->
-> **STOP — 严禁在 record create/update 的 cells 里直接传图片 URL!** 直传 `{"url":"https://..."}` 会导致服务端同步下载图片,批量写入时触发 TIMEOUT_ERROR。正确做法:先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。
## 准备附件上传
@@ -40,8 +38,8 @@ dws aitable record create --base-id --table-id \
dws aitable attachment upload --base-id --file-name report.pdf --size 204800 --format json
# → 返回 uploadUrl、fileToken
-# 2. PUT 上传(Content-Type 必须是文件的具体 MIME type)
-curl -X PUT "" -H "Content-Type: application/pdf" --data-binary @report.pdf
+# 2. PUT 上传(Content-Type 留空)
+curl -X PUT "" -H "Content-Type:" --data-binary @report.pdf
# 3. 写入记录
dws aitable record update --base-id --table-id \
diff --git a/skills/mono/references/products/aitable/aitable-best-practices.md b/skills/mono/references/products/aitable/aitable-best-practices.md
index db63f634..a851af83 100644
--- a/skills/mono/references/products/aitable/aitable-best-practices.md
+++ b/skills/mono/references/products/aitable/aitable-best-practices.md
@@ -26,7 +26,7 @@
| 全量拉取/统计 | `record query --all` | 不要手动循环 cursor |
| 全量导出为文件 | `export data` | 不要 `--all` 拉全量再写文件 |
| 批量写入 | `record create`(分批 100 条) | 不要一次传超过 100 条 |
-| 附件/图片上传 | `attachment upload` 获取 fileToken → `record create/update` 用 fileToken 写入 | **严禁直接传图片 URL 到附件字段**(服务端同步下载会超时) |
+| 附件上传 | `attachment upload` + `record update` | 不要在 cells 里伪造附件值 |
| 文件级导入 | `import upload` + `import data` | 不要手动解析 xlsx 再逐条写入 |
## 4. 创建/修改后回读确认
diff --git a/skills/mono/references/products/aitable/aitable-cell-value.md b/skills/mono/references/products/aitable/aitable-cell-value.md
index bc3a8125..9f747bf9 100644
--- a/skills/mono/references/products/aitable/aitable-cell-value.md
+++ b/skills/mono/references/products/aitable/aitable-cell-value.md
@@ -86,16 +86,11 @@
{"fldDateId": "2026-03-15T09:00+08:00"}
```
-**读取**:RFC3339 字符串(带时区)
+**读取**:RFC3339 字符串
```json
{"fldDateId": "2026-03-15T09:00:00+08:00"}
```
-**过滤**(`record query --filters`):日期字段**只能用日期专用操作符** `date_eq` / `before` / `after` / `not_before` / `not_after` / `exist` / `un_exist`,比较值用日期字符串(如 `"2026-03-15"`)。
-- ❌ 通用 `eq` / `ne` / `gt` / `gte` / `lt` / `lte` / `contain` 对日期字段无效,会静默返回 0 条;
-- ❌ 不支持区间 `date_between` 与相对 `from_now`(CLI 会直接拒绝),范围查询用 `not_before` + `not_after` 组合。
-- 详见 [aitable-filter-sort.md](./aitable-filter-sort.md) §日期字段过滤。
-
---
### currency(货币)
@@ -241,14 +236,15 @@
### attachment(附件)
-**写入**:对象数组,**必须使用 `fileToken`**
+**写入**:对象数组,支持 `fileToken` 或 `url` 形式
```json
{"fldAttachId": [{"fileToken": "ft_xxx"}]}
+{"fldAttachId": [{"url": "https://example.com/file.pdf"}]}
```
-> ⚠️ **必须先通过 [attachment upload 流程](./aitable-attachment.md) 上传文件获取 `fileToken`,再将 `fileToken` 写入 cells。**
-> ❌ **严禁直接传 `{"url": "https://..."}` 形式写入附件/图片字段** — 服务端会同步下载图片,10 条记录即触发 TIMEOUT_ERROR 超时。
+> ⚠️ **必须先通过 [attachment upload 流程](./aitable-attachment.md) 获取 `fileToken`**。
+> URL 形式是 best-effort 异步转存,不保证立即可用。
> 写入会**整体覆盖**原附件列表,不是追加。
**读取**:对象数组(含下载链接、文件名、大小)
@@ -339,7 +335,7 @@
|------|----------|
| cells key 用字段名称 `"课程名称"` | 用 fieldId `"fldXXX"` |
| progress 写入 `75` | 写入 `0.75`(范围 0~1) |
-| attachment 直接传文件路径或图片 URL | 必须先 `attachment upload` 获取 fileToken,再用 fileToken 写入(直传 URL 会超时) |
+| attachment 直接传文件路径 | 必须先 upload 获取 fileToken |
| user 字段传用户名字符串 | 传对象数组 `[{"userId":"...", "corpId":"..."}]` |
| group 字段用 `openConversationId` | 用 `cid` |
| singleSelect 传 option id 字符串 | 传 name 字符串或 `{"id":"...", "name":"..."}` 对象 |
diff --git a/skills/mono/references/products/aitable/aitable-dashboard-chart.md b/skills/mono/references/products/aitable/aitable-dashboard-chart.md
index 94d576e2..ec96fb4f 100644
--- a/skills/mono/references/products/aitable/aitable-dashboard-chart.md
+++ b/skills/mono/references/products/aitable/aitable-dashboard-chart.md
@@ -27,7 +27,6 @@ dws aitable chart get --base-id --dashboard-id --chart-
| `dashboard update` | 更新仪表盘 | `--base-id` `--dashboard-id` + (`--config` 或 `--name`) | `--name` 仅改名;`--config` 更新完整配置 |
| `dashboard delete` | 删除仪表盘 | `--base-id` `--dashboard-id` `--yes` | — |
| `dashboard config-example` | 查看仪表盘配置模板 | 无 | 创建前先调此命令了解 config 结构 |
-| `dashboard arrange` | 自动重排图表布局 | `--base-id` `--dashboard-id` | 把图表按行铺满网格,避免某行只占半幅、留下大片空白;返回 `{totalColumns, layout, alignedChartCount}` |
## chart 子命令
diff --git a/skills/mono/references/products/aitable/aitable-export-import.md b/skills/mono/references/products/aitable/aitable-export-import.md
index fc755451..05e83466 100644
--- a/skills/mono/references/products/aitable/aitable-export-import.md
+++ b/skills/mono/references/products/aitable/aitable-export-import.md
@@ -111,3 +111,30 @@ dws aitable import data --import-id --table-id --format js
> **导入数据无法整体撤销**:文件一旦导入成功,数据即写入表中,没有"撤销导入"操作。如需清理导入的测试数据,只能手动通过 `record delete` 逐条或批量删除记录;如果是新建表模式导入的,可以直接 `table delete` 删除整张表。因此:
> - 测试/验证场景建议导入到**独立的测试表或测试 Base**,用完后整体删除
> - 如果用户明确表示不想导入测试数据或要求先预览内容再决定,应先解析文件内容展示给用户确认,而非直接导入
+
+---
+
+## 导入数据:两条链路怎么选
+
+用户给文件让导入 AI 表格时,路径选择决定成败:
+
+| 用户原话 | 链路 | 命令 / 脚本 | 行为 |
+|---------|------|------------|------|
+| "把这个 Excel 导入到 AI 表格"(无指定目标表) | **文件导入任务** | `python scripts/aitable_import_via_task.py ` 或 `dws aitable import upload --base-id B --file ./x.xlsx` + `dws aitable import data --import-id ` | 服务端解析文件,**新建数据表**,自动识别表头 |
+| "把这个 Excel 导入新表 / 自动建表" | 同上 | 同上 | 同上 |
+| "把这批记录追加到已有的『成员表』里" | **记录批量写入** | `python scripts/import_records.py ` | 走 `record create`,**写入已有 tableId**,需要字段名匹配 |
+| "Excel 列名和表字段对不上但要追加" | 文件导入 + 追加 + 字段映射 | `dws aitable import upload --base-id B --file ./x.xlsx` → `dws aitable import data --import-id --table-id --field-mapping '{"目标":"源"}'` | 服务端按映射追加 |
+
+## 大表 / 长任务超时续等
+
+默认整体轮询超时 5 分钟。大表导出/导入超时后命令会返回 `taskId` / `importId`,用同命令带 ID 续等:
+
+```bash
+# 续等导出
+dws aitable export data --base-id --task-id --output ./out.xlsx
+
+# 续等导入
+dws aitable import data --import-id
+```
+
+或一次性把 timeout 提到 15 分钟:`--timeout-sec 900`。
diff --git a/skills/mono/references/products/aitable/aitable-filter-sort.md b/skills/mono/references/products/aitable/aitable-filter-sort.md
index 8fabf8b4..8819a31f 100644
--- a/skills/mono/references/products/aitable/aitable-filter-sort.md
+++ b/skills/mono/references/products/aitable/aitable-filter-sort.md
@@ -1,7 +1,5 @@
# filters & sort — 筛选排序语法参考
-> 视图(view)配置的 filter/sort/group **整体写入**请优先用 `view update filter` / `view update sort` / `view update group` 子命令,详见 [aitable-view-config.md](./aitable-view-config.md)。本文件聚焦于 `record query --filters` 与 view config filter 的语法和差异。
-
## filters 结构规范
### 强制规则
@@ -51,34 +49,11 @@ CLI 同时兼容两种子条件写法(推荐格式 A):
| `exist` / `un_exist` | 有值 / 为空 | `["fieldId"]`(无需第二项) |
| `any_of` / `none_of` / `all_of` | 包含任一 / 不包含任一 / 全包含(多选字段) | `["fieldId", "optionName"]` |
| `date_eq` / `before` / `after` | 日期等于 / 早于 / 晚于 | `["fieldId", "dateStr"]` |
-| `not_before` / `not_after` | 不早于(≥) / 不晚于(≤) | `["fieldId", "2026-05-22"]` |
+| `not_before` / `not_after` | 不早于 / 不晚于 | `["fieldId", "dateStr"]` |
+| `from_now` | 从现在起 N 天内 | `["fieldId", "天数"]` |
+| `date_between` | 日期区间 | `["fieldId", "[startTs, endTs]"]` |
> **操作符拼写必须严格匹配上表**,CLI 会在调用前校验,错误拼写会被拒绝。
->
-> **没有 `date_between`(区间)操作符**,也**不支持 `from_now`**——date 字段不支持区间/相对过滤,传了会被 CLI 拒绝。范围查询用 `not_before` + `not_after` 组合,见下方专节。
-
-### 日期字段过滤(date / 创建时间 / 修改时间)
-
-日期类字段的过滤规则与其它字段**不同**,是线上反馈最高频的踩坑点。**经集成测试实测**确认的规则:
-
-1. **只能用日期专用操作符**:`date_eq` / `before` / `after` / `not_before` / `not_after` / `exist` / `un_exist`(与前端筛选 UI 的「等于 / 早于 / 晚于 / 早于或等于 / 晚于或等于 / 不为空 / 为空」一一对应)。
-2. **比较值用日期字符串**,如 `"2026-05-22"`(也接受 RFC3339 / 毫秒时间戳,内部统一转成毫秒比较)。读取返回的是带时区 RFC3339(如 `"2026-05-22T00:00:00+08:00"`)。
-3. **通用操作符 `eq` / `ne` / `gt` / `gte` / `lt` / `lte` / `contain` 对 date 字段无效**——无论传 ISO 字符串还是毫秒时间戳,都会**静默返回 0 条**。这是后端 date 字段的比较规则,不是 bug,CLI 也无法在本地拦截(不知道字段类型),务必用对操作符。
-4. **没有区间操作符 `date_between`**,也**不支持 `from_now`(相对天数)**——均会静默返回 0 条,CLI 已直接拒绝。范围查询用 `not_before`(≥起点)+ `not_after`(≤终点)两个条件 `and` 组合。
-
-| 需求 | 操作符 | 示例 operands |
-|------|--------|--------------|
-| 等于某天 | `date_eq` | `["fldDate", "2026-05-22"]` |
-| 早于 / 晚于(不含当天) | `before` / `after` | `["fldDate", "2026-05-22"]` |
-| 不早于(≥) / 不晚于(≤) | `not_before` / `not_after` | `["fldDate", "2026-05-22"]` |
-| 有值 / 为空 | `exist` / `un_exist` | `["fldDate"]` |
-
-**日期区间查询(替代 between)**——查 `2026-05-01 ~ 2026-05-31`(含端点):
-
-```bash
-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"]}]}'
-```
### 常见错误拼写(CLI 会自动提示纠正)
@@ -129,41 +104,3 @@ dws aitable record query --base-id X --table-id Y \
```bash
--sort '[{"fieldId":"fldPriority","direction":"desc"},{"fieldId":"fldCreatedAt","direction":"asc"}]'
```
-
----
-
-## view update --config 中的 filter / sort 格式
-
-> **重要区分**:`record query --filters` 和 `view update --config` 中的 filter **格式不同**!
-
-| 场景 | filter 格式 | 说明 |
-|------|-------------|------|
-| `record query --filters` | **对象**:`{"operator":"and","operands":[...]}` | 直接传最外层逻辑对象 |
-| `view update --config` 的 filter | **数组**:`[{"operator":"and","operands":[...]}]` | 外面多一层数组包裹 |
-| `view update --config` 的 sort | **数组**:`[{"fieldId":"X","direction":"asc"}]` | 与 record query --sort 一致 |
-
-### 正确示例
-
-```bash
-# view update 设置筛选(filter 是数组)
-dws aitable view update --base-id X --table-id Y --view-id Z \
- --config '{"filter":[{"operator":"and","operands":[{"operator":"eq","operands":["fldStatus","待处理"]}]}]}'
-
-# view update 设置排序(sort 是数组)
-dws aitable view update --base-id X --table-id Y --view-id Z \
- --config '{"sort":[{"fieldId":"fldPriority","direction":"desc"}]}'
-
-# 同时设置 filter + sort + visibleFieldIds
-dws aitable view update --base-id X --table-id Y --view-id Z \
- --config '{"filter":[{"operator":"and","operands":[{"operator":"eq","operands":["fldStatus","进行中"]}]}],"sort":[{"fieldId":"fldDate","direction":"asc"}],"visibleFieldIds":["fld1","fld2","fld3"]}'
-```
-
-### CLI 自动容错
-
-CLI 会自动修正以下常见错误格式(不会报错,但建议直接使用正确格式):
-
-| 错误写法 | CLI 自动修正为 |
-|----------|---------------|
-| `"filter":{"operator":"and",...}` (对象) | `"filter":[{"operator":"and",...}]` (数组) |
-| `"sort":{"fieldId":"X","direction":"asc"}` (对象) | `"sort":[{"fieldId":"X","direction":"asc"}]` (数组) |
-| 子条件用 MCP 简写 `{"fieldId":"X","operator":"eq","value":"Y"}` | 自动转为 `{"operator":"eq","operands":["X","Y"]}` |
diff --git a/skills/mono/references/products/aitable/aitable-record-create.md b/skills/mono/references/products/aitable/aitable-record-create.md
index bf605899..eb737228 100644
--- a/skills/mono/references/products/aitable/aitable-record-create.md
+++ b/skills/mono/references/products/aitable/aitable-record-create.md
@@ -27,7 +27,6 @@ Flags:
| cells key 用字段名 | ❌ cells key 必须是 fieldId(如 `fldXXX`),不是字段名称(如 `"课程名称"`) |
| 不先获取 fieldId | ❌ 必须先 `table get` 获取 fieldId,再写入记录 |
| 单次超 100 条 | ❌ 单次最多 100 条,超过需分批 |
-| 附件/图片字段直传 URL | ❌ 严禁 `{"url":"https://..."}` — 会触发 TIMEOUT_ERROR。必须先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。详见 [aitable-attachment.md](./aitable-attachment.md) |
## 正确流程
diff --git a/skills/mono/references/products/aitable/aitable-record-query.md b/skills/mono/references/products/aitable/aitable-record-query.md
index 20ed92da..8d04ab1b 100644
--- a/skills/mono/references/products/aitable/aitable-record-query.md
+++ b/skills/mono/references/products/aitable/aitable-record-query.md
@@ -72,49 +72,3 @@ dws aitable record query --base-id X --table-id Y --all --cursor "上次返回
- `--sort` 用 `"order":"desc"` → 必须用 `"direction":"desc"`
- 不加 `--field-ids` 拉全字段 → 大表响应体积过大
- 全量拉取后在 context 里手动统计 → 应优先用 `--filters` 服务端过滤
-
-## record query-empty — 找空行
-
-`record query-empty` 是与 `record query` 平行的独立子命令,专门按表内顺序扫描出"完全没填用户字段"的空行。
-
-```bash
-dws aitable record query-empty --base-id BASE_ID --table-id TABLE_ID
-```
-
-| flag | 说明 |
-|------|------|
-| `--base-id` / `--base` | 必填 |
-| `--table-id` | 必填 |
-| `--limit` | 单次**扫描预算**(不是返回数);范围 [1, 100],默认 100 |
-| `--cursor` | 分页游标。响应中 `nextCursor` 非空 → 用它翻页继续扫;nextCursor 为空(或不存在)→ 已扫完整表 |
-
-返回结构:
-
-```jsonc
-{ "data": { "records": [...], "nextCursor": "..." } }
-```
-
-### 关键语义
-
-1. **`--limit` 是扫描预算不是返回数**:可能扫了 100 条但全部非空,本页 `records: []`。
-2. **本页空 records ≠ 全表无空行**:必须看 `nextCursor`,nextCursor 还在就要继续翻。
-3. **空行定义**:除系统字段(recordId / 创建人 / 创建时间 / 修改人 / 修改时间)外,所有 cell 都是 null、空字符串、空集合或空 Map。一般是用户在 UI 上"插入空行"产生的。
-
-### 典型用法
-
-```bash
-# 扫一页,看本页有没有空行
-dws aitable record query-empty --base-id BASE --table-id TBL
-
-# 翻页
-dws aitable record query-empty --base-id BASE --table-id TBL --cursor <上次的nextCursor>
-
-# 把整表扫完(手动循环 cursor)
-NC=""
-while : ; do
- R=$(dws aitable record query-empty --base-id BASE --table-id TBL ${NC:+--cursor "$NC"} --format json)
- echo "$R" | jq '.data.records[] | .recordId'
- NC=$(echo "$R" | jq -r '.data.nextCursor // empty')
- [ -z "$NC" ] && break
-done
-```
diff --git a/skills/mono/references/products/attendance-report.md b/skills/mono/references/products/attendance-report.md
new file mode 100644
index 00000000..e845abf4
--- /dev/null
+++ b/skills/mono/references/products/attendance-report.md
@@ -0,0 +1,619 @@
+# 考勤报表导出参考 (attendance-report)
+
+> 本文档由 `attendance.md` 路由调用。当用户提到"考勤报表"、"导出考勤"、"出勤汇总"、"考勤明细"、"迟到早退统计"、"全员考勤数据"、"某月考勤统计"、"考勤表格"、"考勤 Excel" 时,应阅读本文档执行。
+> 不适用于:个人单日打卡查询(用 `attendance check record`)、班次查询(用 `attendance schedule get`)、假期余额(用 `vacation balance`)、审批进度(用 `oa`)。
+
+## 强制门禁(必须先读完本文档才能执行)
+
+**任何调用 `attendance_report_detail.py` / `attendance_report_monthly.py` / `attendance_report_daily.py` 的请求,都必须经过本文档定义的工作流,严禁绕过本文档直接拼脚本命令执行。**
+
+违反将出现以下任一问题:
+1. 未按"阶段 1"做人员解析 → `--users` 传入部门 ID 而非员工 userId,脚本虽内置回退但会浪费一次失败的接口调用
+2. 未按"阶段 0"判断报表类型 → 用户说"汇总"被理解成"明细",导致输出粒度错误
+3. 未按"列选择"判断是否传 `--column-keywords` → 用户要"迟到情况报表"被输出成全字段默认报表
+4. 未按"错误处理"规则处理 403 / `HSF_ILLEGALPARAMS` → 把环境错误当成业务错误反馈给用户
+5. 未按"阶段 4"返回结果 → 把 Excel 内容贴在对话里,或者裸 userId 直接输出
+
+**执行前自检(必须能在心中回答)**:
+- [ ] 报表类型是?(明细 / 月度汇总 / 每日统计)
+- [ ] 人员列表的来源是?(`aisearch person` 还是 `contact dept list-members`?)
+- [ ] 列选择方式是?(预设报表关键词 / 自定义 `--column-keywords` / 默认列集合)
+- [ ] 报错时如何向用户解释?
+
+如果上述任何一项答不出,**回到本文档对应章节重新阅读**,禁止凭记忆/想象组装命令。
+
+**前提**:当前用户必须是钉钉管理员,否则 report 系列接口返回 403 权限错误。
+
+## 核心原则
+
+Agent 解析用户意图(报表类型、人员范围、时间范围、关注维度),获取 userId 列表后,**直接调用对应的 Python 脚本 CLI 生成 Excel**。
+- **脚本自包含**:数据查询(分批、分段、翻页)、字段解析、聚合计算、Excel 生成全部由脚本内部完成,Agent 不参与数据查询和计算
+- **月度汇总 / 每日统计**:脚本内部调用 `report columns` + `report query-data`
+- **明细**:脚本内部调用 `check result` + `check record`(数据源不同)
+- 列选择是独立维度:用户未指定关注维度时脚本使用内置默认字段;用户指定了关注维度时 Agent 通过 `--column-keywords` 参数传给脚本
+
+## 严格禁止 (NEVER DO)
+
+- 禁止凭历史记忆复用 userId 等任何 ID,必须从当次命令返回值中提取
+- 禁止用大模型口算/目测做考勤数据聚合(求和、计数、分组),必须通过 Python 脚本完成
+- 禁止 Agent 直接调用 `report query-data` / `report columns` / `check result` / `check record`,这些由脚本内部自动完成
+- 禁止 `dws` 命令缺省 `--format json`(Agent 仅在阶段 1 获取人员时直接调用 dws 命令)
+- 禁止编造任何字段值或用户姓名
+- 禁止直接输出裸 userId,脚本已内置 userId → 姓名转换
+
+## 严格要求 (MUST DO)
+
+- 所有 `dws` 命令必须携带 `--format json`
+- 时间参数 `--start` / `--end` 格式必须为 `yyyy-MM-dd HH:mm:ss`
+- 字段 ID 与字段名的映射必须从 `report columns` 实时建立,禁止硬编码
+- 任何接口失败(含 403)必须向用户清晰报错,禁止静默吞掉
+
+## 涉及工具
+
+| 工具 | 用途 | 安全等级 |
+|------|------|---------|
+| `dws attendance report columns` | 获取当前管理员可见的考勤字段清单(字段 ID → 字段名) | 只读 |
+| `dws attendance report query-data` | 按字段查询考勤数据(≤20 人/次,≤32 天/次) | 只读 |
+| `dws attendance report query-leave` | 按假期名称查询假期数据(≤20 人/次,≤32 天/次),月度汇总/每日统计的"请假"列由脚本自动调用 | 只读 |
+| `dws attendance approve list` | 查询审批单记录(考勤记录报表专用),支持类型:leave/trip/out/patch | 只读 |
+| `dws oa approval detail` | 获取审批单详情(考勤记录报表专用),解析 formValueVOS / extValue | 只读 |
+| `dws attendance check result` | 查询打卡结果(≤100 人/次,≤1 月,明细报表专用) | 只读 |
+| `dws attendance check record` | 查询打卡流水(≤1 月,明细报表专用) | 只读 |
+| `dws aisearch person` | 按姓名搜索用户获取 userId(搜人首选) | 只读 |
+| `dws contact user get` | 批量查询 userId → 用户信息(姓名/部门/工号/职位) | 只读 |
+| `dws contact dept search` | 搜索部门获取 deptId | 只读 |
+| `dws contact dept list-members` | 获取部门成员 userId 列表 | 只读 |
+
+## 意图判断
+
+### 报表类型(五选一)
+
+| 用户说 | 报表类型 | 映射脚本 |
+|--------|---------|---------|
+| "导出研发部 3 月份的考勤明细" / "每条打卡记录" / "明细" / "原始记录" | 明细 | `attendance_report_detail.py` |
+| "生成研发部 3 月考勤汇总" / 用户明确说"月度汇总" / 用户未指明类型 | **月度汇总(默认)** | `attendance_report_monthly.py` |
+| "导出研发部 3 月每天的出勤情况" / "按天统计" / "每日" / 用户明确说"每日统计" | 每日统计 | `attendance_report_daily.py` |
+| "导出研发部 4 月的请假记录" / "补卡记录" / "出差记录" / "外出记录" / "xx记录" | 考勤记录 | `attendance_report_record.py` |
+| "导出签到记录" / "签到报表" / "签到数据导出" / "签到明细" / "外勤签到" | 签到报表 | `attendance_report_checkin.py` |
+
+> **默认报表类型**:用户未指明报表类型时,**默认走月度汇总**,事后告知"已按月度汇总输出,如需明细/每日统计/考勤记录请告知"。
+>
+> **考勤记录 vs 其他报表**:当用户明确提到"请假记录"/"补卡记录"/"出差记录"/"外出记录"时,走考勤记录报表(数据源为审批单)。而"请假报表"/"出差时长统计"等走月度汇总(数据源为 report query-data)。区别在于:考勤记录导出的是**审批单维度的原始数据**(含审批单状态、每天明细),月度汇总导出的是**按人按月聚合后的统计数据**。
+
+### 列选择(独立维度,与报表类型正交)
+
+> **"报表类型"与"列选择"是两个独立维度,需分别判断。**
+> 例如用户说"帮我出一份加班报表":报表类型未指明 → 默认月度汇总;列选择命中"加班报表" → 使用加班预设关键词。
+> 例如用户说"帮我出每日的异常报表":报表类型命中"每日" → 每日统计脚本;列选择命中"异常报表" → 使用异常预设关键词。
+> 预设报表**不会改变报表类型的判断逻辑**,报表类型始终按下方「报表类型(三选一)」规则判断。
+
+| 用户说 | 列选择方式 |
+|--------|-----------|
+| "帮我出一份考勤报表" / "导出考勤" / 未提及特定关注维度 | 不传 `--column-keywords`,使用脚本内置**默认列集合** |
+| "加班报表" / "加班统计" / "加班时长报表" | 传 `--column-keywords`,使用下方「加班报表预设关键词」 |
+| "请假报表" / "请假出差报表" / "请假外出统计" | 传 `--column-keywords`,使用下方「请假报表预设关键词」 |
+| "异常报表" / "异常考勤" / "迟到早退报表" / "缺卡报表" | 传 `--column-keywords`,使用下方「异常报表预设关键词」 |
+| 提及了其他自定义关注维度(如"工作时长报表") | 传 `--column-keywords`,由 Agent 自行拼接关键词 |
+
+> **预设报表优先级**:当用户提到的关键词同时命中"预设报表"和一般自定义维度时,**优先使用预设报表的完整关键词列表**,确保列不遗漏。
+
+### 易混淆场景
+
+| 用户说 | 应路由到 |
+|--------|---------|
+| "今天打卡了吗" | `dws attendance record get`(单次查询,非报表) |
+| "帮我排班" | [attendance-schedule.md](./attendance-schedule.md) 排班工作流(排班,非报表) |
+| "我的假期还剩多少" | `dws attendance vacation balance`(单次查询) |
+| "帮我请假" | `dws oa`(审批流程,非报表) |
+| "我这个月的考勤怎么样" | `dws attendance summary`(个人统计,非报表) |
+
+## 工作流
+
+### 阶段 0: 参数解析与确认查询范围
+
+1. **解析用户输入**(两个独立维度):
+ - **报表类型**(四选一):明细 / 月度汇总(默认) / 每日统计 / 考勤记录
+ - **列选择**(独立维度):预定义列集合(默认) / 用户指定维度筛选(考勤记录不适用)
+ - **人员维度**:指定员工 / 某个部门 / 多个部门(暂不支持全公司查询)
+ - **时间维度**:本周 / 本月 / 自定义时间段
+ - **记录子类型**(仅考勤记录):leave(请假) / trip(出差) / out(外出) / patch(补卡)
+2. **缺失信息处理**:
+ - **报表类型** 缺失 → 默认走月度汇总(不追问),事后告知
+ - **列选择**:用户提及了特定关注维度 → 传 `--column-keywords`;未提及 → 使用脚本内置默认字段集
+ - **用户范围 / 时间范围** 缺失 → 追问,禁止猜测
+
+### 阶段 1: 获取完整人员列表
+
+**场景 A — 指定员工姓名**:
+```bash
+dws aisearch person --keyword "<员工姓名>" --dimension name --format json
+```
+
+**场景 B — 按部门查询**:
+```bash
+dws contact dept search --query "<部门名>" --format json
+dws contact dept list-members --ids --format json
+```
+
+**场景 C — 多个部门**: 对每个部门分别执行 B,汇总去重。
+
+**场景 D — 全公司**: 暂不支持,引导用户指定部门。
+
+**场景 E — 用户已给 userId 列表**: 直接跳过本步。
+
+### 阶段 2: 列选择(决定脚本参数)
+
+> Agent 不需要手动调用 `report columns`,字段获取由脚本内部完成。Agent 只需根据用户意图决定是否传 `--column-keywords` 参数。
+
+**判断顺序**(优先级从高到低):
+
+1. **明细报表** → 列固定,不支持 `--column-keywords`
+2. **用户提到预设报表关键词** → 传 `--column-keywords`,使用本文档「预设报表列集合」中定义的完整关键词列表:
+ - "加班报表" / "加班统计" / "加班时长" → 使用「加班报表预设关键词」
+ - "请假报表" / "请假出差" / "外出统计" → 使用「请假报表预设关键词」
+ - "异常报表" / "迟到早退" / "缺卡报表" / "异常考勤" → 使用「异常报表预设关键词」
+3. **用户提及了其他自定义关注维度** → 传 `--column-keywords "..."`
+4. **用户未提及特定关注维度** → 不传 `--column-keywords`,脚本使用内置默认字段集
+
+### 阶段 3: 调用脚本生成 Excel
+
+#### 月度汇总 / 每日统计
+
+```bash
+python scripts/attendance_report_monthly.py \
+ --users ,,... \
+ --start "" \
+ --end "" \
+ [--column-keywords "出勤天数,迟到次数,迟到时长,..."] \
+ [--out 月度汇总_研发部_202604.xlsx]
+```
+
+参数说明:
+- `--users`(必填):逗号分隔的 userId 列表
+- `--start`(必填):开始日期,支持 `YYYY-MM-DD` 或 `YYYY-MM-DD HH:mm:ss`
+- `--end`(必填):结束日期,同上
+- `--column-keywords`(可选):逗号分隔的字段名关键词。不传则使用脚本内置默认字段集。预设报表(加班/请假/异常)也通过本参数传入对应的预设关键词列表
+- `--out`(可选):输出文件名,不传则自动生成
+
+脚本内部自动处理:`report columns` 获取字段清单 → 按预设/关键词匹配 → `report query-data` 分批分段查询 → `contact user get` 姓名映射 → 聚合计算 → 生成 Excel
+
+#### 明细
+
+```bash
+python scripts/attendance_report_detail.py \
+ --users ,,... \
+ --start "" \
+ --end "" \
+ [--out 考勤明细_研发部_202604.xlsx]
+```
+
+- **没有 `--column-keywords`**,明细列固定
+- 数据来源不同:`check result` + `check record`(非 `report query-data`)
+- 分批限制:≤100 人/次(而非 20 人)
+
+#### 考勤记录
+
+```bash
+python scripts/attendance_report_record.py \
+ --type \
+ --users ,,... \
+ --start "" \
+ --end "" \
+ [--out 请假记录_研发部_202604.xlsx]
+```
+
+参数说明:
+- `--type`(必填):记录类型,支持 `leave`(请假) / `trip`(出差) / `out`(外出) / `patch`(补卡)
+- `--users`(必填):逗号分隔的 userId 列表
+- `--start`(必填):开始日期 `YYYY-MM-DD`
+- `--end`(必填):结束日期 `YYYY-MM-DD`
+- `--out`(可选):输出文件名,不传则自动生成
+- **没有 `--column-keywords`**,列由 `--type` 决定
+
+脚本内部自动处理:`attendance approve list` 获取审批摘要 → `oa approval detail` 获取详情 → 解析 DDHolidayField / extValue → 按天拆行 → `contact user get` 姓名映射 → 生成 Excel
+
+**记录类型选择规则**(Agent 需从用户意图中判断):
+
+| 用户说 | --type 值 |
+|--------|----------|
+| "请假记录" / "年假记录" / "调休记录" / "病假记录" | `leave` |
+| "出差记录" | `trip` |
+| "外出记录" | `out` |
+| "补卡记录" | `patch` |
+
+#### 签到报表
+
+```bash
+python scripts/attendance_report_checkin.py \
+ --users ,,... \
+ --start "