feat(mail-operations): 补充标记为垃圾邮件 / 不是垃圾邮件接口(1.1.0) (#208)

## 变更 / Changes

mail-operations 1.0.6 → 1.1.0:补充客户端新增的「标记为垃圾邮件 / 不是垃圾邮件」接口。
Adds the client's new mark-as-junk / not-junk endpoints to
mail-operations (1.0.6 → 1.1.0).

- 「单封邮件操作」表新增两行:Gmail、IMAP 用 `POST …/{id}/spam`、`…/not-spam`,Outlook 用
`POST /outlook/message/spam?id=`、`…/not-spam?id=`。
  Two rows in the single-message table.
- 补充使用说明:返回 `result: {id, folder}`;Outlook、IMAP 移动后 `id` 会变;IMAP
没有垃圾邮件文件夹时返回 409
`imap_spam_folder_not_found`,不要重试;「不是垃圾邮件」一次一封;移回收件箱不触发规则。
Usage notes: new id after Outlook/IMAP moves, 409 when an IMAP account
has no junk folder, Not junk one at a time, no rule re-trigger.
- `market.required_client_version` 与 sidecar 的
`compatibility.requiredClientVersion` 同步改为
`10.0.178`。内置技能同步会按它拦截,旧客户端继续使用 1.0.6,不会拿到调用后返回 404 的接口说明。
  Client gate raised to 10.0.178 so older clients keep 1.0.6.
- 中文是源语言;英文译文(`translated_by: human`)已人工同步,`source_hash` 用
`translate.py` 的 `compute_source_hash` 重新计算。
zh-CN source and human en-US translation updated together; `source_hash`
recomputed with the repository's own function.

## 合并时机 / Merge timing

客户端实现在 desirecore/desirecore#4205,它依赖 desirecore/desirecore#4195。**请在
#4205 合入、确认随 10.0.178 发布之后再合并本 PR。** 如果实际发布的版本号不是 10.0.178,先把两处
`required_client_version` 改成实际版本再合并。合并后,客户端仓库需要运行 `npm run sync-market`
刷新内置的 `market.zip`。

The client side is desirecore/desirecore#4205 (depends on #4195). Merge
this after #4205 lands and ships in 10.0.178; adjust both version gates
if the release number differs. Afterwards regenerate the client's
bundled `market.zip` with `npm run sync-market`.

## 校验 / Checks

- `validate-i18n.py`:0 error,123 warning(与改动前相同;mail-operations 的两条
license / review evidence 警告原来就有)。
- `validate_catalog_metadata.py --require-complete`:0 error。
- `translate.py --check skills/mail-operations`:en-US 为
human-locked,与源一致。
-
公开信息检查:改动内容只有通用的接口说明,不含任何租户、客户或合作方信息。**未用私有敏感词清单做全量扫描**:本次没有拿到这份清单,合并前请维护者补跑一次。
Public-information review: generic API text only. The private-token scan
was not run because the token list was not available; please run it
before merging.
This commit is contained in:
2026-10-05 22:22:17 -07:00
committed by GitHub
parent d3b5c6f85c
commit 362f800bd2
3 changed files with 28 additions and 10 deletions

View File

@@ -10,7 +10,7 @@ description: >-
Use when 用户提到 邮件、邮箱、收件箱、发邮件、回复邮件、查邮件、Gmail、 Use when 用户提到 邮件、邮箱、收件箱、发邮件、回复邮件、查邮件、Gmail、
Outlook、QQ邮箱、163邮箱、附件、标签、草稿、自动回复、邮件规则、 Outlook、QQ邮箱、163邮箱、附件、标签、草稿、自动回复、邮件规则、
转发、抄送、未读邮件、收信、发信、邮件同步、邮件搜索。 转发、抄送、未读邮件、收信、发信、邮件同步、邮件搜索。
version: 1.0.6 version: 1.1.0
type: procedural type: procedural
risk_level: medium risk_level: medium
status: enabled status: enabled
@@ -24,7 +24,7 @@ tags:
- smtp - smtp
metadata: metadata:
author: desirecore author: desirecore
updated_at: '2026-09-29' updated_at: '2026-10-05'
i18n: i18n:
default_locale: en-US default_locale: en-US
source_locale: zh-CN source_locale: zh-CN
@@ -45,9 +45,9 @@ metadata:
description: >- description: >-
Use this skill whenever the user wants to interact with email. This includes reading inbox, sending emails, replying, searching messages, managing labels and categories, downloading attachments, setting up auto-reply rules, or triggering agents to handle incoming emails. Supports Gmail, Outlook, and IMAP/SMTP (QQ Mail, 163, Yahoo, etc.) through DesireCore's local REST API. Use when the user mentions email, mailbox, inbox, sending email, replying, checking email, Gmail, Outlook, QQ Mail, 163 Mail, attachments, labels, drafts, auto-reply, email rules, forwarding, CC, unread email, receiving, sending, email sync, or email search. Use this skill whenever the user wants to interact with email. This includes reading inbox, sending emails, replying, searching messages, managing labels and categories, downloading attachments, setting up auto-reply rules, or triggering agents to handle incoming emails. Supports Gmail, Outlook, and IMAP/SMTP (QQ Mail, 163, Yahoo, etc.) through DesireCore's local REST API. Use when the user mentions email, mailbox, inbox, sending email, replying, checking email, Gmail, Outlook, QQ Mail, 163 Mail, attachments, labels, drafts, auto-reply, email rules, forwarding, CC, unread email, receiving, sending, email sync, or email search.
body: ./SKILL.md body: ./SKILL.md
source_hash: sha256:02d90959ea1d7e29 source_hash: sha256:ec3be525ab586e62
translated_by: human translated_by: human
translated_at: '2026-05-03' translated_at: '2026-10-05'
market: market:
icon: >- icon: >-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0
@@ -63,7 +63,7 @@ market:
name: DesireCore Official name: DesireCore Official
verified: true verified: true
channel: latest channel: latest
required_client_version: 10.0.148 required_client_version: 10.0.178
--- ---
# mail-operations Skill # mail-operations Skill
@@ -202,7 +202,7 @@ In the following, `{p}` denotes the provider (`gmail`, `outlook`, `imap`); `{ema
**folder values**: **folder values**:
- Local cache list and search: `inbox, sent, drafts, trash, spam, archive, other`, case-insensitive; server names (e.g. `INBOX`, `Sent Messages`) are accepted too. IMAP custom folders are all stored locally as `other`. - Local cache list and search: `inbox, sent, drafts, trash, spam, archive, other`, case-insensitive; server names (e.g. `INBOX`, `Sent Messages`) are accepted too. IMAP custom folders are all stored locally as `other`.
- Remote sync: generic names (`inbox`, `sent`, ...) are mapped automatically to the actual IMAP folder, the Outlook folder, or the Gmail label; for IMAP you may also pass a `path` returned by `GET /imap/folders`. Gmail has no label for `archive`, so it fetches without filtering. - Remote sync: generic names (`inbox`, `sent`, ...) are mapped automatically to the actual IMAP folder, the Outlook folder, or the Gmail label; for IMAP you may also pass a `path` returned by `GET /imap/folders`. Gmail has no label for `archive`, so it fetches without filtering.
- **IMAP single-message operations** (detail on a cache miss, mark as read/unread, delete, reply, attachment download) pass `folder` verbatim to the IMAP server, so it must be a real `path` returned by `GET /imap/folders` (e.g. `INBOX`, `Sent Messages`); it defaults to `INBOX` when omitted. The `folder` field of a list item is a normalized value (e.g. `sent`) and cannot be used as an IMAP folder name directly. - **IMAP single-message operations** (detail on a cache miss, mark as read/unread, delete, mark as junk / not junk, reply, attachment download) pass `folder` verbatim to the IMAP server, so it must be a real `path` returned by `GET /imap/folders` (e.g. `INBOX`, `Sent Messages`); it defaults to `INBOX` when omitted. The `folder` field of a list item is a normalized value (e.g. `sent`) and cannot be used as an IMAP folder name directly.
**Response format** (message list item): **Response format** (message list item):
```json ```json
@@ -228,9 +228,18 @@ In the following, `{p}` denotes the provider (`gmail`, `outlook`, `imap`); `{ema
| Mark as read | POST `/{id}/read?email=` | POST `/message/read?id={id}&email=` | POST `/{uid}/read?email=&folder=` | | Mark as read | POST `/{id}/read?email=` | POST `/message/read?id={id}&email=` | POST `/{uid}/read?email=&folder=` |
| Mark as unread | POST `/{id}/unread?email=` | POST `/message/unread?id={id}&email=` | POST `/{uid}/unread?email=&folder=` | | Mark as unread | POST `/{id}/unread?email=` | POST `/message/unread?id={id}&email=` | POST `/{uid}/unread?email=&folder=` |
| Delete | DELETE `/{id}?email=` | DELETE `/message?id={id}&email=` | DELETE `/{uid}?email=&folder=` | | Delete | DELETE `/{id}?email=` | DELETE `/message?id={id}&email=` | DELETE `/{uid}?email=&folder=` |
| Mark as junk | POST `/{id}/spam?email=` | POST `/message/spam?id={id}&email=` | POST `/{uid}/spam?email=&folder=` |
| Not junk | POST `/{id}/not-spam?email=` | POST `/message/not-spam?id={id}&email=` | POST `/{uid}/not-spam?email=&folder=` |
> All paths are prefixed with `/api/{provider}/messages` (Gmail/IMAP) or `/api/outlook/` (Outlook's special routing). For IMAP, `{uid}` may be the list's `id` (`imap:<uid>`) as is; see section 2 for `folder`, which may be omitted for INBOX. > All paths are prefixed with `/api/{provider}/messages` (Gmail/IMAP) or `/api/outlook/` (Outlook's special routing). For IMAP, `{uid}` may be the list's `id` (`imap:<uid>`) as is; see section 2 for `folder`, which may be omitted for INBOX.
**Mark as junk / Not junk**: moves the message into the junk folder, or out of it back to the inbox. The server and the local cache change together, and the response is `result: {id, folder}`.
- Gmail swaps the `SPAM` / `INBOX` labels and keeps the `id`; Outlook and IMAP assign a new `id` after the move, so use the returned `id` for any later operation on this message. When the IMAP server lacks UIDPLUS the `id` is `null`; run `messages/fetch` on the destination folder before using the message again.
- If the IMAP account has no junk folder on the server, the call returns 409 `imap_spam_folder_not_found`. Tell the user as is; do not retry and do not substitute another folder.
- A message already in the destination folder returns success without moving. A message moved back to the inbox does not trigger mail rules or a new-mail notification.
- Not junk handles one message at a time; do not move a batch back to the inbox together. Mark as junk may be called once per message.
- Requires client 10.0.178 or later; older clients return 404, in which case ask the user to upgrade the client.
**Extra fields in message detail**: `body: {content, contentType}`, `ccRecipients`, `attachments: [{id, filename, contentType, size}]`. For IMAP, an attachment's `id` is its index within the message as a string (`"0"`, `"1"`, ...). **Extra fields in message detail**: `body: {content, contentType}`, `ccRecipients`, `attachments: [{id, filename, contentType, size}]`. For IMAP, an attachment's `id` is its index within the message as a string (`"0"`, `"1"`, ...).
### 4. Send and Reply ### 4. Send and Reply

View File

@@ -136,7 +136,7 @@ mail-operations 是一个**流程型技能(Procedural Skill)**,通过 Desi
**folder 取值**: **folder 取值**:
- 本地缓存列表与搜索:`inbox, sent, drafts, trash, spam, archive, other`,大小写不敏感,也接受服务器原名(如 `INBOX`、`Sent Messages`);IMAP 的自定义文件夹在本地统一归为 `other`。 - 本地缓存列表与搜索:`inbox, sent, drafts, trash, spam, archive, other`,大小写不敏感,也接受服务器原名(如 `INBOX`、`Sent Messages`);IMAP 的自定义文件夹在本地统一归为 `other`。
- 远程同步:通用名(`inbox`、`sent` 等)会自动映射到 IMAP 实际文件夹、Outlook 文件夹或 Gmail 标签;IMAP 也可直接传 `GET /imap/folders` 返回的 `path`。Gmail 的 `archive` 没有对应标签,会不加过滤地拉取。 - 远程同步:通用名(`inbox`、`sent` 等)会自动映射到 IMAP 实际文件夹、Outlook 文件夹或 Gmail 标签;IMAP 也可直接传 `GET /imap/folders` 返回的 `path`。Gmail 的 `archive` 没有对应标签,会不加过滤地拉取。
- **IMAP 单封操作**(详情未命中缓存、标记已读/未读、删除、回复、附件下载)的 `folder` 会原样交给 IMAP 服务器,必须是 `GET /imap/folders` 返回的真实 `path`(如 `INBOX`、`Sent Messages`),省略时默认 `INBOX`。列表项里的 `folder` 是归一化后的值(如 `sent`),不能直接当作 IMAP 文件夹名。 - **IMAP 单封操作**(详情未命中缓存、标记已读/未读、删除、标记为垃圾邮件 / 不是垃圾邮件、回复、附件下载)的 `folder` 会原样交给 IMAP 服务器,必须是 `GET /imap/folders` 返回的真实 `path`(如 `INBOX`、`Sent Messages`),省略时默认 `INBOX`。列表项里的 `folder` 是归一化后的值(如 `sent`),不能直接当作 IMAP 文件夹名。
**响应格式**(邮件列表项): **响应格式**(邮件列表项):
```json ```json
@@ -162,9 +162,18 @@ mail-operations 是一个**流程型技能(Procedural Skill)**,通过 Desi
| 标记已读 | POST `/{id}/read?email=` | POST `/message/read?id={id}&email=` | POST `/{uid}/read?email=&folder=` | | 标记已读 | POST `/{id}/read?email=` | POST `/message/read?id={id}&email=` | POST `/{uid}/read?email=&folder=` |
| 标记未读 | POST `/{id}/unread?email=` | POST `/message/unread?id={id}&email=` | POST `/{uid}/unread?email=&folder=` | | 标记未读 | POST `/{id}/unread?email=` | POST `/message/unread?id={id}&email=` | POST `/{uid}/unread?email=&folder=` |
| 删除 | DELETE `/{id}?email=` | DELETE `/message?id={id}&email=` | DELETE `/{uid}?email=&folder=` | | 删除 | DELETE `/{id}?email=` | DELETE `/message?id={id}&email=` | DELETE `/{uid}?email=&folder=` |
| 标记为垃圾邮件 | POST `/{id}/spam?email=` | POST `/message/spam?id={id}&email=` | POST `/{uid}/spam?email=&folder=` |
| 不是垃圾邮件 | POST `/{id}/not-spam?email=` | POST `/message/not-spam?id={id}&email=` | POST `/{uid}/not-spam?email=&folder=` |
> 所有路径前缀为 `/api/{provider}/messages`(Gmail/IMAP)或 `/api/outlook/`(Outlook 特殊路由)。IMAP 的 `{uid}` 可直接用列表返回的 `id`(`imap:<uid>`),`folder` 规则见第 2 节,INBOX 可省略。 > 所有路径前缀为 `/api/{provider}/messages`(Gmail/IMAP)或 `/api/outlook/`(Outlook 特殊路由)。IMAP 的 `{uid}` 可直接用列表返回的 `id`(`imap:<uid>`),`folder` 规则见第 2 节,INBOX 可省略。
**标记为垃圾邮件 / 不是垃圾邮件**:把邮件移入垃圾邮件文件夹,或从垃圾邮件文件夹移回收件箱,服务器与本地缓存同时修改,返回 `result: {id, folder}`。
- Gmail 改 `SPAM` / `INBOX` 标签,`id` 不变;Outlook、IMAP 移动后 `id` 会变,之后再操作这封邮件要用返回的新 `id`。IMAP 服务器不支持 UIDPLUS 时 `id` 为 `null`,对目标文件夹做一次 `messages/fetch` 后再取。
- IMAP 账户在服务器上没有垃圾邮件文件夹时返回 409 `imap_spam_folder_not_found`,如实告知用户,不要重试,也不要改用其他文件夹代替。
- 邮件已经在目标文件夹时直接返回成功。移回收件箱的邮件不会触发邮件规则,也不会推送新邮件通知。
- 「不是垃圾邮件」一次只处理一封,不要把一批邮件一起移回收件箱;标记为垃圾邮件可以逐封调用。
- 需要客户端 10.0.178 及以上;旧客户端返回 404,提示用户升级客户端。
**邮件详情额外字段**:`body: {content, contentType}`, `ccRecipients`, `attachments: [{id, filename, contentType, size}]`。IMAP 附件的 `id` 是附件在该邮件中的序号字符串(`"0"`、`"1"`……)。 **邮件详情额外字段**:`body: {content, contentType}`, `ccRecipients`, `attachments: [{id, filename, contentType, size}]`。IMAP 附件的 `id` 是附件在该邮件中的序号字符串(`"0"`、`"1"`……)。
### 4. 发送与回复 ### 4. 发送与回复

View File

@@ -31,13 +31,13 @@
}, },
"release": { "release": {
"state": "known", "state": "known",
"version": "1.0.6", "version": "1.1.0",
"versionScheme": "semver" "versionScheme": "semver"
}, },
"timestamps": { "timestamps": {
"catalogUpdatedAt": { "catalogUpdatedAt": {
"state": "known", "state": "known",
"value": "2026-09-29", "value": "2026-10-05",
"precision": "day" "precision": "day"
}, },
"releasePublishedAt": { "releasePublishedAt": {
@@ -68,7 +68,7 @@
"platforms": { "platforms": {
"state": "unknown" "state": "unknown"
}, },
"requiredClientVersion": "10.0.148" "requiredClientVersion": "10.0.178"
}, },
"spec": { "spec": {
"kind": "skill", "kind": "skill",