From 6ecbfaf69064830415dcbd2becbb59bdb93a08d2 Mon Sep 17 00:00:00 2001 From: evandance <120630830+evandance@users.noreply.github.com> Date: Tue, 14 Jul 2026 21:05:44 +0800 Subject: [PATCH] fix(skills): align skill guidance with the typed error contract (#1786) Skill references written before the typed-error refactor still taught retired envelope shapes. AI agents following them now read what the CLI actually emits: - permission recovery reads error.missing_scopes instead of the upstream permission_violations detail - confirmation gates use type=confirmation, subtype=confirmation_required, and flat risk/action fields - drive duplicate-remote failures are typed validation envelopes (failed_precondition with params[]), not duplicate_remote_path with error.detail - drive batch partial failures are ok:false results on stdout, not an error.type=partial_failure stderr envelope - minutes edit-permission and word-replace misses branch on error.subtype, not retired error.type values - slides replace failures are stderr typed envelopes only; no raw backend response is printed to stdout - slides command outputs show the ok/identity/data success envelope instead of the raw {code,msg} OpenAPI wrapper --- .../references/lark-apps-openapi-key.md | 2 +- skills/lark-base/SKILL.md | 2 +- .../lark-base-dashboard-block-get-data.md | 14 +++++----- .../references/lark-drive-member-add.md | 2 +- .../lark-drive/references/lark-drive-pull.md | 6 ++--- .../lark-drive/references/lark-drive-push.md | 2 +- .../references/lark-drive-status.md | 26 +++++++++---------- skills/lark-minutes/SKILL.md | 4 +-- .../references/lark-minutes-todo.md | 4 +-- skills/lark-shared/SKILL.md | 18 ++++++------- skills/lark-slides/references/examples.md | 23 +++++++++------- .../references/lark-slides-screenshot.md | 6 ++--- ...rk-slides-xml-presentation-slide-create.md | 6 ++--- ...rk-slides-xml-presentation-slide-delete.md | 6 ++--- .../lark-slides-xml-presentation-slide-get.md | 6 ++--- ...k-slides-xml-presentation-slide-replace.md | 22 +++++++++------- .../lark-slides-xml-presentations-get.md | 6 ++--- skills/lark-vc-agent/SKILL.md | 4 +-- .../references/lark-vc-agent-meeting-leave.md | 2 +- tests/cli_e2e/drive/coverage.md | 2 +- 20 files changed, 83 insertions(+), 80 deletions(-) diff --git a/skills/lark-apps/references/lark-apps-openapi-key.md b/skills/lark-apps/references/lark-apps-openapi-key.md index 37cb7d37e..39d109741 100644 --- a/skills/lark-apps/references/lark-apps-openapi-key.md +++ b/skills/lark-apps/references/lark-apps-openapi-key.md @@ -76,4 +76,4 @@ CLI 提供三种互斥的 scope 表达方式: ## 不在本 skill 范围 - OpenAPI spec 全量导出、实时日志 tail、Webhook 消费、多鉴权方式:本期不支持。 -- 身份选择、权限不足处理(`permission_violations`→`console_url`)、exit-10 审批、通用"禁输出密钥"红线、高风险操作通用框架:见 [`../lark-shared/SKILL.md`](../lark-shared/SKILL.md),不在此重复。 +- 身份选择、权限不足处理(`missing_scopes`→`console_url`)、exit-10 审批、通用"禁输出密钥"红线、高风险操作通用框架:见 [`../lark-shared/SKILL.md`](../lark-shared/SKILL.md),不在此重复。 diff --git a/skills/lark-base/SKILL.md b/skills/lark-base/SKILL.md index 3399146dc..ca1380703 100644 --- a/skills/lark-base/SKILL.md +++ b/skills/lark-base/SKILL.md @@ -85,7 +85,7 @@ metadata: ## 身份与权限降级 - 默认显式使用 `--as user` 操作用户资源;只有用户明确要求应用身份时,才直接用 `--as bot`。 -- user 身份报 scope/授权不足,或错误中包含 `permission_violations` / `hint`,先转 `lark-shared` 做用户授权恢复,不要直接降级 bot。 +- user 身份报 scope/授权不足,或错误中包含 `missing_scopes` / `hint`,先转 `lark-shared` 做用户授权恢复,不要直接降级 bot。 - user 身份报资源级无访问且无授权恢复提示时,才可用 `--as bot` 重试一次;bot 仍失败就停止重试并按权限错误处理。 - `91403` 或明确不可访问错误不要循环换身份重试。 - `+base-create` / `+base-copy` 若用 bot 身份执行,关注返回中的 `permission_grant`,并把用户是否可打开新 Base 告知用户。 diff --git a/skills/lark-base/references/lark-base-dashboard-block-get-data.md b/skills/lark-base/references/lark-base-dashboard-block-get-data.md index 8470323f8..a49d9f977 100644 --- a/skills/lark-base/references/lark-base-dashboard-block-get-data.md +++ b/skills/lark-base/references/lark-base-dashboard-block-get-data.md @@ -98,21 +98,21 @@ lark-cli base +dashboard-block-get \ ## 返回结构总览 -服务端响应外层仍然是标准 OpenAPI 包装: +CLI 成功输出使用标准 `{ok, identity, data}` 信封: ```json { - "code": 0, - "msg": "success", + "ok": true, + "identity": "user", "data": { - "dimensions": [...], - "measures": [...], - "main_data": [...] + "dimensions": [], + "measures": [], + "main_data": [] } } ``` -其中 `data` 就是 CLI 图表协议本体。不同图表类型的 `data` 结构略有不同: +其中 `identity` 是本次调用实际使用的身份,`data` 是 CLI 图表协议本体。不同图表类型的 `data` 结构略有不同: | 图表类型 | 一定有 | 可能有 | |----------|--------|--------| diff --git a/skills/lark-drive/references/lark-drive-member-add.md b/skills/lark-drive/references/lark-drive-member-add.md index 8b36d27eb..146b14311 100644 --- a/skills/lark-drive/references/lark-drive-member-add.md +++ b/skills/lark-drive/references/lark-drive-member-add.md @@ -54,7 +54,7 @@ lark-cli drive +member-add \ } ``` -批量部分失败时,`partial` 为 `true`,CLI 以非零退出码返回 `error.type=partial_failure`。检查 `error.detail` 中的 `requested_count`、`succeeded_count`、`members`、`missing_member_ids` 和可选的 `mismatched_member_ids`。响应顺序不影响匹配结果。 +批量部分失败时,`partial` 为 `true`,同一份结果以 `ok:false` 部分失败信封写到 **stdout**(stderr 不再输出单独的错误信封),CLI 以非零退出码结束。检查 `data` 中的 `requested_count`、`succeeded_count`、`members`、`missing_member_ids` 和可选的 `mismatched_member_ids`。响应顺序不影响匹配结果。 ## 行为说明 diff --git a/skills/lark-drive/references/lark-drive-pull.md b/skills/lark-drive/references/lark-drive-pull.md index 5568c8cb9..703660df1 100644 --- a/skills/lark-drive/references/lark-drive-pull.md +++ b/skills/lark-drive/references/lark-drive-pull.md @@ -17,11 +17,11 @@ | `summary.deleted_local` | 启用 `--delete-local --yes` 时删除的本地文件数 | | `items[]` | 每个文件的明细(`rel_path` / `file_token` / `source_id` / `action` / 失败时的 `error`) | -`summary.failed > 0` 时命令以 **非零状态码**(`exit=1`,`error.type=partial_failure`)退出,且同一份 `summary + items` 会在 `error.detail` 里返回;脚本/agent 直接通过 exit code 判断成败即可,不需要再去解 `summary.failed`。 +`summary.failed > 0` 时命令以 **非零状态码**(`exit=1`)退出:同一份 `summary + items` 会以 `ok:false` 部分失败信封写到 **stdout**(字段在 `data.summary` / `data.items`),stderr 不再输出单独的错误信封;脚本/agent 直接通过 exit code 判断成败即可,不需要再去解 `summary.failed`。 ## 远端同名文件冲突 -如果 Drive 中多个条目映射到同一个 `rel_path`,默认直接失败(`error.type=duplicate_remote_path`),且不会下载、覆盖或删除任何本地文件。只有“多个 `type=file` 同名”的场景支持显式策略;`file-folder` 这类异构冲突始终直接失败。 +如果 Drive 中多个条目映射到同一个 `rel_path`,默认直接失败(stderr 类型化错误信封:`error.type=validation`、`error.subtype=failed_precondition`,`error.params[]` 逐条列出冲突的 `rel_path` 及碰撞条目),且不会下载、覆盖或删除任何本地文件。只有“多个 `type=file` 同名”的场景支持显式策略;`file-folder` 这类异构冲突始终直接失败。 | 策略 | 行为 | |------|------| @@ -80,7 +80,7 @@ lark-cli drive +pull --local-dir ./repo --folder-token fldcnxxxxxxxxx \ - `--delete-local`(无 `--yes`)→ Validate 直接报错:`--delete-local requires --yes`,没有任何下载、列表请求或删除发生。 - `--delete-local --yes`,**且下载阶段全部成功** → 扫一遍 `--local-dir` 下所有常规文件,把不在云端清单里的逐个 `os.Remove`。**只删常规文件,不删目录**:远端文件夹被删除后,对应本地目录会保留空壳。 -- `--delete-local --yes`,**但下载阶段有任何条目失败** → **跳过整个删除阶段**,命令以 `partial_failure` 非零退出。设计意图:避免出现"前面下载失败、后面继续删本地文件"的半同步状态;操作者修好下载错误后再重跑即可。 +- `--delete-local --yes`,**但下载阶段有任何条目失败** → **跳过整个删除阶段**,命令以 `ok:false` 部分失败结果非零退出。设计意图:避免出现"前面下载失败、后面继续删本地文件"的半同步状态;操作者修好下载错误后再重跑即可。 - 远端同名文件冲突且使用默认 `fail` → 在下载阶段前失败,删除阶段不会运行。 - 不传 `--delete-local` → `summary.deleted_local` 永远是 0;命令对本地"多余"文件视而不见。 diff --git a/skills/lark-drive/references/lark-drive-push.md b/skills/lark-drive/references/lark-drive-push.md index c885146b8..123667297 100644 --- a/skills/lark-drive/references/lark-drive-push.md +++ b/skills/lark-drive/references/lark-drive-push.md @@ -24,7 +24,7 @@ ## 远端同名文件冲突 -如果 Drive 中多个条目映射到同一个 `rel_path`,默认直接失败(`error.type=duplicate_remote_path`),且不会上传、覆盖或进入 `--delete-remote` 删除阶段。只有“多个 `type=file` 同名”的场景支持显式策略;`file-folder` 这类异构冲突始终直接失败。 +如果 Drive 中多个条目映射到同一个 `rel_path`,默认直接失败(stderr 类型化错误信封:`error.type=validation`、`error.subtype=failed_precondition`,`error.params[]` 逐条列出冲突的 `rel_path` 及碰撞条目),且不会上传、覆盖或进入 `--delete-remote` 删除阶段。只有“多个 `type=file` 同名”的场景支持显式策略;`file-folder` 这类异构冲突始终直接失败。 | 策略 | 行为 | |------|------| diff --git a/skills/lark-drive/references/lark-drive-status.md b/skills/lark-drive/references/lark-drive-status.md index 69fdcd799..0a44e4a1e 100644 --- a/skills/lark-drive/references/lark-drive-status.md +++ b/skills/lark-drive/references/lark-drive-status.md @@ -19,7 +19,7 @@ ## 远端同名文件冲突 -如果 Drive 中多个条目映射到同一个 `rel_path`,`+status` 会在下载/hash 前直接失败,返回 `error.type=duplicate_remote_path`,并在 `error.detail.duplicates_remote[]` 中列出该路径下所有冲突条目的 `file_token`、`type`、名称、大小和时间字段;其中 `created_time`、`modified_time` 缺失时会省略,`size` 在缺失或为 `0` 时都可能被省略。不要把这种情况当成普通 `modified`;它表示同步域本身有歧义,需要先整理云端结构,或在 `+pull` / `+push` 中仅对“duplicate file”场景显式选择冲突策略。 +如果 Drive 中多个条目映射到同一个 `rel_path`,`+status` 会在下载/hash 前直接失败,在 stderr 返回类型化错误信封(`error.type=validation`、`error.subtype=failed_precondition`);`error.params[]` 每条的 `name` 是冲突的 `rel_path`,`reason` 枚举该路径下所有碰撞条目(`type` + `file_token`)。不要把这种情况当成普通 `modified`;它表示同步域本身有歧义,需要先整理云端结构,或在 `+pull` / `+push` 中仅对“duplicate file”场景显式选择冲突策略。 ## 命令 @@ -76,20 +76,18 @@ lark-cli drive +status \ ```json { "ok": false, + "identity": "user", "error": { - "type": "duplicate_remote_path", - "message": "multiple Drive entries map to the same rel_path", - "detail": { - "duplicates_remote": [ - { - "rel_path": "dup.txt", - "entries": [ - {"file_token": "", "type": "file", "name": "dup.txt", "size": 5, "created_time": "1730000000", "modified_time": "1730000000"}, - {"file_token": "", "type": "folder", "name": "dup.txt", "created_time": "1730000060", "modified_time": "1730000060"} - ] - } - ] - } + "type": "validation", + "subtype": "failed_precondition", + "message": "1 rel_path(s) map to multiple Drive entries", + "hint": "resolve the duplicate remote files first: re-run +pull with --on-duplicate-remote=rename (downloads each with a hashed suffix), or use --on-duplicate-remote=newest|oldest (supported by +pull/+sync/+push) to pick one, or delete the extra remote files; a plain retry will not help", + "params": [ + { + "name": "dup.txt", + "reason": "2 Drive entries collide here: file , folder " + } + ] } } ``` diff --git a/skills/lark-minutes/SKILL.md b/skills/lark-minutes/SKILL.md index 7a6f6b208..2b5525518 100644 --- a/skills/lark-minutes/SKILL.md +++ b/skills/lark-minutes/SKILL.md @@ -135,9 +135,9 @@ lark-cli minutes +todo --minute-token --as user --todos '[ **更新 / 删除前**:先用 `minutes +detail --minute-tokens --todo` 读取 `todos[].todo_id`(按 `content` 匹配目标条目;列表顺序不保证稳定,**不要**用"第 2 条"代替 `todo_id`)。 -**无编辑权限**:若 CLI 返回 `error.type=no_edit_permission`,表示对**这条妙记**没有编辑权,应请所有者授权;**不要**误走 `auth login --scope`。 +**无编辑权限**:若 CLI 返回 `error.subtype=permission_denied`,表示对**这条妙记**没有编辑权,应请所有者授权;**不要**误走 `auth login --scope`。 -**逐字稿关键词替换无命中**:`minutes +word-replace` 时,若 CLI 返回 `error.type=words_not_found`,表示传入的 `source_word` 在该妙记逐字稿中**一个都没匹配到**,未做任何替换。这是**参数问题不是权限问题**:先用 `minutes +detail --minute-tokens --transcript` 读取当前逐字稿,核对 `source_word` 的精确写法与大小写后重试。 +**逐字稿关键词替换无命中**:`minutes +word-replace` 时,若 CLI 返回 `error.subtype=not_found`,表示传入的 `source_word` 在该妙记逐字稿中**一个都没匹配到**,未做任何替换。这是**参数问题不是权限问题**:先用 `minutes +detail --minute-tokens --transcript` 读取当前逐字稿,核对 `source_word` 的精确写法与大小写后重试。 **替换 AI 总结全文**:见 [minutes +summary](references/lark-minutes-summary.md)。 diff --git a/skills/lark-minutes/references/lark-minutes-todo.md b/skills/lark-minutes/references/lark-minutes-todo.md index d86bcd6d6..ad4c0232d 100644 --- a/skills/lark-minutes/references/lark-minutes-todo.md +++ b/skills/lark-minutes/references/lark-minutes-todo.md @@ -126,8 +126,8 @@ lark-cli minutes +todo --minute-token obcnxxxxxxxxxxxxxxxxxxxx --operation add - | 未指定操作 | 单条模式传 `--operation`,或批量传 `--todos` | | `--todos` 与单条 flags 冲突 | 二选一 | | `todos[i]` 校验失败 | 检查该条 `operation` 与字段组合 | -| `error.type` = `no_edit_permission` | **妙记资源无编辑权**:向妙记所有者申请该妙记的编辑/协作权限;**不要**走 `auth login --scope` | -| 缺少 OAuth scope(`permission_violations` 含 `minutes:minutes:update`) | `lark-cli auth login --scope "minutes:minutes:update"` | +| `error.subtype` = `permission_denied` | **妙记资源无编辑权**:向妙记所有者申请该妙记的编辑/协作权限;**不要**走 `auth login --scope` | +| 缺少 OAuth scope(`error.missing_scopes` 含 `minutes:minutes:update`) | `lark-cli auth login --scope "minutes:minutes:update"` | ## 参考 diff --git a/skills/lark-shared/SKILL.md b/skills/lark-shared/SKILL.md index e78ddd496..5b1c94e24 100644 --- a/skills/lark-shared/SKILL.md +++ b/skills/lark-shared/SKILL.md @@ -69,7 +69,7 @@ LARKSUITE_CLI_NO_UPDATE_NOTIFIER=1 LARKSUITE_CLI_NO_SKILLS_NOTIFIER=1 lark-cli a 遇到权限相关错误时,**根据当前身份类型采取不同解决方案**。 错误响应中包含关键信息: -- `permission_violations`:列出缺失的 scope (N选1) +- `missing_scopes`:列出缺失的 scope (N选1) - `console_url`:飞书开发者后台的权限配置链接 - `hint`:建议的修复命令 @@ -159,7 +159,7 @@ lark-cli update 错误信封写入 **stderr**(退出码非 0): ```json -{ "ok": false, "identity": "user", "error": { "type": "api", "subtype": "...", "code": 99991679, "message": "...", "hint": "..." } } +{ "ok": false, "identity": "user", "error": { "type": "authorization", "subtype": "missing_scope", "code": 99991679, "message": "...", "hint": "...", "missing_scopes": ["..."] } } ``` **判断成功必须用 `ok == true`(或进程退出码 0),不要用 `code == 0`**:成功信封没有顶层 `code` / `msg` 字段,`code` 只出现在错误信封的 `error` 内,含义是上游 OpenAPI 的 numeric code。按 OpenAPI 老格式 `{"code": 0, "msg": "ok"}` 判断会把所有成功调用误判为失败;封装写入类命令(如 `task +create`)时尤其危险,误判会绕过幂等逻辑导致重复创建。 @@ -178,22 +178,22 @@ lark-cli 对高风险写操作(`risk: "high-risk-write"`)有强制确认门 ```json { "ok": false, + "identity": "bot", "error": { - "type": "confirmation_required", + "type": "confirmation", + "subtype": "confirmation_required", "message": "drive +delete requires confirmation", "hint": "add --yes to confirm", - "risk": { - "level": "high-risk-write", - "action": "drive +delete" - } + "risk": "high-risk-write", + "action": "drive +delete" } } ``` **遇到这种情况,不要当普通错误放弃。** 按以下流程处理: -1. **识别**:看到子进程 exit code = `10` 且 stderr JSON 里 `error.type == "confirmation_required"` -2. **向用户确认**:把 `error.risk.action` 和关键参数展示给用户,明确告知"这是高风险操作",等待用户显式同意 +1. **识别**:看到子进程 exit code = `10` 且 stderr JSON 里 `error.type == "confirmation"`、`error.subtype == "confirmation_required"` +2. **向用户确认**:把 `error.action`、`error.risk` 和关键参数展示给用户,明确告知"这是高风险操作",等待用户显式同意 3. **用户同意** → 在你**原始 argv 的末尾追加 `--yes`** 后重试 4. **用户拒绝** → 终止流程,不要擅自改写参数或跳过门禁 diff --git a/skills/lark-slides/references/examples.md b/skills/lark-slides/references/examples.md index b85394b09..ed441d1bb 100644 --- a/skills/lark-slides/references/examples.md +++ b/skills/lark-slides/references/examples.md @@ -65,15 +65,15 @@ lark-cli slides xml_presentations get --as user --params '{ ```json { - "code": 0, + "ok": true, + "identity": "user", "data": { "xml_presentation": { "presentation_id": "slides_example_presentation_id", "revision_id": 3, "content": "..." } - }, - "msg": "success" + } } ``` @@ -94,12 +94,12 @@ lark-cli slides xml_presentation.slide create --as user --params '{ ```json { - "code": 0, + "ok": true, + "identity": "user", "data": { "slide_id": "slide_example_id", "revision_id": 100 - }, - "msg": "success" + } } ``` @@ -116,11 +116,11 @@ lark-cli slides xml_presentation.slide delete --as user --params '{ ```json { - "code": 0, + "ok": true, + "identity": "user", "data": { "revision_id": 101 - }, - "msg": "success" + } } ``` @@ -179,6 +179,7 @@ lark-cli slides +replace-slide --as user \ ```json { "ok": true, + "identity": "user", "data": { "xml_presentation_id": "slides_example_presentation_id", "slide_id": "slide_example_id", @@ -211,8 +212,10 @@ lark-cli slides +replace-slide --as user \ ```json { "ok": false, + "identity": "user", "error": { "type": "api", + "subtype": "unknown", "code": 3350001, "message": "API error: [3350001] invalid param", "hint": "common causes: (1) block_id not found in current slide ..." @@ -220,7 +223,7 @@ lark-cli slides +replace-slide --as user \ } ``` -整批作为原子事务,任一 part 失败则整批不生效;按 `failed_part_index` 定位修正后重发。 +整批作为原子事务,任一 part 失败则整批不生效;按 `error.hint` 检查 `block_id`、XML 结构或页面边界后重发。 ## 常见处理技巧 diff --git a/skills/lark-slides/references/lark-slides-screenshot.md b/skills/lark-slides/references/lark-slides-screenshot.md index 23557fcb9..9935657d7 100644 --- a/skills/lark-slides/references/lark-slides-screenshot.md +++ b/skills/lark-slides/references/lark-slides-screenshot.md @@ -66,7 +66,8 @@ lark-cli slides +screenshot --as user \ ```json { - "code": 0, + "ok": true, + "identity": "user", "data": { "xml_presentation_id": "slides_example_presentation_id", "output_dir": ".lark-slides/screenshots", @@ -79,8 +80,7 @@ lark-cli slides +screenshot --as user \ "size": 12345 } ] - }, - "msg": "success" + } } ``` diff --git a/skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md b/skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md index d636b0aa9..a0ad45ec1 100644 --- a/skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md +++ b/skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md @@ -141,12 +141,12 @@ lark-cli slides xml_presentation.slide create --as user \ ```json { - "code": 0, + "ok": true, + "identity": "user", "data": { "slide_id": "slide_example_id", "revision_id": 100 - }, - "msg": "success" + } } ``` diff --git a/skills/lark-slides/references/lark-slides-xml-presentation-slide-delete.md b/skills/lark-slides/references/lark-slides-xml-presentation-slide-delete.md index da2e7f3e3..a787cca2b 100644 --- a/skills/lark-slides/references/lark-slides-xml-presentation-slide-delete.md +++ b/skills/lark-slides/references/lark-slides-xml-presentation-slide-delete.md @@ -61,11 +61,11 @@ lark-cli slides xml_presentation.slide delete --as user --params '{"xml_presenta ```json { - "code": 0, + "ok": true, + "identity": "user", "data": { "revision_id": 100 - }, - "msg": "success" + } } ``` diff --git a/skills/lark-slides/references/lark-slides-xml-presentation-slide-get.md b/skills/lark-slides/references/lark-slides-xml-presentation-slide-get.md index 4d2af6143..6e1d18f7f 100644 --- a/skills/lark-slides/references/lark-slides-xml-presentation-slide-get.md +++ b/skills/lark-slides/references/lark-slides-xml-presentation-slide-get.md @@ -65,15 +65,15 @@ lark-cli slides xml_presentation.slide get --as user --params '{ ```json { - "code": 0, + "ok": true, + "identity": "user", "data": { "slide": { "slide_id": "slide_example_id", "content": "