@plumeai/plugin-sdk 0.2.1 → 0.3.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.
- package/dist/client/ui/src/avatar/AgentAvatar.d.ts +2 -5
- package/dist/client/ui/src/i18n/messages/en.d.ts +3 -0
- package/dist/client/ui/src/i18n/messages/zh-CN.d.ts +3 -0
- package/dist/client/ui/src/image/AnnotateToolbar.d.ts +2 -4
- package/dist/client/ui/src/image/Annotator.d.ts +3 -2
- package/dist/client/ui/src/image/screenshot-geometry.d.ts +2 -2
- package/dist/client/ui/src/inputs/SearchField.d.ts +1 -1
- package/dist/client/ui/src/inputs/pickers.d.ts +1 -1
- package/dist/client/ui/src/layout/PageTopRow.d.ts +5 -6
- package/dist/client/ui/src/motion/RollingText.d.ts +1 -1
- package/dist/client/ui/src/motion/loaders.d.ts +4 -4
- package/dist/client/ui/src/overlay/Confirm.d.ts +1 -2
- package/dist/client/ui/src/overlay/ContextMenu.d.ts +2 -2
- package/dist/client/ui/src/overlay/Dialog.d.ts +5 -6
- package/dist/client/ui/src/overlay/Menu.d.ts +3 -4
- package/dist/client/ui/src/overlay/Popover.d.ts +1 -1
- package/dist/client/ui/src/overlay/Tooltip.d.ts +1 -1
- package/dist/client/ui/src/primitives/IconButton.d.ts +2 -2
- package/dist/client/ui/src/primitives/Text.d.ts +2 -2
- package/dist/client/ui/src/scroll/ScrollText.d.ts +1 -1
- package/dist/client/ui/src/scroll/Scroller.d.ts +2 -1
- package/dist/contract/src/diagnostics.d.ts +39 -1
- package/dist/contract/src/service.d.ts +13 -0
- package/dist/contract/src/ui.d.ts +72 -68
- package/package.json +1 -1
- package/skill/SKILL.md +86 -79
- package/skill/references/components.md +233 -1418
- package/skill/references/logic.md +240 -75
- package/skill/references/style-i18n.md +16 -12
- package/skill/references/ui.md +170 -123
package/skill/references/ui.md
CHANGED
|
@@ -24,86 +24,86 @@ export default {
|
|
|
24
24
|
| --- | --- |
|
|
25
25
|
| `ui.name` | 插件名 |
|
|
26
26
|
| `ui.openSession(id)` | 用户点击后打开已有会话,和点侧栏那一行一样,不自动发送;没有的会话抛错 |
|
|
27
|
-
| `ui.codeBlock(language, Component, { order, copyText })` |
|
|
28
|
-
| `ui.toolCard(tool, Component, { order, match, inline })` |
|
|
29
|
-
| `ui.
|
|
30
|
-
| `ui.
|
|
31
|
-
| `ui.
|
|
32
|
-
| `ui.
|
|
33
|
-
| `ui.
|
|
34
|
-
| `ui.skin({ name?, mode?, light, dark })` / `ui.skin(null)` | 更新皮肤或撤下自己的皮肤;name 与 mode 都提供时管理配色和深浅模式 |
|
|
27
|
+
| `ui.codeBlock(language, Component, { order, copyText })` | 按语言接管助手正文的围栏块,失败或停用显示源码,见「回复正文的代码块」 |
|
|
28
|
+
| `ui.toolCard(tool, Component, { order, match, inline })` | 给工具注册工具卡片,见「工具卡片」 |
|
|
29
|
+
| `ui.requestCard(kind, Component, { order })` | 给 `run.request` 画等人给数据的卡(问题、计划、表单),按 `request.kind` 认,见「等人给数据的卡」 |
|
|
30
|
+
| `ui.toolLine(tool, { verb, subject, file, note, command, job, group, apart, times, callOnly, icon }, { match? })` | 转录里怎么说自己的工具(动作、对象、文件、归类、怎么数、图标),画法还是通用卡片,见「工具在转录里的说法」 |
|
|
31
|
+
| `ui.panel(id, Component, options)` | 往面板区加一个面板,见「面板」 |
|
|
32
|
+
| `ui.page(id, Component, options)` | 往侧栏加一个页面,见「页面」 |
|
|
33
|
+
| `ui.skin({ name?, mode?, light, dark })` / `ui.skin(null)` | 更新皮肤或撤下自己的皮肤,见「皮肤」 |
|
|
35
34
|
| `ui.useTheme()` / `ui.setTheme(theme)` | 读、改应用的主题偏好(设置 › 外观的跟随系统、浅色、深色),见「皮肤」 |
|
|
36
|
-
| `ui.ChatBackgroundSettings` | 自包含的背景设置组件,可传 `title
|
|
37
|
-
| `ui.onSessionsChanged(listener)` |
|
|
38
|
-
| `ui.effect(dispose)` | 界面入口的生命周期清理,停用、重载或 apply
|
|
39
|
-
| `ui.composerItem(id, Component, { side, order })` |
|
|
40
|
-
| `ui.composerChip(id, Component, { side, order })` |
|
|
35
|
+
| `ui.ChatBackgroundSettings` | 自包含的背景设置组件,可传 `title`,见「皮肤」 |
|
|
36
|
+
| `ui.onSessionsChanged(listener)` | 订阅所有会话的已保存消息、摘要和删除变化,见「会话变化与停止按钮」 |
|
|
37
|
+
| `ui.effect(dispose)` | 界面入口的生命周期清理,停用、重载或 apply 失败时逆序执行,见「remote」 |
|
|
38
|
+
| `ui.composerItem(id, Component, { side, order })` | 输入框底下那排控件加一格,见「输入框里的一格」 |
|
|
39
|
+
| `ui.composerChip(id, Component, { side, order })` | 输入框上方、项目签那一行加一格,见「项目签那一行」 |
|
|
41
40
|
| `ui.composerAbove(id, Component, { order })` | 主输入框上方独占一行,收到 `{ session }`;返回 null 不占位,组件自己管理展开退场,不会浮到转录侧面 |
|
|
42
|
-
| `ui.conversationCard(id, Component, { order, lane })` |
|
|
43
|
-
| `ui.environmentSection(id, Component, { label, order
|
|
44
|
-
| `ui.
|
|
45
|
-
| `ui.
|
|
46
|
-
| `ui.
|
|
47
|
-
| `ui.
|
|
48
|
-
| `ui.
|
|
49
|
-
| `ui.
|
|
50
|
-
| `ui.
|
|
51
|
-
| `ui.
|
|
52
|
-
| `ui.
|
|
53
|
-
| `ui.
|
|
54
|
-
| `ui.
|
|
55
|
-
| `ui.
|
|
56
|
-
| `ui.
|
|
57
|
-
| `ui.
|
|
58
|
-
| `ui.
|
|
59
|
-
| `ui.
|
|
60
|
-
| `ui.
|
|
41
|
+
| `ui.conversationCard(id, Component, { order, lane })` | 跟着会话的一张卡片,见「跟着会话的卡片」 |
|
|
42
|
+
| `ui.environmentSection(id, Component, { label, order?, attention? })` | 往会话的环境卡片里加一节,见「环境卡片」 |
|
|
43
|
+
| `ui.useEnvironmentSections()` | 给画环境卡片的插件用的 React hook,拿到各插件登记的节,见「环境卡片」 |
|
|
44
|
+
| `ui.sessionToolbar(id, Component)` | 有会话的标题栏里放局部控件,见「标题栏和会话菜单」 |
|
|
45
|
+
| `ui.sessionMenuItem(id, { label, icon?, run })` | 会话操作菜单加一项,见「标题栏和会话菜单」 |
|
|
46
|
+
| `ui.agentDelivery(Component)` | 画本插件交回的结果在对话里的那一行,见「送达的那一行」 |
|
|
47
|
+
| `ui.mentions(provider)` | 往输入框的 `@` 菜单里供条目,见「`@` 菜单、`/` 命令列表」 |
|
|
48
|
+
| `ui.composerHint(session => text)` | 输入框空着时的灰字建议,按 Tab 填进去,见「输入框的灰字建议、回复里的标记」 |
|
|
49
|
+
| `ui.welcomeLine(locale => ({ text, source }))` | 换掉欢迎页问候下面的那一行,见「欢迎页的那一行」 |
|
|
50
|
+
| `ui.replyText(text => text)` | 画回复正文、复制回复前过一遍,拿掉插件让模型顺带写的标记,见「输入框的灰字建议、回复里的标记」 |
|
|
51
|
+
| `ui.sessionMark(Component)` | 侧栏会话行首空闲时的记号,见「侧栏的记号、删除确认框」 |
|
|
52
|
+
| `ui.deleteNote((sessionId, locale) => text)` | 删除确认框里补一句:删掉这个会话会连带什么、会留下什么,同上 |
|
|
53
|
+
| `ui.notify(message, level)` | 窗口顶上弹一条提示,过一会儿自己消失,见「提示、开面板、写输入框」 |
|
|
54
|
+
| `ui.openPanel(id, sessionId?, { tab, background }?)` / `ui.closePanel(id, sessionId?, tab?)` | 打开、关闭自己注册的面板,同上 |
|
|
55
|
+
| `ui.turnFooter(id, Component, { order })` | 最新结束的回复下面挂组件,见「回复下面的卡片和按钮」 |
|
|
56
|
+
| `ui.replyAction(id, Component, { order })` | 每一轮收尾那条回复底下的操作排里加一个按钮,同上 |
|
|
57
|
+
| `ui.terminalOpener(open)` | 为文件树等入口登记「在终端中打开」,见「文件、终端和系统打开」 |
|
|
58
|
+
| `ui.terminalRunner(run)` | 为回复里 shell 代码块的「在终端运行」登记处理器,同上 |
|
|
59
|
+
| `ui.openFile(path, sessionId)` | 在指定会话的文件面板打开文件,同上 |
|
|
60
|
+
| `ui.findFile(path, sessionId)` | 认出纯文字里印出来的路径指的那个文件,同上 |
|
|
61
|
+
| `ui.droppedPaths(transfer)` | 拖放带着的本机绝对路径,同上 |
|
|
62
|
+
| `ui.useFileMenu(root)` | 组件内调用的 hook,复用打开方式、在访达中显示和复制路径菜单,同上 |
|
|
61
63
|
| `ui.FileDiff` | `{ hunks, path?, showPath?, maxLines? }`,按文件语言高亮的差异视图,行号和横向滚动复用产品实现 |
|
|
62
64
|
| `ui.FilePreview` | `{ sessionId, path, contents }`,文件标题栏和只读预览;`contents` 是插件经 remote 读取的 `FileContents`,不授予额外文件读取权限 |
|
|
63
|
-
| `ui.documentViewer(extensions, Component)` |
|
|
64
|
-
| `ui.DocumentPreview` |
|
|
65
|
-
| `ui.
|
|
66
|
-
| `ui.
|
|
67
|
-
| `ui.
|
|
65
|
+
| `ui.documentViewer(extensions, Component)` | 注册只读文档预览,见「文件、终端和系统打开」 |
|
|
66
|
+
| `ui.DocumentPreview` | 文件面板和插件共用的文档展示,同上 |
|
|
67
|
+
| `ui.ProjectOpener` | 以 `{ path }` 显示目录的打开方式,同上 |
|
|
68
|
+
| `ui.quoteAction({ label, run })` | 在主会话划词菜单加一项,见「划词询问」 |
|
|
69
|
+
| `ui.quoteInPanel(panelId, sessionId, quote)` | 把划词引用追加到本插件面板的输入框,同上 |
|
|
70
|
+
| `ui.draftInSession(sessionId \| undefined, { text, images?, send?, replace? })` | 把字和图片写进输入框的草稿,见「提示、开面板、写输入框」 |
|
|
71
|
+
| `ui.openConversationDraft({ project?, cwd?, text })` | 人点击明确的按钮后,打开一个项目或目录的空白对话并写进草稿,同上 |
|
|
68
72
|
| `ui.revealToolCall(sessionId, toolCallId)` | 打开会话,转录滚到那次调用的工具卡片放在中间,外层带一下 `data-plume-flash`(插件的样式按它闪);只在主窗口里能用 |
|
|
69
|
-
| `ui.
|
|
70
|
-
| `ui.
|
|
71
|
-
| `ui.
|
|
72
|
-
| `ui.
|
|
73
|
-
| `ui.
|
|
74
|
-
| `ui.
|
|
75
|
-
| `ui.linkOpener({ label, icon?, web?, takesClicks?, file?, open })` | 回复里的网址、文件签多一种打开方式(内置浏览器),见下文 |
|
|
76
|
-
| `ui.queueAction({ label, icon?, run })` | 主输入框排队条上,每条排着的消息「更多」菜单里多一项(在侧边聊天中打开),见下文 |
|
|
77
|
-
| `ui.openConversationDraft({ project?, cwd?, text })` | 人点击明确的按钮后,打开已登记项目或绝对目录的空白对话,把 text 追加在目标已有草稿后;不发送、不落会话记录。project 与 cwd 二选一,目录由逻辑入口准备,失败不改变当前页面 |
|
|
73
|
+
| `ui.mediaUrl(name, thumb?)` | 会话媒体目录里一张图给 `<img src>` 的地址,见「图片」 |
|
|
74
|
+
| `ui.openImage(images, index, { from?, edit? })` | 用产品的看图器看几张图,同上 |
|
|
75
|
+
| `ui.captureWindow({ x, y, width, height })` | 截取当前插件界面所在窗口的可见矩形,同上 |
|
|
76
|
+
| `ui.saveImage(src)` / `ui.copyImage(src)` | 和看图器的「另存为」「复制」一样,同上 |
|
|
77
|
+
| `ui.linkOpener({ label, icon?, web?, takesClicks?, file?, open })` | 回复里的网址、文件签多一种打开方式(内置浏览器),见「网址、文件签的打开方式」 |
|
|
78
|
+
| `ui.queueAction({ label, icon?, run })` | 主输入框排队条上,每条排着的消息「更多」菜单里多一项(在侧边聊天中打开),见「排队消息拿到别处去发」 |
|
|
78
79
|
| `ui.openSettings(id)` | 打开本插件注册的、单独占导航一行(`page: "nav"`)的设置分区;不能跳到别的插件或未登记分区 |
|
|
79
80
|
| `ui.openPage(id)` | 人点了按钮时打开本插件注册的页面(输入框上方提示里的「查看」);不能开别的插件的页面 |
|
|
80
|
-
| `ui.pageBadge(id, { count, label } \| null)` |
|
|
81
|
+
| `ui.pageBadge(id, { count, label, attention? } \| null)` | 侧栏去处里本插件那一页的那一行右端挂一个数,见「页面」 |
|
|
81
82
|
| `ui.openExternal(url)` | 用户点击后在系统浏览器打开 HTTP(S) 链接,不接受带凭据的网址或其他协议 |
|
|
82
|
-
| `ui.openPath(path)` |
|
|
83
|
-
| `ui.revealPath(path)` |
|
|
84
|
-
| `ui.openLink(url, sessionId)` |
|
|
85
|
-
| `ui.settingsSection(id, Component, options)` |
|
|
86
|
-
| `ui.ModelSelect` |
|
|
87
|
-
| `ui.ModelControls` |
|
|
83
|
+
| `ui.openPath(path)` | 用户点击后把本机的文件交给默认程序、文件夹交给文件管理器打开,见「文件、终端和系统打开」 |
|
|
84
|
+
| `ui.revealPath(path)` | 用户点击后在系统的文件管理器里选中它(「在访达中显示」),同上 |
|
|
85
|
+
| `ui.openLink(url, sessionId)` | 用户点击后像回复里的网址那样打开,见「网址、文件签的打开方式」 |
|
|
86
|
+
| `ui.settingsSection(id, Component, options)` | 加一节设置,见「设置分区」 |
|
|
87
|
+
| `ui.ModelSelect` | 产品的模型下拉,见「复用产品的组件」 |
|
|
88
|
+
| `ui.ModelControls` | 输入框右下角那两枚(模型、思考档位),同上 |
|
|
88
89
|
| `ui.useModelDefaults()` | React hook:`{ defaultModel?, sessionModel?, thinking }`——新会话的默认模型、当前会话的模型、默认思考档位,「跟随」时写哪个模型名用它 |
|
|
89
90
|
| `ui.useProjects()` | React hook:侧栏里的项目 `{ id, name, path }[]`,随增删更新;几个项目可能登记在同一个目录上 |
|
|
90
91
|
| `ui.usePluginDisplayName()` | React hook:返回 `(plugin) => string \| undefined`,按插件名(`owner`、`fromPlugin` 里的那种)查人看到的显示名,和侧栏会话卡片、「来自…」同一份;停用、卸载了或还没读到时是 undefined,退回一个通用的词,不把 `plume.xxx` 这种插件名画给人 |
|
|
91
|
-
| `ui.screen` |
|
|
92
|
+
| `ui.screen` | 截屏的宿主能力:开截图浮层、全局快捷键、选文件夹,见「截屏」 |
|
|
92
93
|
| `ui.screenOverlay(Component)` | 截图浮层里画的组件 |
|
|
93
94
|
| `ui.pinnedShot(Component)` | 钉在桌面上的截图窗口里画的组件 |
|
|
94
|
-
| `ui.
|
|
95
|
-
| `ui.ComposerSend` | 产品的发送/停止按钮,收到 `{ running, disabled?, tip?, onSend, onStop }`;签名与使用见下文。经 `ui` 使用,不从 `@plume/ui` 导入 |
|
|
95
|
+
| `ui.ComposerSend` | 产品的发送/停止按钮,见「会话变化与停止按钮」 |
|
|
96
96
|
| `ui.useSession(sessionId)` | React hook:一个会话的视图(摘要、消息、草稿、进度、审批),随它的事件更新;还没读到是 undefined |
|
|
97
|
-
| `ui.useSessionActivity(sessionId)` | React hook
|
|
98
|
-
| `ui.listCommands({ cwd? })` | 输入框 `/`
|
|
99
|
-
| `ui.Conversation` |
|
|
100
|
-
| `ui.Markdown` |
|
|
101
|
-
| `ui.remote<T>()` | 调自己逻辑入口经 `ctx.serve`
|
|
97
|
+
| `ui.useSessionActivity(sessionId)` | React hook:侧栏行首给这个会话的记号,见「会话变化与停止按钮」 |
|
|
98
|
+
| `ui.listCommands({ cwd? })` | 输入框 `/` 菜单此刻有的命令,见「`@` 菜单、`/` 命令列表」 |
|
|
99
|
+
| `ui.Conversation` | 产品的对话视图组件:画一个会话(插件自己的子会话、独立会话),可选底下一个输入框,见「画一个子会话」 |
|
|
100
|
+
| `ui.Markdown` | 复用产品的正文渲染,见「复用产品的组件」 |
|
|
101
|
+
| `ui.remote<T>()` | 调自己逻辑入口经 `ctx.serve` 开的对象,见「remote」 |
|
|
102
102
|
| `ui.provide(name, service)` / `ui.useService(name)` | 界面入口之间的服务:一方提供,别的插件的组件里按名字拿到(没人提供是 undefined),见下文「服务」 |
|
|
103
103
|
|
|
104
104
|
## 会话变化与停止按钮
|
|
105
105
|
|
|
106
|
-
`SessionChange` 是 `{ sessionId, type: "messages" | "summary" | "deleted", tools?: string[] }`。`messages` 在消息保存或撤回后通知;工具结果带 `tools: [toolName]`,其他消息不带。`summary` 通知运行状态等摘要变化,`deleted` 通知删除。监听只用于刷新界面数据,不是模型流式增量;通过 `ui.useSession` 或自己的 remote 重新读取所需内容。订阅例子:
|
|
106
|
+
`ui.onSessionsChanged(listener)` 订阅所有会话的已保存消息、摘要和删除变化,包含当前未打开的会话;返回取消订阅函数,组件 effect 结束时调用,插件停用时也会撤下。事件 `SessionChange` 是 `{ sessionId, type: "messages" | "summary" | "deleted", tools?: string[] }`。`messages` 在消息保存或撤回后通知;工具结果带 `tools: [toolName]`,其他消息不带。`summary` 通知运行状态等摘要变化,`deleted` 通知删除。监听只用于刷新界面数据,不是模型流式增量;通过 `ui.useSession` 或自己的 remote 重新读取所需内容。订阅例子:
|
|
107
107
|
|
|
108
108
|
```tsx
|
|
109
109
|
useEffect(() => ui.onSessionsChanged((event) => {
|
|
@@ -111,20 +111,15 @@ useEffect(() => ui.onSessionsChanged((event) => {
|
|
|
111
111
|
}), [refresh]);
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
`ui.
|
|
114
|
+
`ui.useSessionActivity(sessionId)` 是侧栏行首给这个会话的记号:`"waiting"`(它或子会话在等批准)、`"done"`、`"failed"`(人没看着时跑完、没跑完,人打开看过就清掉),没有记号是 undefined;只在内存里,重开应用都算看过。卡片上要「看过就不再提」时用它。
|
|
115
|
+
|
|
116
|
+
`ui.ComposerSend` 是产品的发送/停止按钮,收到 `{ sessionId, running, disabled?, tip?, onSend }`,经 `ui` 使用,不从 `@plume/ui` 导入。`running` 决定显示发送还是停止;`disabled` 只禁用发送,运行中始终保留停止入口,`tip` 只覆盖发送时的提示。跑着时人按下它,宿主停下 `sessionId` 这一轮,和主输入框的停止键一样,出错时弹提示条:
|
|
115
117
|
|
|
116
118
|
```tsx
|
|
117
|
-
<ui.ComposerSend
|
|
118
|
-
running={running}
|
|
119
|
-
disabled={!canSend}
|
|
120
|
-
onSend={send}
|
|
121
|
-
onStop={(event) => {
|
|
122
|
-
void ui.stopSession(sessionId, event).catch((error) => ui.notify(String(error), "error"));
|
|
123
|
-
}}
|
|
124
|
-
/>
|
|
119
|
+
<ui.ComposerSend sessionId={sessionId} running={running} disabled={!canSend} onSend={send} />
|
|
125
120
|
```
|
|
126
121
|
|
|
127
|
-
|
|
122
|
+
停止只经这枚按钮、只在人按下时发生:插件没有直接停会话的入口,不能放进定时器、订阅回调或后台完成逻辑。
|
|
128
123
|
|
|
129
124
|
## 回复正文的代码块
|
|
130
125
|
|
|
@@ -163,7 +158,7 @@ ui.toolLine("symbol", {
|
|
|
163
158
|
|
|
164
159
|
- 一次调用说成「动作 对象」(「查找符号 runAgent」),一段活里几次合在一起数个数(「查找符号 3 个」)。`verb` 写成事后描述的动词短语,和内置的「读取文件」「搜索内容」一个口吻。
|
|
165
160
|
- `subject` 收到的参数是模型给的、没校验过:取不出就返回 undefined,只说动作;抛错也按没有对象处理。
|
|
166
|
-
-
|
|
161
|
+
- 工具定义上的 `kind`(`ToolDefinition.kind`,宿主把工具表带给界面):`view` 读文件、`list` 列目录、`find` 按名字找文件、`search` 按内容搜、`create` 写出一个文件、`modify` 改已有的文件、`command` 跑一条命令、`watch` 起一条长跑的命令边跑边送输出、`job` 查看或停掉一个后台任务。前四种归「探索」、`create`、`modify` 归「改文件」、`command`、`watch`、`job` 归「跑命令」,连着做时并进同一段活;宿主按它给默认的说法(没给 `verb` 时「读取文件」「执行命令」……)、图标和运行行。一段活里有跑命令的(`command`、`watch`)时,查后台任务输出的那几次不另数。没有 `kind` 就自成一类。
|
|
167
162
|
- 下面几个从参数里取值,参数同样没校验过,取不出就返回 undefined:
|
|
168
163
|
- `file(args)`:这次调用对着的本机文件 `{ path, lines? }`(`path` 照模型写的样子,绝对路径或相对工作目录;`lines: { from, to? }` 是只读了其中几行;列目录的是目录,空串是工作目录)。没给 `verb` 时,`view`、`list` 的那一行说相对工作区的路径(`src/a.ts:10-29`、`src/`),别的说文件名;不是列目录、调用成功了的,回复里写到这个文件时直接画成文件签。
|
|
169
164
|
- `note(args)`:模型在参数里写给人看的一句说明(`description`),有就整行说它。命令、正则一眼读不出在干什么的工具给它。
|
|
@@ -178,12 +173,10 @@ ui.toolLine("symbol", {
|
|
|
178
173
|
- `times: true`:几次合在一起时数次数(「操作网页 4 次」),不数个数。
|
|
179
174
|
- `callOnly: true`:`verb` 只说这一次调用(跑着时那一行、展开后那一行),一段活收起后照没登记说法的工具说「使用工具」。给不算一件活、只是收尾手续的工具(子智能体交结果的 `yield`)。
|
|
180
175
|
- `icon` 是 `@plume/ui/icons` 里的图标,13px。
|
|
181
|
-
- 同时登记了 `toolCard` 的工具单独一行画卡片(`inline: true` 的照常并进一段活),`fallback`
|
|
176
|
+
- 同时登记了 `toolCard` 的工具单独一行画卡片(`inline: true` 的照常并进一段活),`fallback` 用这里的说法。同一工具后登记的生效,它撤下后前一个恢复。说法、图标和 `group` 归类看这里登记的。
|
|
182
177
|
|
|
183
178
|
## 工具卡片
|
|
184
179
|
|
|
185
|
-
工具卡片还收到 `argumentsText`(流式原始 JSON)和 `streaming`(参数尚未完成)。草稿只能作静态展示,不执行未完成的代码。`send(text, event)` 仅在可写视图、工具成功完成时提供,发送到卡片所属会话;必须同步传真实点击/提交事件,或卡片内部当前聚焦 iframe 的原生 `MessageEvent`。宿主校验用户激活、来源、会话、挂载及注册状态,重复事件不能再次发送;iframe 消息间隔至少 1500ms。嵌入页仍须在自身桥里校验真实用户事件,不能用计时器代替点击。文本最多 32000 字符;页面插件应进一步限制短消息。可传第三参 `displayText` 作为气泡中的简短说明(同样最多 32000 字符),第一参仍是完整模型输入;主会话及提供 `composer.sendMessage` 的子会话保留两者。
|
|
186
|
-
|
|
187
180
|
```tsx
|
|
188
181
|
function MyCard({ call, status, progress, result, fallback }: ToolCardProps) {
|
|
189
182
|
const data = (result?.details as { items?: unknown } | undefined)?.items;
|
|
@@ -193,7 +186,23 @@ function MyCard({ call, status, progress, result, fallback }: ToolCardProps) {
|
|
|
193
186
|
ui.toolCard("my_tool", MyCard);
|
|
194
187
|
```
|
|
195
188
|
|
|
196
|
-
|
|
189
|
+
工具卡片收到 `ToolCardProps`:
|
|
190
|
+
|
|
191
|
+
- `call`(`{ id, name, args }`)、`status`(`"approval" | "running" | "done" | "failed"`)、`progress`(执行中的输出)、`result`(执行完才有,含 `details`)、`fallback`(通用卡片)、`sessionId`(这张卡所在的会话)。
|
|
192
|
+
- `argumentsText`(流式原始 JSON)和 `streaming`(参数尚未完成)。草稿只能作静态展示,不执行未完成的代码。
|
|
193
|
+
- 卡片在子会话里(侧边聊天)时还有 `handToParent(instruction)`:人点了按钮时把一件事排进那个子会话的父会话的队列,和 `run.parent.enqueue` 一样。
|
|
194
|
+
- `send(text, event, displayText?)`:以人的身份发一句话,规则见下面。
|
|
195
|
+
|
|
196
|
+
`send` 的规则:
|
|
197
|
+
|
|
198
|
+
- 仅在可写视图、工具成功完成时提供,发送到卡片所属会话。
|
|
199
|
+
- 必须同步传真实点击/提交事件,或卡片内部当前聚焦 iframe 的原生 `MessageEvent`。宿主校验用户激活、来源、会话、挂载及注册状态,重复事件不能再次发送。
|
|
200
|
+
- iframe 消息间隔至少 1500ms。嵌入页仍须在自身桥里校验真实用户事件,不能用计时器代替点击。
|
|
201
|
+
- 文本最多 32000 字符;页面插件应进一步限制短消息。
|
|
202
|
+
- 可传第三参 `displayText` 作为气泡中的简短说明(同样最多 32000 字符),第一参仍是完整模型输入;主会话及提供 `composer.sendMessage` 的子会话保留两者。
|
|
203
|
+
|
|
204
|
+
画卡片时:
|
|
205
|
+
|
|
197
206
|
- **先校验再画**:`details` 可能来自旧版本存下的会话,认不出就返回 `fallback`,不画半成品。
|
|
198
207
|
- **动态工具名**:例如 MCP 可用 `ui.toolCard("mcp__", Card, { match: "prefix" })`。精确工具名的卡片优先于前缀卡片;所有匹配前缀按 order、注册先后排序。不匹配结果仍返回 fallback,停用后撤销前缀注册。
|
|
199
208
|
- **只补东西的卡片**:登记了卡片的调用默认单独一行,不和前后的调用合成一段活。卡片只是在通用卡片下面补一点(认不出时直接返回 `fallback`)、调用本身该和同类的合在一起时,加 `inline: true`:调用照常按归类并进一段活,单独一次时那一行、展开一段活后的那一行由这张卡画。例子是 `plume.mcp` 的文档预览。只有一部分调用画大东西、其余只画 `fallback` 时,`inline` 传函数逐次判断,返回 true 的那次并进一段活:参数还在写时 `args` 是空对象,只有 `argumentsText` 在长;只看这次调用自己的参数,参数写完后答案不能再变(一次调用从头到尾是转录里的同一行),不能等结果或后面的调用再定。例子是网页预览:截图检查只画一行,并进前后的活,发布的那次单独一行画网页。
|
|
@@ -201,24 +210,7 @@ ui.toolCard("my_tool", MyCard);
|
|
|
201
210
|
- **后注册的生效**:同一工具 `order` 小的在前;`order` 相同时后注册的在前,它撤下后前一个恢复。卡片的 `order` 默认是 0,写更小的 `order` 也能排到前面。
|
|
202
211
|
- 外框用 `@plume/ui` 的 `Card`(`title`、`subtitle` 放命令或路径、`status`);输出用 `CodeBlock`,改动用 `ui.FileDiff`。
|
|
203
212
|
- 卡片只占一行时(参数还在写、事情已经交出去了)用 `@plume/ui` 的 `FlowRow`:和转录里思考、工具活、送达那几行同一个骨架(前置图标、标题、摘要、尾部、在跑时摘要扫光、`onToggle` 给了才可点),例子是 `plume.spawn-session` 的卡片写参数时那一行和开始以后那一行。
|
|
204
|
-
- 审批卡压在输入框正上方,不在工具卡片里:等待确认时工具卡片收到 `status: "approval"
|
|
205
|
-
|
|
206
|
-
## 审批卡
|
|
207
|
-
|
|
208
|
-
```tsx
|
|
209
|
-
function ShellApproval({ request, sessionId, respond, permission, allowAuto, compact, fallback }: ApprovalCardProps) {
|
|
210
|
-
if (!request.detail) return <>{fallback}</>;
|
|
211
|
-
return <div className="my-card">…<Button onClick={() => void respond("once")}>允许</Button></div>;
|
|
212
|
-
}
|
|
213
|
-
ui.approvalCard("my_shell", ShellApproval);
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
- 只给自己的工具画(`request.tool`):卡片压在输入框正上方的那一格、排队一次一张、停下这一轮时撤卡归产品;组件画整张卡(外框、正文、按钮)。认不出这次请求就返回 `fallback`(产品的通用卡),渲染出错时让给下一个注册者,都不行用通用卡。
|
|
217
|
-
- 回答只有 `"once"`、`"always"`、`"reject"`,问的是放不放行。要人给答案(回答问题、批准计划、填表)用下面的「等人给数据的卡」。
|
|
218
|
-
- `permission` 是现在的权限模式;`allowAuto()` 只把请求批准切到帮我批准(「接受并开启自动模式」),先切再 `respond`,别的档调用不改。只在人点了卡上的按钮时调。
|
|
219
|
-
- `compact`:窄窗口,面板区一次只摆一格,开面板会盖住卡片,要读的东西画在卡里。`from` 是问的子会话的标题,卡上要写明。
|
|
220
|
-
- 没人答时卡会过期(5 分钟),截止时刻在 `request.expiresAt`;`queued` 是排在这张后面还在等的张数。外框想和产品的审批卡一样(收放的标题栏、倒计时、+N),用 `@plume/ui` 的 `ApprovalFrame`,正文和按钮放在里面;自己画外框的,标题行放一个 `ApprovalExpiry`(`at={request.expiresAt}`),有排队时写 `+{queued}`。
|
|
221
|
-
- 等审批卡时对话末尾、侧栏悬停和系统通知说的是「等待你的批准」,插件改不了;要说自己的那句状态(「等你批准计划」),那就不是审批,用 `run.request` 的 `waiting`。
|
|
213
|
+
- 审批卡压在输入框正上方,不在工具卡片里:等待确认时工具卡片收到 `status: "approval"`,卡片里不放允许、拒绝。审批卡只有产品的那一张,插件换不掉;要人给答案(回答问题、批准计划、填表)的不是审批,用下面「等人给数据的卡」。
|
|
222
214
|
|
|
223
215
|
## 等人给数据的卡
|
|
224
216
|
|
|
@@ -234,7 +226,7 @@ ui.requestCard("ask_user", QuestionCard);
|
|
|
234
226
|
- 工具经 `run.request({ kind, title, payload })` 要人给数据(见 logic.md),卡片按 `kind` 登记;`request.payload` 是工具给的数据,认不出就返回 `fallback`(产品的通用卡,只能「不答」)。渲染出错时让给下一个注册者。
|
|
235
227
|
- 人答了调 `answer(value)`,`value` 原样交回 `run.request`(工具那边会再核一遍,卡上也先查一遍,拦住手误);人不答调 `decline()`,工具得到 `reason: "declined"`。没送到时两者都抛错,卡片把错误摆出来、让人再答。一个回答连同附言(评论、修改意见)一起放进 `value`,不用经 `remote` 另交。
|
|
236
228
|
- 代管这个会话的插件可能在外面先答了(`ctx.sessions.answer`、`respond`,见 logic.md「代管会话」):卡和过期、停下时一样被产品收起,你的卡不用为它另做处理;那次调用底下由产品写「由 X 代答」。
|
|
237
|
-
-
|
|
229
|
+
- 卡片压在输入框正上方的那一格、和审批卡一起排队一次一张、过期(30 分钟,截止时刻在 `request.expiresAt`)、停下这一轮时撤卡都归产品。`queued` 是排在这张后面还在等的张数;`from` 是问的子会话的标题,卡上要写明;`compact` 是窄窗口,面板区一次只摆一格,开面板会盖住卡片,要读的东西画在卡里。`permission` 是现在的权限模式,`allowAuto()` 只把请求批准切到帮我批准(「接受并开启自动模式」),先切再 `answer`,别的档调用不改,只在人点了卡上的按钮时调。外框用 `ApprovalFrame`(收放的标题栏、倒计时、+N),或自己画、标题行放 `ApprovalExpiry`(`at={request.expiresAt}`)、有排队时写 `+{queued}`。例子是 `packages/builtin/ask-user`(`ApprovalFrame`)、`plan` 插件(`ApprovalExpiry`、「接受并开启自动模式」先 `allowAuto()` 再 `answer`)和 `mcp` 插件的表单。
|
|
238
230
|
- 卡片一出来把焦点给它,键盘能直接答。
|
|
239
231
|
- 等这张卡时那句状态由 `run.request` 带 `waiting` 给(「等你批准计划」,按 `ctx.locale()`);不给时宿主说「等待你的回答」。
|
|
240
232
|
|
|
@@ -251,14 +243,14 @@ ui.panel("plan", PlanPanel, {
|
|
|
251
243
|
listed: true, // false:只从卡片等入口打开,不在开始页或「+」菜单列出
|
|
252
244
|
restore: false, // true:重启恢复标签,插件须按 tab 恢复数据
|
|
253
245
|
detachable: false, // true:可弹出独立窗口,插件须跨渲染进程按 tab 接回资源
|
|
254
|
-
keepAlive: false, // true
|
|
246
|
+
keepAlive: false, // true:标签关掉之前组件一直挂着,见下面
|
|
255
247
|
available: ({ cwd, session }) => Boolean(cwd), // 现在能不能用,可选;可以返回 Promise
|
|
256
248
|
});
|
|
257
249
|
```
|
|
258
250
|
|
|
259
251
|
工作区显示当前会话的面板,标签按会话保留;`multiple` 只控制同一会话能否开多个标签,不是整个窗口的单例限制。组件状态按标签隔离,跨挂载的资源按稳定 `tab` 保存,不能用一个全局「当前 session/cwd」覆盖其他会话的标签。
|
|
260
252
|
|
|
261
|
-
`keepAlive` 给卸掉就丢东西、又没法从逻辑入口接回来的面板:网页的 `<webview>` 一卸页面就没了,后台会话里 agent 也操作不了它。所有 `keepAlive` 标签放在统一扁平常驻层,换会话、去设置页只是隐藏,不重挂。后台仍收到标签归属会话的 `session` 和 `cwd`,`shown` 按是否正在显示所属会话的当前标签计算;挂着的每一页都占着资源,用不着的自己少挂。终端这类能按 `tab`
|
|
253
|
+
`keepAlive` 给卸掉就丢东西、又没法从逻辑入口接回来的面板:网页的 `<webview>` 一卸页面就没了,后台会话里 agent 也操作不了它。所有 `keepAlive` 标签放在统一扁平常驻层,换会话、去设置页只是隐藏,不重挂。后台仍收到标签归属会话的 `session` 和 `cwd`,`shown` 按是否正在显示所属会话的当前标签计算;挂着的每一页都占着资源,用不着的自己少挂。终端这类能按 `tab` 从逻辑入口接回来的不用它。
|
|
262
254
|
|
|
263
255
|
`available` 不给就一直能新开。产品按目录(`cwd`)和有无会话(`session`,新对话还没发消息时是 false)分别计算;这两个值变化、窗口回到前台、一轮结束时重算。答 false、还没答上来或抛错时,只从开始页等新开列表排除它、快捷键不新开;已有标签不隐藏、不卸载。只拿来判断「这里有没有东西可看」(Git 面板:这个项目是不是仓库;侧边聊天、子 Agent:有没有会话),不拿来藏错误:要说明的原因在面板里说。
|
|
264
256
|
|
|
@@ -273,7 +265,7 @@ ui.panel("plan", PlanPanel, {
|
|
|
273
265
|
|
|
274
266
|
宽度和收起偏好在窗口内共享;关闭最后一个标签后收起面板区,可从标题栏重新打开开始页。全屏状态按会话保存,盖满工作区;窄窗口也盖满工作区,正文和面板放不下横排时上下排列。
|
|
275
267
|
|
|
276
|
-
组件卸掉不等于标签关了:未设 `keepAlive`
|
|
268
|
+
组件卸掉不等于标签关了:未设 `keepAlive` 时换会话、去设置页可能卸掉;弹出或收回窗口(设了 `keepAlive` 也一样)仍会卸掉组件,回来再挂上同一个 `tab`,`detachable` 的资源恢复语义不变。要跨卸载保留的状态放在逻辑入口(经 `remote`),不放组件里。`restore`、`detachable` 默认 false,只有能按稳定 `tab` 恢复资源的面板才声明。弹出、收回不调 `onClose`;独立窗口的系统关闭与标签 ✕ 都先调 `onClose`,返回 false 保留窗口。终端可跨窗口接回 shell,应用退出后恢复的是新 shell,不会复活旧进程。
|
|
277
269
|
|
|
278
270
|
## 页面
|
|
279
271
|
|
|
@@ -283,7 +275,7 @@ ui.page("scheduled", ScheduledPage, { title: (locale) => MESSAGES[locale].title,
|
|
|
283
275
|
|
|
284
276
|
侧栏多一行,点了正文区整块换成这个页面。组件收到 `session`(打开着的会话)和 `workspace`(当前工作区:打开着的会话的 `project`、`cwd`,没打开会话时是空白页选着的项目或目录,都没选时为空;项目的目录按 `project` 在 `ui.useProjects()` 里查)。要在「人现在所在的项目」里建东西(定时任务的新任务)用 `workspace`,不用 `session`:人停在某个项目的空白页时没有会话。插件停用时侧栏那一行撤掉。
|
|
285
277
|
|
|
286
|
-
那一行右端可以挂一个数,告诉人这一页上有东西在等他看(定时任务没人看见的失败):`ui.pageBadge("scheduled", { count, label })`,数变了再调一次,`null`
|
|
278
|
+
那一行右端可以挂一个数,告诉人这一页上有东西在等他看(定时任务没人看见的失败):`ui.pageBadge("scheduled", { count, label })`,数变了再调一次,`null` 或 0 拿掉;`label` 给读屏和悬停提示。人正在那一页上时不画,停用时一起拿掉。带 `attention: true` 时同时在通知菜单中显示该页面的待处理事项,`label` 是通知标题,点击打开该页面。数要跟着逻辑入口走、不等人打开页面,所以在 `apply` 里一直连着逻辑入口的 `watch`,每次推来时调它。别处的按钮(输入框上方那一行的「查看」)经 `ui.openPage(id)` 把人带到这一页。
|
|
287
279
|
|
|
288
280
|
平时宿主在页面上方留一行和标题栏一样高的空白,它是拖动窗口的地方,页面从它下面开始。怎么选:
|
|
289
281
|
|
|
@@ -334,8 +326,35 @@ ui.conversationCard("todo", TodoCard);
|
|
|
334
326
|
|
|
335
327
|
一直在变的一件事(todo 清单做到哪了),不是转录里的又一条记录,所以不随消息滚走。对话那一列放得下它(320px 的卡片之外还剩 420px 正文)时浮在转录右上角,放不下时(窄窗口、面板区开着)在输入框正上方;只画开着的会话,新对话不画。组件收到 `session`(会话的视图:消息、`summary.running`、`summary.stop`)和 `placement`(`floating` / `inline`):浮着的要自己的实底(`--plume-float`)和阴影(`--plume-shadow-glass`),压在正文上面也读得清。没什么可说时(清单空了、全勾完了)返回 null,不占地方。
|
|
336
328
|
|
|
329
|
+
`lane: true` 的卡片占右边一列:够宽时停靠,不够时浮在转录上,从不到输入框上方,太长自己滚。
|
|
330
|
+
|
|
337
331
|
卡片上可以有停下、接着做的按钮:`stop()` 停下这个会话正在跑的一轮(和输入框的停止按钮一样),`carryOn(text)` 替人说一句让它接着做(和转录底下的「继续」一样,会话里标成界面替人说的话)。只在人点了卡片上的按钮时调,不在渲染、定时器里自己调。
|
|
338
332
|
|
|
333
|
+
## 回复下面的卡片和按钮
|
|
334
|
+
|
|
335
|
+
- `ui.turnFooter(id, Component, { order })`:最新结束的回复下面挂组件,收到 `{ session, timestamp }`;随转录滚动,运行中、新问未答和翻看旧窗口时不挂。
|
|
336
|
+
- `ui.replyAction(id, Component, { order })`:每一轮收尾那条回复底下的操作排(时间、复制、分叉)里加一个按钮,排在复制和分叉之间,收到 `{ sessionId, from, to }`:`from` 是这一轮开口那条消息的位置,`to` 是收尾回复的位置加一,插件按自己记下的消息位置挑出归这一轮的东西。悬停才显出来、行高 24,按钮不高过 `IconButton` 默认尺寸,点开的东西用 `Popover`;返回 null 不占位(`plume.hooks` 的运行记录)。
|
|
337
|
+
|
|
338
|
+
## 送达的那一行
|
|
339
|
+
|
|
340
|
+
`ui.agentDelivery(Component)` 画本插件交回的结果在对话里的那一行(`ctx.sessions.report`、`channel.report`、`reportBack` 带了 `delivery` 的,见 logic.md「后台与会话间递话」):
|
|
341
|
+
|
|
342
|
+
- 收到 `{ sessionId, reports }`,一次送达里本插件交的几份一起给(连着几次只带 `inline` 的送达并成一次),画成一行(子 Agent 的「消息来自 @explore」、plume.tools 的「后台命令完成」,用 `FlowRow`)。每份的 `details` 是你交的时候给的自己的数据,宿主不读,原样带回来。
|
|
343
|
+
- 宿主记着每份是哪个插件交的,只交给那个插件登记的组件;再登记换掉自己前一个,停用时撤下。
|
|
344
|
+
- 没登记(或停用了)时宿主按 `title`、`status` 画默认的一行,带 `sessionId` 的(新会话交回)画成「来自新会话」、点了过去;组件出错时只丢这一行。
|
|
345
|
+
|
|
346
|
+
## 环境卡片
|
|
347
|
+
|
|
348
|
+
会话的环境卡片由内置的 `plume.environment` 画。`ui.environmentSection(id, Component, { label, order?, attention? })` 往里加一节:和会话相关的一件周边的事,每一行只是入口,点了去自己的页面或面板(`ui.openPage`、`ui.openPanel`)。
|
|
349
|
+
|
|
350
|
+
- 组件收到 `{ session }`;没东西时返回 null,不占位、不画分隔线。`label` 是卡片「显示哪些内容」菜单里的名字。
|
|
351
|
+
- 卡片不替插件画小标题,要小标题的自己画(文字用 `Text size="caption" tone="faint"`)。
|
|
352
|
+
- order 排进卡片自己的几节(打开方式 0、子会话 20(按建它的插件分几节,节名是 `ui.usePluginDisplayName` 查到的显示名)、引用 90)和别的插件的节之间;内置插件已经用了的:后台任务 10、定时唤醒 50、压缩记录 85(`plume.diagnostics`),计划 30(`plume.plan`),建议的新会话 40(`plume.spawn-session`)。例子:`plume.pull-requests` 用 60 放当前分支的拉取请求(一行,没有小标题);`plume.summary` 用 80 放会话摘要(带「摘要」小标题)。
|
|
353
|
+
- `attention(session)` 是一个 React hook(规则同 hook),这一节有要提醒人的事时返回一句话(按界面语言),没有返回 undefined:卡片收着、这一节没被藏起来时(只在这时调,要读的数据可以只在被调时拉),标题栏的环境开关上挂一个圆点,这句话按节的 order 接在开关的说明后面。
|
|
354
|
+
- 要提醒的是两种事:等人回来决定的,和会自己叫醒这个会话的(人不回来,会话也会自己开一轮);普通的「有东西在跑」不算(开着一个开发服务器是常态),算进去圆点就一直亮着。例子:`plume.spawn-session` 还没人点开始的建议经 `attention` 说「N 条建议的新会话等你决定」;`plume.diagnostics` 的后台任务一节说「N 个监视在跑」(监视有输出就开一轮,普通后台命令不算),定时唤醒一节说「N 个定时唤醒待触发」。
|
|
355
|
+
|
|
356
|
+
`ui.useEnvironmentSections()` 是给画环境卡片的插件用的 React hook:返回各插件登记的节(`key` 为 `插件名/id`、`label`、`order`、已包好错误边界的 `component`),按 order 排好。
|
|
357
|
+
|
|
339
358
|
## 输入框的灰字建议、回复里的标记
|
|
340
359
|
|
|
341
360
|
```ts
|
|
@@ -361,6 +380,11 @@ ui.welcomeLine((locale) => api.today(locale));
|
|
|
361
380
|
- 返回 undefined、空串或抛错时显示默认的那句(抛错记日志)。每个插件只能有一个,再注册换掉上一个。
|
|
362
381
|
- 要花时间的事(调模型、读网络)放在逻辑入口里做好缓存,界面入口经 `remote()` 取;欢迎页常开,别每次打开都调一次模型。
|
|
363
382
|
|
|
383
|
+
## 标题栏和会话菜单
|
|
384
|
+
|
|
385
|
+
- `ui.sessionToolbar(id, Component)` 在有会话的标题栏放局部控件,收到 `{ session }`;新会话不画,不能接管标题与布局。使用 `IconButton`,停用自动撤下。
|
|
386
|
+
- `ui.sessionMenuItem(id, { label, icon?, run })` 在会话操作菜单加一项。`run` 收到被点的会话摘要,异步操作固定使用它的 id,不跟随焦点;报错由宿主提示。同插件同 id 替换。
|
|
387
|
+
|
|
364
388
|
## 侧栏的记号、删除确认框
|
|
365
389
|
|
|
366
390
|
```tsx
|
|
@@ -375,11 +399,14 @@ ui.deleteNote(async (sessionId, locale) => leftovers(sessionId, MESSAGES[locale]
|
|
|
375
399
|
## 提示、开面板、写输入框
|
|
376
400
|
|
|
377
401
|
- `ui.notify(message, level)`:`level` 是 `"info"`(默认)、`"warn"`、`"error"`。说的是刚才那一下的结果(操作失败、导出好了),不是一直存在的状态:一直存在的状态画在自己的界面里。同样的话连着说几次合成一条。
|
|
378
|
-
- `ui.openPanel(id)
|
|
379
|
-
- `ui.closePanel(id, sessionId?, tab?)
|
|
380
|
-
- `ui.draftInSession(undefined, { text, replace
|
|
381
|
-
-
|
|
382
|
-
-
|
|
402
|
+
- `ui.openPanel(id, sessionId?, { tab, background }?)`:只能开自己注册的面板,和按它的快捷键一样(开着就切过去,没开就新开标签,面板区收着就展开);指定会话时不因异步期间切换会话而串到别处。能开好几个的面板给了 `tab`,开的是 id 为它的那个标签,没有就用这个 id 新开(id 要全局唯一,用 `crypto.randomUUID()`):逻辑入口那边先有了东西(agent 开了一页网页),再让界面开出对应的标签。`background: true` 只加标签、不切过去、不展开面板区。
|
|
403
|
+
- `ui.closePanel(id, sessionId?, tab?)`:关掉自己在该会话打开的面板,照样经过 `onClose`;给了 `tab` 只关那一个。
|
|
404
|
+
- `ui.draftInSession(sessionId | undefined, { text, images?, send?, replace? })`:把字和图片写进输入框的草稿,不替人发送(除了下面的 `send`)。给了 `sessionId` 先打开那个会话,不给就是眼前的输入框(当前对话或新对话);会话不在了抛错。
|
|
405
|
+
- 默认接在已有草稿后面(原来有字时空一行),人原来打的字不丢;`images` 是 `{ data, mimeType }`(base64、不带 `data:` 前缀,png、jpeg、webp、gif;`from` 记着它是媒体目录里哪张图的标注副本),作为附件挂上、标记落在最后。用在「把选中的这一块连同截图交给 Agent」这类地方。
|
|
406
|
+
- `replace: true` 整段换掉草稿、光标移进去。用在「让 Agent 处理」这类按钮上:`ui.draftInSession(undefined, { text, replace: true })` 把要说的话写好,人看过再发。按钮上已经写明了要它做什么、不用人改的,在项目签那一行的格子里用 `send(text)` 直接发。
|
|
407
|
+
- `send: true` 只在人点了按钮时给:宿主核对人刚点过(用户激活还在)、输入框原本是空的,才直接发出去,还没发消息的新对话就此开出会话(Git 面板的「发给 Agent」);人写了一半、或没人点时照草稿留着。
|
|
408
|
+
- 在弹出去的面板窗口里调也行:那边没有输入框,只能交给面板来源的会话,转交主窗口里那个会话的输入框。
|
|
409
|
+
- `ui.openConversationDraft({ project?, cwd?, text })`:人点击明确的按钮后,打开已登记项目或绝对目录的空白对话,把 `text` 追加在目标已有草稿后;不发送、不落会话记录。`project` 与 `cwd` 二选一,目录由逻辑入口准备,失败不改变当前页面。
|
|
383
410
|
|
|
384
411
|
## 网址、文件签的打开方式
|
|
385
412
|
|
|
@@ -398,9 +425,32 @@ ui.linkOpener({
|
|
|
398
425
|
|
|
399
426
|
`takesClicks`、`file` 在点击、渲染时同步调,别做耗时的事;抛错的当作「不」。`open` 出错弹成提示条。几个插件都注册时按注册先后排,点网址时第一个 `takesClicks` 的接手。一个插件只能有一个,再注册换掉上一个。
|
|
400
427
|
|
|
428
|
+
插件自己界面上的链接用 `ui.openLink(url, sessionId)`:用户点击后像回复里的网址那样打开,有插件接手点击(`takesClicks`,内置浏览器)时开在它那里,否则系统浏览器;限制同 `ui.openExternal`。
|
|
429
|
+
|
|
430
|
+
## 文件、终端和系统打开
|
|
431
|
+
|
|
432
|
+
- `ui.openFile(path, sessionId)`:在指定会话的文件面板打开文件;读取仍受会话目录边界约束,会话媒体目录里的图(`ctx.media.save` 交回的 `path`)也在边界里,只有这个会话最近一次请求的「记忆文件」(`PromptSection.files` 标的来源)在边界外也能只读打开;独立窗口里交给主窗口对应会话。
|
|
433
|
+
- `ui.findFile(path, sessionId)`:纯文字里印出来的路径(终端输出)指的那个文件,返回绝对路径:绝对路径看在不在,相对路径先按会话目录拼、再按结尾找唯一的一个;不存在、不止一个、文件夹、边界外都是 `null`,答出来的 `ui.openFile` 打得开。
|
|
434
|
+
- `ui.droppedPaths(transfer)`:拖放带着的本机绝对路径(访达的文件、文件面板的行),不带路径是 `null`;在 `drop` 里同步调,`dragover` 时只答空数组。
|
|
435
|
+
- `ui.useFileMenu(root)`:组件内调用的 hook,返回 `{ open, onContextMenu(event, { path, onOpen }), element }`;复用打开方式、在访达中显示和复制路径菜单。
|
|
436
|
+
- `ui.documentViewer(extensions, Component)`:注册只读文档预览,扩展名小写且不带点;同扩展名后注册的生效,停用后恢复前一个。
|
|
437
|
+
- `ui.DocumentPreview`:`{ name, identity, readBytes(signal), fallback? }`,文件面板和插件共用的文档展示;调用方负责读取与权限校验,只交数据、不交磁盘路径,identity 随文件版本改变;无匹配或渲染失败回到 fallback。
|
|
438
|
+
- `ui.ProjectOpener`:以 `{ path }` 显示目录的打开方式,复用文件预览的默认应用与选择菜单;只有用户点击才调用系统打开。
|
|
439
|
+
- `ui.terminalOpener(open)`:为文件树等入口登记「在终端中打开」:人点击后调用 `open(directory, sessionId)`,目录和归属会话由宿主提供。一个插件保留一个处理器,最新登记的优先;停用后恢复前一个,没有处理器时宿主提示安装并启用终端插件,执行失败弹提示条。
|
|
440
|
+
- `ui.terminalRunner(run)`:为回复里 shell 代码块(`bash`、`sh`、`zsh`、`shell`、`console`、`terminal`)的「在终端运行」登记处理器:人点击后调用 `run(command, sessionId)`,命令已去掉 `$ ` / `> ` 提示符和注释行,要带回车原样执行;登记规则和没有处理器时的提示同 `ui.terminalOpener`。
|
|
441
|
+
- `ui.openPath(path)`:用户点击后把本机的文件交给默认程序、文件夹交给文件管理器打开;直接打开会运行它的(程序、脚本、应用)和网络共享上的改成在所在文件夹里指出来并提示;打不开时抛错(话给人看)。路径由逻辑入口算好经 `ctx.serve` 交回,逻辑入口不自己起系统的打开命令。
|
|
442
|
+
- `ui.revealPath(path)`:用户点击后在系统的文件管理器里打开上级目录并选中它(「在访达中显示」);和 `ui.openPath` 不同,文件夹不进到里面、文件不交给程序,所以不会运行它;不存在时抛错(话给人看)。路径同样由逻辑入口交回(媒体图用 `ctx.media.save` 交回、记在 `details` 里的 `path`)。菜单上的字插件自带,按平台叫访达、资源管理器、文件管理器(`@plume/ui` 的 `macKeyboard()`,`navigator.platform` 以 `Win` 开头是 Windows)。
|
|
443
|
+
|
|
444
|
+
## 图片
|
|
445
|
+
|
|
446
|
+
- `ui.mediaUrl(name, thumb?)`:会话媒体目录里一张图(`ImageContent.media`、`ctx.media.save` 交回的名字)给 `<img src>` 的地址;名字不对是空串。给 `thumb`(像素)时是长边缩到这么长的缩略图(宿主算一次存在盘上,原图不比它大时就是原图;GIF 不缩),格子、列表里用它,看图、另存、复制用不带 `thumb` 的原图。
|
|
447
|
+
- `ui.openImage(images, index, { from?, edit? })`:用产品的看图器看几张图:从 `from` 元素飞出、关上飞回;`edit` 打开就进标注,标注完交给那张图的 `onAttach(dataUrl)`。
|
|
448
|
+
- `ui.saveImage(src)` / `ui.copyImage(src)`:和看图器的「另存为」「复制」一样:存一份到磁盘、放进剪贴板(转 PNG,剪贴板不接时退回存文件);`src` 是 `data:` 或 `mediaUrl`。
|
|
449
|
+
- `ui.captureWindow({ x, y, width, height })`:截取当前插件界面所在窗口的可见矩形,单位 CSS 像素、整数,返回 PNG data URL;临时隐藏浮动导航、通知与提示;只能截本窗口,不能越界或读取别的窗口。
|
|
450
|
+
|
|
401
451
|
## 划词询问
|
|
402
452
|
|
|
403
|
-
`DraftQuote` 是 `{ id, text, from: "user" | "assistant" }`,`id` 标识这一次划选,不是原消息 id;只保留文字和说话者,不携带 DOM。`QuoteAction` 是 `{ label: LocalizedText, run(quote, sessionId): void | Promise<void> }
|
|
453
|
+
`DraftQuote` 是 `{ id, text, from: "user" | "assistant" }`,`id` 标识这一次划选,不是原消息 id;只保留文字和说话者,不携带 DOM。`QuoteAction` 是 `{ label: LocalizedText, run(quote, sessionId): void | Promise<void> }`。`ui.quoteAction` 在主会话划词菜单加一项,人点击后调用 `run(quote, sessionId)`;标签随界面语言取值,操作放在“引用到输入框”之前;抛错由宿主弹提示条。一个插件保留一项,重复登记替换前一项,停用时撤下。
|
|
404
454
|
|
|
405
455
|
```tsx
|
|
406
456
|
ui.quoteAction({
|
|
@@ -431,6 +481,17 @@ ui.queueAction({
|
|
|
431
481
|
|
|
432
482
|
宿主只在人点了那一项时调 `run`,调之前已经把这条从排队里拿掉;抛错弹提示条,这条不放回排队。没有插件登记时排队条直接放「编辑」按钮,不出菜单。几个插件按注册先后排;一个插件只能有一个,再注册换掉上一个。
|
|
433
483
|
|
|
484
|
+
## 复用产品的组件
|
|
485
|
+
|
|
486
|
+
- `ui.Markdown`:`{ text, sessionId?, streaming?, className?, inline? }`,复用产品正文渲染;指定 sessionId 时文件签按该会话解析,不指定时继承所在转录。`inline` 只画一句话里的行内记号(代码、加粗、强调、删除线),包在一个带 `className` 的 `span` 里,链接和图片只留字;代码和加粗的样子由宿主定(卡片底色、正文色、等宽代码字号),不跟着正文的代码外观设置走。
|
|
487
|
+
- `ui.ModelSelect`:产品的模型下拉,`{ value, onChange(modelId), ariaLabel, inheritLabel, inheritDetail?, inheritedModelId?, inheritedSource?, disabled?, compact? }`;`value` 为空串是「跟随」,菜单顶上那一项就是它,选了 `onChange("")`;`compact` 只剩一枚箭头,贴在它管的按钮旁边当分体按钮(计划卡的「在新会话里执行」),选了什么由那颗按钮自己说。设置页里的用法见「设置分区」的 `session-models`。
|
|
488
|
+
- `ui.ModelControls`:输入框右下角那两枚(模型、思考档位),样子和菜单都和输入框里的一样:`{ model, thinking?, onModelChange(modelId), onThinkingChange(level), disabled? }`;`model` 为空串、`thinking` 不给时显示默认模型和默认推理强度。不改哪个会话,只把选择交出来,给开新会话前先选好怎么跑的地方用(建议新会话的卡片),选的再交给 `ctx.sessions.launch`。
|
|
489
|
+
|
|
490
|
+
## `@` 菜单、`/` 命令列表
|
|
491
|
+
|
|
492
|
+
- `ui.mentions(provider)`:每次打开 `@` 时传 `{ cwd?, sessionId? }`,返回 `ComposerMention[]` 或 Promise:`id/title/kind/token` 必填,可带 description/origin/avatar;`kind` 只管画法和分组:`agent` 归「智能体」、画 `avatar` 头像(输入框里的 token 也带头像),`plugin` 归「插件和技能」、画插件图标;只插入 token,用户自行发送,停用自动撤销。
|
|
493
|
+
- `ui.listCommands({ cwd? })`:输入框 `/` 菜单此刻有的命令(`ListedCommand[]`:`SlashCommand` 加 `plugin`),同名去重后的样子:产品自己的 `/clear`(没有 `plugin`)在前,再是各插件经 `ctx.commands` 供的;`cwd` 决定项目里的命令文件、技能列不列。只读,展开、发送仍走输入框;每次现问,要看时再调。命令设置页靠它列「内置」那一段(没有 `source` 的)。
|
|
494
|
+
|
|
434
495
|
## 画一个子会话
|
|
435
496
|
|
|
436
497
|
插件自己的子会话(逻辑入口经 `ctx.sessions.create` 建的,见 logic.md「子会话」)用产品的对话视图画,转录、工具卡片、思考和主列一样:
|
|
@@ -459,7 +520,7 @@ function SidePanel({ tab }: PanelProps) {
|
|
|
459
520
|
- `reask: true`:人说的最后一句底下多一颗「编辑并重新提问」,宿主截掉它和之后的、拿改过的字重跑一轮(照常经 `prompt.submit`,插件按截掉后的记录算),不问要不要退文件;一轮在跑时置灰。没有撤回、分叉。侧边聊天开它,子 Agent 的全过程不开(话是派它的会话写的)。
|
|
460
521
|
- 这个子会话经 `run.parent.enqueue`、卡片的 `handToParent` 交给父会话的事走到了哪(`ChildTask`:排队中、在跑、做完、失败、随父会话停下)由宿主给,要画就给 `tasks={Component}`:组件收到 `{ sessionId, parentId, tasks, runNow, withdraw?, retry, dismiss }`,画在转录和输入框之间,留几件、摆哪些按钮由你定(侧边聊天的回执条见 `packages/builtin/side-chat/src/TaskStrip.tsx`);不给就不画。`withdraw` 把那件从父会话的队里收回、那句话放回 `composer.draftKey` 那份草稿,没给 `draftKey` 时没有它。操作出错时宿主弹提示条。
|
|
461
522
|
- 没有「继续」:子会话的一轮只有插件能发起。等人批准的那一次压在主输入框上方,转录里只标「等待确认」。
|
|
462
|
-
- 画的是独立会话(`create` 不给 `parentId`,见 logic.md「独立会话」)时:等人批准的那一次(和 `run.request` 等人给数据的卡)就压在这个视图的输入框上方(插件的 `ui.
|
|
523
|
+
- 画的是独立会话(`create` 不给 `parentId`,见 logic.md「独立会话」)时:等人批准的那一次(和 `run.request` 等人给数据的卡)就压在这个视图的输入框上方(插件的 `ui.requestCard` 照样用),因为它不挂在哪个对话下、没有别处可压;所以只在人看得到的地方画它,页面没开着时用 `ui.useSession` 读 `approval`、经 `ui.pageBadge` 带 `attention: true` 提醒人。`model: "choose"` 的菜单没有「跟随主会话」,没选的用默认模型;没有划词引用。
|
|
463
524
|
- 外面那一层给它高度(面板里 `height: 100%`),它自己撑满、底下贴着输入框。
|
|
464
525
|
|
|
465
526
|
## 设置分区
|
|
@@ -551,14 +612,12 @@ function ModelSetting() {
|
|
|
551
612
|
|
|
552
613
|
要让人在插件里切「跟随系统 / 浅色 / 深色」,就是改外观页那三张主题卡片,不是做一套皮肤:`ui.setTheme("dark")` 等同人在外观页点「深色」,存盘、各窗口同步;组件里用 `ui.useTheme()` 读当前选的是哪张。有皮肤接管深浅模式时外观页的主题只读,`setTheme` 同样不改,所以先 `ui.skin(null)` 撤下自己的皮肤再调。只在人点了插件界面上的按钮时调,不在工具或定时逻辑里替人改。
|
|
553
614
|
|
|
554
|
-
背景操作复用 `<ui.ChatBackgroundSettings title="…" />`,`title`
|
|
615
|
+
背景操作复用 `<ui.ChatBackgroundSettings title="…" />`,`title` 来自插件文案,省略时用产品的「会话背景」。这个自包含的组件管理全局的图片、显示位置、铺满/平铺、效果、浓淡,以及整个窗口的磨砂质感(外圈、停靠侧栏和对话区一起,不看皮肤、不要图;外观页不再有背景设置),支持选图和拖入图片,不向插件暴露磁盘路径。用户在该组件做的修改直接保存到全局背景偏好,切换皮肤或停用插件不撤销;图片格式和体积检查、复制、失败提示和跨窗口更新均由宿主负责,不另存一份背景。
|
|
555
616
|
|
|
556
617
|
`root` 是窗口最外层与透明停靠侧栏共用的底色,默认跟随 `shell`;`sidebar` 只给 compact 浮出侧栏铺底,不给停靠侧栏另铺一层;`accent-dim` 是强调弱底,默认是强调色的 10% 透明底。数据皮肤可直接给出完整的面、线、字 token;若一份配色只适用于一个模式,给 `light`、`dark` 同一组 token,并用 `mode` 指定深浅模式。
|
|
557
618
|
|
|
558
619
|
`root-image` 是盖在 `root` 上的底纹,值是 CSS 图像,可以逗号分隔叠几层(例如 `linear-gradient(160deg, #…, #…)`,先写的在上面),默认 `none`。颗粒、噪点这类质感不写进皮肤:磨砂是用户在背景设置里开关的,宿主叠在底纹上面,哪套皮肤都一样。只在管理配色的数据皮肤下铺在窗口外圈和透明的停靠侧栏上,正文卡片仍是 `shell` 的实底。宿主把它固定在视口上(`background-attachment: fixed`),外圈、侧栏吸顶标题和 Windows 顶带画的是同一片;平铺的小图按自身尺寸重复,渐变铺满整个窗口。侧栏的字直接压在它上面,渐变两端的颜色都要和 `root` 一样满足文字对比度。
|
|
559
620
|
|
|
560
|
-
在界面入口的 `apply` 中开启远端 watch 时,用 `ui.effect(() => call.cancel())` 登记取消。清理按后登记先执行,一项抛错不妨碍其余清理;宿主先让旧入口失效再清理,因此停用、重载或 apply 失败后,旧异步回调的 `ui.skin` 不会复活皮肤。组件内的订阅仍用 React effect 清理。
|
|
561
|
-
|
|
562
621
|
## remote:调自己的逻辑入口
|
|
563
622
|
|
|
564
623
|
```tsx
|
|
@@ -572,6 +631,7 @@ apply(ui) {
|
|
|
572
631
|
- 类型参数写逻辑入口那边的接口(`src/api.ts`),末尾的 `CallContext` 参数这边不传。
|
|
573
632
|
- 每个方法返回一个调用:`await` 拿结果;逻辑入口返回异步迭代器的,`for await` 逐个拿中间值;`.cancel()` 取消,对端的 `signal` 中止。组件卸载时记得取消还在流的调用。
|
|
574
633
|
- 逻辑入口没开或插件停用了,调用以错误结束,界面要能显示这种状态。
|
|
634
|
+
- 在界面入口的 `apply` 中开启远端 watch 时,用 `ui.effect(() => call.cancel())` 登记取消。清理按后登记先执行,一项抛错不妨碍其余清理;宿主先让旧入口失效再清理,因此停用、重载或 apply 失败后,旧异步回调的 `ui.skin` 不会复活皮肤。组件内的订阅仍用 React effect 清理。
|
|
575
635
|
|
|
576
636
|
## 服务:给别的插件的界面入口用
|
|
577
637
|
|
|
@@ -606,16 +666,3 @@ Plume 自带的界面侧服务:
|
|
|
606
666
|
## 共享模块
|
|
607
667
|
|
|
608
668
|
`react`、`react/jsx-runtime`、`@plume/ui`、`@plume/styles` 不打包,运行时用产品的那一份;其余(包括 `@plume/ui/icons` 和第三方库)打进 `dist/ui.mjs`。宿主启动时就加载每个插件的 `ui.mjs`:只在某个操作里才用到的大库(预览、图表、编辑器)写成 `await import("…")`,构建时拆到 `dist/chunks/`,用到时才取。能用哪些组件、各自的签名和用法,见 [components.md](components.md)(从源码生成);`@plume/ui` 里没列在那里的导出是产品内部用的,引用了 `pnpm check:plugins` 会拦。
|
|
609
|
-
|
|
610
|
-
## 运行诊断与环境卡片入口
|
|
611
|
-
|
|
612
|
-
- `ui.sessionToolbar(id, Component)` 在有会话的标题栏放局部控件,收到 `{ session }`;新会话不画,不能接管标题与布局。使用 `IconButton`,停用自动撤下。
|
|
613
|
-
- `ui.sessionMenuItem(id, { label, icon?, run })` 在会话操作菜单加一项。`run` 收到被点的会话摘要,异步操作固定使用它的 id,不跟随焦点;报错由宿主提示。同插件同 id 替换。
|
|
614
|
-
- `ui.agentDelivery(Component)` 画本插件交回的结果在对话里的那一行(`ctx.sessions.report`、`channel.report`、`reportBack` 带了 `delivery` 的):收到 `{ sessionId, reports }`,一次送达里本插件交的几份一起给(连着几次只带 `inline` 的送达并成一次),画成一行(子 Agent 的「消息来自 @explore」、plume.tools 的「后台命令完成」,用 `FlowRow`)。每份的 `details` 是你交的时候给的自己的数据,宿主不读,原样带回来。宿主记着每份是哪个插件交的,只交给那个插件登记的组件;再登记换掉自己前一个,停用时撤下;没登记(或停用了)时宿主按 `title`、`status` 画默认的一行,带 `sessionId` 的(新会话交回)画成「来自新会话」、点了过去;组件出错时只丢这一行。
|
|
615
|
-
- `ui.ProjectOpener` 以 `{ path }` 显示目录的打开方式,复用文件预览的默认应用与选择菜单;只有用户点击才调用系统打开。
|
|
616
|
-
- `ui.environmentSection(id, Component, { label, order?, attention? })` 往环境卡片里加一节:和会话相关的一件周边的事,每一行只是入口,点了去自己的页面或面板(`ui.openPage`、`ui.openPanel`)。没东西时返回 null。order 排进卡片自己的几节(打开方式 0、子会话 20(按建它的插件分几节,节名是 `ui.usePluginDisplayName` 查到的显示名)、引用 90)和别的插件的节之间;内置插件已经用了的:后台任务 10、定时唤醒 50、压缩记录 85(`plume.diagnostics`),计划 30(`plume.plan`),建议的新会话 40(`plume.spawn-session`)。卡片不替插件画小标题,要小标题的自己画(文字用 `Text size="caption" tone="faint"`)。例子:`plume.pull-requests` 用 60 放当前分支的拉取请求(一行,没有小标题);`plume.summary` 用 80 放会话摘要(带「摘要」小标题)。`attention` 是一个 React hook(规则同 hook),这一节有要提醒人的事时返回一句话(按界面语言),没有返回 undefined:卡片收着、这一节没被藏起来时(只在这时调,要读的数据可以只在被调时拉),标题栏的环境开关上挂一个圆点,这句话按节的 order 接在开关的说明后面。要提醒的是两种事:等人回来决定的,和会自己叫醒这个会话的(人不回来,会话也会自己开一轮);普通的「有东西在跑」不算(开着一个开发服务器是常态),算进去圆点就一直亮着。例子:内置的 `plume.spawn-session` 用 40 放「建议的新会话」,还没人点开始的建议经 `attention` 说「N 条建议的新会话等你决定」;`plume.diagnostics` 的后台任务一节说「N 个监视在跑」(监视有输出就开一轮,普通后台命令不算),定时唤醒一节说「N 个定时唤醒待触发」。
|
|
617
|
-
- `ui.useEnvironmentSections()` 是给画环境卡片的插件用的 React hook:返回各插件登记的节(`key` 为 `插件名/id`、`label`、`order`、已包好错误边界的 `component`),按 order 排好。
|
|
618
|
-
|
|
619
|
-
内置 `plume.diagnostics` 用以上入口和 `ui.panel` 提供任务和轨迹,经 `ui.environmentSection` 往环境卡片里放后台任务、定时唤醒、压缩记录三节;工具结果和历史正文读 `SessionView`,观察数据与后台任务经自己的 `remote` 读取。环境卡片本身是内置的 `plume.environment`:`ui.sessionToolbar` 放开关,`ui.conversationCard`(`lane: true`)画卡片,`ui.useEnvironmentSections()` 拿各插件的节,挂上各节的 `attention` 决定开关上的圆点;它不认是谁登记的节,两个插件互不点名。
|
|
620
|
-
|
|
621
|
-
`ui.pageBadge` 的 badge 可带 `attention: true`,同时在通知菜单中显示该页面的待处理事项;label 是通知标题,点击打开该页面。
|