@plumeai/plugin-sdk 0.1.6 → 0.1.8
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/i18n/messages/en.d.ts +8 -3
- package/dist/client/ui/src/i18n/messages/zh-CN.d.ts +8 -3
- package/dist/contract/src/plugin.d.ts +12 -2
- package/dist/contract/src/service.d.ts +18 -9
- package/dist/contract/src/ui.d.ts +19 -1
- package/dist/core/llm/types/message.d.ts +8 -3
- package/dist/theme.css +5 -1
- package/package.json +1 -1
- package/skill/SKILL.md +3 -3
- package/skill/references/logic.md +4 -4
- package/skill/references/ui.md +16 -0
|
@@ -168,8 +168,6 @@ export declare const en: {
|
|
|
168
168
|
"sessionCard.input": string;
|
|
169
169
|
"sessionCard.output": string;
|
|
170
170
|
"sessionCard.partMain": string;
|
|
171
|
-
"sessionCard.partSide": string;
|
|
172
|
-
"sessionCard.partSubagents": string;
|
|
173
171
|
"sessionCard.started": string;
|
|
174
172
|
"sessionCard.readCache": string;
|
|
175
173
|
"reorder.saveFailed": string;
|
|
@@ -186,6 +184,12 @@ export declare const en: {
|
|
|
186
184
|
"projectList.projects": string;
|
|
187
185
|
"projectList.recent": string;
|
|
188
186
|
"projectList.newLooseChat": string;
|
|
187
|
+
"welcome.lateNight": string;
|
|
188
|
+
"welcome.morning": string;
|
|
189
|
+
"welcome.forenoon": string;
|
|
190
|
+
"welcome.noon": string;
|
|
191
|
+
"welcome.afternoon": string;
|
|
192
|
+
"welcome.evening": string;
|
|
189
193
|
"empty.chat": string;
|
|
190
194
|
"empty.projectQuestion": string;
|
|
191
195
|
"empty.explore": string;
|
|
@@ -851,7 +855,8 @@ export declare const en: {
|
|
|
851
855
|
"questionNav.pick": string;
|
|
852
856
|
"questionNav.goTo": string;
|
|
853
857
|
"questionNav.imageMessage": string;
|
|
854
|
-
"userMessage.
|
|
858
|
+
"userMessage.fromChild": string;
|
|
859
|
+
"userMessage.fromPlugin": string;
|
|
855
860
|
"userMessage.showMore": string;
|
|
856
861
|
"userMessage.showLess": string;
|
|
857
862
|
"conversation.quoteToComposer": string;
|
|
@@ -145,8 +145,6 @@ export declare const zhCN: {
|
|
|
145
145
|
readonly "sessionCard.input": "输入";
|
|
146
146
|
readonly "sessionCard.output": "输出";
|
|
147
147
|
readonly "sessionCard.partMain": "主会话";
|
|
148
|
-
readonly "sessionCard.partSide": "侧边聊天";
|
|
149
|
-
readonly "sessionCard.partSubagents": "子智能体";
|
|
150
148
|
readonly "sessionCard.started": "{at} 开始";
|
|
151
149
|
readonly "sessionCard.readCache": "读缓存 {n}";
|
|
152
150
|
readonly "reorder.saveFailed": "排序未能保存:{reason}";
|
|
@@ -160,6 +158,12 @@ export declare const zhCN: {
|
|
|
160
158
|
readonly "projectList.projects": "项目";
|
|
161
159
|
readonly "projectList.recent": "聊天";
|
|
162
160
|
readonly "projectList.newLooseChat": "新对话(不在项目中)";
|
|
161
|
+
readonly "welcome.lateNight": "夜深了";
|
|
162
|
+
readonly "welcome.morning": "早上好";
|
|
163
|
+
readonly "welcome.forenoon": "上午好";
|
|
164
|
+
readonly "welcome.noon": "中午好";
|
|
165
|
+
readonly "welcome.afternoon": "下午好";
|
|
166
|
+
readonly "welcome.evening": "晚上好";
|
|
163
167
|
readonly "empty.chat": "想聊点什么?";
|
|
164
168
|
readonly "empty.projectQuestion": "要在 {project} 内开发什么?";
|
|
165
169
|
readonly "empty.explore": "理解代码";
|
|
@@ -747,7 +751,8 @@ export declare const zhCN: {
|
|
|
747
751
|
readonly "questionNav.goTo": "跳转到第 {n} 个问题:{text}";
|
|
748
752
|
readonly "questionNav.imageMessage": "图片消息";
|
|
749
753
|
readonly "userMessage.showMore": "展开全文";
|
|
750
|
-
readonly "userMessage.
|
|
754
|
+
readonly "userMessage.fromChild": "来自子会话";
|
|
755
|
+
readonly "userMessage.fromPlugin": "来自{name}";
|
|
751
756
|
readonly "userMessage.showLess": "收起";
|
|
752
757
|
readonly "conversation.quoteToComposer": "引用到输入框";
|
|
753
758
|
readonly "composer.quotesTitle": "对话引用";
|
|
@@ -187,8 +187,8 @@ export interface ParentStatus {
|
|
|
187
187
|
export interface ParentSession {
|
|
188
188
|
id: string;
|
|
189
189
|
/**
|
|
190
|
-
*
|
|
191
|
-
* (`UserMessage.origin
|
|
190
|
+
* 把一件事排进父会话的队列(和人在输入框里排的同一条,落盘),父会话真闲着就马上开始。发出去的消息标着来自哪个插件
|
|
191
|
+
* (`UserMessage.origin` 是 `child`,`fromPlugin` 是建这个子会话的插件),前面带一句给模型的来处说明。`started` 是排上那一刻它就开始了。指令为空时抛错。
|
|
192
192
|
*/
|
|
193
193
|
enqueue(instruction: string): Promise<{
|
|
194
194
|
started: boolean;
|
|
@@ -274,6 +274,16 @@ export interface PromptSection {
|
|
|
274
274
|
*/
|
|
275
275
|
export interface ModelContext {
|
|
276
276
|
sessionId: string;
|
|
277
|
+
/**
|
|
278
|
+
* 这个会话挂在哪个会话下面,和 `SessionSummary.parentId` 同一件事:只有插件建的子会话(侧边聊天、子 Agent)有,
|
|
279
|
+
* 顶层会话不带这个键。按会话藏工具、改提示词时看它,不用为此去 `ctx.sessions.list()`。
|
|
280
|
+
*/
|
|
281
|
+
parentId?: string;
|
|
282
|
+
/**
|
|
283
|
+
* 建这个会话的插件,和 `SessionSummary.owner` 同一件事:插件的子会话和独立会话(`SessionAccess.create` 不给 `parentId`)有,
|
|
284
|
+
* 侧栏里的会话不带这个键。有 `owner` 没有 `parentId` 的是插件的独立会话。
|
|
285
|
+
*/
|
|
286
|
+
owner?: string;
|
|
277
287
|
cwd: string;
|
|
278
288
|
/** 当前项目的源文件夹;只授予读取,工作树隔离后为空。 */
|
|
279
289
|
projectRoots?: readonly string[];
|
|
@@ -313,10 +313,10 @@ export interface QueuedMessage {
|
|
|
313
313
|
preview: string;
|
|
314
314
|
/** 轮到它时整份发出去的东西。 */
|
|
315
315
|
message: OutgoingMessage;
|
|
316
|
-
/** 不是人在这个会话的输入框里排的:`
|
|
317
|
-
origin?: "
|
|
318
|
-
/**
|
|
319
|
-
|
|
316
|
+
/** 不是人在这个会话的输入框里排的:`child` 是它的一个子会话(比如侧边聊天)交过来的任务,发出去的消息带着同样的来处(`UserMessage.origin`)。 */
|
|
317
|
+
origin?: "child";
|
|
318
|
+
/** 子会话交过来的:是哪一个子会话交的,它的回执条(`HandedTask`)据此列出这一条。 */
|
|
319
|
+
childId?: string;
|
|
320
320
|
createdAt: number;
|
|
321
321
|
}
|
|
322
322
|
/**
|
|
@@ -468,11 +468,15 @@ export type AgentEvent = {
|
|
|
468
468
|
type: "projects";
|
|
469
469
|
projects: ProjectEntry[];
|
|
470
470
|
}
|
|
471
|
-
/**
|
|
471
|
+
/**
|
|
472
|
+
* 一条消息写进了会话,`count` 是写进它以后的条数(它是第 `count - 1` 条)。回复(assistant)同时顶替正在写的草稿,不另发清空草稿的 draft 事件。
|
|
473
|
+
* 按位置放而不是接在末尾:界面拿快照接上事件流时会把快照里已有的几条再收一遍,放回原位就不会重复。
|
|
474
|
+
*/
|
|
472
475
|
| {
|
|
473
476
|
type: "message";
|
|
474
477
|
sessionId: string;
|
|
475
478
|
message: Message;
|
|
479
|
+
count: number;
|
|
476
480
|
}
|
|
477
481
|
/** 会话截掉了尾巴(撤回、编辑重发):只留前 `count` 条消息,压缩记录和压缩行换成留下的那些。 */
|
|
478
482
|
| {
|
|
@@ -677,7 +681,7 @@ export interface AgentService {
|
|
|
677
681
|
moveQueued(sessionId: string, id: string, targetId: string, placement: "before" | "after"): Promise<void>;
|
|
678
682
|
/**
|
|
679
683
|
* 人在子会话(侧边聊天)的卡片上点了「交给主会话」:把这件事排进 `sessionId` 这个子会话的父会话,和插件的
|
|
680
|
-
* `ParentSession.enqueue`
|
|
684
|
+
* `ParentSession.enqueue` 同一条路(标着来自建这个子会话的插件)。只认父会话,不是子会话时报错。
|
|
681
685
|
*/
|
|
682
686
|
queueTask(sessionId: string, instruction: string): Promise<void>;
|
|
683
687
|
/**
|
|
@@ -692,16 +696,21 @@ export interface AgentService {
|
|
|
692
696
|
* 正在跑时报错。图片块可以只带 `media`(会话里存着的那张),不用把像素再传一遍。
|
|
693
697
|
* `files` 为真时先把截掉的那段改过的文件退回去(钩子 session.rewind),退不回去就报错,对话不截、也不重发。
|
|
694
698
|
* 插件的子会话也能编辑重发(插件经 `ConversationProps.reask` 同意了),但不带 `files`;撤回、演算、分叉仍然只给人自己的会话。
|
|
699
|
+
* `timestamp` 是界面上那一条的时间戳:序号对不上时按它认回那一条,认不出就报错,不截错地方。
|
|
695
700
|
*/
|
|
696
701
|
editMessage(sessionId: string, index: number, message: OutgoingMessage, options?: {
|
|
697
702
|
files?: boolean;
|
|
703
|
+
timestamp?: number;
|
|
698
704
|
}): Promise<void>;
|
|
699
|
-
/** 撤回:第 `index` 条(人发的消息)连同它之后的全部截掉。正在跑时报错。`files` 同 `editMessage`。 */
|
|
705
|
+
/** 撤回:第 `index` 条(人发的消息)连同它之后的全部截掉。正在跑时报错。`files`、`timestamp` 同 `editMessage`。 */
|
|
700
706
|
revertMessage(sessionId: string, index: number, options?: {
|
|
701
707
|
files?: boolean;
|
|
708
|
+
timestamp?: number;
|
|
702
709
|
}): Promise<void>;
|
|
703
|
-
/** 撤回、编辑重发第 `index` 条会截掉的那段改过哪些文件、能不能一起退回,只演算,不写(钩子 session.rewind
|
|
704
|
-
revertPlan(sessionId: string, index: number
|
|
710
|
+
/** 撤回、编辑重发第 `index` 条会截掉的那段改过哪些文件、能不能一起退回,只演算,不写(钩子 session.rewind)。`timestamp` 同 `editMessage`。 */
|
|
711
|
+
revertPlan(sessionId: string, index: number, options?: {
|
|
712
|
+
timestamp?: number;
|
|
713
|
+
}): Promise<RevertPlan>;
|
|
705
714
|
/**
|
|
706
715
|
* 从第 `index` 条分叉出一个新会话,原会话不动。人发的消息:新会话停在它之前;落定的回复:新会话带着它。
|
|
707
716
|
* `timestamp` 用来核对是不是那一条,对不上时按时间戳重找。正在跑也可以分叉。
|
|
@@ -35,7 +35,7 @@ export interface ToolCardProps {
|
|
|
35
35
|
/** 发送到卡片所属会话。只在真实点击/提交,或聚焦的卡片 iframe 发来的用户操作消息中同步调用;只读视图没有此入口。displayText 可提供气泡中显示的简短说明,text 仍是完整模型输入。 */
|
|
36
36
|
send?: (text: string, event: import("react").SyntheticEvent | MessageEvent, displayText?: string) => Promise<void>;
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
38
|
+
* 卡片画在插件的子会话(侧边聊天)里时才有:把一件事排进它父会话的队列,标着来自哪个插件,和
|
|
39
39
|
* `ToolRun.parent.enqueue` 同一条路。只在人点了卡片上写明要交出去什么的按钮时调(「交给主会话」),不拿来替人做决定。
|
|
40
40
|
*/
|
|
41
41
|
handToParent?(instruction: string): Promise<void>;
|
|
@@ -709,6 +709,12 @@ export interface SessionChange {
|
|
|
709
709
|
type: "messages" | "summary" | "deleted";
|
|
710
710
|
tools?: string[];
|
|
711
711
|
}
|
|
712
|
+
/** 欢迎页问候下面那一行(`ui.welcomeLine`):一句话,可以带出处。 */
|
|
713
|
+
export interface WelcomeLine {
|
|
714
|
+
text: string;
|
|
715
|
+
/** 出处(人名、书名),跟在这句话后面,小一号、更淡。 */
|
|
716
|
+
source?: string;
|
|
717
|
+
}
|
|
712
718
|
/** 插件在 `@` 菜单里提供的一项,选中只插入 token,不会自动发送。 */
|
|
713
719
|
export interface ComposerMention {
|
|
714
720
|
id: string;
|
|
@@ -818,6 +824,13 @@ export interface UiContext {
|
|
|
818
824
|
page(id: string, component: ComponentType<PageProps>, options: PageOptions): void;
|
|
819
825
|
/** 换皮肤:可重复调用更新,null 撤下自己的皮肤;最后注册的生效,撤下后前一个恢复。 */
|
|
820
826
|
skin(skin: Skin | null): void;
|
|
827
|
+
/** 应用的主题偏好,就是设置 › 外观那三张主题卡片(跟随系统、浅色、深色);在组件里用,偏好变了跟着重画。 */
|
|
828
|
+
useTheme(): "system" | "light" | "dark";
|
|
829
|
+
/**
|
|
830
|
+
* 改主题偏好,等同人在外观页点那张卡片:存盘、各窗口同步。有皮肤接管深浅模式时外观页的主题只读,这里同样不改;
|
|
831
|
+
* 要改先 `skin(null)` 撤下自己的皮肤。只在人点了插件界面上的按钮时调。
|
|
832
|
+
*/
|
|
833
|
+
setTheme(theme: "system" | "light" | "dark"): void;
|
|
821
834
|
/** 登记界面入口的订阅清理;停用、重载或加载失败时执行,旧入口不能再注册皮肤。 */
|
|
822
835
|
effect(dispose: () => void): void;
|
|
823
836
|
/**
|
|
@@ -875,6 +888,11 @@ export interface UiContext {
|
|
|
875
888
|
sessionId?: string;
|
|
876
889
|
}) => ComposerMention[] | Promise<ComposerMention[]>): void;
|
|
877
890
|
composerHint(hint: (session: SessionView) => string | undefined): void;
|
|
891
|
+
/**
|
|
892
|
+
* 换掉欢迎页问候下面的那一行(默认是「要在某个项目里开发什么?」):宿主每次画欢迎页时按界面语言问一次,等答复时那一行先空着,
|
|
893
|
+
* 回来了再淡入。只问最后注册的那一个;返回 undefined、空串或抛错时显示默认的那一句。一个插件只能有一个,再注册换掉上一个。
|
|
894
|
+
*/
|
|
895
|
+
welcomeLine(line: (locale: Locale) => WelcomeLine | undefined | PromiseLike<WelcomeLine | undefined>): void;
|
|
878
896
|
/**
|
|
879
897
|
* 画回复的正文、复制回复之前,把字过一遍:插件让模型在回复里顺带写的标记(下一步建议的 `:followup[…]`)从这里拿掉。
|
|
880
898
|
* 只改显示和复制,会话里存的、下一轮发给模型的都不变。正在生成的回复也会过,写了一半的标记也该拿掉。
|
|
@@ -231,14 +231,19 @@ export interface UserMessage {
|
|
|
231
231
|
/**
|
|
232
232
|
* Who sent this, when it was not the person looking at the transcript.
|
|
233
233
|
*
|
|
234
|
-
*
|
|
235
|
-
* message. Without this you would scroll back and find an instruction you have no memory
|
|
234
|
+
* `child`: a task one of this session's child sessions (the side chat, say) handed over lands
|
|
235
|
+
* here as an ordinary user message. Without this you would scroll back and find an instruction you have no memory
|
|
236
236
|
* of writing, in your own voice, with no way to tell where it came from.
|
|
237
237
|
*
|
|
238
238
|
* `parent` 是子智能体转录里「派它出去的那个 Agent 说的话」——开头那份任务、续跑时补的那一句。
|
|
239
239
|
* 面板上它不该画成人发的气泡:那不是看着面板的人说的。
|
|
240
240
|
*/
|
|
241
|
-
origin?: "
|
|
241
|
+
origin?: "child" | "parent";
|
|
242
|
+
/**
|
|
243
|
+
* `origin` 是 `child` 时,交来这件事的子会话是哪个插件建的(子会话的 `owner`,比如 `plume.side-chat`),界面按它标出插件的显示名。
|
|
244
|
+
* 宿主在这件事从队列里发出去时照子会话填上;那时子会话已经删掉了就不带,界面改用通用的说法。
|
|
245
|
+
*/
|
|
246
|
+
fromPlugin?: string;
|
|
242
247
|
/**
|
|
243
248
|
* 侧边聊天每次提问前附上的主会话进展,读到了哪里。
|
|
244
249
|
*
|
package/dist/theme.css
CHANGED
|
@@ -289,7 +289,7 @@
|
|
|
289
289
|
--font-mono: var(--plume-font-mono);
|
|
290
290
|
|
|
291
291
|
/*
|
|
292
|
-
*
|
|
292
|
+
* 字号八档加代码一档,都是界面字号的 calc,改一个数整套一起动。档与档至少差 1px,各自带行高和字距:
|
|
293
293
|
* 字越大字距越紧。字重不放进档里,和字号配对写在用的地方(title 用 medium,heading、display 用 semibold)。
|
|
294
294
|
*/
|
|
295
295
|
--text-caption: calc(var(--plume-ui-size) - 2px);
|
|
@@ -311,6 +311,10 @@
|
|
|
311
311
|
--text-display: calc(var(--plume-ui-size) + 16px);
|
|
312
312
|
--text-display--line-height: 1.2;
|
|
313
313
|
--text-display--letter-spacing: -0.022em;
|
|
314
|
+
/* 只给欢迎页的问候:整页只有这一行字,比设置页的标题还大一档;配 font-medium,大字不再加粗。 */
|
|
315
|
+
--text-hero: calc(var(--plume-ui-size) + 22px);
|
|
316
|
+
--text-hero--line-height: 1.15;
|
|
317
|
+
--text-hero--letter-spacing: -0.025em;
|
|
314
318
|
/* 代码另起一档:另一种字体,按列读;行高 1.6 比正文紧,缩进和括号的形状不被拉散。 */
|
|
315
319
|
--text-code: var(--plume-code-size);
|
|
316
320
|
--text-code--line-height: var(--plume-code-line-height);
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -46,13 +46,13 @@ description: 写、改、排查 Plume 插件:工具、钩子(tool.execute、
|
|
|
46
46
|
- 在最新结束的回复下面画卡片(`ui.turnFooter`),随转录滚走,新一轮开始就撤下;每一轮回复底下的操作排里加按钮(`ui.replyAction`);复用产品的高亮差异(`ui.FileDiff`)、文件预览(`ui.FilePreview`)、统一只读文档预览(`ui.DocumentPreview`;经 `ui.documentViewer` 按扩展名注册,读取与权限仍由调用方负责)、文件右键菜单(`ui.useFileMenu`),经 `ui.openFile` 打开指定会话工作目录内的文件。
|
|
47
47
|
- 用 `ui.codeBlock(language, Component)` 接管助手正文的某种围栏代码块;解析、具体 UI、状态和使用说明都归插件,收到原文、会话/消息/块标识、流式与完成状态;可选 `copyText` 在复制回复时提供当前可见值的文字。解析失败返回 `fallback`;宿主在渲染失败或停用时回到源码。完整块里真实点击后可经 `send(text, event)` 回到所属会话,不能在渲染或计时器中发送。
|
|
48
48
|
- 用 `ui.mentions` 给 `@` 菜单提供当前目录可用的智能体或技能引用;选中只插入 token,不自动发送。页面角标 `ui.pageBadge` 可带 `attention: true` 同时进入通知菜单。
|
|
49
|
-
- 给输入框一句灰字建议,人按 Tab 填进去(`ui.composerHint`);画回复前拿掉插件让模型顺带写的标记(`ui.replyText`,只改显示和复制);改会话标题(`ctx.sessions.setTitle`)。
|
|
49
|
+
- 给输入框一句灰字建议,人按 Tab 填进去(`ui.composerHint`);换掉欢迎页问候下面的那一行(`ui.welcomeLine`);画回复前拿掉插件让模型顺带写的标记(`ui.replyText`,只改显示和复制);改会话标题(`ctx.sessions.setTitle`)。
|
|
50
50
|
- 用设置里的生图模型画图、改图(`ctx.model.findImageModel`、`ctx.model.image`),图存进会话媒体目录(`ctx.media`);界面入口显示(`ui.mediaUrl`)、用产品的看图器看和标注(`ui.openImage`),另存、复制(`ui.saveImage`、`ui.copyImage`),把图带回某个会话的输入框(`ui.draftInSession`),跳到某次工具调用的卡片(`ui.revealToolCall`);媒体图可在文件面板打开或在系统文件管理器定位(`ui.openMediaFile`、`ui.revealMediaFile`,菜单名取 `ui.revealMediaLabel()`)。
|
|
51
51
|
- 侧栏会话行首空闲时画自己的记号(`ui.sessionMark`);删除确认框里补一句会连带、会留下什么(`ui.deleteNote`);人点了按钮时归档会话(`ctx.sessions.setArchived`);模型照人的要求归档它所在的对话时,工具那次调用经 `run.archiveAfterTurn()` 交给本体,这一轮以完成收场后才归档(只有顶层会话有)。
|
|
52
52
|
- 弹提示条(界面入口 `ui.notify`,逻辑入口 `ctx.notice`)、打开、关闭自己的面板(`ui.openPanel`、`ui.closePanel`)、把一段话写进输入框等人自己发(`ui.draftInSession`,`replace` 时整段换掉),或在项目签那一行的格子里,人点了写明要发什么的按钮时以人的身份直接发(`send`);逻辑入口用默认模型做一次性的小调用(`ctx.model.complete`)、读会话(`ctx.sessions`)、读模型调用的花销(`ctx.model.usage`)。一次性结果要覆盖持久化内容时用 `ModelCompletion.requireComplete` 拒收截断结果;经 `ctx.sessions.onContextReset` 在压缩、撤回或上下文来源变化时作废自己的缓存。
|
|
53
53
|
- 人点了明确的按钮时,经 `ui.openConversationDraft({ project?, cwd?, text })` 打开项目或目录的空白对话并准备草稿,保留已有草稿、不自动发送;经 `ui.openSettings(id)` 打开自己占导航一行(`page: "nav"`)的设置分区,经 `ui.openExternal(url)` 在系统浏览器打开不带凭据的 HTTP(S) 链接。
|
|
54
54
|
- 开一个新的顶层会话并发出第一句话(`ctx.sessions.launch`,定时任务到点了、人点了「在新会话里执行」):它进侧栏、不归插件,人接着聊,插件之后碰不到它;`done` 是第一轮怎么结束的,`idle` 在它第一次真闲下来时兑现(后台运行都回来、问出去的请求都有了答复、唤起的几轮也做完),`reportBack(text, status)` 把结果交回开出它的会话一次(`idle` 兑现时带着最后一轮的收场和最后一段回复,交回用它);`spawnedFrom` 记着从哪个会话、哪次调用(`run.toolCallId`)开出来,要交回时那次调用得是你自己的工具在那个会话里的调用,不然 `reportBack` 抛错;人点了按钮时经 `ui.openSession(id)` 打开它。页面那一行挂数目(`ui.pageBadge`),别处的按钮经 `ui.openPage(id)` 带人去自己的页面。
|
|
55
|
-
- 建自己的子会话并发送消息(`ctx.sessions.create` / `send`),或经 `start` / `wait` 在后台跑、经 `report` 把该次结果送给真实父会话;`steer` 在运行中插话,`onStopped` 撤销尚未启动的排队工作。停止父会话后后台结果不能再唤醒它。自己的子会话里,自己的工具那次调用拿得到父会话(`run.parent`:`enqueue` 排进它的队列、`stop` 停下、`carryOn` 接着做、`status`),工具卡片上人点的按钮经 `handToParent`
|
|
55
|
+
- 建自己的子会话并发送消息(`ctx.sessions.create` / `send`),或经 `start` / `wait` 在后台跑、经 `report` 把该次结果送给真实父会话;`steer` 在运行中插话,`onStopped` 撤销尚未启动的排队工作。停止父会话后后台结果不能再唤醒它。自己的子会话里,自己的工具那次调用拿得到父会话(`run.parent`:`enqueue` 排进它的队列、`stop` 停下、`carryOn` 接着做、`status`),工具卡片上人点的按钮经 `handToParent` 把一件事排进父会话;作用对象只有这个父会话,写进去的话标着来自你的插件(显示名)、不算人的授权。`models` 只给无密钥的模型目录,建子会话时可选模型和思考档位。
|
|
56
56
|
- 给自己的页面建一个一直在的助手:`ctx.sessions.create` 不给 `parentId` 就是插件的独立会话(不挂在任何会话下、不进侧栏,只有你能发),界面经 `ui.Conversation` 画它,审批卡就压在这个视图的输入框上方。
|
|
57
57
|
- 界面经 `ui.Conversation`、`ui.useSession` 复用转录与实时状态;可局部定制任务消息、去掉回报重复正文,操控输入框用 `mode: "steer"`。`ui.Markdown` 复用正文和文件链接;`ui.composerAbove` 在主输入框上方独占一行。经 `ui.quoteAction` 接主会话划词菜单,`ui.quoteInPanel` 把引用追加到自己的面板草稿;`composer.draftKey` 按标签保存文字、附件和引用。`/` 命令可以自己办掉、不发消息(`{ handled: true }`)。
|
|
58
58
|
- 界面经 `ui.onSessionsChanged` 订阅已保存消息、摘要和删除事件,刷新画廊等页面;经 `ui.ComposerSend` 复用发送/停止按钮,人真实点击停止时调用 `ui.stopSession`。终端插件经 `ui.terminalOpener` 承接文件树等入口的目录打开请求,经 `ui.terminalRunner` 承接代码块的「在终端运行」,经 `ui.findFile` / `ui.openFile` 打开输出里的路径、`ui.openLink` 打开网址、`ui.droppedPaths` 读拖进来的文件路径。
|
|
@@ -125,7 +125,7 @@ description: 写、改、排查 Plume 插件:工具、钩子(tool.execute、
|
|
|
125
125
|
|
|
126
126
|
## 可以照着写的插件
|
|
127
127
|
|
|
128
|
-
都在 Plume 插件仓库(plume-plugins)的 `plugins/` 下,`todo`、`commands`、`web`、`skills`、`git` 随应用发、在本体仓库的 `packages/builtin/` 下(拿不到仓库时,以模板和本 skill 里的代码片段为准):
|
|
128
|
+
都在 Plume 插件仓库(plume-plugins)的 `plugins/` 下,`todo`、`commands`、`web`、`skills`、`git`、`side-chat`、`subagents`、`plan` 随应用发、在本体仓库的 `packages/builtin/` 下(拿不到仓库时,以模板和本 skill 里的代码片段为准):
|
|
129
129
|
|
|
130
130
|
| 插件 | 看它的什么 |
|
|
131
131
|
| --- | --- |
|
|
@@ -122,7 +122,7 @@ ctx.tools.register({
|
|
|
122
122
|
|
|
123
123
|
### `model.context`
|
|
124
124
|
|
|
125
|
-
每次调模型前经过一次。最里层原样返回。输入输出都是 `{ sessionId, cwd, projectRoots, scratchDir, permissionMode, systemPrompt, sections, messages, tools }`(`scratchDir` 同 `run.scratchDir`,`projectRoots` 同 `run.projectRoots`;`permissionMode` 是这个会话现在的权限模式,含项目覆盖,取值同 `run.permissionMode()`,Windows 上受约束的命令跑 PowerShell、完全访问跑 Git Bash,plume.prompt 按它在环境里点名 shell;外层要求这一轮的命令都只读时设 `readOnly: true`,宿主不给,计划模式插件在 plume.prompt 外面(order -1001)设上,plume.prompt 就按只读那档点名 shell),只作用于这一次请求,会话里存的不变。
|
|
125
|
+
每次调模型前经过一次。最里层原样返回。输入输出都是 `{ sessionId, parentId?, owner?, cwd, projectRoots, scratchDir, permissionMode, systemPrompt, sections, messages, tools }`(`parentId`、`owner` 同会话摘要上的同名字段:插件的子会话两个都有,插件的独立会话只有 `owner`,侧栏里的会话都不带,按「是不是子会话」藏工具时看它们,不用每次去 `ctx.sessions.list()`;`scratchDir` 同 `run.scratchDir`,`projectRoots` 同 `run.projectRoots`;`permissionMode` 是这个会话现在的权限模式,含项目覆盖,取值同 `run.permissionMode()`,Windows 上受约束的命令跑 PowerShell、完全访问跑 Git Bash,plume.prompt 按它在环境里点名 shell;外层要求这一轮的命令都只读时设 `readOnly: true`,宿主不给,计划模式插件在 plume.prompt 外面(order -1001)设上,plume.prompt 就按只读那档点名 shell),只作用于这一次请求,会话里存的不变。
|
|
126
126
|
|
|
127
127
|
系统提示词按段给在 `sections` 里(`{ id, text, files? }[]`,`systemPrompt` 就是它们用空行连起来)。本体的段依次是 `identity`、`guidelines`、`boundaries`、`environment`、`tone`(设置里的语气,不是「专业严谨」时才有)、`userInstructions`(设置里写了全局指令时才有)、`projectInstructions`(项目里有 PLUME.md / AGENTS.md / CLAUDE.md 时才有)、`workspace`(工作目录和会话的临时目录),plume.tools 再加一段 `resources`(资源地址的清单)。加自己的说明就加一段,段名用自己的;要换掉或去掉本体的某段,按 `id` 改:
|
|
128
128
|
|
|
@@ -137,7 +137,7 @@ ctx.hook("model.context", (input, next) =>
|
|
|
137
137
|
|
|
138
138
|
`systemPrompt` 只读:宿主在每一层交接时按 `sections` 重拼,直接改它不作数。
|
|
139
139
|
|
|
140
|
-
系统提示词在会话里冻结:plume.prompt 在最外层把里层交回的段和这个会话冻结的那份比对,发出去的始终是冻结的那份,服务商的前缀缓存才接得上。某段中途变了(项目指令改了、自己的段换了内容、加了或去掉一段),改动作为一条增量消息(synthetic 的用户消息,`promptUpdate`
|
|
140
|
+
系统提示词在会话里冻结:plume.prompt 在最外层把里层交回的段和这个会话冻结的那份比对,发出去的始终是冻结的那份,服务商的前缀缓存才接得上。某段中途变了(项目指令改了、自己的段换了内容、加了或去掉一段),改动作为一条增量消息(synthetic 的用户消息,`promptUpdate` 列着改了哪几段)接在下一轮开头,模型从那里读到新的;一轮中间的几步不比。压缩以后(或撤回到第一条消息)按那时的段重新冻结。所以段的内容要只随该变的东西变:每次请求都不一样的东西(时间、计数)放进段里,每一轮都会多出一条增量。工具表也按会话冻结:`ctx.tools.register` 和撤下、插件装卸带来的变化(包括里层经 `model.context` 改的 `tools`)在下一轮开头才发给模型,原有工具的顺序不变、新的接在后面;一轮中间撤下的工具还留在表里,模型调它会拿到「没有这个工具」。
|
|
141
141
|
|
|
142
142
|
段里有内容是从文件读来的,就在段上带 `files: [{ path, chars }]`(`path` 绝对路径,`chars` 是那一截在 `text` 里的字数,连同包着它的标签):`projectInstructions`、`projectMemory` 两段的来源会按文件列在上下文用量的「记忆文件」里,人点了在文件面板里打开,文件在会话目录外(插件数据目录里的记忆、工作目录上层的指令)也能只读看,只认这次请求标出的这几个路径;别的段带了不显示。plume.prompt 给每份项目指令标了来源;自己出 `projectMemory` 段的插件也该标。改了某段 `text` 的层要么跟着改 `files`,要么去掉它(`{ id, text }` 新建的段本来就没有)。
|
|
143
143
|
|
|
@@ -355,7 +355,7 @@ const reply = (await ctx.sessions.messages(child.id)).findLast((message) => mess
|
|
|
355
355
|
- 后台用 `start`,它只等运行登记,不等回答;工具先交回子会话 id,后台用 `wait(run.id)` 等结果、`report(run.id, text)` 交给父会话。每次续跑是新的运行句柄,不能按子会话 id 重复交差。所有异步错误都要处理;不要用轮询或直接改会话库模拟送达。
|
|
356
356
|
- 父轮正常结束不取消后台,明确停止父会话会取消后台及待送达结果,不连带停止独立 `send` 的侧边聊天。插件停用、父会话删除或应用退出也会取消。插件自己的排队名额和未启动项经 `onStopped`、`onDeleted`、`ctx.effect` 清理。
|
|
357
357
|
- 续跑复用原子会话,重建该种类的提示词与工具限制后再次 `start`;别创建新会话丢掉上下文。种类、排队状态和模型偏好由插件存到 `ctx.dataDir`,宿主不识别这些业务字段。
|
|
358
|
-
- 子会话用什么提示词、哪些工具,经 `model.context`、`tool.execute` 按 `sessionId` 认出自己的会话来改(建的时候记下 id;重启后从 `list()` 里按 `owner`
|
|
358
|
+
- 子会话用什么提示词、哪些工具,经 `model.context`、`tool.execute` 按 `sessionId` 认出自己的会话来改(建的时候记下 id;重启后从 `list()` 里按 `owner` 找回)。`model.context` 的输入直接带着 `parentId`、`owner`,只想分清子会话和顶层会话时看它们就够。隐藏不等于禁用:藏起的工具在 `tool.execute` 里再拦一次。
|
|
359
359
|
- 子会话要人批准时,审批卡压在主输入框上方,卡上写着子会话的标题;界面入口用 `ui.Conversation` 画它、`ui.useSession` 读它(见 ui.md)。
|
|
360
360
|
- 子会话够得着它的父会话,只此一个:你自己建的子会话里,你自己注册的工具的那次调用拿得到 `run.parent`(别的插件的工具、别人的子会话、顶层会话里都是 `undefined`)。只在这次调用里用:调用答完以后再调它的方法就抛错。
|
|
361
361
|
|
|
@@ -369,7 +369,7 @@ async execute(args, run) {
|
|
|
369
369
|
}
|
|
370
370
|
```
|
|
371
371
|
|
|
372
|
-
排进去的话标着 `origin: "
|
|
372
|
+
排进去的话标着 `origin: "child"`、`fromPlugin` 是你的插件名(界面上是「来自<你的显示名>」)、前面带一句来处说明,不算人的授权,审批照常。人在卡片上点的「交给主会话」用 `ToolCardProps.handToParent`(见 ui.md)。
|
|
373
373
|
|
|
374
374
|
## 独立会话
|
|
375
375
|
|
package/skill/references/ui.md
CHANGED
|
@@ -31,6 +31,7 @@ export default {
|
|
|
31
31
|
| `ui.panel(id, Component, options)` | 往面板区加一个面板 |
|
|
32
32
|
| `ui.page(id, Component, options)` | 往侧栏加一个页面 |
|
|
33
33
|
| `ui.skin({ name?, mode?, light, dark })` / `ui.skin(null)` | 更新皮肤或撤下自己的皮肤;name 与 mode 都提供时管理配色和深浅模式 |
|
|
34
|
+
| `ui.useTheme()` / `ui.setTheme(theme)` | 读、改应用的主题偏好(设置 › 外观的跟随系统、浅色、深色),见「皮肤」 |
|
|
34
35
|
| `ui.ChatBackgroundSettings` | 自包含的背景设置组件,可传 `title`;管理全局的图片、显示位置、铺满/平铺、效果、浓淡,以及整个窗口的磨砂质感(外圈、停靠侧栏和对话区一起,不看皮肤、不要图;外观页不再有背景设置),支持选图和拖入图片,不向插件暴露磁盘路径 |
|
|
35
36
|
| `ui.onSessionsChanged(listener)` | 订阅所有会话的已保存消息、摘要和删除变化,包含当前未打开的会话;返回取消订阅函数,组件 effect 结束时调用,插件停用时也会撤下。事件 `SessionChange` 的字段见下文 |
|
|
36
37
|
| `ui.effect(dispose)` | 界面入口的生命周期清理,停用、重载或 apply 失败时逆序执行 |
|
|
@@ -41,6 +42,7 @@ export default {
|
|
|
41
42
|
| `ui.environmentSection(id, Component, { label, order, attention? })` | 往会话的环境卡片(`plume.diagnostics` 画)里加一节,收到 `{ session }`;返回 null 不占位、不画分隔线;`label` 是卡片「显示哪些内容」菜单里的名字;`attention(session)` 是 hook,有事等人决定时返回一句话,卡片收着时开关上挂圆点 |
|
|
42
43
|
| `ui.mentions(provider)` | 每次打开 `@` 时传 `{ cwd?, sessionId? }`,返回 `ComposerMention[]` 或 Promise:`id/title/kind/token` 必填,kind 是 subagent 或 plugin,可带 description/origin/avatar;只插入 token,用户自行发送,停用自动撤销 |
|
|
43
44
|
| `ui.composerHint(session => text)` | 输入框空着时的灰字建议,按 Tab 填进去 |
|
|
45
|
+
| `ui.welcomeLine(locale => ({ text, source }))` | 换掉欢迎页问候下面的那一行(默认问要在哪个项目里做什么),只问最后注册的那一个 |
|
|
44
46
|
| `ui.replyText(text => text)` | 画回复正文、复制回复前过一遍,拿掉插件让模型顺带写的标记 |
|
|
45
47
|
| `ui.sessionMark(Component)` | 侧栏会话行首空闲时的记号,收到 `{ session, fallback }`,不是自己的会话返回 `fallback` |
|
|
46
48
|
| `ui.deleteNote((sessionId, locale) => text)` | 删除确认框里补一句:删掉这个会话会连带什么、会留下什么 |
|
|
@@ -314,6 +316,18 @@ ui.replyText((text) => withoutFollowups(text));
|
|
|
314
316
|
- `ui.replyText(transform)`:回复正文画出来、复制出去之前过一遍,只改显示和复制,会话里存的、发给模型的都不变。正在生成的回复也会过:写了一半的标记也要拿掉。拿完只剩空白的一段整段不画。
|
|
315
317
|
- 两样每个插件各只能有一个,再注册换掉上一个;停用时撤下。
|
|
316
318
|
|
|
319
|
+
## 欢迎页的那一行
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
ui.welcomeLine((locale) => api.today(locale));
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
欢迎页问候(早上好、下午好……)下面的那一行字,默认是「要在某个项目里开发什么?」(不在项目里时「想聊点什么?」)。宿主每次画欢迎页时按界面语言问一次,可以返回 Promise:等答复时那一行先空着,回来了再淡入,不会先闪一下默认的那句;`text` 是这句话,`source` 是出处(可不给),跟在后面、小一号、更淡。
|
|
326
|
+
|
|
327
|
+
- 只问最后注册的那一个;它停用后还给前一个,都没有就是默认的那句。
|
|
328
|
+
- 返回 undefined、空串或抛错时显示默认的那句(抛错记日志)。每个插件只能有一个,再注册换掉上一个。
|
|
329
|
+
- 要花时间的事(调模型、读网络)放在逻辑入口里做好缓存,界面入口经 `remote()` 取;欢迎页常开,别每次打开都调一次模型。
|
|
330
|
+
|
|
317
331
|
## 侧栏的记号、删除确认框
|
|
318
332
|
|
|
319
333
|
```tsx
|
|
@@ -502,6 +516,8 @@ function ModelSetting() {
|
|
|
502
516
|
|
|
503
517
|
只传 `{ name: "Plume", mode: "system" }`、不传 token 时,使用产品原生配色并跟随系统;`light`、`dark` 则固定模式。这和 `ui.skin(null)` 不同:前者暂时管理配色并屏蔽原有种子覆盖,后者退出管理、恢复原用户偏好。
|
|
504
518
|
|
|
519
|
+
要让人在插件里切「跟随系统 / 浅色 / 深色」,就是改外观页那三张主题卡片,不是做一套皮肤:`ui.setTheme("dark")` 等同人在外观页点「深色」,存盘、各窗口同步;组件里用 `ui.useTheme()` 读当前选的是哪张。有皮肤接管深浅模式时外观页的主题只读,`setTheme` 同样不改,所以先 `ui.skin(null)` 撤下自己的皮肤再调。只在人点了插件界面上的按钮时调,不在工具或定时逻辑里替人改。
|
|
520
|
+
|
|
505
521
|
背景操作复用 `<ui.ChatBackgroundSettings title="…" />`,`title` 来自插件文案,省略时用产品的「会话背景」。用户在该组件做的修改直接保存到全局背景偏好,切换皮肤或停用插件不撤销;图片格式和体积检查、复制、失败提示和跨窗口更新均由宿主负责,不另存一份背景。
|
|
506
522
|
|
|
507
523
|
`root` 是窗口最外层与透明停靠侧栏共用的底色,默认跟随 `shell`;`sidebar` 只给 compact 浮出侧栏铺底,不给停靠侧栏另铺一层;`accent-dim` 是强调弱底,默认是强调色的 10% 透明底。数据皮肤可直接给出完整的面、线、字 token;若一份配色只适用于一个模式,给 `light`、`dark` 同一组 token,并用 `mode` 指定深浅模式。
|