@xiongtao.xt/tau-plugins 0.31.1 → 0.33.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 (66) hide show
  1. package/CHANGELOG.md +20 -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 +590 -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 +336 -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 +38 -1
  31. package/types/packages/db-schema/content-resources.d.ts +76 -0
  32. package/types/packages/editor/contracts.d.ts +1 -1
  33. package/types/packages/editor/source-contracts.d.ts +41 -1
  34. package/types/packages/editor/src/core/api/extension.d.ts +15 -0
  35. package/types/packages/editor/src/core/api/index.d.ts +5 -1
  36. package/types/packages/editor/src/core/api/resources.d.ts +6 -0
  37. package/types/packages/editor/src/core/components/ElementFrame/index.d.ts +25 -0
  38. package/types/packages/editor/src/core/utils/blockKeyTarget.d.ts +9 -0
  39. package/types/packages/plugins/src/categories.d.ts +3 -0
  40. package/types/packages/plugins/src/contract.d.ts +0 -2
  41. package/types/packages/plugins/src/editor.d.ts +1 -0
  42. package/types/packages/plugins/src/element.d.ts +11 -0
  43. package/types/packages/plugins/src/index.d.ts +4 -1
  44. package/types/packages/plugins/src/media.d.ts +69 -0
  45. package/types/packages/plugins/src/package.d.ts +2 -0
  46. package/types/packages/ui/src/index.d.ts +1 -0
  47. package/types/packages/ui/src/primitives/Sortable.d.ts +32 -0
  48. package/types/packages/ui/src/primitives/Tabs.d.ts +1 -3
  49. package/AI.md +0 -116
  50. package/API.md +0 -881
  51. package/BACKEND.md +0 -68
  52. package/DEVELOPMENT.md +0 -173
  53. package/EDITOR.md +0 -192
  54. package/FLASHCARDS.md +0 -92
  55. package/IMAGES.md +0 -16
  56. package/MCP.md +0 -118
  57. package/NOTIFICATIONS.md +0 -48
  58. package/PUBLISH-DATA.md +0 -48
  59. package/PUBLISH.md +0 -50
  60. package/SERVICES.md +0 -119
  61. package/SYNC.md +0 -37
  62. package/TTS.md +0 -20
  63. package/UPLOAD.md +0 -35
  64. package/VIDEO.md +0 -65
  65. package/WHITEBOARD.md +0 -115
  66. package/types/packages/plugins/src/skills.d.ts +0 -8
