@xiongtao.xt/tau-plugins 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +2 -2
  3. package/client/{chunk-ECEBBIHP.js → chunk-SVY5LBAT.js} +44 -0
  4. package/docs/README.md +43 -0
  5. package/docs/ai.md +169 -0
  6. package/docs/backend.md +168 -0
  7. package/docs/editor.md +457 -0
  8. package/docs/excerpts.md +126 -0
  9. package/docs/flashcards.md +127 -0
  10. package/docs/getting-started.md +134 -0
  11. package/docs/mcp.md +173 -0
  12. package/docs/plugin-basics.md +209 -0
  13. package/docs/providers/README.md +108 -0
  14. package/docs/providers/images.md +65 -0
  15. package/docs/providers/publish.md +144 -0
  16. package/docs/providers/sync.md +75 -0
  17. package/docs/providers/tts.md +62 -0
  18. package/docs/providers/upload.md +77 -0
  19. package/docs/providers/video.md +118 -0
  20. package/docs/release.md +130 -0
  21. package/docs/services.md +159 -0
  22. package/docs/site-data.md +90 -0
  23. package/docs/skills.md +129 -0
  24. package/docs/storage-and-network.md +62 -0
  25. package/docs/ui.md +335 -0
  26. package/docs/user-data.md +389 -0
  27. package/docs/whiteboard.md +290 -0
  28. package/package.js +1 -1
  29. package/package.json +1 -1
  30. package/publish-artifacts.js +1 -1
  31. package/publish-client.js +3 -1
  32. package/types/packages/plugins/src/categories.d.ts +3 -0
  33. package/types/packages/plugins/src/contract.d.ts +0 -2
  34. package/types/packages/plugins/src/index.d.ts +0 -1
  35. package/types/packages/plugins/src/package.d.ts +2 -0
  36. package/AI.md +0 -116
  37. package/API.md +0 -881
  38. package/BACKEND.md +0 -68
  39. package/DEVELOPMENT.md +0 -173
  40. package/EDITOR.md +0 -192
  41. package/FLASHCARDS.md +0 -92
  42. package/IMAGES.md +0 -16
  43. package/MCP.md +0 -118
  44. package/NOTIFICATIONS.md +0 -48
  45. package/PUBLISH-DATA.md +0 -48
  46. package/PUBLISH.md +0 -50
  47. package/SERVICES.md +0 -119
  48. package/SYNC.md +0 -37
  49. package/TTS.md +0 -20
  50. package/UPLOAD.md +0 -35
  51. package/VIDEO.md +0 -65
  52. package/WHITEBOARD.md +0 -115
  53. package/types/packages/plugins/src/skills.d.ts +0 -8
