feat: 新增企业微信助手 Agent,并修正 wecom-cli 条目 ref 漂移 (#112)

## 概述 / Overview

两件事:新增「企业微信助手」Agent(自带 15 个技能),并修正 `wecom-cli` 条目钉在 6 月快照的 ref 漂移。

Two changes: adds the **WeCom Assistant** agent (bundling 15 skills),
and fixes the `wecom-cli` entry whose pinned ref was stuck on a June
snapshot.

## 1. 新增企业微信助手 Agent

覆盖企业微信 **14 类服务、95
个方法**:消息、群聊历史、通讯录、日程、会议、待办、邮件、在线文档、在线表格、智能表格、智能文档、文档管理、微盘、媒体文件。

**采用内联形态 + 自带私有技能**:Agent 安装对 `agents/<id>/` 整目录递归复制且 `skills/`
不在排除集合里,因此装 Agent 即带全部技能,用户无需再单独获取技能合集。

### 技能集(15 个,约 5000 行)

- 基于上游 [wecom-cli](https://github.com/WecomTeam/wecom-cli) 官方
Skill(MIT,© WecomTeam)改写,每个技能末尾保留归属声明
- **新增 `wecom-chat`**:补齐上游零覆盖的群聊历史读取
- 补齐上游未覆盖的 `message.send`、`doc.create`,方法覆盖达 **95/95**
- 修正上游三处文档漂移:邮件能力描述与实际相反、会议室参数名已过时、`title_highlight` 字段不存在

### 相对上游的核心增量:风险治理

- 26 个对外可见或不可逆的方法逐个写明**执行前确认要求**
- 4 个条件升级方法给出**参数级判据**,而非按方法名一刀切
- 文档权限扩散两项加重处理,涉及**企业外可见**时单独再确认一次
- 内部标识禁止外露,不因用户索要而放宽
- 拒绝导出可识别到具体自然人的隐私字段

### 三条真机实测得出、上游未覆盖的硬约束

1. 机器人**只能写入/修改自己创建的数据**,真人创建的只能读
2. 每次响应携带的 `extra_identity_context` **禁止透露给用户**
3. 权限错误(`850002`/`851008`/`853006`)**不得重试**,须将 `help_message`
**逐字原样**转给用户

## 2. 修正 wecom-cli 条目 ref 漂移

`source.ref` 原钉在 2026-06-28 的 `72e14f7`,该快照只有 7
个子技能且用已废弃的旧命名(`msg`/`schedule`)。上游 v1.2.0 已扩展到 **14 个**技能。按旧 ref
安装的用户拿到的是三个月前的快照。

- `source.ref` → `78c514b2afee7c0d3d7be715628478421f37ee63`
- `children` 由 `scripts/gen-collection-children.py` 重新生成,**7 → 14**
- sidecar 同步 `provenance.content.ref`、`childCount` 与 `children`

## 验证 / Verification

**静态**
- 215 条示例命令追加 `--dry-run` 实跑,**215/215 退出码 0**
- 未知方法 0、未知参数 0、`--json` 未知字段 0、枚举违规 0
- 15 个 `SKILL.md` 的 frontmatter 经客户端 `skillFrontmatterSchema` 校验全部通过
- `validate_catalog_metadata.py --require-complete` 与
`gen-collection-children.py`:**0 error**

**真机(在真实企业微信账号上端到端)**
- **待办域 6/6 方法全通**(含 2 个 write-high),`items` 必填的隐蔽坑实测证实
- **日程域 5 个方法全通**(含 3 个 write-high)
- 消息发送、通讯录解析、微盘列表、邮件搜索、文档搜索、会议列表、智能表格创建均已实测通过
- 测试数据已全部清理,未污染真实账号

**尚未实测**:群聊历史(机器人未开通该品类)。相关文档已明确标注验证状态,未实测的能力不写「实际效果」段落。
This commit is contained in:
2026-09-03 03:50:00 -04:00
committed by GitHub
parent c83f917901
commit aec2e7c28b
57 changed files with 16893 additions and 45 deletions

View File

@@ -0,0 +1,271 @@
---
name: wecom-disk
description: >-
企业微信微盘(网盘)文件操作:列出最近浏览、按关键词/类型/创建者/空间搜索、读取文件元信息(在哪个空间、哪个文件夹、多大、谁建的)、
上传本地文件、下载文件到本地、重命名文件、新建文件夹。
当用户说"微盘""网盘""共享空间""团队盘",或说"传到微盘""微盘里搜一下""微盘那个 PPT 在哪""下载微盘那个文件""把微盘那个文件改个名"
或直接给出 https://drive.weixin.qq.com/s?k=... 形式的链接时使用。
只做文件级操作在线文档doc/sheet/smartsheet/smartpage的正文读写不归本技能改文档权限/加成员也不归;
移动、删除、复制文件、管理共享空间与分享链接企微 CLI 均不支持,需引导用户去客户端。
version: 1.0.0
type: procedural
risk_level: medium
status: enabled
tags:
- wecom
- disk
---
# 企业微信微盘
帮用户在微盘里**找到文件、拿到文件、放进文件、理顺文件名和目录**。
微盘装的既有离线二进制文件Word/Excel/PPT/PDF/图片/音视频),也有在线协作文档的入口——
这两类的处理方式完全不同,是本技能最需要分清的一件事。
> **前置**:执行任何 `wecom-cli` 命令前,必须先完成 `wecom-shared` 的前置检查
> CLI 已安装、版本达标、`auth show --status` 为 `authorized`;具体版本门槛以 `wecom-shared` 为准)。
## 能力清单
| 能力 | 命令 | 风险 |
|---|---|---|
| 列出最近浏览过的文件 | `wecom-cli disk files list` | read |
| 搜索文件 / 文件夹 / 共享空间 | `wecom-cli disk files search` | read |
| 读取一个文件的元信息 | `wecom-cli disk files get` | read |
| 下载文件到本地 | `wecom-cli disk files download` | read只写本地磁盘无远端副作用 |
| 上传本地文件到微盘 | `wecom-cli disk files upload` | write-low |
| 新建文件夹 | `wecom-cli disk folders create` | write-low |
| 重命名文件 | `wecom-cli disk files rename` | write-low ⚠️ **条件升级为 write-high** |
### ⚠️ `disk files rename` 的条件升级判据
重命名个人空间里自己的文件是可回退的小操作;但**共享空间里的重命名对全体协作者立刻可见**
别人看到的是文件"凭空改名了"。判据如下:
1. 改名前先 `wecom-cli disk files get --file-id '<file_id>'`,读返回的 `file.space_name``file.path`
2. `space_name` 指向**团队 / 共享空间**(而非该用户自己的个人空间)→ **按 write-high 处理**
3. 接口没有"是不是个人空间"的布尔字段,**判不准时一律按共享空间处理**(保守升级,不赌)。
命中升级时:
> ⚠️ **高风险操作**:共享空间里的文件改名对该空间全体协作者立刻可见,别人会看到文件"凭空改了名"。
> 执行前必须向用户复述「把共享空间「<空间名>」里的「<原文件名>」改名为「<新文件名>」」并取得明确同意;
> 用户未明确同意时不得执行。
(改名本身可以再 `rename` 一次改回去,所以是"对外可见"而非"不可逆";确认的目的是别在别人眼皮底下动共享文件。)
`disk files upload` 上传到共享空间文件夹时同样对该空间成员可见。它仍是 write-low新增文件可再删
但目标位置不明确时要先问清楚传到哪里,不要默认往共享空间塞。
## 场景:找文件
### 「微盘里搜一下 XX」「那个季度汇报的 PPT 在哪」
```bash
wecom-cli disk files search --json '{"keywords": ["季度汇报"], "limit": 10}'
```
`keywords` / `creator_userids` / `search_type` / `file_types` **四选一,至少传一个**才能发起搜索
`space_keywords` 只是附加过滤,单独传不足以触发)。四者全空时用自然语言追问用户搜什么。
按类型收窄(用户明确点了形态时才传):
```bash
wecom-cli disk files search --json '{
"keywords": ["报告"],
"file_types": ["sheet", "offline_excel"],
"search_type": "file",
"sort_by": "modify_time",
"sort_order": "desc",
"limit": 20
}'
```
- **`keywords` 里不要混文件类型后缀**「Excel 报告」应拆成 `keywords:["报告"]` + `file_types:["sheet","offline_excel"]`
- **在线/离线拿不准就都传**用户说「Excel」「Word」「PPT」「PDF」而没说在线还是离线时
两个枚举一起传(`["sheet","offline_excel"]` / `["doc","offline_word"]` / `["ppt","offline_ppt"]` / `["pdf","offline_pdf"]`)。
- **限定空间**:用户说「在 XX 空间里搜」时用 `space_keywords`(填空间**名称关键词**,本接口不接受空间 ID
- **限定创建者**:用户说「张三上传的」时,先用 `wecom-contact` 把姓名解析成 `userid`,再填 `creator_userids`
- **没有时间范围参数**:接口没有 `begin_time` / `end_time`**禁止伪造**。
用户说「最近三天的」时改用 `sort_by: "modify_time"` + `sort_order: "desc"` 拉取,再按返回的 `update_time` 自行筛。
### 「我最近看过的微盘文件」
```bash
wecom-cli disk files list --json '{"limit": 10}'
```
注意这是**最近浏览过**的列表(按最后浏览时间倒序),不是全盘目录树。
用户想看"微盘里都有什么"时要用搜索,不是这个。
### 「这个文件在微盘哪里」「这文件多大、谁建的」
```bash
wecom-cli disk files get --file-id '<file_id>'
# 或者用户直接给了微盘分享链接:
wecom-cli disk files get --url 'https://drive.weixin.qq.com/s?k=XXXXXXXX'
```
`--file-id``--url` 二选一(同时给时以 `file_id` 为准)。
返回 `file.space_name`(所在空间)、`file.folder_name`(所在文件夹)、`file.path`(完整路径)、
`file.file_size``file.create_time` / `update_time``file.type`
`file.creator_userid` 要展示创建者时,先用 `wecom-contact` 换成姓名再说。
## 场景:拿文件
### 「把微盘那个文件下载下来 / 看看里面写了什么」
```bash
wecom-cli disk files download --file-id '<file_id>'
# 或
wecom-cli disk files download --url 'https://drive.weixin.qq.com/s?k=XXXXXXXX'
```
返回本地 `file_path`(内容不长时还会直接给 `file_content`)与 `size`。拿到本地路径后按常规方式读内容。
> **[CRITICAL] 只有 `type=file` 的离线二进制文件能下载。**
> 搜索/列表返回的 `type` 若是 `smartsheet` / `smartpage` / `sheet` / `word` / `ppt` / `journal` / `collect` / `mind` / `flow`
> 这些是**在线协作文档**,正文存在云端,把它们的 `id` 或 `doc_url` 当 `file_id` / `url` 传进来会失败或拿到空壳。
> 正确路由见下方「在线文档怎么办」。
### 在线文档怎么办
| 命中项 `type` | 用户想读内容时 |
|---|---|
| `word``docid``a1_`/`b1_` 开头)、`doc` | 把 `docid` 交给 `wecom-doc` |
| `sheet` | 把 `docid` 交给 `wecom-sheet` |
| `smartsheet` | 把 `docid` 交给 `wecom-smartsheet` |
| `smartpage`,或 `word``docid``a1_` / `b1_` 开头 | 把 `docid` 交给 `wecom-smartpage` |
| `ppt` / `journal` / `collect` / `mind` / `flow` | **没有任何技能或 CLI 能读正文**。如实告知暂不支持,把 `doc_url` 给用户,引导其在企业微信客户端打开 |
`doc_url` 是可读链接,**允许直接展示给用户**,也可以直接当分享链接发出去。
## 场景:放文件
### 「把这个文件传到微盘」
手上是本地文件时,直接传路径,**不需要**先过 `wecom-media`
```bash
wecom-cli disk files upload --file-path '/abs/path/季度汇报.pptx' --folder-id '<folder_id 或 space_id>'
```
手上已经有 `media_id`(前置技能返回或用户给出)时复用它,此时 `--file-name` 必填:
```bash
wecom-cli disk files upload --json '{
"file_content_media": "<media_id>",
"file_name": "季度汇报.pptx",
"folder_id": "<folder_id 或 space_id>"
}'
```
- `--file-path``--file-content-media` **二选一,必须有其一,不能同时传**。两者都没有时追问用户,禁止靠搜索凑一个文件。
- `--file-name`:传 `file_path` 时可不传(从路径自动提取);传 `file_content_media` 时**必填**。
名称长度 1~255且**不能含** `/ \ : * ? " < > |`
- `--folder-id` 不传则上传到默认空间。目标位置不明确时先问清楚,不要默认往共享空间塞。
### 「在微盘建个文件夹」
```bash
wecom-cli disk folders create --folder-name '2026 年季度材料' --folder-id '<父文件夹 file_id 或 space_id>'
```
`--folder-name` 必填1~255禁含 `/ \ : * ? " < > |``--folder-id` 不传则建到个人空间根目录。
### 「把微盘那个文件改个名」
```bash
# 第一步:文件名不是 file_id先搜出来
wecom-cli disk files search --json '{"keywords": ["季度汇报"], "search_type": "file", "limit": 10}'
# 第二步:确认它在哪个空间(决定是否需要用户确认,见上方条件升级判据)
wecom-cli disk files get --file-id '<第一步命中的 files[].id>'
# 第三步:改名
wecom-cli disk files rename --file-id '<file_id>' --new-name '2026Q2 季度汇报.pptx'
```
`--file-id``--new-name` 均必填。新名称 1~255**不能含** `/ \ : * ? " < > |`,且要**带上原扩展名**。
返回只有 `status: "success"`,不带文件对象;需要最新元数据就再 `disk files get` 一次。
> 在线文档doc/sheet/smartsheet/smartpage的改名归 `wecom-doc-manage`,不走这里。
> **文件夹(`folder`)不支持重命名**,如实告知用户去客户端操作。
## 参数速查
| 方法 | 必填 | 关键可选与约束 |
|---|---|---|
| `disk files list` | 无 | `--cursor``--limit` |
| `disk files search` | `keywords` / `creator_userids` / `search_type` / `file_types` 至少一个 | `--keywords` ≤20、`--file-types` ≤10、`--creator-userids` ≤50、`--space-keywords` ≤10、`--limit` ≤100默认 10`--cursor``--sort-by``--sort-order` |
| `disk files get` | `--file-id``--url` 二选一 | — |
| `disk files download` | `--file-id``--url` 二选一 | — |
| `disk files upload` | `--file-path``--file-content-media` 二选一 | `--file-name`(用 media 时必填)、`--folder-id` |
| `disk files rename` | `--file-id``--new-name` | — |
| `disk folders create` | `--folder-name` | `--folder-id` |
**枚举取值(写枚举外的值会失败):**
- `search_type``all`(默认)/ `file` / `folder` / `space`
- `sort_by``best_match`(默认)/ `modify_time` / `file_size`
- `sort_order``asc` / `desc`(默认)
- `file_types``doc` / `sheet` / `ppt` / `collect` / `mind` / `flow` / `smartsheet` / `smartpage` / `journal` / `pdf` /
`offline_word` / `offline_excel` / `offline_ppt` / `offline_pdf` / `image` / `videoaudio` / `design`
- 返回的 `type``file` / `folder` / `space` / `smartsheet` / `smartpage` / `sheet` / `word` / `ppt` / `collect` / `journal` / `flow` / `mind`
**可选参数的默认策略:默认不传,仅当用户明确点名时才传。**
用户笼统说「搜一下 xxx / 找找资料」时,`search_type` / `sort_by` / `file_types` 都不传,让后端用默认值。
## 明确不支持(如实告知,引导去客户端)
- 移动 / 删除 / 复制文件;删除或重命名**文件夹**;调整目录树
- 创建 / 删除共享空间,修改空间成员与设置
- 修改分享权限、生成或撤销分享链接、设置访问密码与有效期
- 版本管理(看历史版本、恢复旧版、比对)
- 覆盖上传 / 秒传 / 断点续传(需要替换就重新上传一份新文件)
- 持续监视微盘变更、新文件到达通知 —— **不要承诺「有新文件我告诉你」**,请用户稍后自己再问
- **给机器人授予某空间权限 / 把机器人加进共享空间成员**:微盘**没有**这个功能,客户端也做不到。
**禁止**向用户提这类建议,也不要引导用户「联系空间管理员给机器人授权」
## 易错点
- **文件名不是 `file_id`**:用户给名字/关键词时先 `search``id`,禁止把文件名当 `file_id` 拼进命令。
- **域名分不清就全错**`drive.weixin.qq.com` 才是微盘;`doc.weixin.qq.com` / `page.weixin.qq.com` 是在线文档,
传进本技能的 `--url` 会失败。
- **在线文档不能下载**:见上方 [CRITICAL]。只有 `type=file` 才可 `download`
- **搜索必须有界**一组条件搜完必要时再调整一次2~3 轮仍无结果就停下来如实告知"未搜到"
并请用户补更准的关键词 / 类型 / 创建者,**禁止无限换词硬搜**。
停下时要说清楚是「搜不到文件」还是「搜不到这个空间」。
- **重名要追问**:搜出多个同名空间或文件夹时,用序号 + 可读信息(名称/路径/时间)让用户选,禁止随手选第一个。
- **`path` 才是层级真相**`space_name``folder_name` 同名时不一定是父子关系,可能平级,判断层级看 `path`
- **翻页**`has_more=true` 时把 `next_cursor` 填进下一次的 `cursor`;首次调用 `cursor` 传空串或不传。
- **展示顺序跟随排序方向**`sort_order=desc` 时向用户也从新到旧展示,不要颠倒。
- **内部 ID 一律不外露**`id` / `file_id` / `space_id` / `folder_id` / `docid` / `creator_userid` / `cursor` / `next_cursor`
只能内部流转。要展示创建者就先用 `wecom-contact` 换姓名。**唯一例外是 `doc_url` 等可读链接**,可正常展示。
- **禁止绕过 CLI**:不得用 `curl` / `python` 等手段直接请求企微接口。CLI 报错时原样转达错误信息并给替代建议。
## 结果展示规范
展示 `list` / `search` 结果时:
-**markdown 无序列表**逐条展示,**不要用表格**,最多展示 10 条。
- 每条首行:`doc_url` 非空(在线文档)写成 `- [文件名](doc_url)``doc_url` 为空(离线文件 / 文件夹 / 空间)
写成 `- 文件名`**不得编造链接**。
- 副行可放 `path` / `update_time` / 可读大小(如 `2.4 MB`),字段间用 `·` 或空格分隔。
- 禁止直接贴原始 JSON禁止出现任何内部 ID。
## 跨技能依赖
| 技能 | 何时触发 |
|---|---|
| `wecom-shared` | 每次执行 `wecom-cli` 前的前置检查(必做) |
| `wecom-contact` | 用户按「谁上传的」搜索时把姓名解析成 `userid`;要展示 `creator_userid` 时换姓名 |
| `wecom-media` | 上下文已有 `media_id` 想传进微盘时,直接填 `--file-content-media` 即可(**不用**再跑 media upload只有本地文件时也直接填 `--file-path` |
| `wecom-doc` / `wecom-sheet` / `wecom-smartsheet` / `wecom-smartpage` | 命中在线文档且用户要读正文时,按 `type` / `docid` 前缀路由 |
| `wecom-doc-manage` | 在线文档的改名 / 加成员 / 改权限 |
---
## 来源
本技能改写自 [wecom-cli](https://github.com/WecomTeam/wecom-cli) 官方 Skill
MIT License© WecomTeam针对 DesireCore 的风险治理与交互约定做了适配。
上游对应技能:`wecomcli-disk`