# Wechatai 插件协议 V1

状态：内置 LLM、DS Chat、AI 生图、OpenClaw、Hermes、角色扮演适配器及平台计量已实现。协议版本 `1.0`，计费政策 `gateway-v1`。
此文是后续第三方接入的契约基础。当前内置 AI 生图已支持图片生成与媒体交付。没有开放插件市场、远程插件注册、用户上传代码、第三方回调或第三方图片生成与媒体交付接口。


## 给 AI agent 的起点

下面的地址无需登录或 API Key，直接 GET 即可读取。文档读取免费，不触发模型调用，不消耗 credits。网页与 Markdown 使用同一份内容；JSON Schema 从平台的实际校验规则生成。

| 格式 | 地址 | 用途 |
| --- | --- | --- |
| 网页 | [插件协议](https://wechatai.app/developers/plugins) | 阅读协议与示例 |
| Markdown | [完整协议原文](https://wechatai.app/developers/plugins.md) | 提供给 AI agent 的首选入口 |
| JSON API | [协议索引](https://wechatai.app/api/plugins/protocol) | 版本、开放状态、计费政策、示例与 Schema 链接 |
| 固定版本 | [协议 1.0](https://wechatai.app/api/plugins/protocol/1.0) | 读取指定版本，不自动切到下一版 |
| JSON Schema | [校验规则](https://wechatai.app/api/plugins/protocol/1.0/schema.json) | Draft 2020-12，包含五种协议对象的定义 |
| 发现入口 | [llms.txt](https://wechatai.app/llms.txt) | 找到所有开发资料 |

可把这段指令交给你的 AI 编程工具：

```text
请先读取 Wechatai 插件协议：
https://wechatai.app/developers/plugins.md

再读取协议索引、示例和 JSON Schema：
https://wechatai.app/api/plugins/protocol
https://wechatai.app/api/plugins/protocol/1.0/schema.json

按 protocolVersion 1.0 为我开发插件适配器，先确认我的插件功能和运行环境，再生成 manifest、适配器代码和契约测试。
保留平台传入的 runId；尊重 limits；只返回协议允许的最终结果字段；不要上报 usage、credits 或 imageCount。
用 JSON Schema 校验输入和输出，同时测试 runId 匹配、预算、重试幂等及附件所属任务等语义约束。
内置 AI 生图已支持图片交付；第三方注册、执行 API、代码上传与第三方媒体接入尚未开放。先实现可在本地验证的适配逻辑，标明待接入部分，不要编造平台接口或认证方式。
```

也可以直接下载文档和 Schema：

```sh
curl -fsSL https://wechatai.app/developers/plugins.md -o wechatai-plugin-protocol.md
curl -fsSL https://wechatai.app/api/plugins/protocol -o wechatai-plugin-protocol.json
curl -fsSL https://wechatai.app/api/plugins/protocol/1.0/schema.json -o wechatai-plugin-schema.json
```

所有文档接口只支持 GET、HEAD 和 OPTIONS，允许跨域匿名读取，不设置登录 cookie。不存在公开的插件执行、注册或回调接口；此处的 Invocation 是数据契约，不是一个可 POST 的 HTTP 地址。

## 最小适配示例

建议先实现纯文本适配器：接收 Invocation，转换为目标工具的输入，再将最终回复转换为 Result。供应商或 Agent 的内部消息、工具调用和计费信息不要加入最终输出。

### 1. 声明插件

下面是实际运行的内置 LLM 插件声明。第三方可以参考它编写自己的声明，但不能仅将 runtime 改为 remote 或 sandbox 就在本站执行。

```json
{
  "protocolVersion": "1.0",
  "id": "wechatai/llm-chat",
  "version": "1.0.0",
  "name": "LLM 对话",
  "author": "Wechatai",
  "runtime": "builtin",
  "capabilities": {
    "input": [
      "text",
      "image"
    ],
    "output": [
      "text"
    ],
    "sessions": "platform",
    "streaming": false,
    "cancellation": false,
    "approvals": false,
    "commands": "platform"
  },
  "configSchema": {
    "type": "object",
    "required": [
      "modelId"
    ],
    "properties": {
      "modelId": {
        "type": "string"
      }
    }
  },
  "permissions": {
    "modelAccess": true,
    "networkOrigins": [],
    "storage": false
  }
}
```

### 2. 接收平台调用

本例的 creditUnits 为 5000，即本站服务费用上限为 5 credits。模型连接与密钥由宿主另行注入，不能添加到 Invocation 的顶层字段中。

```json
{
  "protocolVersion": "1.0",
  "runId": "run_example_001",
  "instanceId": "instance_example_001",
  "pluginId": "wechatai/llm-chat",
  "pluginVersion": "1.0.0",
  "sessionId": "session_example_001",
  "messages": [
    {
      "role": "system",
      "content": [
        {
          "type": "text",
          "text": "请用简洁的中文回答。"
        }
      ]
    },
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "给我三个早餐搭配建议。"
        }
      ]
    }
  ],
  "limits": {
    "inputTokens": 128000,
    "outputTokens": 1024,
    "outputImages": 0,
    "creditUnits": 5000
  }
}
```

### 3. 返回最终结果

runId 必须原样返回。status=partial 用于有可用内容但未完整完成的结果；它与完整结果使用同一计量规则。

```json
{
  "protocolVersion": "1.0",
  "runId": "run_example_001",
  "status": "completed",
  "content": [
    {
      "type": "text",
      "text": "1. 鸡蛋、全麦面包和牛奶\n2. 燕麦、酸奶和香蕉\n3. 豆浆、玉米和鸡蛋"
    }
  ]
}
```

### 4. 实现转换函数

以下 JavaScript 仅演示本地转换，不是远程传输协议。callYourTool 和持久化由你自己的适配器实现；接入平台的认证、传输、取消与超时规范尚未开放。

```js
export async function execute(invocation, callYourTool) {
  // 先用 Invocation Schema 验证输入；此最小示例仅处理纯文本。
  const messages = invocation.messages.map((message) => ({
    role: message.role,
    content: message.content.map((block) => {
      if (block.type !== "text") throw new Error("Unsupported input block");
      return block.text;
    }).join(""),
  }));
  const text = await callYourTool({
    messages,
    maxOutputTokens: invocation.limits.outputTokens,
  });
  if (typeof text !== "string" || !text.trim()) throw new Error("Empty result");
  return {
    protocolVersion: invocation.protocolVersion,
    runId: invocation.runId,
    status: "completed",
    content: [{ type: "text", text }],
  };
}
```

适配器还应在执行前处理输入限制，并实现超时、失败与幂等策略；上述函数不包含这些生产要求。平台会独立执行最终的输入/输出与费用限制，不信任插件返回的计量字段。

## Schema 与校验

下载的 JSON Schema 是一个定义集合。请按对象类型选择对应定义进行验证，不要只用不含根类型约束的集合本身验证数据。

| 对象 | JSON Pointer | 当前状态 |
| --- | --- | --- |
| Manifest | #/$defs/Manifest | 内置声明已使用 |
| Invocation | #/$defs/Invocation | 内置同步调用已使用 |
| Result | #/$defs/Result | 内置最终结果已使用 |
| Block | #/$defs/Block | 文本与附件引用结构；结果附件交付未开放 |
| Event | #/$defs/Event | 仅预留信封；payload 的事件专用结构及传输未开放 |

例如，在支持 Draft 2020-12 的 JSON Schema 校验器中，注册下载的 Schema（包括其 $id），再以如下 $ref 校验 Invocation：

```json
{
  "$ref": "https://wechatai.app/api/plugins/protocol/1.0/schema.json#/$defs/Invocation"
}
```

主要边界：

- 协议对象采用严格字段校验。未知顶层字段会被拒绝；configSchema 与 Event.payload 是明确声明的开放对象。
- runId、instanceId、sessionId、assetId 和 Invocation.pluginId 的长度为 1–200；Manifest.id 必须为小写 author/name（字母、数字、连字符）；插件版本形如 1.0.0。
- 每次最多 20,001 条消息，每条最多 100 个内容块；单个文本块最多 8,000,000 个 UTF-16 编码单元，实际还受输入和预算限制。
- inputTokens、outputTokens 是 1–2,000,000 的整数；outputImages 是 0–20 的整数；creditUnits 是 null 或 0–1,000,000,000 的整数。这是协议边界，产品和执行器可以设置更低的上限。
- JSON Schema 只验证结构。runId 一致、权限、去重、附件归属与 ready 状态、Token 和预算等业务约束仍必须由宿主检查。
- 契约测试至少覆盖：合法结果、未知字段与伪造 usage、runId 不匹配、空结果、超限结果、同一任务重试和未验证的图片引用。

不兼容的数据结构变更需要新的 protocolVersion；插件自身的功能迭代使用 pluginVersion。指定旧协议版本应继续使用其结构，不应静默接受新字段。新运行时开放前仍需补齐传输与能力协商规范。

## 责任与身份

- 模型渠道保存协议、地址和密钥；模型保存供应商模型标识及能力。它们不决定插件的功能。
- 插件定义用 `author/name` 标识，版本用语义化版本号标识。声明见 `src/shared/plugins.ts` 的 `pluginManifestSchema`。
- 插件实例是用户的一份配置，具有独立实例 ID、显示名称、`@` 快捷名称和会话。更名不得改变身份。升级必须固定目标版本，保留旧版本并支持回退。
- 内置 `wechatai/llm-chat@1.0.0` 沿用已有模型 ID 作为兼容实例 ID，不重建历史或改变旧快捷名称。`wechatai/web-research@1.3.0` 使用独立实例 ID，在工作台「插件」管理，绑定已有模型与用户自己的搜索 API，限 Plus 使用。
- 账号身份及微信链接由平台认证层提供，不能由插件指定。会话与附件须按账号、实例、会话/任务隔离。远端会话 ID 由适配器与本站会话映射。
- 插件仅接收获准的输入、配置和能力，不拥有钱包、账本、微信凭证、支付密钥或其他实例数据。

## 声明

Manifest 包括 `protocolVersion`、`id`、`version`、`name`、`author`、`runtime`、`capabilities`、`configSchema`、`permissions`。

- `runtime` 可声明 `builtin`、`remote`、`sandbox`。当前执行器仅注册平台内置插件；声明一个运行时不授予执行权限。
- 能力声明包含输入/输出的 `text`、`image`、`file`，会话所有者 `none`、`platform`、`remote`，以及流式、取消、审批支持。
- `configSchema` 使用 JSON Schema 描述表单。密钥字段由宿主管理，运行时按授权注入或提供受限代理，不放入普通配置、事件与日志。
- `permissions` 声明模型访问、允许的网络源与存储需求。宿主必须实施权限，不能只做 UI 提示；用户授权不足或执行器不支持的能力直接拒绝。

现有内置 LLM 和 DS Chat 支持文本/多图输入、文本结果、平台会话，不声明流式、运行中取消或审批能力。微信附件由宿主暂存，收到文字或语音转写指令后才调用插件；PDF、DOCX 和纯文本在宿主侧提取正文，以文本块传入并参与计量。附件二进制与 CDN 引用不直接计入文字 Token。`/cancel` 仅清除待处理附件，不代表插件运行时具备取消能力。

DS Chat 插件支持文本输入/输出和独立的平台会话，不开放代码执行。搜索与读取采用 DeepSeek Harness 的工具/供应商分离思路，由本站实现适配和有界动作循环；不依赖 Harness 运行时。用户配置 Tavily、Exa、SearXNG 或 DeepSeek 原生搜索的完整 HTTPS 地址及自己的密钥，不存在平台搜索密钥或备用搜索源。新配置默认选择 DeepSeek 并预填官方 Messages 地址，搜索模型 ID 可省略或留空，调用时使用 `deepseek-flash`，也可自定义；回答模型另行选择。DeepSeek 适配通过 Anthropic 兼容 Messages 的 `web_search_20250305` 工具取得结构化搜索结果。

联网插件的 Manifest 中 `networkOrigins: []` 表示没有静态预授权地址。内置宿主另外绑定实例的搜索地址，并校验每次搜索、公开网页读取及重定向的 HTTPS 地址和 DNS；第三方不能继承此内置权限。密钥只发送至该实例的搜索地址，不跟随重定向；读取网页不带搜索或模型密钥。网页资料是不可信参考内容，不能作为执行指令。读取只接受搜索结果或用户提供的链接，每次最多搜索 5 次、阅读 8 页；工具调用最长 45 秒，每个模型请求最长 5 分钟，总任务最多 14 分钟。

工作台 `/app/plugins` 支持配置、启停、测试、删除和切换。微信 `/plugins` 列出快捷名称，`@快捷名 问题` 单次调用并保留在当前会话，不改变默认模式；`/use 名称或编号` 进入默认插件模式，`/exit` 返回之前的普通会话，`/model 编号` 切换普通模型。模型和插件快捷名称不能重复。配置密钥加密保存，公开文档及账户查询仅返回 `hasKey`。删除配置保留历史，但不再执行；删除所绑定的模型会同时删除配置和相关会话。Plus 到期后配置保留，调用被拒绝。第三方注册和执行 API 仍未开放。

计量在整个联网插件调用的边界执行一次：实际传入的系统提示词、上下文和当前消息算输入，最终回答及平台附加的来源链接算输出。规划、搜索结果、网页正文和中间模型调用不重复计收；上游自报的 usage 不参与结算。用户的上游服务会独立计费。输入/输出和 credits 上限仍由宿主执行，无法取得资料的失败请求不结算。

## 调用边界

`PluginInvocation` 的必需字段：

| 字段                         | 含义                                                         |
| ---------------------------- | ------------------------------------------------------------ |
| `protocolVersion`            | `1.0`                                                        |
| `runId`                      | 平台创建的幂等调用 ID；重试沿用，不允许插件改变              |
| `instanceId`                 | 本次调用的插件实例                                           |
| `pluginId` / `pluginVersion` | 平台固定的插件及版本                                         |
| `sessionId`                  | 本站会话 ID；无会话时为 null                                 |
| `messages`                   | 有序消息；角色为 system/user/assistant，内容为结构化 blocks  |
| `limits`                     | 平台确认的输入/输出 Token、结果图片数、整数 credits 单位上限 |

当前宿主在闭包内注入模型连接和凭证。远程/沙箱运行时必须另外制定配置及凭证绑定、认证传输、超时和取消机制，完成契约测试后才能开放；不要直接把 `Env` 或整份账号配置序列化到远端。

文本块：`{"type":"text","text":"你好"}`。
图片/文件块：`{"type":"image","assetId":"..."}` 或 `{"type":"file","assetId":"..."}`。
附件引用必须由宿主在当前账号/任务内解析。禁止把任意 URL、Base64 或插件声称的图片数量视为已验证图片。

同一输入消息内的文本块先拼接再分词，各消息分别分词求和。不统计 role、ID、字段名、密钥、附件元数据中的 URL、附件二进制和序列化格式开销；文本内容中的网址仍按普通文字计量。平台界面说明这些 Token 是本站计费单位，不代表特定供应商账单。

对 LLM 对话，平台先整理上下文，再把系统提示词、有效摘要、最近历史与当前消息交给适配器，计数的是实际通过这个边界的内容。外部 Agent 若自行维护历史，只有本站显式交给它的新消息及上下文参与本站计量。

## 结果与事件

同步最终结果使用 `PluginResult`：`protocolVersion`、`runId`、`status`（completed/partial）、`content`。
只有平台校验、接受并保存的可用结果才能结算。当前 LLM 是同步最终结果适配器。

未来异步传输统一使用 `PluginEvent` 信封：`protocolVersion`、`runId`、`eventId`、单调递增的 `sequence`、`type` 和 `payload`。
已定义事件名：accepted、progress、text.delta、approval.required、completed、failed、cancelled。
事件传输与持久化处理器尚未开放，实施时必须满足：

1. 远程请求/回调必须认证并绑定账号、实例与 runId，防重放；不能以 runId 本身充当认证凭证。
2. 流式文字是展示增量；最终结果作为完整权威结果，不能把增量与完整结果各算一次。
3. 进度、日志、工具调用、内部推理、审批通知不计入最终输出。
4. 重复 eventId 不再处理；乱序要恢复或拒绝，不能重复执行。终态后不可改写结果与账单。
5. 取消是请求，不能在远端尚未停止时声称已停止。缺少取消能力时明确告知不支持。
6. `/new` 为远端创建新会话映射；清空本站历史不等于清空 Agent 的远端记忆。
7. 需要确认的操作必须停在 waiting-for-approval，不能把确认伪装成普通最终文字并继续执行。

插件不能返回可写的 usage、credits、imageCount 或账本字段；结果校验拒绝这些未声明字段。协议扩展必须显式升级校验规则与能力协商。

## 平台计量

分词规则固定为 `cl100k_base@gpt-tokenizer-4.0.0`，计量代码在 `src/server/tokenizer.ts`。
词表本地打包，无第三方分词服务；关闭跨请求的文本合并缓存。特殊标记字符串按普通文字处理，不能成为低价“特殊 token”。

```text
credits = (inputTokens + 8 × outputTokens) / 5000 + 5 × outputImages
整数账本单位 = ceil((inputTokens + 8 × outputTokens) × 1000 / 5000)
             + outputImages × 5000
```

- 输入统计平台保留的原始边界快照，不受插件执行时修改参数影响。
- 最终输出的文本块按顺序拼接后只分词一次，避免拆分块改变账单。
- 上游 `usage`、缓存 token、内部推理和 Agent 自己的用量上报完全不参与扣费。
- 自动摘要、内部模型调用、兼容参数回退和上下文超限重试不产生额外账单。后续实际输入带入的摘要按输入内容正常计算。
- 参考图片不按结果图片计费；输出附件必须由平台独立验证、保存，并由可信 resolver 返回当前任务内 ready 的记录。插件自己声明 ready 不算验证。
- 结果图片按唯一平台 assetId 去重，每张 5 credits；同一任务的重复投递、预览、下载不再收费。输出图片不能通过改标为 file 绕过真实类型校验。
- 缺少平台 resolver 的图片/文件结果拒绝处理，不允许仅凭插件返回值扣图片费。内置生图由宿主校验实际 PNG/JPEG/WebP 内容、尺寸与大小并持久化到私有 R2，再通过账号/任务范围内的 resolver 计费；第三方媒体上传接口仍未开放。
- 失败且没有可用结果不扣费。可用部分结果与完整结果使用同一计量规则；交付层重发已保存结果不得再次执行或再次扣费。

## 限额与原子结算

`limits.creditUnits=null` 表示没有可选整轮预算；数字 0 表示预算为零。UI 现有的“0 为不限”由宿主转换为 null，不能直接透传。

输入超过 Token 或 credits 上限时，在执行插件前拒绝。输出上限同时传给上游并在平台执行；超出的文字按平台 Token 截断到有效 Unicode 前缀，仅对保留部分计费并提示用户。图片数与费用必须落在上限内。

所有真正扣费仍在账户 Durable Object 内进行：结果记录、`usage`、`platform_metering`、钱包扣费及完成状态在同一个事务提交，任一写入失败则整体回滚。相同 runId 重复调用读取已保存结果。

新计量记录保存政策版本、分词器版本、输入/输出 Token、图片数、扣费单位及插件/实例身份。账本和计量记录禁止更新和删除，不保存新增的消息全文副本；已有财务记录不重新计算。

请求不预扣。余额大于零可发起，成功结算可能产生欠额；后续每日赠送和充值优先偿还。单轮预算约束本站服务扣费，不代表远端模型或 Agent 内部费用上限。

## 后续第三方发布的门槛

- 官方与第三方适配器必须通过同一份协议、隔离、限额和伪造 usage 测试。
- 市场目录保存不可变包版本、校验摘要、作者及权限；用户实例固定版本。
- 上传代码在独立受限环境中执行，不能导入宿主数据库或钱包模块；运行时间、内存、网络和存储另设配额。
- 远程插件和上传代码都不能绕过平台直接把计费结果投递给微信。
- 新运行时、事件传输、媒体验证存储、配置迁移和用户授权界面必须实际完成后才开放对应能力。

## DS Chat 插件 1.3 调用语义

DS Chat 是微伴独立插件名称，不限定回答模型或搜索提供商。每轮由所选模型判断是否需要联网：日常聊天、写作、翻译、常识及基于现有资料的追问可直接回答，不调用搜索或网页接口、不添加联网来源；实时、核实和网页阅读任务按需调用工具。尝试联网但未取得任何来源时仍视为失败，不用无依据的回答冒充联网成功。直接回答与联网回答使用同一计量边界。

`@模型/插件快捷名 问题` 在当前会话执行一次（含当前上下文），不修改该微信链接的默认模式，成功的问答仍存入当前会话。`/use 插件名称/快捷名/编号` 和网页「设为默认插件」进入独立插件会话，`/exit` 和网页退出返回该链接之前的普通会话；各链接独立，退出不需要 Plus。删除或停用插件、会员到期后同样可退出。普通模型默认切换使用 `/model 编号`，不再复用 `/use`。

适配器 ID `wechatai/web-research` 不变；旧默认名称「联网问答」「DeepSeek」自动升级为「DS Chat」，自定义名称、快捷名、密钥、历史和已有账单保留。旧实例版本升级为 1.3.0，历史计量版本不改。

### 原生工具循环与兼容路径

主路径采用 DeepSeek Harness 的「工具声明 → 模型回答或调用 → 工具结果 → 模型继续」循环思路，由本站实现三协议适配，不引入完整 Harness 运行时。`web_search({query})` 与 `web_fetch({url})` 是平台执行的客户端工具，搜索凭据只发往用户配置的搜索接口。工具选择为 auto，不额外要求模型先生成行动分类 JSON；无工具调用的正文直接成为最终回答，中间文字不单独展示或计费。

Chat Completions 保留 assistant 的 tool_calls / reasoning_content，并用 role=tool / tool_call_id 回传结果；Responses 使用 function_call / function_call_output，store=false 并保留 encrypted reasoning；Anthropic 保留 thinking / redacted_thinking / tool_use 原文，紧接 user 的 tool_result，包括错误标记。每轮至多接受 8 个工具调用，逐个受剩余搜索/阅读次数约束；每个调用都有对应结果。次数或循环预算用完后要求最终回答，再次调用工具则报错。截断调用、无效编号或重复编号不执行。

输入上限包括工具声明、调用记录和资料；仅压缩工具结果文本，不改写签名推理、不拆散调用与结果、不静默截断用户消息。模型窗口超限时最多压缩重试一次。工具输出属于不可信数据，网页地址仍须来自对话或搜索结果并通过公网检查。

仅首次原生请求明确返回 HTTP 400/422 且表明不支持 tools / tool_choice / function calling 时，进入原 JSON 兼容路径。鉴权、限流、网络超时、服务故障、工具 Schema 错误与无效响应均不回退；工具循环已经开始后也不回退，避免重复执行。平台仍只计量用户传入上下文与最终答案；规划、推理、工具调用参数与结果不单独计费，上游 API 费用由用户承担。

协议参考：[OpenAI Function calling](https://developers.openai.com/api/docs/guides/function-calling)、[Anthropic Handle tool calls](https://platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls)、[Harness web tools](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/web/tool-web/README.md)。

## AI 生图插件 1.0

`wechatai/image-generation@1.2.0` 限 Plus，绑定已有模型 ID，沿用其渠道 URL 与密钥，独立调用 OpenAI Images 兼容的 `/v1/images/generations`，不修改渠道的聊天协议。配置名称、快捷名、模型和启用状态。支持文字生成单张图片；不声明参考图、图像编辑、流式或远端会话。每次仅传当前画面描述，不带聊天系统提示词或历史；输入上限与单次 credits 上限仍生效。宿主自动识别提示词中的常见比例，转换为该次请求的 `size`，如 `3:4` → `960x1280`、`16:9` → `1536x864`；支持 1:1、3:4、4:3、2:3、3:2、9:16、16:9、4:5、5:4、21:9 和 9:21。未指定比例时不传尺寸参数，沿用上游默认值。完整提示词原样转发；自动补充的 `size` 不计入输入 tokens。渠道需支持对应尺寸；上游拒绝时不去掉参数重试，避免重复生成和费用。旧客户端传入的固定 `size` 忽略，旧配置继续自动移除固定尺寸。

边界输入仅描述文字，输出是一个平台验证的图片块；收取输入 token 费用加 5 credits，忽略上游 usage。先检查费用上限，再请求生图；无可用图片或存储失败不结算。生成接口不自动重试，以免重复上游费用。完成结果与平台账单原子保存；微信发送重试只重传已保存图片，使用稳定 client_id，不重复生成或计费。

AI 生图与 OpenClaw 的配置支持 `waitingMessageEnabled`（默认 `true`）和 `waitingMessage`（最多 300 字，开启时不可为空）。缺少这两个字段的旧配置保留原默认提示；编辑已有配置时省略字段会保留已保存的设置。默认文案分别为「已提交 AI 生图，等待上游返回...」和「已成功转发，等待回复...」。关闭后不发送等待提示，只发送最终结果或错误；提示由平台发送，不进入插件输入、对话历史或计量。

支持 data.url 或 data.b64_json；下载 URL/每次跳转只允许公网 HTTPS，不能携带模型密钥。图片最大 8 MB、2500 万像素。结果保存在账号隔离的私有 R2，网页仅本人登录并有 Plus 权限可读，保留 30 天。删除会话或清空历史立即撤销访问并清理图片，归档保留；微信已收到的图片由用户自行管理。网页测试结果同样保存 30 天，测试不会主动发微信。

## 角色扮演插件 1.0

内置 ID 为 `wechatai/roleplay`，版本 `1.0.0`，仅 Plus 可创建和调用。配置 JSON Schema 随 `/api/plugins/protocol` 的 `builtinManifests` 提供。配置没有额外 API Key，引用账户已有的 `modelId`；支持 Chat Completions、Responses、Anthropic Messages。角色替代模型系统提示词，继承该模型的所有输入、输出、上下文窗口和单轮 credits 限制。

- 基础配置：`name`（40 字符）、`shortcut`（24 字符）、`modelId`、`enabled`、`description`（16,000 字符）。高级字段：`personality`、`userName`、`userPersona`、`scenario`、`firstMessage`、`examples`、`instructions`、`postHistoryInstructions`。完整限制以 Manifest 为准；配置序列化后最多 120,000 UTF-8 字节。
- 固定角色指令、设定、性格、用户角色、场景和示例放在 system 中。`postHistoryInstructions` 兼容为 system 末尾的回复要求，不模拟 SillyTavern 的历史后插入位置。只展开 `{{char}}`、`{{user}}`；脚本、其他宏与外部资源不执行或加载。
- V2 `chara_card_v2` / `2.0` JSON、PNG `tEXt/chara` Base64 元数据在浏览器中读取，文件不超过 5 MB，角色文字不超过 64 KB。映射 V2 核心文字和 character_book，原始 JSON 存在不参与 prompt 的 `sourceCard`。PNG 封面不保存，备用开场白和扩展仅保留在原始 JSON 中；正则条目默认停用。可导出编辑后的核心 V2 JSON，也可下载原始 JSON 保留全部源扩展。当前不支持 V3、CHARX 或完整 SillyTavern 运行行为。
- `lore` 最多 50 条，支持 `keys`、`secondaryKeys`（启用后与主关键词同时命中）、`constant`、`caseSensitive`、`enabled`、`priority`。在当前输入和最近 3 条原文的末尾 32,000 字符中进行直接子串匹配，不递归扫描注入内容。按 priority 降序、原始顺序选取完整条目。`loreTokenBudget` 默认 1,000、最大 4,000，0 禁用；还受当前输入空间约束。超预算整条跳过，不截断角色设定或用户问题。
- 角色实例和微信链接分别隔离会话；`/use` 恢复该角色最近的未归档故事，`/new` 新故事，`/exit` 普通模型。`@` 用当前会话上下文执行单次调用并存回当前会话，不修改目标角色独立故事。固定开场白在故事创建时写入记录；微信 `/use` 首次创建、`/new`、归档后新建时显示一次，恢复旧故事不重复。网页创建只写会话记录，不主动推送微信。
- 角色会话压缩使用剧情摘要提示，保留时间地点、关系变化、物品状态、约定和未完成线索；原始记录保留。Plus 在自己的网页会话中查看和修正摘要，编辑保持原压缩游标，校验摘要版本和上下文版本，重置/清空/删除依照既有机制清理。摘要跟随会话所有者，单次调用其他角色不会改变其摘要类型。
- 网关计量使用完成组装的角色提示、命中的世界书、摘要、历史、当前输入及最终回复，插件身份记录为 `wechatai/roleplay`。不信任上游 usage；内部摘要和超限失败不重复计费。开场白直接展示不收费，后续作为输入时计费。

本实现参考 Character Card V2 的公开格式，自行实现协议映射，不包含 SillyTavern 或 RisuAI 运行时代码。参考：https://github.com/malfoyslastname/character-card-spec-v2

## 命令路由

Manifest.capabilities.commands 可选值为 "platform"（默认，兼容旧 manifest）或 "native"。内置 OpenClaw / Hermes 声明 "native"。该声明描述命令归属，不赋予额外执行权限。

- /wx <command> 始终调用微伴命令；/use 与 /exit 保留为全局切换快捷命令。
- 当前插件支持 native 时，其余以 / 开头的输入原样交给插件；platform 插件沿用微伴短命令。
- @快捷名 /command 显式单次调用插件，不再进行第二次微伴命令解析，也不改变默认插件。
- 仅识别消息开头的 `@`（兼容全角 `＠`）；快捷名后可用空白、中英文逗号或冒号分隔，如 `@助理，5 分钟后提醒我喝水`。移除快捷名与分隔符后保留正文中的标点，按快捷名精确匹配，不猜测没有分隔符的名称。
- Agent 原生 /new 或 /reset 保留平台历史与路由；/wx new 新建平台会话，/wx reset 失效旧返回路由。
- Relay 连接器通过 heartbeat.nativeCommands=true 声明命令支持；poll 和 claim 同样必须携带 nativeCommands=true。poll.lane 为 message（默认）或 control；目前 control 仅包含无参数的 /stop，可与普通任务并行。普通任务仍串行，已领取任务不会重跑。旧连接器不会领取原生命令。
- Relay 返回的任务包含 lane（message 或 control）与可选 command（小写命令名），text 保留原文。连接器只为命令任务开启原生命令处理，仍遵守 Agent 的权限设置。/stop 免费，确认回复最多 256 tokens；带参数的 /stop 作为普通命令处理。其他原生命令沿用网关输入与最终输出计费。


## 个人助理插件 1.0

内置 `wechatai/personal-assistant@1.0.0`，仅 Plus 可创建和调用。Manifest 的配置 schema 通过 `/api/plugins/protocol` 公开。配置 `name`、`shortcut`、`modelId`、`enabled`、`currency`（默认 CNY）、`reminderConnectionId`（可留空）。复用用户模型与渠道，需原生工具调用支持，兼容 Chat Completions、Responses、Anthropic Messages；不对不支持工具的模型自动降级。助理提示替换普通模型系统提示，继承模型输入/输出、上下文与单轮 credits 限制。

- 数据按账户及插件实例隔离；记录类型 `note`、`entry`、`task`，独立于聊天会话和摘要。每份配置最多 10,000 条，每条内容最多 6,000 字符。清空/删除聊天、删除回答模型不会删除记录；删除插件配置同时删除全部记录及提醒。
- `personal_query` 返回分页记录、ID/revision、总数及按币种区分的收支汇总；默认 20、最多 50 条。日期按北京时间，金额以正数十进制字符串输入、整数分存储；汇总由数据库计算，不信任模型加总。
- `personal_create`、`personal_update`、`personal_delete` 在本轮草稿操作。编辑/删除前必须取得查询结果中的 ID/revision。工具调用仅暂存，最终模型回复成功后，记录变更、提醒计划、平台账单及运行回执在同步事务中一并保存。上游失败、超时、上下文重试、并发版本冲突均不留下半轮写入。重复消息由平台运行 ID 去重；同轮相同写入参数也去重。每轮最多 9 次模型请求、24 次工具调用。
- 提醒仅用于 task；`remindAt` 为含时区 ISO 时间，创建时要求未来两年内、所选微信链接已连接；`repeat` 为 none/daily/weekly。以固定 UTC+08:00 解释自然语言时间。平台账户 DO 的持久化 alarm 调度，慢模型请求期间并行推进提醒；数据库保存发送状态，发送重试复用每次提醒的稳定 client_id，不保证上游网络的绝对一次交付。
- Plus 到期、插件停用或连接不可用时暂停；缺少微信 context token 等待下一条微信消息，临时失败退避重试。重新扫码导致连接 generation 变化，或修改目标链接后，不将旧提醒转发到新身份，需重新设置提醒。过期超过 24 小时的单次提醒标记 expired，重复提醒跳至下一次，避免集中补发。完成/删除待办取消后续提醒。主动消息使用平台插件来源提示及分隔符。
- 网关仅对输入快照（助理 system、实际上下文、当前输入）和最终回复计量一次；内部记录、工具回执与模型调用不重复计收。到期提醒为固定模板，不调用模型、不扣 credits。配置测试仍计输入/最终输出，但不提交草稿或发送提醒。
- 同源登录 API：`POST /api/plugins/:id/records/query` 查询，`POST /api/plugins/:id/records` 保存 `{record,recordId?,revision?}`，`DELETE /api/plugins/:id/records/:recordId` 接收 `{revision}`。账户来自登录态，插件和记录 ID 均校验归属；这不是第三方插件公共调用接口。Plus 到期可读、导出和删除，新增编辑需有效 Plus 且启用。网页 CSV 每次最多 1,000 条，可分日期导出，单元格防公式注入。