@@ -0,0 +1,126 @@
1
+ # 摘录
2
+
3
+ 如果你的插件从外部服务读取划线、批注或收藏(读书笔记、网页高亮、文献批注等),可以把它们写成 Tau 的**摘录**。写入后,用户能在全局搜索和 AI 对话中找到它们,在文档中引用,也能把它们转为卡片或问题。
4
+
5
+ 摘录属于用户:插件停用或卸载后,已经写入的摘录和引用都会保留。
6
+
7
+ ## 注册提供方
8
+
9
+ 写入摘录之前,先在 `activate` 中注册一次提供方:
10
+
11
+ ```ts
12
+ const bookPage = context.pages.register({
13
+ id: "book",
14
+ title: "书籍",
15
+ component: BookPage,
16
+ match: "params",
17
+ });
18
+
19
+ context.excerpts.register({
20
+ label: "我的读书笔记",
21
+ open: ({ sourceKey, externalId }) => bookPage.open({ bookId: sourceKey, noteId: externalId }),
22
+ });
23
+ ```
24
+
25
+ | 字段 | 说明 |
26
+ | ------- | ------------------------------------------------------------------------------------------ |
27
+ | `label` | 摘录来源的显示名称,1 至 100 字。插件停用后仍用它显示来源 |
28
+ | `open` | 可选。用户点「打开原处」时调用,通常打开插件自己的页面并定位;省略时打开摘录或来源的 `url` |
29
+
30
+ 每次启用只能注册一次,没有注册就写入会报错。
31
+
32
+ ## 写入
33
+
34
+ 摘录按**来源**分组写入,一个来源可以是一本书、一个网页或一篇文献。写入有两种方式:
35
+
36
+ ```ts
37
+ const source = { key: "book-42", title: "设计心理学", url: "https://example.com/books/42" };
38
+
39
+ // 全量:以这批条目为准,不在其中的已有条目会被移除
40
+ await context.excerpts.replace(source, [
41
+ {
42
+ externalId: "n1",
43
+ quote: "好的设计是让人一眼就知道怎么用。",
44
+ location: "第 1 章",
45
+ time: 1760000000000,
46
+ },
47
+ { externalId: "n2", quote: "示能决定了可能的操作。", note: "和交互设计课的观点一致" },
48
+ ]);
49
+
50
+ // 增量:只新增、更新或移除指定条目
51
+ await context.excerpts.patch(source, {
52
+ upsert: [{ externalId: "n3", quote: "反馈要及时。" }],
53
+ remove: ["n1"],
54
+ });
55
+ ```
56
+
57
+ 外部服务能一次给出某个来源的全部条目时用 `replace`,只拿到变化时用 `patch`。两者都返回 `{ inserted, updated, deleted }`。`replace` 传入空数组表示整个来源已经不存在。
58
+
59
+ | 来源字段 | 说明 |
60
+ | -------- | ------------------------------------------------------- |
61
+ | `key` | 来源在本插件内的稳定标识,必填,最长 512 字 |
62
+ | `title` | 来源标题,最长 500 字 |
63
+ | `url` | 可选。http(s) 地址,条目没有自己的 `url` 时用它打开原处 |
64
+
65
+ | 条目字段 | 说明 |
66
+ | ------------ | ------------------------------------------------------------------------------- |
67
+ | `externalId` | 条目在本插件内的唯一标识,必填,最长 512 字 |
68
+ | `quote` | 摘录原文,必填,最长 20000 字 |
69
+ | `note` | 可选。用户写的想法或批注 |
70
+ | `location` | 可选。给人看的位置,如「第 3 章 · 标题」「第 12 页」,最长 500 字 |
71
+ | `url` | 可选。条目级的 http(s) 深链,优先于来源的 `url` |
72
+ | `anchor` | 可选。插件自己的定位数据,JSON 对象,序列化后不超过 8000 字;Tau 只保存,不解读 |
73
+ | `color` | 可选。划线颜色,最长 50 字 |
74
+ | `time` | 可选。在原服务中的创建时间(毫秒时间戳) |
75
+
76
+ ### externalId 要稳定
77
+
78
+ Tau 中对一条摘录的引用,靠 `externalId` 找到它。只要 `externalId` 不变,引用就一直有效。
79
+
80
+ - 用外部数据里稳定的标识生成 `externalId`,例如原服务的笔记 ID。改变生成规则,会让旧摘录被移除、用户的引用失效。
81
+ - 被移除的摘录不会真正删除:原文仍然保留,引用仍然能显示。以后同一个 `externalId` 再次写入时,它会恢复。
82
+
83
+ ### 限制
84
+
85
+ - 单次写入最多 20000 条。同一批中 `externalId` 不能重复,同一次 `patch` 不能同时写入和移除同一条。
86
+ - 任何一条不合法时,整批拒绝。
87
+ - 外部服务分页返回时,确认拿到完整列表后再调用 `replace`。用不完整的结果覆盖,会误移除条目。
88
+
89
+ ## 读回
90
+
91
+ ```ts
92
+ const sources = await context.excerpts.sources(); // [{ key, title, count }]
93
+ const entries = await context.excerpts.items("book-42");
94
+ ```
95
+
96
+ - `sources()` 列出本插件写入过的来源,`count` 是未移除的条目数。
97
+ - `items(sourceKey)` 返回该来源的全部条目,包括已移除的(`deleted: true`),可以用来离线展示,或与外部数据比对。
98
+ - 每个条目带有 `ref`,用于下面的材料动作。
99
+
100
+ ## 材料动作
101
+
102
+ 摘录等内容在 Tau 中统称**材料**。`context.materials.actions(ref)` 返回 Tau 当前能对这条材料执行的操作,例如「转为卡片」「转为问题」,适合放在插件自己的列表或详情页里:
103
+
104
+ ```tsx
105
+ import { useEffect, useState } from "react";
106
+ import { Button } from "@tau/ui";
107
+ import type { PluginContext, PluginMaterialAction, PluginMaterialRef } from "@tau/plugins";
108
+
109
+ function NoteActions({ context, target }: { context: PluginContext; target: PluginMaterialRef }) {
110
+ const [actions, setActions] = useState<PluginMaterialAction[]>([]);
111
+ useEffect(() => {
112
+ void context.materials.actions(target).then(list => {
113
+ if (!context.signal.aborted) setActions(list);
114
+ });
115
+ }, [context, target]);
116
+ return actions.map(action => (
117
+ <Button key={action.id} variant="ghost" onClick={() => void action.run()}>
118
+ {action.label}
119
+ </Button>
120
+ ));
121
+ }
122
+ ```
123
+
124
+ - `ref` 来自 `excerpts.items()` 返回的条目,原样传回即可,不要自己构造或解析。
125
+ - 直接用 `label` 作为按钮文字,不要按 `id` 写死列表,Tau 以后可能增加新的动作。
126
+ - 动作执行后由 Tau 给出成功或失败的提示,插件不需要再提示。
@@ -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>`,注册失败时抛出异常。插件停用时自动注销。