投稿前先完成开发者资格申请
使用现有账号登录,填写自行声明的身份、私有联系方式、开发经历、开发计划及所需权限。初期申请免费,联系核验、开发者资格审核与每个插件的安全审核及签名分别进行,不作为实名或 KYC 认证。已有上架插件不受影响,更新版本需先通过资格审核。
资格通过后,在开发者中心生成可撤销的投稿令牌,在本机设置 AWD_PLUGIN_TOKEN。令牌仅能提交和查询本人插件,最长 90 天有效,原文仅显示一次,不要放进 Agent 指令或插件包。
申请资格与管理投稿本页 SDK 1.6 的视图、连接、MCP、分域设置、$schema/default 兼容修复和宿主服务接口需要新版宿主;本次代码尚未发版,现有已安装客户端不因此自动支持。模拟器只使用合成数据,不能代替新版宿主验收。
先想清楚:你需要的是插件还是 Skill
这两个东西经常被搞混,选错会白做很多工作。区别只有一句话:要写代码才能办到的事用插件,靠说清楚就能办到的事用 Skill。
| 场景 | 该用 |
|---|---|
| 调用某个外部系统的接口取数据 | 插件 |
| 解析一种特殊格式的文件 | 插件 |
| 让 AI 按你所在律所的格式写审查意见 | Skill |
| 规定 AI 处理某类案件时的步骤与产出结构 | Skill |
Skill 是纯文本(提示词 + 触发词),提交即用、无需审核,写起来快得多。如果你的需求 Skill 能满足,直接去写一个 Skill,不用往下读了。
五分钟跑通第一个插件
插件是一个 Java 工程,编译出 JAR,配一份 manifest.json,打成 zip 提交。需要 JDK 21 和 Maven。
下载上面的模板工程,它本身就是一个能跑的完整例子。核心只有两个文件。第一个是工具类:
package com.example.myplugin;
import dev.langchain4j.agent.tool.Tool;
/**
* 插件工具类。
*
* 三条硬约定,违反任意一条工具都不会出现在 AI 面前:
* 1. 必须有无参构造函数——宿主用反射实例化;
* 2. 工具方法加 @Tool 注解,方法名即工具名,要与 manifest.json 的 tools[].name 一致;
* 3. 参数与返回值用 String 最省事(复杂结构自己序列化成 JSON 字符串)。
*
* @Tool 里的描述是写给 AI 看的,直接决定它会不会在恰当的时候调用这个工具。
* 写清楚"什么时候用",比写"这个方法做什么"有用得多。
*/
public class MyTools {
@Tool("统计一段中文文本的字数,返回可读的统计结果。用户问'多少字'时调用。")
public String countChinese(String text) {
if (text == null || text.isBlank()) {
return "输入为空,字数 0";
}
long cjk = text.codePoints()
.filter(cp -> Character.UnicodeScript.of(cp) == Character.UnicodeScript.HAN)
.count();
return String.format("总字符 %d,其中汉字 %d", text.length(), cjk);
}
}
@Tool 里的描述是写给 AI 看的,它直接决定 AI 会不会在恰当的时候调用你的工具。写「什么时候该用它」比写「这个方法做了什么」有用得多——AI 需要判断的是前者。
第二个是 manifest.json,描述这个插件是什么:
{
"id": "my-plugin",
"name": "我的插件",
"version": "1.0.0",
"description": "一句话说明这个插件替用户做什么。会展示在插件广场的卡片上。",
"author": "你的名字或团队",
"homepage": "https://example.com",
"permissions": [],
"tools": [
{
"name": "countChinese",
"description": "统计中文文本字数",
"permissions": []
}
],
"backendJars": ["my-plugin-1.0.0.jar"]
}
工具名必须两边对上:tools[].name 要等于 Java 里的方法名,不一致的话工具注册不上,AI 看不见它。
然后打包:
mvn package
mkdir -p dist && cp target/my-plugin-1.0.0.jar manifest.json dist/
cd dist && zip -r ../my-plugin-1.0.0.zip . && cd ..注意 zip 里是文件本身,不要多套一层目录——解压出来应该直接看到 manifest.json,而不是一个文件夹。
提交前先在自己机器上试
不用等审核。把 dist/ 整个目录复制到本机的插件目录,重启 AI WorkDeck 就能看到:
# macOS / Linux
~/.aiworkdeck/plugins/my-plugin/在「插件广场 → 已安装」里应该出现你的插件。启用后,在对话中提一个会用到你工具的问题,看 AI 是否调用了它。没调用的话,八成是 @Tool 的描述没写清楚使用时机。
manifest.json 每个字段的意思
| 字段 | 说明 |
|---|---|
id | 全局唯一,小写字母数字连字符。一旦上架就不能改——它是升级时认定「同一个插件」的依据。 |
version | 语义化版本。每次提交都要比上一版高,否则会被拒。 |
name / description | 展示在插件广场卡片上。描述写清楚替用户做什么,别写技术实现。 |
author / homepage | 作者与项目主页,可选但建议填,用户会据此判断是否信任。 |
permissions | 这个插件会用到的能力,见下一节。 |
tools | 工具清单。name 必须等于 Java 方法名;description 用中文写清楚用途。 |
backendJars | JAR 文件名列表,相对包根目录。不能用 ../ 指到目录外。 |
permissions:如实声明,审核会交叉核对
四个可选值,按需声明:
file_read读取项目文件file_write创建、修改或删除文件network访问外部网络editor操作文档编辑器
这不是沙箱
插件与主程序在同一个进程里运行,技术上拦不住未声明的行为——声明了空权限的工具,代码里照样能读文件。所以这不是运行时限制,而是审核依据:我们会拿它跟 JAR 的静态扫描结果交叉核对。声明了没有 network 却引用网络 API,或者用到了却没声明,都会被驳回。
审核会看什么
每个版本都要人工过一遍,通常一到两个工作日。提交后自动扫描先跑一遍,扫描报告和你的 permissions 声明会一起摆在审核台上。
这些情况会被直接驳回:
- 声明的 permissions 与实际调用的 API 对不上
- 自定义 TrustManager 或以其他方式绕过证书校验
- 硬编码的 IP 地址、明文 HTTP 外传数据
- 通过反射访问主程序内部对象(数据库连接、配置服务等)
- 代码混淆、加壳,或任何让人看不懂它在干什么的处理
- 版本号没有比上一版高
通过后平台会用私钥对整个包签名,客户端安装时验签。这意味着上架之后任何人(包括我们)都无法在不重新签名的情况下改动包内容。
如果上架后发现问题,我们会撤销该版本。客户端拉到撤销名单后会自动停用它并提示用户。
给用户的承诺,也是给你的约束
我们的用户是律师,他们的机器上有客户的机密材料。一个插件拿到的权限跟主程序一样大——能读到的东西远超它自己需要的范围。
所以审核会偏严,被驳回时我们会说明原因。如果你的插件确实需要某个看起来敏感的能力,在提交说明里讲清楚为什么,这会让审核快很多。
Web 插件:用 HTML/JS 做界面
如果你要做的是一个有界面的东西——一张表单、一个查询工具、一块看板——不必写 Java。包里放一个 web/ 目录(纯静态 HTML/JS/CSS,入口 web/index.html),manifest 把 frontendEntry 指向它就行,可以完全不带 JAR。
权限在这里是真的。桌面端把 Web 插件装进 sandbox iframe,刻意不与应用同源:插件脚本拿不到宿主会话,一切能力都得经过 postMessage 桥,宿主逐调用比对 manifest 的 permissions。跟 JAR 插件不同,这里的权限声明是执行边界,不是自述。
模板里的 web/awd-plugin-sdk.js 封装了这座桥。握手完成前不要调用任何方法:
| 方法 | 参数 | 返回 | 需要权限 |
|---|---|---|---|
settings.open / settings.update | {} / {values} | {opened} / {} | user / project settings; installation settings stay in trusted host UI |
connections.list / connect / disconnect / request | {id, operation?, params?} | connection state / service JSON | network; host-held credentials and explicit developer trust |
mcp.servers / mcp.call | {} / {serverId, name, arguments} | {servers} / MCP CallToolResult | network + project access; public HTTPS Streamable HTTP |
billing.status / billing.checkout | {connectionId?} | {provider, active, plan?} / {opened} | host purchase entitlement or developer subscription; no automatic charge |
services.request / services.cancel | {service, operation, params, requestId} / {requestId} | operation result | explicit service catalog; per-operation permission checks |
context.get | {} | { pluginId, projectId, language, theme, themeTokens } | - |
files.list | {} | { files: [{ path, name, size }] } | file_read |
im.downloadAttachment | { conversationId, attachmentId } | { started, name } | im(服务端附件权限校验,无需项目写权限) |
notifications.publish | { eventKey, title, body?, actionState?, target? } | notification | notifications(当前账号收件箱;目标只打开本插件) |
notifications.resolve | { eventKey, state } | notification | notifications(只更新本插件消息,已读不会完成业务) |
im.request | { action, params } | object | im(宿主 0.61.0+;固定聊天动作,项目归档另需 file_write) |
chat.addFile | { path } | {} | file_read(宿主 0.61.0+;添加材料,不自动发送) |
files.prepareDrag | { path } | { fileId, dragToken, dragType, expiresAt, ... } | file_read(宿主 0.61.0+;短时单次拖拽票据) |
files.endDrag | {} | {} | file_read(宿主 0.61.0+) |
files.read | { path } | { path, content, truncated } | file_read |
ui.toast | { message } | {} | - |
storage.get | { key } | { key, value } | - |
storage.set | { key, value } | {} | - |
evidence.link | { anchor: { selection: true } | { quote }, docPath?, targets: [{ path, locator?, relation?, method?, note? }] } | { linkKey, targetIds } | editor |
evidence.list | { docPath?, path?, sectionPath?, status? } | { links: [{ linkKey, docPath, anchorText, sectionPath, status, targets }] } | file_read |
evidence.locate | { linkKey, targetId? } | {} | editor |
tools.invoke | { name, args? } | { output } | -(工具须为本插件 manifest 声明) |
chat.send | { prompt } | {} | -(上限 4000 字) |
ui.openFile | { path } | {} | file_read |
doc.exec | { action, params? } | { result } | editor(宿主 0.27.4+;action 为 doc_/sheet_/slide_ 安全子集) |
doc.active | {} | { fileId, kind } | editor(宿主 0.27.4+) |
events.subscribe | { events: ["files.changed" | "selection.changed" | "project.switched"] } | { subscribed } | 按事件(宿主 0.27.4+) |
events.unsubscribe | { events } | { subscribed } | - |
ai.request | { prompt, system?, purpose? } | { text, modelId } | ai(宿主 0.27.4+;16000 字符、10 次/分钟,走用户 Credits) |
settings.get | { key } | { key, value } | -(宿主 0.28+;manifest 顶层 settings 声明的配置项,secret 项拿不到) |
<script src="awd-plugin-sdk.js"></script>
<script>
const ctx = await awd.ready(); // { pluginId, projectId, language, theme, themeTokens }
const files = await awd.files.list(); // 需 file_read
const doc = await awd.files.read(files[0].path); // { path, content, truncated }
await awd.ui.toast('已完成');
await awd.storage.set('draft', { title: 'x' }); // 插件级 KV,上限 64 KB
</script>错误以 rejected Promise 抛出,err.code 是错误码:权限不足 permission_denied,宿主不认识的方法 unknown_method。files.read 文本上限 5 MB(超出时 truncated 为 true),storage 每个插件总量上限 64 KB。
主题通道(v2.6)
握手的 context 带 theme('light'|'dark')与 themeTokens(一份 --awd-* CSS 变量表);此后主题切换,宿主还会推送 { type: 'theme', theme, tokens }。SDK 收到即自动挂 data-theme、awd-theme-light/awd-theme-dark class,把 tokens 逐个写成 CSS 自定义属性——插件 CSS 直接用 var(--awd-surface) 这类语义令牌即可跟着主题走,零 JS。需要脚本联动的场景用 awd.theme.get() 读当前值、awd.theme.onChange(cb) 订阅切换。宿主模拟器右上角有「切换到深色/浅色」按钮,不装桌面端也能调两个主题。
模板还带一个宿主模拟器 dev/host-simulator.html:它假扮桌面端完成握手、用假数据实现全部方法、逐条打印桥消息。起个本地静态服务就能在浏览器里开发调试,不需要安装 AI WorkDeck。
python3 -m http.server 8000
# -> http://localhost:8000/dev/host-simulator.html直接双击打开(file://)多半是空白:浏览器不给 file:// 下的 sandbox iframe 加载子页面。静态服务也不能改写 URL——npx serve 默认的 clean URLs 会把 /web/index.html 重定向到 /web,页面里相对引用的 SDK 随之 404。
SDK 1.6 平台接口(新版宿主预览)
下载 Web 模板后,Node.js 20+ 可直接运行内附 scripts/plugin-dev.mjs,无需克隆宿主仓库。scripts/ 与 sdk/plugin-sdk/ 必须保持相对目录。先在模拟器完成界面调试,再在兼容的本地开发宿主测试权限、项目切换、禁用、取消和重连。示例域名只是占位符,不是可调用服务。
views 声明 sidebar 或 workspace 视图、project 或 global 作用域;每种位置最多一个视图,入口必须是包内 web/ 下文件。侧栏停靠可声明 left/right/bottom。settings 的 scope 默认为 installation,另支持 user/project;插件只能更新自身非敏感的 user/project 设置,installation 设置由可信宿主设置页维护。secret 不回传 iframe;用户服务凭据请使用 connections。
connections 声明固定 HTTPS 服务来源及固定 GET/POST 操作路径,支持公开服务、Bearer 或 OAuth2 公共客户端 PKCE。awd.connections.connect 只打开宿主连接流程;令牌只在宿主保存。插件不能传任意 URL、Authorization 或任意宿主配置键。BYO 服务与宿主 AI、语音等通道是两种独立选择;使用宿主通道须相应权限及现有用量约束。
OAuth 使用 Authorization Code + S256 PKCE:单机宿主采用实际监听端口的 http://127.0.0.1:<port>/api/plugins/oauth/callback,服务端部署需显式配置 ai.plugins.oauth-redirect-uri 为可信 HTTPS 地址,并在供应商登记。过期且不能刷新或被服务以 401/403 拒绝的连接返回 needsReconnect,用户应在宿主重新登录。
MCP 当前仅支持公网 HTTPS Streamable HTTP,不支持 stdio、本地进程、私网地址或重定向。须声明 network、服务和全部工具;工具名和 inputSchema 必须与远端工具一致。参数根类型为 object,支持 object/properties/required/布尔 additionalProperties、array/items、string/enum、boolean/integer/number,以及 title/description;不支持 $ref、oneOf、nullable 或其他 JSON Schema 关键字。深度最多 8,单对象最多 64 属性,每服务最多 64 工具。connectionId 引用同插件同源连接;credentialKey 仅为已声明 installation secret 的兼容路径,不能读取任意全局设置。
通过 dev/install 装入的插件默认无远程服务信任。管理员须在可信宿主设置页明确授权;授权绑定完整 manifest 的 SHA256,声明变化后重新授权。开发包不得携带 .awd-dev、.awd-service-trust 或 .awd-bundled 标记。授权不解除开发安装的 JAR/本地进程禁令。
speech、tasks、clipboard 是宿主服务权限;services.request 仅接受宿主目录中的服务/操作,并非任意后端代理。用 services.cancel 取消同插件请求。无权限、宿主能力不存在或宿主版本过旧时,应显示明确说明,不能悄悄改走收费或外部服务。
平台收费目前仅支持插件购买权益,提交价格是一次性买断价;不提供按调用代收或开发者分账接口。开发者自有服务可用 connections.billing 声明订阅状态操作和结账页。billing.checkout 只打开结账流程,不能把返回 opened 当成支付成功。
平台 billing.status 主动刷新当前账户权益,refreshed 为 false 表示本次权益刷新未成功,active 可能来自仍有效的缓存,不能作为新支付确认。checkout 会等待浏览器打开结果并报告失败。服务插件须在真实宿主验收登录、令牌过期、订阅、MCP 错误、账户切换及共享/BYOK 选路;合成供应商只能证明宿主集成,不等于真实 OAuth 或支付认证。
先在开发者中心完成免费资格审核,再生成投稿令牌并在本机临时环境变量 AWD_PLUGIN_TOKEN 中提供,不写入源码、Agent 指令或 zip。合法包仍进入 pending,等待人工审核与签名。开发安装、模拟器通过及 CLI 校验均不代表已上架或获准执行后端代码。
node scripts/plugin-dev.mjs validate .
node scripts/plugin-dev.mjs package . --out ../my-plugin.zip
# Start the synthetic UI simulator, then open /dev/host-simulator.html
python3 -m http.server 8000
# Explicit development installation: select an isolated imported project/folder first.
# Replace PORT with the isolated host port. AWD_SESSION_ID is supplied locally if required.
# Do not paste credentials into source or an Agent prompt.
node scripts/plugin-dev.mjs install --endpoint http://127.0.0.1:PORT --project-id PROJECT_ID --folder-id FOLDER_ID
# Explicit marketplace submission only; provide AWD_PLUGIN_TOKEN locally.
node scripts/plugin-dev.mjs submit . --endpoint https://www.aiworkdeck.com/api/plugins --price-cents 0
node scripts/plugin-dev.mjs submissions --endpoint https://www.aiworkdeck.com/api/plugins{
"id": "my-service-plugin",
"name": "Service panel",
"version": "1.0.0",
"description": "SDK 1.6 host preview example",
"author": "Example developer",
"frontendEntry": "web/index.html",
"permissions": [
"network",
"ai",
"speech",
"tasks",
"clipboard"
],
"settings": [
{
"key": "language",
"label": "Language",
"type": "string",
"scope": "user",
"default": "zh-CN"
},
{
"key": "summaryStyle",
"label": "Summary style",
"type": "string",
"scope": "project",
"default": "brief"
}
],
"contributes": {
"views": [
{
"id": "service-panel",
"name": "Service panel",
"entry": "web/index.html",
"placement": "sidebar",
"scope": "project",
"defaultDock": "left",
"allowedDocks": [
"left",
"right",
"bottom"
]
}
],
"connections": [
{
"id": "service",
"label": "My service",
"baseUrl": "https://api.example.com",
"auth": {
"type": "bearer"
},
"operations": {
"summarize": {
"method": "POST",
"path": "/v1/summarize"
},
"status": {
"method": "GET",
"path": "/v1/subscription"
}
},
"billing": {
"statusOperation": "status",
"checkoutUrl": "https://example.com/subscribe"
}
}
],
"mcpServers": [
{
"id": "remote",
"transport": "streamable-http",
"url": "https://api.example.com/mcp",
"connectionId": "service",
"tools": [
{
"name": "summarize",
"description": "Summarize supplied text",
"inputSchema": {
"type": "object",
"properties": {
"text": {
"type": "string"
}
},
"required": [
"text"
],
"additionalProperties": false
}
}
]
}
]
}
}交给 Agent 的完整任务指令
请基于下载的 AI WorkDeck Web 插件模板实现我的需求:[填写功能、输入、输出与验收标准]。
先读 README.md、sdk/plugin-sdk/awd-plugin-sdk.d.ts 和 manifest.schema.json,再规划最小权限。SDK 1.6 新能力需要尚未发版的新宿主;不要把模拟器成功当作现有客户端兼容证明。
只改插件目录。保留 scripts/ 与 sdk/plugin-sdk/ 相对结构;不要改 SDK 来绕过权限。界面遵循 --awd-* 主题变量、键盘可达、窄侧栏及明暗主题。
需要侧栏或工作区时声明 contributes.views;需要设置时区分 installation/user/project。用户密钥只通过宿主 connections 流程,不能进 iframe、日志、仓库或包。
选择宿主 AI/语音/任务/剪贴板通道或开发者自有 HTTPS 服务;前者声明相应权限,后者声明 network、固定 connections 操作。MCP 只用远程 HTTPS Streamable HTTP,显式声明每个工具和受支持的 inputSchema;禁止 stdio、私网、任意 URL 和任意系统配置读取。
用模拟器合成数据实现成功、权限拒绝、连接断开、失败重试与取消,不访问真实客户资料、登录或付费服务。真实宿主开发安装只使用我指定的隔离项目;服务访问须管理员在宿主设置页明确授权,manifest 改动后重新授权。
执行 node scripts/plugin-dev.mjs validate .;然后 node scripts/plugin-dev.mjs package . --out ../my-plugin.zip。检查压缩包无 .env、令牌、私钥和宿主信任标记。输出修改、验证结果、未验证边界与 zip 路径。
只有我明确要求提交时,再使用我本机提供的 AWD_PLUGIN_TOKEN,执行 submit 命令;不打印令牌。提交后报告 pending 审核状态,不声称已上架、已支付或已发布。