@@ -0,0 +1,127 @@
1
+ # 闪卡
2
+
3
+ 插件可以帮用户创建闪卡。有三种方式,从轻到重:
4
+
5
+ | 方式 | 适合 | 需要的权限 |
6
+ | ------------------------------- | ----------------------------------------- | ------------------------------------------------------- |
7
+ | [打开制卡窗口](#打开制卡窗口) | 让用户在 Tau 的制卡界面里确认、编辑后保存 | 不需要(AI 模式需要 `ai:flashcardGeneration`) |
8
+ | [直接创建](#直接创建卡组与闪卡) | 插件已经整理好了题目,直接写入 | `flashcards:read`、`flashcards:write` |
9
+ | [AI 生成](#ai-生成) | 从一段资料生成草稿,或生成后直接保存 | `ai:flashcardGeneration`,保存还需要 `flashcards:write` |
10
+
11
+ 在 manifest 中声明用到的权限:
12
+
13
+ ```json
14
+ { "permissions": ["flashcards:read", "flashcards:write", "ai:flashcardGeneration"] }
15
+ ```
16
+
17
+ AI 制卡沿用用户在 Tau 中为「闪卡制作」分配的模型,不需要插件另外配置。用量、额度与授权方式和 [AI](ai.md#使用-tau-已有的模型分配) 中的 `requestCapability` 相同,用户撤销授权会取消进行中的生成。
18
+
19
+ ## 打开制卡窗口
20
+
21
+ ```ts
22
+ const result = await context.flashcards.openCreate({
23
+ mode: "ai", // 默认 "manual":手动制卡
24
+ content: "牛顿第二定律:F = ma。",
25
+ title: "力学笔记",
26
+ });
27
+
28
+ // result.status:completed 或 cancelled
29
+ // result.cards:用户在窗口中已经保存的闪卡
30
+ ```
31
+
32
+ | 参数 | 说明 |
33
+ | ------------ | --------------------------------------------------------------------------------- |
34
+ | `mode` | `manual`(默认)预填题干和参考资料;`ai` 打开后自动开始生成,用户挑选、编辑后保存 |
35
+ | `content` | 资料内容,Markdown 字符串或编辑器节点 |
36
+ | `title` | 资料标题 |
37
+ | `deckId` | 可选。目标卡组;省略时由用户选择,也可以现场新建 |
38
+ | `references` | 可选。闪卡的来源,见下文 |
39
+
40
+ - 必须在用户操作(点击、按键)中调用。在定时器、后台任务里调用会被拒绝;也不要在 `activate` 中等待它。
41
+ - 返回的 Promise 在用户关闭窗口后才完成。用户取消时,`cards` 里仍包含取消前已经保存的闪卡,取消不会撤销它们。
42
+ - 同一个窗口同一时间只能打开一个制卡会话。
43
+ - 窗口里的保存由用户确认,所以不需要闪卡读写权限。
44
+
45
+ ### 来源
46
+
47
+ 不传 `references` 时,`content` 作为自定义参考资料随闪卡保存,`title` 是来源标题。传 `references: []` 表示不记录来源。
48
+
49
+ 资料来自 Tau 中已有的正文时,传入正文引用,而不是把正文复制一份:
50
+
51
+ ```ts
52
+ await context.flashcards.openCreate({
53
+ mode: "ai",
54
+ content: snapshot.markdown,
55
+ references: [{ refType: "content", contentRef: card.contentRef, title: card.title }],
56
+ });
57
+ ```
58
+
59
+ `contentRef` 来自 [`context.data`](user-data.md) 或 [`context.entities`](user-data.md#插件内容)。引用其他内容的正文时需要对应的读取权限,插件自己的内容不需要。引用已失效或没有权限时,保存会被拒绝。
60
+
61
+ ## 直接创建卡组与闪卡
62
+
63
+ ```ts
64
+ const decks = await context.flashcards.listDecks();
65
+ const deck = await context.flashcards.createDeck({ name: "力学", description: "基础概念" });
66
+
67
+ const card = await context.flashcards.createCard({
68
+ deckId: deck.id,
69
+ type: "qa",
70
+ prompt: [
71
+ { type: "paragraph", children: [{ type: "formatted", text: "牛顿第二定律的表达式是什么?" }] },
72
+ ],
73
+ data: { answer: "F = ma" },
74
+ tags: ["力学"],
75
+ difficulty: "foundation",
76
+ });
77
+ ```
78
+
79
+ | 闪卡字段 | 说明 |
80
+ | ------------ | ---------------------------------------------------------------------------------------------------------- |
81
+ | `deckId` | 所属卡组 |
82
+ | `type` | 题型:`qa`(问答)、`boolean`(判断)、`cloze`(填空)、`single-choice`(单选)、`multiple-choice`(多选) |
83
+ | `prompt` | 题干,编辑器节点 |
84
+ | `detail` | 可选。补充说明,编辑器节点 |
85
+ | `data` | 答案,结构随题型不同,见下表 |
86
+ | `tags` | 可选。标签 |
87
+ | `difficulty` | 可选。`foundation`、`standard`、`advanced` |
88
+ | `references` | 可选。来源,同上文 |
89
+
90
+ | 题型 | `data` 中的答案 |
91
+ | ---------------------------------- | -------------------------------------------------------------- |
92
+ | `qa` | `answer` |
93
+ | `boolean` | `booleanAnswer` |
94
+ | `cloze` | `blanks`:`[{ id, placeholder, answer, alternativeAnswers? }]` |
95
+ | `single-choice`、`multiple-choice` | `options`:`[{ id, label, isCorrect, explanation? }]` |
96
+
97
+ 所有题型都可以附带 `explanation`(解析)、`hint`(提示)和 `referenceNotes`(参考笔记)。
98
+
99
+ - 这些方法直接写入用户的数据,调用前确认这是用户想要的。
100
+ - 闪卡属于用户,卸载插件不会删除。接口不提供修改和删除已有闪卡的能力。
101
+ - 创建后,打开中的卡组、闪卡列表和计数会自动刷新。
102
+
103
+ ## AI 生成
104
+
105
+ ```ts
106
+ // 只生成草稿,插件可以先筛选,再逐张 createCard
107
+ const drafts = await context.flashcards.generate({ content: "需要学习的内容", title: "资料" });
108
+
109
+ // 生成后直接保存,跳过用户预览
110
+ const result = await context.flashcards.generateAndSave({
111
+ content: "需要学习的内容",
112
+ title: "资料",
113
+ deckId: deck.id,
114
+ });
115
+ // result.cards:保存成功的闪卡
116
+ // result.failed:保存失败的项,{ index, message }
117
+ ```
118
+
119
+ - `generate` 返回的草稿和 `createCard` 的参数结构相同,可以直接传给 `createCard`。
120
+ - `generateAndSave` 会绕过用户预览直接写入,只在用户明确要求时使用。
121
+ - `generateAndSave` 逐张保存,不是一个整体事务:部分成功、部分失败都有可能。**不要对整个调用重试**,否则已成功的闪卡会重复创建。需要精确重试时,用 `generate` 拿到草稿,再逐张 `createCard`。
122
+ - 取消或切换工作区会阻止后续写入,但已经写入的闪卡不会回滚。
123
+ - 用户没有为「闪卡制作」分配模型、未授权、额度不足或内容为空时,调用会明确失败。
124
+
125
+ ## 适用范围
126
+
127
+ 闪卡接口只在插件运行时中可用,编辑器块的渲染组件、博客中都不能使用。
@@ -0,0 +1,134 @@
1
+ # 快速开始
2
+
3
+ 这一篇带你跑通模板自带的计数器插件,再把它改成你自己的插件。全程大约十分钟。
4
+
5
+ ## 准备环境
6
+
7
+ - Node.js 22 或更高版本
8
+ - pnpm 11.9.0(模板通过 `packageManager` 字段锁定,用 `corepack enable` 可以自动获得对应版本)
9
+ - Tau 桌面客户端 0.59.0 或更高版本
10
+
11
+ ## 跑通示例
12
+
13
+ 在 GitHub 上打开 [tau-plugin-template](https://github.com/LastKnightCoder/tau-plugin-template),点击 **Use this template** 创建你自己的仓库,克隆到本地后执行:
14
+
15
+ ```sh
16
+ pnpm install
17
+ pnpm build
18
+ pnpm install:local
19
+ ```
20
+
21
+ - `pnpm build` 检查类型并打包,生成 `dist/<插件ID>-<版本>.zip`。
22
+ - `pnpm install:local` 把这个 ZIP 解压到 Tau 的插件目录 `~/.editor/plugins/<插件ID>/`。
23
+
24
+ 然后在 Tau 中:
25
+
26
+ 1. 打开侧边栏的「插件」页,点右上角的「⋯ → 重新扫描」,列表里会出现「计数器示例」。
27
+ 2. 启用它。
28
+ 3. 点击侧边栏「日常」分组下的「计数器」,或按 Ctrl+K 搜索「打开计数器」。
29
+ 4. 点「增加并保存」。重启 Tau 后再打开,计数还在。
30
+
31
+ 到这里,你已经完成了一次完整的开发、安装和运行。
32
+
33
+ ## 模板里有什么
34
+
35
+ | 文件 | 作用 |
36
+ | --------------------------------- | --------------------------------------------------------- |
37
+ | `manifest.json` | 插件的身份证:ID、名称、版本、需要的权限等 |
38
+ | `src/index.tsx` | 插件入口,计数器的页面、设置和命令都在这里 |
39
+ | `src/styles.css` | 插件样式,构建时打包成 `main.css` |
40
+ | `src/CounterIcon.tsx` | 侧边栏图标示例 |
41
+ | `README.md` | 给使用者看的插件说明,显示在插件详情的「说明」里 |
42
+ | `CHANGELOG.md`(需要时自己创建) | 给使用者看的更新日志,显示在插件详情的「更新日志」里 |
43
+ | `build.mjs` / `install-local.mjs` | 打包和本地安装脚本,一般不用改 |
44
+ | `.github/workflows/release.yml` | 推送版本 tag 后自动构建、发布到 GitHub Release 和插件中心 |
45
+ | `AGENTS.md` | 给 AI 编程助手的项目指引 |
46
+ | `docs/` | 本开发文档,不会打进插件包 |
47
+
48
+ 打开 `src/index.tsx` 看一下,核心只有这么多:
49
+
50
+ ```tsx
51
+ export default definePlugin({
52
+ ...__PLUGIN_META__,
53
+ create: () => ({
54
+ activate(context) {
55
+ const page = context.pages.register({ id: "counter", title: "计数器", component: Page });
56
+ context.sidebar.registerItem({
57
+ id: "counter",
58
+ title: "计数器",
59
+ icon: CounterIcon,
60
+ group: SIDEBAR_GROUPS.daily,
61
+ page,
62
+ });
63
+ context.settings.register({ id: "preferences", title: "计数设置", component: Settings });
64
+ context.commands.register({
65
+ id: "open",
66
+ title: "打开计数器",
67
+ run: async () => {
68
+ await page.open();
69
+ return null;
70
+ },
71
+ });
72
+ },
73
+ }),
74
+ });
75
+ ```
76
+
77
+ 插件启用时,Tau 调用 `activate`,你在里面注册页面、入口、设置和命令;插件停用时,这些注册会被自动撤销。具体机制见[插件的组成](plugin-basics.md)。
78
+
79
+ ## 改成你自己的插件
80
+
81
+ 1. **改身份**。编辑 `manifest.json`:
82
+ - `id` 换成你自己的唯一名称,例如 `yourname.water-tracker`。ID 发布后不要再改,改了会被当成另一个插件,用户已保存的数据也找不回来。
83
+ - `name`、`description` 改成插件的名称和一句话简介(160 字以内)。
84
+ - `version` 从 `1.0.0` 开始即可。
85
+ - `categories`、`keywords` 决定插件在插件中心的分类和搜索词,含义见[插件的组成 · manifest](plugin-basics.md#manifest)。
86
+ 2. **改项目名**。把 `package.json` 的 `name` 改成你的项目名,`version` 与 manifest 保持一致。
87
+ 3. **写功能**。在 `src/index.tsx` 里实现你的功能,样式写在 `src/styles.css`。
88
+ 4. **写说明**。把 `README.md` 改成给使用者看的介绍:插件能做什么、怎么开始用、常见问题。不要写开发过程或内部实现,它会原样显示给用户。
89
+ 5. **构建并安装**,按下一节的方法在 Tau 中测试。
90
+
91
+ ## 日常开发循环
92
+
93
+ 改完代码后:
94
+
95
+ ```sh
96
+ pnpm build
97
+ ```
98
+
99
+ `pnpm install:local` 只负责首次安装,目标目录已存在时会拒绝覆盖,以免误删。更新已安装的开发版本时:
100
+
101
+ 1. 在 Tau 的插件页停用你的插件。
102
+ 2. 删除旧目录 `~/.editor/plugins/<插件ID>/`,再执行 `pnpm install:local`。
103
+ 3. 在插件页点「⋯ → 重新扫描」,然后重新启用。
104
+
105
+ 插件保存的数据在笔记库里,不在这个目录中,删除目录不会丢数据。
106
+
107
+ 带有[编辑器扩展](editor.md)或[白板扩展](whiteboard.md)的插件在 Tau 启动时装配,更新代码后需要完全退出并重新打开 Tau 才会生效。
108
+
109
+ ## 常用命令
110
+
111
+ | 命令 | 作用 |
112
+ | -------------------- | ------------------------------- |
113
+ | `pnpm check` | 只检查 TypeScript 类型 |
114
+ | `pnpm build` | 检查类型并生成插件 ZIP |
115
+ | `pnpm install:local` | 把最近构建的 ZIP 首次安装到 Tau |
116
+
117
+ 依赖用 pnpm 管理,添加依赖执行 `pnpm add <包名>`,并提交 `pnpm-lock.yaml`。普通 npm 包会被打进插件,React、`@tau/plugins`、`@tau/ui` 由 Tau 提供,不要改动 `build.mjs` 里的 external 列表,也不要升级模板锁定的 React 版本。
118
+
119
+ ## 排错
120
+
121
+ | 现象 | 处理 |
122
+ | ---------------------- | ------------------------------------------------------------------------------------------- |
123
+ | 插件列表里没有我的插件 | 点「重新扫描」;确认解压后的目录是 `~/.editor/plugins/<插件ID>/manifest.json`,没有多套一层 |
124
+ | 提示目标目录已存在 | 按上面「日常开发循环」先删除旧目录 |
125
+ | 改了代码没有变化 | 确认重新构建并安装了;编辑器、白板插件需要重启 Tau |
126
+ | 启用失败 | 看插件卡片上的错误信息,通常是 `activate` 里抛了异常 |
127
+ | 提示需要重启 | 完全退出 Tau 再打开 |
128
+
129
+ 类型检查通过只说明代码能编译。每次改完,至少在 Tau 中试一遍:启用、使用功能、保存数据、停用、重启后再用。
130
+
131
+ ## 下一步
132
+
133
+ - 读[插件的组成](plugin-basics.md),理解生命周期和 `context`。
134
+ - 回到[文档首页](README.md),按你要做的功能找到对应文档。
package/docs/mcp.md ADDED
@@ -0,0 +1,173 @@
1
+ # MCP 工具
2
+
3
+ 通过 `context.mcp.register`,你可以给 Tau 的 AI 助手提供工具。用户和 AI 对话时,AI 会按需调用这些工具,比如查询插件里的数据、在插件里执行操作。
4
+
5
+ 注册的工具会出现在 Tau 的 MCP 页面和聊天的快捷面板里,用户可以整组或逐个开关工具。插件不需要自己做管理界面。
6
+
7
+ 一个 MCP 有两种提供方式:
8
+
9
+ - **本地工具**:在插件里直接写工具函数,最简单。
10
+ - **外部服务器**:连接一个已有的 MCP 服务器,通过 HTTP 或本机进程。
11
+
12
+ ## 本地工具
13
+
14
+ ```ts
15
+ context.mcp.register({
16
+ id: "notes",
17
+ name: "我的笔记",
18
+ description: "读取本插件保存的笔记",
19
+ tools: [
20
+ {
21
+ name: "read_note",
22
+ description: "按键读取本插件中的一条笔记",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: { key: { type: "string", description: "笔记的键" } },
26
+ required: ["key"],
27
+ additionalProperties: false,
28
+ },
29
+ async execute(args, { signal }) {
30
+ if (typeof args.key !== "string") throw new Error("请提供笔记的键");
31
+ const note = await context.storage.get(args.key);
32
+ signal.throwIfAborted();
33
+ return { content: note === undefined ? "未找到笔记" : JSON.stringify(note) };
34
+ },
35
+ },
36
+ ],
37
+ });
38
+ ```
39
+
40
+ | MCP 字段 | 说明 |
41
+ | ------------- | ----------------------------------------- |
42
+ | `id` | 局部 ID,一个插件可以注册多个 MCP |
43
+ | `name` | 显示名称 |
44
+ | `description` | 可选。说明 |
45
+ | `tools` | 本地工具列表(与 `transport` 二选一) |
46
+ | `transport` | 外部服务器的连接方式(与 `tools` 二选一) |
47
+
48
+ | 工具字段 | 说明 |
49
+ | ------------- | ---------------------------------------------------------------------------------------- |
50
+ | `name` | 工具名,同一个 MCP 内唯一,规则同[局部 ID](plugin-basics.md#id-规则)。不要自己加插件前缀 |
51
+ | `description` | 工具说明。AI 根据它判断什么时候调用,要写清楚用途和适用场景 |
52
+ | `inputSchema` | 参数的 JSON Schema,顶层必须是 `type: "object"` |
53
+ | `execute` | 执行函数,收到参数和 `{ signal }`,返回 `{ content: string }` |
54
+
55
+ - `content` 是给 AI 读的文本。结构化数据用 `JSON.stringify` 转成字符串。
56
+ - 参数来自 AI,不一定符合 Schema,使用前要校验。
57
+ - 单次调用最长 60 秒。用户取消、关闭工具、插件停用时 `signal` 会中止,异步代码要响应它。取消无法撤销已经完成的写入或外部请求。
58
+ - 工具执行前,Tau 会按用户的设置请求确认。
59
+
60
+ ### 和页面上下文配合
61
+
62
+ 如果你的插件有页面,可以用[页面上下文](ui.md#向-ai-提供页面上下文)告诉 AI 用户正在看什么,再用 MCP 工具让 AI 对它执行操作。例如页面上下文提供当前书籍的 ID,工具 `add_note` 接收书籍 ID 和内容。
63
+
64
+ ## 外部服务器
65
+
66
+ ### HTTP
67
+
68
+ ```ts
69
+ context.mcp.register({
70
+ id: "remote",
71
+ name: "知识服务",
72
+ transport: { type: "streamable-http", url: "https://your-service.example/mcp" },
73
+ });
74
+ ```
75
+
76
+ `streamable-http` 支持可选的 `headers`。
77
+
78
+ ### 本机进程
79
+
80
+ 用 stdio 启动本机的 MCP 服务器进程,需要插件带有[本机后台](backend.md),并在 manifest 的 `backend` 中声明要启动的程序:
81
+
82
+ ```json
83
+ {
84
+ "backend": {
85
+ "systemAccess": ["process"],
86
+ "tools": [{ "id": "mcp", "command": "node", "args": ["/path/to/your-server.mjs"] }]
87
+ }
88
+ }
89
+ ```
90
+
91
+ 这里只列出相关字段,`backend` 的完整写法见[本机后台](backend.md)。然后注册:
92
+
93
+ ```ts
94
+ context.mcp.register({
95
+ id: "local-server",
96
+ name: "本地 MCP 服务",
97
+ transport: { type: "stdio", command: "node", args: ["/path/to/your-server.mjs"] },
98
+ });
99
+ ```
100
+
101
+ - `stdio` 支持可选的 `args`、`env`、`cwd`。`cwd` 必须是绝对路径。
102
+ - 命令不经过 Shell 执行,参数按数组原样传递。
103
+ - 进程的环境变量只包含最小的系统集合和你显式传入的 `env`。
104
+ - 在 Windows 上,命令必须能解析到一个 `.exe` 文件。
105
+ - Tau 不会替用户安装 Node.js、Python 或其他依赖,启动前需要用户本机已经具备运行环境。在 README 中写清楚要求。
106
+
107
+ 外部服务器的连接配置、日志和重连,用户都可以在 MCP 页面管理。不要把密钥、令牌或你机器上的路径打进插件包,让用户在 MCP 页面里填写。
108
+
109
+ ## 生命周期
110
+
111
+ - 插件启用成功后,注册的 MCP 才会出现。插件停用、切换工作区或卸载时自动移除。
112
+ - 用户在 MCP 页面关闭某个 MCP,只关闭这一组 AI 工具,不影响插件的其他功能。用户的开关选择会一直保留,插件重新注册不会强行打开。
113
+ - `register` 返回的 `Disposable` 可以提前移除这个 MCP。
114
+
115
+ ## 自定义工具结果的展示
116
+
117
+ 默认情况下,聊天里的工具调用显示为通用的输入、输出。你可以为自己的工具注册一个 React 组件,把结果显示成卡片、图表等更易读的样子。
118
+
119
+ ### 1. 工具返回展示数据
120
+
121
+ 本地工具除了 `content`,还可以返回 `render`:
122
+
123
+ ```ts
124
+ async execute(args) {
125
+ const books = await searchBooks(String(args.query));
126
+ return {
127
+ content: JSON.stringify(books),
128
+ render: { version: 1, data: { books } },
129
+ };
130
+ }
131
+ ```
132
+
133
+ - `content` 仍然是给 AI 读的文本,也是默认展示的内容。
134
+ - `render.data` 是给你的组件用的展示数据,必须是 JSON,会随聊天记录保存。
135
+ - `render.version` 是正整数。以后数据结构变化时提高版本,组件按版本处理旧数据。
136
+
137
+ ### 2. 注册组件
138
+
139
+ 在 `async activate` 中,先注册 MCP,再注册组件:
140
+
141
+ ```tsx
142
+ await context.chat.registerToolRenderer({
143
+ mcpId: "books",
144
+ toolName: "search_books",
145
+ component: ({ execution, data, dataVersion, renderDefault }) => {
146
+ if (execution.status !== "success" || dataVersion !== 1) return renderDefault();
147
+ return <BookList books={(data as { books: Book[] }).books} />;
148
+ },
149
+ });
150
+ ```
151
+
152
+ | 字段 | 说明 |
153
+ | ----------- | ---------------------------- |
154
+ | `mcpId` | 本插件注册 MCP 时的 `id` |
155
+ | `toolName` | 工具的原始名称,不加任何前缀 |
156
+ | `component` | 渲染组件 |
157
+
158
+ 组件收到的属性:
159
+
160
+ | 属性 | 说明 |
161
+ | -------------------------------- | ------------------------------------------------------------------ |
162
+ | `execution` | 这次调用的状态和结果,`status` 为 `success` 时 `output` 是结果文本 |
163
+ | `data`、`dataVersion` | 工具返回的 `render.data` 和 `render.version`,没有时为 `null` |
164
+ | `parsedInput`、`inputParseError` | 解析后的调用参数,解析失败时的错误 |
165
+ | `isDark` | 当前是否为暗色主题 |
166
+ | `renderDefault()` | 渲染默认的通用展示 |
167
+
168
+ - 只能为本插件的工具注册,包括本地工具和本插件外部服务器提供的工具。外部服务器的工具没有 `render`,组件从 `execution.output` 读取结果。
169
+ - 遇到不认识的 `dataVersion`、失败或进行中的状态,调用 `renderDefault()`。
170
+ - 组件只负责展示,不要在渲染或挂载时自动执行工具。
171
+ - 用 `@tau/ui` 组件和主题变量编写样式,与聊天界面保持一致。
172
+ - 插件停用后,历史消息回到通用展示;重新启用后恢复。
173
+ - `registerToolRenderer` 返回 `Promise<Disposable>`,注册失败时抛出异常。插件停用时自动注销。
@@ -0,0 +1,209 @@
1
+ # 插件的组成
2
+
3
+ 一个 Tau 插件由以下几部分组成:
4
+
5
+ - **manifest.json**:插件是谁、需要什么。Tau 不运行代码就能读到它。
6
+ - **入口模块**(构建后是 `main.js`):默认导出一个插件定义,包含静态声明和运行时逻辑。
7
+ - **样式**(构建后是 `main.css`,可选)。
8
+ - **Skill 目录**(`skills/<id>/`,可选):给 AI 看的使用说明,见 [Skill](skills.md)。
9
+
10
+ 这一篇依次讲 manifest 和入口,以及贯穿所有能力的几条规则:生命周期、`context`、ID、取消与清理。
11
+
12
+ ## manifest
13
+
14
+ 模板的 `manifest.json`:
15
+
16
+ ```json
17
+ {
18
+ "formatVersion": 1,
19
+ "id": "example.external-counter",
20
+ "name": "计数器示例",
21
+ "description": "在侧边栏记录并保存计数。",
22
+ "categories": ["page"],
23
+ "keywords": ["计数", "打卡"],
24
+ "version": "1.0.0",
25
+ "minAppVersion": "0.59.0",
26
+ "apiVersion": 1,
27
+ "platform": "desktop",
28
+ "format": "cjs",
29
+ "main": "main.js"
30
+ }
31
+ ```
32
+
33
+ ### 需要你填写的字段
34
+
35
+ | 字段 | 说明 |
36
+ | --------------- | --------------------------------------------------------------------------------------------------------- |
37
+ | `id` | 插件的唯一标识,由小写字母、数字组成,用点或连字符分段,例如 `yourname.counter`。发布后不要再改 |
38
+ | `name` | 插件名称,显示在插件页和插件中心 |
39
+ | `description` | 一句话简介,1 至 160 字 |
40
+ | `version` | 插件版本,格式为 `主版本.次版本.修订号`,可带预发布后缀,如 `1.2.0-beta.1` |
41
+ | `minAppVersion` | 需要的最低 Tau 版本,如 `0.59.0`。写完整版本号,不写 `>=`、`^` 之类的范围。客户端版本低于它时不会加载插件 |
42
+ | `categories` | 插件中心的分类,可多选,也可以不写,见下表 |
43
+ | `keywords` | 插件中心的搜索词,最多 5 个,每个不超过 20 字。例如 S3 插件可以写 `MinIO`、`R2` |
44
+ | `permissions` | 插件可能申请的用户数据权限,见[用户数据 · 权限](user-data.md#权限)和 [AI](ai.md) |
45
+ | `editor` | 声明了编辑器扩展时写 `true`,见[编辑器扩展](editor.md) |
46
+ | `whiteboard` | 声明了白板扩展时写 `true`,见[白板扩展](whiteboard.md) |
47
+ | `backend` | 需要本机后台时填写,同时把 `formatVersion` 改为 `2`,见[本机后台](backend.md) |
48
+
49
+ | 分类 | 适合的插件 |
50
+ | --------------- | ----------------------------- |
51
+ | `page` | 提供页面和侧边栏入口 |
52
+ | `editor` | 在文档中添加块 |
53
+ | `whiteboard` | 在白板中添加元素或工具 |
54
+ | `ai` | MCP 工具、AI 功能、图片生成 |
55
+ | `skill` | Skill 包,或附带 Skill 的插件 |
56
+ | `sync` | 同步存储 |
57
+ | `upload` | 图片与附件上传 |
58
+ | `voice` | 语音合成 |
59
+ | `video` | 在线视频来源 |
60
+ | `blog-publish` | 博客发布目标 |
61
+ | `blog-template` | 博客模板 |
62
+
63
+ 分类只影响插件中心的展示,不授予任何能力。只提供命令或普通设置的插件可以不写分类。
64
+
65
+ ### 保持模板值的字段
66
+
67
+ `formatVersion`、`apiVersion`、`platform`、`format`、`main` 保持模板里的值。`styles`、`skills` 和 `integrity`(文件摘要)由 `pnpm build` 自动写入构建产物,不要手写,也不要直接修改 ZIP 里的文件。
68
+
69
+ ## 入口与插件定义
70
+
71
+ 入口模块默认导出 `definePlugin(...)` 的结果。最小的插件长这样:
72
+
73
+ ```tsx
74
+ import { definePlugin } from "@tau/plugins";
75
+
76
+ declare const __PLUGIN_META__: { id: string; name: string; version: string };
77
+
78
+ export default definePlugin({
79
+ ...__PLUGIN_META__,
80
+ create: () => ({
81
+ activate(context) {
82
+ context.commands.register({
83
+ id: "hello",
84
+ title: "打个招呼",
85
+ run: () => {
86
+ context.notifications.send({ title: "你好" });
87
+ return null;
88
+ },
89
+ });
90
+ },
91
+ }),
92
+ });
93
+ ```
94
+
95
+ `__PLUGIN_META__` 由构建脚本从 manifest 注入,包含 `id`、`name`、`version`,这样插件定义和 manifest 始终一致。
96
+
97
+ 插件定义里的字段分两类:
98
+
99
+ | 字段 | 类型 | 说明 |
100
+ | ---------------------- | ---------- | --------------------------------------------------------------------- |
101
+ | `create` | 运行时逻辑 | 每次启用时调用,返回本次运行的实例。只有静态声明的插件可以省略 |
102
+ | `editorExtensions` | 静态声明 | 编辑器块,见[编辑器扩展](editor.md) |
103
+ | `editorCodecs` | 静态声明 | 编辑器块的 Markdown 转换,见[编辑器扩展](editor.md#markdown-导入导出) |
104
+ | `whiteboardExtensions` | 静态声明 | 白板元素与工具,见[白板扩展](whiteboard.md) |
105
+ | `ai` | 静态声明 | 插件自定义的 AI 功能,见 [AI](ai.md#自定义-ai-功能) |
106
+
107
+ **静态声明**直接写在插件定义上,Tau 读取插件包时就能拿到,不依赖 `activate`。例如编辑器块不仅在桌面端显示,也会出现在用户发布的博客里,那里根本不会运行 `activate`。**运行时逻辑**则写在 `activate` 里,通过 `context` 注册。
108
+
109
+ ## 生命周期
110
+
111
+ ```tsx
112
+ create: () => ({
113
+ async activate(context) {
114
+ // 注册功能,读取必要的配置
115
+ },
116
+ async deactivate() {
117
+ // 可选:额外清理
118
+ },
119
+ }),
120
+ ```
121
+
122
+ - **启用**:Tau 调用 `create()` 得到一个新实例,再调用 `activate(context)`。`activate` 完成后插件才算启用成功;它抛出异常则启用失败,已注册的内容全部撤销。
123
+ - **停用**:用户停用插件、切换工作区,或者 Tau 退出时,本次运行结束。通过 `context` 注册的命令、页面、设置等全部自动撤销,`context.signal` 被中止,然后调用 `deactivate`(如果有)。
124
+ - **再次启用**:重新调用 `create()` 和 `activate()`,拿到一个全新的 `context`。
125
+
126
+ 据此有几条规则:
127
+
128
+ - 运行状态放在 `create` 返回的闭包或 React 组件里,不放在模块顶层。模块只会加载一次,顶层变量会跨越多次启用残留下来。
129
+ - 需要跨启用、跨重启保留的数据,存进 [`context.storage`](storage-and-network.md)。
130
+ - `activate` 应尽快返回。不要在里面等待用户操作或永不结束的任务;长时间的工作在后台启动,并响应 `context.signal`。
131
+ - 不要依赖 `deactivate` 保存数据,它不保证在所有情况下都有机会执行完。数据应该在用户操作时就保存。
132
+ - 不要把 `context` 保存下来留到下次启用使用。旧的 `context` 失效后,调用它的方法会报错。
133
+
134
+ ## context
135
+
136
+ `activate` 收到的 `context` 是插件使用 Tau 能力的唯一入口。页面、设置组件也会通过 props 拿到同一个 `context`。
137
+
138
+ | 属性 | 用途 | 文档 |
139
+ | ----------------------------------------------------- | -------------------------------------------- | --------------------------------------------- |
140
+ | `pluginId`、`workspaceId` | 当前插件 ID 与工作区 ID(不要解析工作区 ID) | |
141
+ | `signal` | 本次运行结束时中止 | [取消与清理](#取消与清理) |
142
+ | `register` | 托管需要清理的资源 | [取消与清理](#取消与清理) |
143
+ | `commands` | 注册命令 | [界面](ui.md#命令) |
144
+ | `pages`、`sidebar` | 注册页面与侧边栏入口 | [界面](ui.md#页面) |
145
+ | `settings` | 注册设置区域 | [界面](ui.md#设置) |
146
+ | `notifications` | 发送通知 | [界面](ui.md#通知) |
147
+ | `storage` | 插件私有存储 | [存储与网络](storage-and-network.md) |
148
+ | `fetch` | 网络请求 | [存储与网络](storage-and-network.md#网络请求) |
149
+ | `permissions` | 查询与申请权限 | [用户数据](user-data.md#权限) |
150
+ | `data`、`content`、`events` | 读写用户数据与正文、订阅变化 | [用户数据](user-data.md) |
151
+ | `app` | 选择与打开已有内容、导出与演示 | [用户数据](user-data.md#选择并打开已有内容) |
152
+ | `entities` | 插件自己的内容 | [用户数据](user-data.md#插件内容) |
153
+ | `excerpts`、`materials` | 摘录与材料动作 | [摘录](excerpts.md) |
154
+ | `ai` | 调用大模型 | [AI](ai.md) |
155
+ | `mcp`、`chat` | 给 AI 提供工具、自定义工具结果的展示 | [MCP 工具](mcp.md) |
156
+ | `flashcards` | 闪卡 | [闪卡](flashcards.md) |
157
+ | `services` | 编译与转换服务 | [编译与转换服务](services.md) |
158
+ | `backend` | 调用本机后台 | [本机后台](backend.md) |
159
+ | `sync`、`upload`、`tts`、`images`、`video`、`publish` | 注册服务提供方 | [服务提供方](providers/README.md) |
160
+
161
+ ## ID 规则
162
+
163
+ 命令、页面、设置、侧边栏条目、MCP 等注册时填写的 `id` 都是**插件内的局部 ID**:Tau 会自动加上插件命名空间,你不需要也不应该自己拼接插件 ID。不同插件可以使用相同的局部 ID,互不影响。
164
+
165
+ 局部 ID 和存储的 key 都是 1 至 128 个字符,以字母或数字开头,后面可以是字母、数字、点、横线和下划线。同一类注册里重复的 ID 会报错。
166
+
167
+ ## 取消与清理
168
+
169
+ **通过 `context` 注册的东西都会自动清理**,包括命令、页面、设置、侧边栏、MCP、通知、事件订阅等。注册方法返回的 `Disposable` 可以用来提前撤销:
170
+
171
+ ```ts
172
+ const command = context.commands.register({ id: "temp", title: "临时命令", run: () => null });
173
+ // 不再需要时
174
+ await command.dispose();
175
+ ```
176
+
177
+ **你自己创建的资源**,如计时器、DOM 事件监听、第三方库的连接,交给 `context.register` 托管,停用时按注册顺序的逆序清理:
178
+
179
+ ```ts
180
+ const timer = setInterval(refresh, 60_000);
181
+ context.register({ dispose: () => clearInterval(timer) });
182
+
183
+ const onOnline = () => refresh();
184
+ window.addEventListener("online", onOnline);
185
+ context.register({ dispose: () => window.removeEventListener("online", onOnline) });
186
+ ```
187
+
188
+ React 组件内的订阅照常在 `useEffect` 的清理函数里处理。
189
+
190
+ **异步任务**要响应 `context.signal`。网络请求用 [`context.fetch`](storage-and-network.md#网络请求),它会随插件停用自动取消。异步结果写回界面前检查 `context.signal.aborted`,避免切换工作区后旧请求的结果出现在新界面上:
191
+
192
+ ```ts
193
+ const result = await loadSomething();
194
+ if (context.signal.aborted) return;
195
+ setState(result);
196
+ ```
197
+
198
+ ## 错误处理
199
+
200
+ - 预期内的失败(网络错误、配置缺失、用户拒绝授权)要在界面上给出可读的提示,并提供重试或修复的入口。
201
+ - 命令的 `run` 抛出异常表示执行失败。需要告诉用户失败原因时,自己发送[通知](ui.md#通知)或在页面上显示。
202
+ - 读取存储、网络响应、用户配置等外部数据时,先校验类型再使用,不要假定数据一定符合预期。
203
+ - 不要在遇到额度不足、拒绝授权这类错误后自动循环重试。
204
+
205
+ ## 运行环境
206
+
207
+ 插件代码与 Tau 运行在同一个浏览器环境中,可以使用标准的 Web API 和浏览器兼容的 npm 包,但不能导入 Node.js 内置模块(`fs`、`path`、`child_process` 等)。需要 Node.js 能力时,使用[本机后台](backend.md)。
208
+
209
+ 插件中心会审核提交的版本,但插件与 Tau 之间没有安全沙箱。只申请功能确实需要的权限,不要把密钥、令牌或你机器上的路径写进源码和插件包。