Plugin Development
Write a plugin
Plugins add new tools to AI Workdeck so the AI can call your code from a conversation. This guide starts with a minimal working example and ends with submitting for review.
First, decide: do you need a plugin or a Skill
The two are easy to confuse, and picking the wrong one wastes a lot of work. The difference fits in one sentence: if it takes code, write a plugin; if explaining it clearly is enough, write a Skill.
| Scenario | Use |
|---|---|
| Call an external system's API to fetch data | Plugin |
| Parse a special file format | Plugin |
| Have the AI write review comments in your firm's format | Skill |
| Define the steps and deliverable structure for a type of matter | Skill |
A Skill is plain text (a prompt plus trigger words): it goes live on submission with no review, and is much faster to write. If a Skill covers your need, go write a Skill and skip the rest of this page.
Run your first plugin in five minutes
A plugin is a Java project compiled into a JAR, plus a manifest.json, zipped up and submitted. You need JDK 21 and Maven.
Download the template project above — it is itself a complete working example. Only two files matter. The first is the tool class:
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);
}
}
The description in @Tool is written for the AI — it directly decides whether the AI calls your tool at the right moment. Writing "when to use it" is far more useful than "what this method does": the former is what the AI has to judge.
The second is manifest.json, which describes what the plugin is:
{
"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"]
}
Tool names must match on both sides: tools[].name must equal the Java method name. If they differ, the tool fails to register and the AI cannot see it.
Then package it:
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 ..Note the zip contains the files themselves — don't nest an extra directory. Unzipping should reveal manifest.json directly, not a folder.
Try it on your own machine before submitting
No need to wait for review. Copy the entire dist/ directory into your local plugin folder and restart AI Workdeck:
# macOS / Linux
~/.aiworkdeck/plugins/my-plugin/Your plugin should appear under Plugin Marketplace → Installed. Enable it, then ask something in a conversation that should use your tool and see whether the AI calls it. If it doesn't, the @Tool description most likely fails to explain when to use it.
What each manifest.json field means
| Field | Description |
|---|---|
id | Globally unique; lowercase letters, digits and hyphens. It can never change once published — it is how upgrades identify "the same plugin". |
version | Semantic version. Every submission must be higher than the previous one, or it is rejected. |
name / description | Shown on the marketplace card. Describe what it does for the user, not the implementation. |
author / homepage | Author and project homepage. Optional but recommended — users judge trust by them. |
permissions | The capabilities this plugin uses; see the next section. |
tools | Tool list. name must equal the Java method name; write the description so its purpose is clear. |
backendJars | JAR file names relative to the package root. ../ paths outside the package are not allowed. |
permissions: declare honestly — review cross-checks
Four optional values; declare what you use:
file_readRead project filesfile_writeCreate, modify or delete filesnetworkAccess the external networkeditorOperate the document editor
This is not a sandbox
Plugins run in the same process as the host app, so undeclared behavior cannot be blocked technically — a tool that declares no permissions can still read files. The declaration is not a runtime restriction but review evidence: we cross-check it against the JAR's static scan. Declaring no network while referencing network APIs — or the reverse — gets the submission rejected.
What review looks at
Every version is reviewed by a human, usually within one to two business days. An automated scan runs first, and its report sits next to your permissions declaration on the reviewer's desk.
These are rejected outright:
- Declared permissions don't match the APIs actually called
- Custom TrustManager or any other way of bypassing certificate validation
- Hardcoded IP addresses, or data sent out over plaintext HTTP
- Reflection into the host's internal objects (database connections, config services, etc.)
- Obfuscated or packed code, or anything that hides what the code does
- Version number not higher than the previous one
Once approved, the platform signs the whole package with its private key and clients verify the signature on install. After that, nobody — including us — can alter the package contents without re-signing.
If a problem surfaces after release, we revoke that version. Clients pick up the revocation list, disable it automatically and notify the user.
A promise to users — and a constraint on you
Our users are lawyers, and their machines hold clients' confidential material. A plugin gets the same access as the host app — far beyond what it needs for itself.
So review errs on the strict side, and rejections come with reasons. If your plugin genuinely needs a sensitive-looking capability, explain why in the submission notes — it makes review much faster.
