mirror of
https://git.openapi.site/https://github.com/desirecore/market.git
synced 2026-09-06 02:44:45 +08:00
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:
305
agents/wecom-assistant/skills/wecom-doc/SKILL.md
Normal file
305
agents/wecom-assistant/skills/wecom-doc/SKILL.md
Normal file
@@ -0,0 +1,305 @@
|
||||
---
|
||||
name: wecom-doc
|
||||
description: >-
|
||||
企业微信**在线文档(Word 类,doc)**的正文读写:新建 doc 文档、把本地 .docx/.doc/.txt 导入成
|
||||
doc 文档、读取正文、向末尾追加内容、全量覆盖正文。**仅当**用户明确说了 "doc""docx""word"
|
||||
"在线文档""office 文档",或给出 https://doc.weixin.qq.com/doc/xxx 链接时才用它。
|
||||
用户只说"创建文档 / 写个文档 / 整理成文档 / 输出到文档"而没指明类型时,**默认走
|
||||
wecom-smartpage(智能文档),本技能不得抢占**。搜索文档、改名、加成员、改权限找 wecom-doc-manage;
|
||||
在线表格找 wecom-sheet;智能表格找 wecom-smartsheet;智能文档找 wecom-smartpage。
|
||||
version: 1.0.0
|
||||
type: procedural
|
||||
risk_level: high
|
||||
status: enabled
|
||||
tags:
|
||||
- wecom
|
||||
- doc
|
||||
---
|
||||
|
||||
# 企业微信在线文档(Word 类)正文读写
|
||||
|
||||
本技能只管一件事:**一份 `doc` 类型在线文档里的文字**——怎么把它建出来、读出来、往里加、整个换掉。
|
||||
文件本身叫什么、谁能看,不归本技能。
|
||||
|
||||
> **前置**:执行任何 `wecom-cli` 命令前,必须先完成 `wecom-shared` 的前置检查
|
||||
> (CLI 安装 / 版本 ≥ 1.2.0 / 授权状态),并遵守其中的 ID 禁露约束与风险确认约定。
|
||||
|
||||
## 文档类技能的分工边界
|
||||
|
||||
| 用户想做的事 | 归属技能 |
|
||||
|---|---|
|
||||
| 搜索任何文档(唯一入口) | `wecom-doc-manage` |
|
||||
| 改文档名 / 加成员 / 改权限 / 改加入规则(任何类型) | `wecom-doc-manage` |
|
||||
| **读写在线文档(Word 类)正文** | **本技能** |
|
||||
| 读写在线表格数据 / 增删子表 | `wecom-sheet` |
|
||||
| 读写智能表格字段与记录 | `wecom-smartsheet` |
|
||||
| 读写智能文档 / 智能主页内容 | `wecom-smartpage` |
|
||||
|
||||
### 什么时候**不是**本技能(先判这一段,再往下看)
|
||||
|
||||
- 用户说"创建文档 / 写个文档 / 整理成文档 / 输出到文档"**且没指明类型** → `wecom-smartpage`。
|
||||
这是产品默认落点,**本技能不得抢占**。
|
||||
- 链接是 `https://doc.weixin.qq.com/smartpage/...` 或 `https://page.weixin.qq.com/smartpage/...`
|
||||
→ `wecom-smartpage`。
|
||||
- `docid` 以 `a1_` / `b1_` 开头 → `wecom-smartpage`;以 `s3_` 开头 → `wecom-smartsheet`。
|
||||
- 链接是 `https://doc.weixin.qq.com/sheet/...` → `wecom-sheet`。
|
||||
- 域名是 `drive.weixin.qq.com` → 微盘,转 `wecom-disk`。
|
||||
- 请求里有**字段 / 记录 / 筛选 / 排序 / 统计 / 分组**这类结构化数据语义
|
||||
→ **严禁**用"doc + markdown 静态表格"变通替代,改用 `wecom-smartsheet`(智能表格)
|
||||
或 `wecom-smartpage`(智能文档)。
|
||||
|
||||
## 能力清单
|
||||
|
||||
| 能力 | 命令 | 风险 |
|
||||
|---|---|---|
|
||||
| 导入本地文件为 doc 文档(**也是"新建"的落地方式**) | `wecom-cli doc import` | write-low |
|
||||
| 读取 doc 文档正文 | `wecom-cli doc contents get` | read |
|
||||
| 向 doc 文档末尾追加文本 | `wecom-cli doc contents append` | write-low |
|
||||
| 全量覆盖 doc 文档正文 | `wecom-cli doc contents overwrite` | **write-high(不可逆覆盖)** |
|
||||
| 直接新建空/纯文本 doc | `wecom-cli doc create` | write-low —— **本技能刻意不用**,新建统一走「生成 .docx → `doc import`」,理由见下方专节 |
|
||||
|
||||
> 本技能的 `risk_level` 是 `high`:它包含 `doc.contents.overwrite` 这个不可逆覆盖方法。
|
||||
> 虽然影响范围限于**单份文档的正文**,但按统一口径,含 write-high 方法的技能一律标 `high`。
|
||||
> 必须按下方场景四的确认要求执行——技能级的 `risk_level` 不会降低单个方法的确认档位。
|
||||
|
||||
## `docid` 的获取与展示规则
|
||||
|
||||
`docid` **只能内部流转,禁止自造,禁止展示给用户**。三级获取优先级:
|
||||
|
||||
1. **从用户给的链接提取(优先)**:`https://doc.weixin.qq.com/<type>/<docid>?scode=...`,
|
||||
取 `/<type>/` 后、`?` 前的一段。
|
||||
2. **用 `wecom-doc-manage` 搜索获得(备选)**:用户只给了文档名或关键词时。
|
||||
搜到多条时按可读候选让用户选定,不得自行挑一个。
|
||||
3. **用户直接给出完整 `docid`**:可直接用。
|
||||
|
||||
展示给用户时一律写成 `[doc_name](url)`,用接口返回的 `url` 原样。
|
||||
|
||||
## 场景一:新建一篇 doc 文档
|
||||
|
||||
### 用户会怎么说
|
||||
|
||||
"给我建个 word 文档写周报" / "新建一个 doc 文档" / "把这些内容做成一份 docx 放到企微上"
|
||||
|
||||
### 主流程:生成 `.docx` → `doc import`(两步,**不用 `doc.create`**)
|
||||
|
||||
**本技能刻意不使用 `doc.create` 新建 doc 文档**,而是保留上游"先在本地生成 `.docx`,
|
||||
再 `doc import` 导入"的两步流程。理由见下方「为什么不用 `doc.create`」。
|
||||
|
||||
**Step 1:写一份 JSONL 描述文件,用 `scripts/build_docx.py` 生成 `.docx`**
|
||||
|
||||
JSONL 的完整书写规范(4 个 action、样式、表格、混排格式)见
|
||||
[references/docx-build.md](references/docx-build.md)——**首次生成 `.docx` 前必须先读完它**。
|
||||
|
||||
```bash
|
||||
# WECOMAGENT_READABLE_DIRS / WECOMAGENT_WRITABLE_DIRS 必须显式设置,
|
||||
# 否则脚本直接以退出码 2 失败(详见 references/docx-build.md)
|
||||
WECOMAGENT_READABLE_DIRS='[{"path":"<工作目录绝对路径>","label":"work"}]' \
|
||||
WECOMAGENT_WRITABLE_DIRS='[{"path":"<工作目录绝对路径>","label":"work"}]' \
|
||||
python3 scripts/build_docx.py '<工作目录绝对路径>/项目周报.jsonl'
|
||||
```
|
||||
|
||||
成功时脚本打印 `Successfully built <绝对路径>`,产物落在
|
||||
`<第一个可写根>/docx/<jsonl 文件名主干>.docx`。**把这一行里的路径抓出来给 Step 2 用。**
|
||||
|
||||
**Step 2:导入为企微 doc 文档**
|
||||
|
||||
`file_name` **必须与你想要的文档标题一致**(含 `.docx` 后缀)——导入后的文档名取自它:
|
||||
|
||||
```bash
|
||||
wecom-cli doc import \
|
||||
--doc-type doc \
|
||||
--file-name '项目周报.docx' \
|
||||
--file-path '<Step 1 打印出来的绝对路径>'
|
||||
```
|
||||
|
||||
返回 `docid` / `url` / `task_id` / `task_status`(`succ` / `fail` / `processing`)。
|
||||
`task_status=succ` 时把 `[项目周报](url)` 给用户;`processing` 时说明仍在处理,
|
||||
`fail` 时把错误如实告知,**不要**假装成功。
|
||||
|
||||
### 只有纯文本、不需要排版时
|
||||
|
||||
`doc import` 也接受 `.txt`(上游声明支持 `.doc` / `.docx` / `.txt`)。内容是纯文本且用户没有
|
||||
排版要求时,可以直接写一个 `.txt` 再导入,跳过 `build_docx.py`:
|
||||
|
||||
```bash
|
||||
wecom-cli doc import --doc-type doc --file-name '会议纪要.txt' --file-path '/abs/path/会议纪要.txt'
|
||||
```
|
||||
|
||||
### 为什么不用 `doc.create`
|
||||
|
||||
`doc.create` 确实存在(`wecom-cli doc create --doc-name '<名称>'`,`doc_name` 是唯一必填),
|
||||
且能带初始内容。上游 `wecomcli-doc` **刻意绕开了它**,本技能保留这一设计,依据有三条:
|
||||
|
||||
1. **`doc.create` 的初始内容通道能力太弱**。它的 `content` 只接受
|
||||
`content_type` ∈ `text` / `markdown`(schema enum),本质是往文档里灌一段纯文本或 markdown;
|
||||
而用户对"生成一份 word 文档"的期待通常包含**封面标题、多级标题、列表、表格、局部加粗与配色**。
|
||||
走 `.docx` 导入能一次性把这些排版带进去,走 `doc.create` 则只能拿到一坨没有结构的文字。
|
||||
(`doc.create` 另有 `doc_requests` 这条"document 节点编辑写入"的结构化通道,但
|
||||
`OaUpdateRequest` 的节点结构在 schema 里没有可直接照抄的书写规范,**上游没有任何技能用过它**,
|
||||
现场发明极易失败。)
|
||||
2. **两步流程与 `wecom-sheet` / `wecom-smartpage` 的形态一致**,都是"本地产物 → import",
|
||||
Agent 只需要掌握一套心智模型;而 `doc.create` 与 `sheet.create`
|
||||
在后端其实是**同一个方法的两个别名**(两者的请求体都是 `OaDocCreateReq`,靠 `doc_type` 区分,
|
||||
已逐字段核对 schema 确认;`smartsheet.create` 是另一个请求体 `SmartSheetCreateReq`,不在此列),
|
||||
在 doc 这一侧单独引入它并不会带来新能力。
|
||||
3. **`doc.create` 属于 R2 报告认定的"零技能覆盖"方法**,上游 14 个 SKILL.md 全文没有一次用到它,
|
||||
因此它在真实链路上的行为**没有任何上游经验背书**。
|
||||
|
||||
> **保留意见(供后续验证,不影响当前主流程)**:单纯"建一个空文档"或"建一个只有几行纯文字的文档"
|
||||
> 这类场景,`doc create --doc-name 'X' --content '...' --content-type text` 一条命令就能完成,
|
||||
> 比"写 JSONL → 跑 python → import"轻得多。若后续实测确认其行为符合预期,
|
||||
> 可以把它作为**纯文本 / 空文档场景的快捷路径**补进来;
|
||||
> **在获得实测证据前,主流程一律走导入**,不要临场切换。
|
||||
|
||||
## 场景二:读取 doc 文档正文
|
||||
|
||||
### 用户会怎么说
|
||||
|
||||
"这份文档写了什么" / "把周报内容读出来" / "总结一下这个文档"
|
||||
|
||||
```bash
|
||||
wecom-cli doc contents get --docid '<docid>'
|
||||
```
|
||||
|
||||
`--content-type` 可选 `text` / `markdown` / `ooxml`,**不传默认 `markdown`**。
|
||||
|
||||
| 想要什么 | 传什么 |
|
||||
|---|---|
|
||||
| 给用户看 / 让模型总结(默认) | 不传,或 `--content-type markdown` |
|
||||
| 只要纯文字、不要标记 | `--content-type text` |
|
||||
| 需要底层文档对象结构 | `--content-type ooxml`(返回 `document` 对象,不返回 `content`) |
|
||||
|
||||
**返回里有两条互斥的取内容路径**:
|
||||
|
||||
- 内容不长 → `content` 字段直接是正文,可直接消费。
|
||||
- 内容超长 → 框架**自动落盘**,`content` 为空、`file_path` 是本地文件绝对路径。
|
||||
这时**必须再用文件读取工具把该路径读进来**才能展示或分析。
|
||||
向用户汇报时**不要展示这个本地路径**,说"内容较长,我已读取完"即可。
|
||||
|
||||
返回还带 `name`(文档标题)、`url`(文档链接)、`version`(版本号)。
|
||||
展示时用 `[name](url)`。
|
||||
|
||||
## 场景三:向文档末尾追加内容
|
||||
|
||||
### 用户会怎么说
|
||||
|
||||
"在这个文档里再加一段" / "把今天的进展记到周报里" / "补充一条" / "写进去"
|
||||
|
||||
### 追加 vs 覆盖的裁定规则(每次写入前都要过一遍)
|
||||
|
||||
- **默认追加**:用户用"写入 / 写到 / 记录 / 补充 / 加进去 / 记一下 / 追加"等**中性动词**,
|
||||
且没有明确要求清空或替换 → 一律走 `append`。
|
||||
- **仅显式覆盖**:只有出现"覆盖 / 重写 / 替换 / 清空重写 / 整个换成"等**强语义词**时才走 `overwrite`。
|
||||
- 判不准就**按追加处理**——追加错了可以再覆盖修正,覆盖错了原文就没了。
|
||||
|
||||
```bash
|
||||
wecom-cli doc contents append \
|
||||
--docid '<docid>' \
|
||||
--content '2026-08-31 进展:完成联调,进入压测阶段。'
|
||||
```
|
||||
|
||||
- `content` 只支持 **`text`(纯文本)**,没有 `content_type` 参数。写 markdown 标记不会被渲染。
|
||||
- `content` 的长度上限是 **10000 字符**(schema `maxLength`)。
|
||||
内容更长时分多次追加,或改用覆盖(其上限是 1000000)。
|
||||
- schema 上 `content` 是可选、只有 `docid` 必填;但**不传 `content` 的追加没有任何意义**,
|
||||
实际使用时必须传。
|
||||
|
||||
成功返回空对象。执行后汇报"已追加到《文档名》",给出 `[doc_name](url)`。
|
||||
|
||||
## 场景四:全量覆盖文档正文
|
||||
|
||||
### 用户会怎么说
|
||||
|
||||
"把这个文档整个重写" / "覆盖成下面的内容" / "清空重写" / "整份换成新版"
|
||||
|
||||
> ⚠️ **高风险操作(不可逆覆盖)**:本方法会**用新内容替换掉文档的全部原有正文**。
|
||||
> 原文没有任何备份,CLI 也**没有回滚接口**——写下去就找不回来了。
|
||||
> 执行前必须向用户复述
|
||||
> 「将把《\<文档名\>》的**全部现有正文**替换为新内容(约 \<N\> 字),原内容不可恢复」
|
||||
> 并取得明确同意;用户未明确同意时不得执行。
|
||||
|
||||
**执行前的三条硬要求**:
|
||||
|
||||
1. **先读再写**。覆盖前**必须**先 `doc contents get` 读一遍现有正文,
|
||||
在复述里说清"这份文档现在有什么"(一两句摘要即可),让用户知道自己要毁掉的是什么。
|
||||
跳过这一步的覆盖等于蒙眼删除。
|
||||
2. **复述必须带上文档名与新内容规模**,用姓名/文档名等可读信息,不要出现 `docid`。
|
||||
3. 用户回复含糊("嗯""你看着办")**不算**明确同意,需要再确认一次。
|
||||
|
||||
### 命令
|
||||
|
||||
内容直接给(推荐用于中短内容):
|
||||
|
||||
```bash
|
||||
wecom-cli doc contents overwrite \
|
||||
--docid '<docid>' \
|
||||
--content-type text \
|
||||
--content '<完整的新正文>'
|
||||
```
|
||||
|
||||
内容较长时先落到本地文件,再用 `--file-path`(与 `--content` **二选一**):
|
||||
|
||||
```bash
|
||||
wecom-cli doc contents overwrite \
|
||||
--docid '<docid>' \
|
||||
--content-type text \
|
||||
--file-path '/abs/path/新正文.txt'
|
||||
```
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|---|:--:|---|
|
||||
| `--docid` | 是 | 目标文档 |
|
||||
| `--content` | 否* | 完整新正文,上限 **1000000** 字符 |
|
||||
| `--file-path` | 否* | 本地文件路径,与 `--content` 二选一 |
|
||||
| `--content-type` | 否 | `text` / `markdown`(**没有 `ooxml`**,与读取不同);通常传 `text` |
|
||||
|
||||
\* schema 上只有 `docid` 是 required,但 `content` 与 `file_path` **两者不可同时缺省**,
|
||||
否则等于没给内容。
|
||||
|
||||
**清空文档不能传空值**:`content` 传 `null`、空字符串或干脆不传都会被拒。
|
||||
要清空请传 `" "`(**一个空格**)。(此规则来自上游 reference 的明文声明,未经实测复核。)
|
||||
|
||||
## 参数速查
|
||||
|
||||
| 方法 | 必填参数 | 高频可选参数 |
|
||||
|---|---|---|
|
||||
| `doc import` | schema 无 required;**实际必须**给 `--file-path`(或 `--file-content`)与 `--file-name` | `--doc-type`(**必须显式传 `doc`**) `--passwd` `--append-doc-id` |
|
||||
| `doc contents get` | `--docid` | `--content-type`(`text`/`markdown`/`ooxml`,默认 `markdown`) |
|
||||
| `doc contents append` | `--docid`(`--content` 实际必传) | 无 |
|
||||
| `doc contents overwrite` | `--docid` | `--content` / `--file-path`(二选一) `--content-type`(`text`/`markdown`) |
|
||||
|
||||
完整参数请用 `wecom-cli doc <resource> <method> --help` 现查,不要凭记忆补参数。
|
||||
|
||||
## 易错点
|
||||
|
||||
- **未指明类型的"写个文档"不归本技能**,默认落 `wecom-smartpage`。抢占是最常见的路由错误。
|
||||
- **`doc import` 的 `--doc-type` 默认是 `doc`,但仍要显式写上**。这个参数在
|
||||
`doc import` / `sheet import` / `smartsheet` 三处共用同一个后端方法,
|
||||
默认值只有一个(`doc`),显式写出来才不会在复制粘贴命令时串味。
|
||||
- **`doc import` 的 schema 没有任何 required 字段**——不传 `file_path` / `file_name`
|
||||
在本地校验阶段**不会报错**,会一路发到服务端才失败。别指望 CLI 帮你兜底。
|
||||
- **`file_name` 决定导入后的文档标题**,且必须含后缀。想让文档叫《项目周报》就传 `项目周报.docx`。
|
||||
- **`append` 的 `content` 上限 10000,`overwrite` 的上限 1000000**,两者差两个数量级。
|
||||
长内容追加要自己分段。
|
||||
- **`append` 不支持 markdown**(只有 `text`),而 `overwrite` 与 `contents get`
|
||||
支持 `markdown`。三个方法的格式能力**不一致**,别互相套用。
|
||||
- **`contents get` 的 `content_type` 有 `ooxml`,`overwrite` 没有**。
|
||||
读得出 ooxml 不等于写得回去。
|
||||
- **内容超长时 `contents get` 返回的是 `file_path` 而不是 `content`**,
|
||||
漏判会让你以为文档是空的。拿到 `file_path` 必须再读一次文件。
|
||||
- **覆盖前必须先读**。没读过就覆盖,等于在不知道毁掉什么的情况下毁掉它。
|
||||
- **清空要传一个空格 `" "`**,不是空字符串。
|
||||
- **`docid` 禁止自造、禁止展示**,展示一律用 `[doc_name](url)`;
|
||||
`contents get` 返回的本地 `file_path` 也不展示。
|
||||
- **`build_docx.py` 需要两个环境变量**(`WECOMAGENT_READABLE_DIRS` / `WECOMAGENT_WRITABLE_DIRS`)
|
||||
和 `python-docx` 依赖,缺任何一个都会以退出码 2 失败且**只打印一行笼统错误**。
|
||||
见 [references/docx-build.md](references/docx-build.md) 的排错表。
|
||||
|
||||
---
|
||||
|
||||
## 来源
|
||||
|
||||
本技能改写自 [wecom-cli](https://github.com/WecomTeam/wecom-cli) 官方 Skill
|
||||
(MIT License,© WecomTeam),针对 DesireCore 的风险治理与交互约定做了适配。
|
||||
上游对应技能:`wecomcli-doc`。
|
||||
`scripts/build_docx.py` 原样取自上游 `skills/wecomcli-doc/scripts/build_docx.py`,未做修改。
|
||||
174
agents/wecom-assistant/skills/wecom-doc/references/docx-build.md
Normal file
174
agents/wecom-assistant/skills/wecom-doc/references/docx-build.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 生成 `.docx`:`scripts/build_docx.py` 使用规范
|
||||
|
||||
新建企微 doc 文档的第一步。**模型只需写一份 JSONL 描述文件,不需要写 Python 脚本**——
|
||||
分发器 `build_docx.py` 会把每条命令派发到对应函数,生成带完整排版的 `.docx`。
|
||||
|
||||
## 整体工作流
|
||||
|
||||
| 步骤 | 做什么 | 产物 |
|
||||
|---|---|---|
|
||||
| 1 | 用文件写入工具输出一个 `*.jsonl` | `<工作目录>/项目周报.jsonl` |
|
||||
| 2 | `python3 scripts/build_docx.py <*.jsonl>` | `<可写根>/docx/项目周报.docx` |
|
||||
| 3 | `wecom-cli doc import --doc-type doc --file-name '项目周报.docx' --file-path '<Step 2 路径>'` | 企微在线文档 |
|
||||
|
||||
## ⚠️ 运行前置:两个环境变量 + 一个 Python 依赖
|
||||
|
||||
`build_docx.py` 自带沙箱式的路径白名单,**两个环境变量都必须显式设置,否则脚本直接失败**:
|
||||
|
||||
| 环境变量 | 作用 | 格式 |
|
||||
|---|---|---|
|
||||
| `WECOMAGENT_READABLE_DIRS` | 允许**读取** JSONL 的目录白名单 | JSON 数组:`[{"path":"/abs/dir","label":"任意标签"}]` |
|
||||
| `WECOMAGENT_WRITABLE_DIRS` | 允许**写出** `.docx` 的目录白名单 | 同上 |
|
||||
|
||||
- 两个变量都**不设置就用不了**(`_parse_roots` 会抛 `环境变量 ... 未设置或为空`)。
|
||||
上游是在企微自己的 Agent 宿主里跑的,那边由宿主注入;**在 DesireCore 里没有人注入,必须自己带上**。
|
||||
- **输出路径不是你指定的**:脚本取 `WECOMAGENT_WRITABLE_DIRS` 的**第一个** root,
|
||||
在其下拼出 `<root>/docx/<jsonl 文件名主干>.docx`。目录不存在会自动创建。
|
||||
同名文件已存在时追加 `_<毫秒时间戳>_<pid>` 后缀,**不会覆盖**已有文件。
|
||||
- 路径里**不允许出现 `.` 或 `..` 片段**,必须给完全展开的绝对路径。
|
||||
- Python 依赖:**`python-docx`**(`import docx`)。缺了会在 import 阶段就崩。
|
||||
- 读入 / 写出都有 **30 MiB** 硬上限。
|
||||
|
||||
完整调用形态:
|
||||
|
||||
```bash
|
||||
WORKDIR='<工作目录绝对路径>'
|
||||
WECOMAGENT_READABLE_DIRS="[{\"path\":\"$WORKDIR\",\"label\":\"work\"}]" \
|
||||
WECOMAGENT_WRITABLE_DIRS="[{\"path\":\"$WORKDIR\",\"label\":\"work\"}]" \
|
||||
python3 scripts/build_docx.py "$WORKDIR/项目周报.jsonl"
|
||||
```
|
||||
|
||||
成功时 stdout 打印一行:`Successfully built <绝对路径>`。**把这个路径抓出来喂给 `doc import`。**
|
||||
|
||||
### 排错表(脚本的错误信息很笼统,靠这张表反查)
|
||||
|
||||
| 现象 | 真实原因 |
|
||||
|---|---|
|
||||
| `Error: failed to pick output path`(退出码 2) | `WECOMAGENT_WRITABLE_DIRS` 没设 / 不是合法 JSON 数组 / 元素缺 `path` |
|
||||
| `Error: 路径不在允许范围内`(退出码 2) | JSONL 路径不在 `WECOMAGENT_READABLE_DIRS` 的任一 root 之内,或路径里含 `./` `../` |
|
||||
| `Error: 类型错误,无法执行: ...`(退出码 2) | JSONL 的 `action` 名写错、`params` 字段名/类型不对、或取值越界 |
|
||||
| `ModuleNotFoundError: No module named 'docx'` | 缺 `python-docx` 依赖 |
|
||||
| `Error: 执行失败,请检查输入文件格式或稍后重试`(退出码 2) | JSONL 不是每行一个合法 JSON(常见:有空行、或 JSON 跨了多行) |
|
||||
|
||||
排错失败时**如实告诉用户生成 `.docx` 失败**,不要伪造一个 `.docx` 路径去 import。
|
||||
纯文本内容也可以退回到"写 `.txt` 直接 import"的轻量路径。
|
||||
|
||||
## JSONL 书写规范
|
||||
|
||||
### 格式硬要求
|
||||
|
||||
- 文件后缀 `.jsonl`。
|
||||
- 每行一个 JSON 对象,结构固定:`{"action": "<函数名>", "params": {<入参对象>}}`。
|
||||
- **每个 JSON 对象必须压缩到单行**(表格这种嵌套结构也一样)。
|
||||
- **整个文件不得出现空行**,行与行直接相连。
|
||||
- 文件名主干只能是 `[A-Za-z0-9_.-]{1,128}`;不满足时脚本会把输出名回退成 `document.docx`
|
||||
(**中文文件名会触发这个回退**,想让产物名可控就用 ASCII 命名 JSONL)。
|
||||
|
||||
### 4 个 action
|
||||
|
||||
| action | 用途 |
|
||||
|---|---|
|
||||
| `add_heading` | **所有标题**:封面主标题(`level: 0`)+ 章节标题(`level: 1~4`) |
|
||||
| `add_paragraph` | 段落:纯文本 / 列表样式 / Subtitle / 多 run 混排格式 |
|
||||
| `add_table` | 固定布局表格 |
|
||||
| `add_page_break` | 分页(无参数,传 `{}`) |
|
||||
|
||||
> **硬性规则**:任何"标题"性质的文本一律用 `add_heading`,
|
||||
> **禁止**写成 `add_paragraph` + `style: "Title"`。
|
||||
> 只有确实需要"副标题段落"时才用 `add_paragraph` + `style: "Subtitle"`。
|
||||
|
||||
### `add_heading` — 标题
|
||||
|
||||
| 参数 | 类型 | 默认 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `text` | string | `""` | 标题文本 |
|
||||
| `level` | int | `1` | `0` = 封面主标题(Word 的 Title 样式),`1~4` = 一~四级章节标题 |
|
||||
|
||||
```jsonl
|
||||
{"action": "add_heading", "params": {"text": "项目周报", "level": 0}}
|
||||
{"action": "add_heading", "params": {"text": "第一章 引言", "level": 1}}
|
||||
{"action": "add_heading", "params": {"text": "1.1 背景", "level": 2}}
|
||||
```
|
||||
|
||||
### `add_paragraph` — 段落
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `text` | string | 单 run 纯文本(与 `runs` 二选一;同时传以 `runs` 为准) |
|
||||
| `runs` | array | 多 run 混排,元素字段见下 |
|
||||
| `style` | string | 内置样式名:`List Bullet` / `List Number` / `Subtitle`(及其 2/3 级变体) |
|
||||
| `alignment` | string | 段落级对齐:`left` / `center` / `right` / `justify` |
|
||||
|
||||
`runs` 元素字段(**仅字符级格式**,没有段落级字段):
|
||||
`text` / `bold` / `italic` / `underline` / `color_hex`(6 位 hex,不带 `#`)/
|
||||
`size_pt` / `font`(西文字体)/ `east_asia_font`(中文字体)。
|
||||
|
||||
列表**必须用内置样式**,绝不手写 `•` 或 `1.`:
|
||||
|
||||
| 级别 | Bullet 样式 | Number 样式 |
|
||||
|---|---|---|
|
||||
| 0 | `List Bullet` | `List Number` |
|
||||
| 1 | `List Bullet 2` | `List Number 2` |
|
||||
| 2 | `List Bullet 3` | `List Number 3` |
|
||||
|
||||
> 内置最深 3 级。需要更深嵌套时应**重组内容结构**,而不是手写 `List Bullet 4`
|
||||
> ——该样式不存在,运行会报错。
|
||||
|
||||
```jsonl
|
||||
{"action": "add_paragraph", "params": {"text": "这是一段正文。"}}
|
||||
{"action": "add_paragraph", "params": {"text": "2026 年第 22 周", "style": "Subtitle"}}
|
||||
{"action": "add_paragraph", "params": {"text": "一级要点", "style": "List Bullet"}}
|
||||
{"action": "add_paragraph", "params": {"text": "二级要点", "style": "List Bullet 2"}}
|
||||
{"action": "add_paragraph", "params": {"runs": [{"text": "重要:"}, {"text": "请按时提交", "bold": true, "color_hex": "C00000"}, {"text": ",谢谢配合。"}]}}
|
||||
```
|
||||
|
||||
### `add_table` — 固定布局表格
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|:--:|---|
|
||||
| `data` | array<array> | 是 | 二维数组,每个元素是一个 cell |
|
||||
|
||||
Cell 只有两种合法形态(**不支持 `runs` 多 run 混排**):
|
||||
|
||||
| 形态 | 示例 | 说明 |
|
||||
|---|---|---|
|
||||
| 字符串 | `"张三"` | 纯文本 cell |
|
||||
| 单 run 对象 | `{"text": "字段", "bold": true, "color_hex": "FF0000"}` | 整个 cell 共享一组字符格式 |
|
||||
|
||||
Cell 对象支持的字段与 `add_paragraph.runs` 元素完全一致。
|
||||
|
||||
> **单元格内无法做"段内局部高亮"**(一句话里只标红其中几个字)。
|
||||
> 有这类需求时把高亮文本拆出表格,作为表格上方/下方的独立 `add_paragraph + runs` 段落。
|
||||
|
||||
```jsonl
|
||||
{"action": "add_table", "params": {"data": [[{"text": "任务", "bold": true}, {"text": "负责人", "bold": true}, {"text": "DDL", "bold": true}], ["完成联调", "张三", "周三"], ["性能压测", "李四", "周四"]]}}
|
||||
```
|
||||
|
||||
### `add_page_break` — 分页
|
||||
|
||||
```jsonl
|
||||
{"action": "add_page_break", "params": {}}
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
这是一份 `.jsonl` 文件的**真实形态**——每行一条 action,表格压缩为单行,行间无空行:
|
||||
|
||||
```jsonl
|
||||
{"action": "add_heading", "params": {"text": "项目周报", "level": 0}}
|
||||
{"action": "add_paragraph", "params": {"text": "2026 年第 22 周", "style": "Subtitle"}}
|
||||
{"action": "add_heading", "params": {"text": "一、本周进展", "level": 1}}
|
||||
{"action": "add_paragraph", "params": {"text": "完成核心模块开发,进入联调阶段。"}}
|
||||
{"action": "add_paragraph", "params": {"text": "完成 API 设计评审", "style": "List Bullet"}}
|
||||
{"action": "add_paragraph", "params": {"text": "完成 60% 核心代码", "style": "List Bullet"}}
|
||||
{"action": "add_heading", "params": {"text": "二、风险提示", "level": 1}}
|
||||
{"action": "add_paragraph", "params": {"runs": [{"text": "需重点关注:"}, {"text": "依赖方接口延期", "bold": true, "color_hex": "C00000"}, {"text": ",预计影响排期 2 天。"}]}}
|
||||
{"action": "add_heading", "params": {"text": "三、下周计划", "level": 1}}
|
||||
{"action": "add_table", "params": {"data": [[{"text": "任务", "bold": true}, {"text": "负责人", "bold": true}, {"text": "DDL", "bold": true}], ["完成联调", "张三", "周三"], ["性能压测", "李四", "周四"], ["发版评审", "王五", "周五"]]}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
本文件改写自 [wecom-cli](https://github.com/WecomTeam/wecom-cli) 的
|
||||
`skills/wecomcli-doc/references/doc-create.md`(MIT License,© WecomTeam),
|
||||
补充了 DesireCore 环境下的环境变量前置、输出路径规则与排错表。
|
||||
1375
agents/wecom-assistant/skills/wecom-doc/scripts/build_docx.py
Normal file
1375
agents/wecom-assistant/skills/wecom-doc/scripts/build_docx.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user