@plumeai/plugin-sdk 0.1.5 → 0.1.7

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 (33) hide show
  1. package/dist/client/styles/src/index.d.ts +0 -12
  2. package/dist/client/ui/src/avatar/AgentAvatar.d.ts +1 -3
  3. package/dist/client/ui/src/avatar/AvatarPile.d.ts +1 -2
  4. package/dist/client/ui/src/i18n/messages/en.d.ts +11 -4
  5. package/dist/client/ui/src/i18n/messages/zh-CN.d.ts +12 -5
  6. package/dist/client/ui/src/index.d.ts +0 -15
  7. package/dist/client/ui/src/inputs/TextArea.d.ts +1 -2
  8. package/dist/client/ui/src/inputs/day-range.d.ts +0 -2
  9. package/dist/client/ui/src/layout/RetainedViews.d.ts +2 -13
  10. package/dist/client/ui/src/lib/tiling.d.ts +0 -21
  11. package/dist/client/ui/src/motion/freeze.d.ts +0 -1
  12. package/dist/client/ui/src/motion/loaders.d.ts +1 -6
  13. package/dist/client/ui/src/overlay/Overlay.d.ts +1 -2
  14. package/dist/client/ui/src/overlay/Tooltip.d.ts +1 -1
  15. package/dist/client/ui/src/primitives/ClosableTab.d.ts +1 -2
  16. package/dist/client/ui/src/primitives/CountUp.d.ts +1 -4
  17. package/dist/contract/src/plugin.d.ts +37 -12
  18. package/dist/contract/src/service.d.ts +13 -6
  19. package/dist/contract/src/ui.d.ts +13 -2
  20. package/dist/infra/rpc/src/endpoint.d.ts +1 -1
  21. package/dist/infra/rpc/src/rpc.d.ts +2 -7
  22. package/dist/theme.css +7 -1
  23. package/dist/ui.d.ts +1 -1
  24. package/package.json +1 -1
  25. package/skill/SKILL.md +2 -2
  26. package/skill/references/components.md +4 -80
  27. package/skill/references/logic.md +9 -7
  28. package/skill/references/ui.md +17 -2
  29. package/dist/client/ui/src/avatar/AvatarStack.d.ts +0 -26
  30. package/dist/client/ui/src/lib/typing.d.ts +0 -19
  31. package/dist/client/ui/src/motion/TypedText.d.ts +0 -9
  32. package/dist/client/ui/src/primitives/GitHubMark.d.ts +0 -12
  33. package/dist/client/ui/src/primitives/RowDeleteButton.d.ts +0 -6
@@ -105,19 +105,7 @@ export declare const card: {
105
105
  };
106
106
  };
107
107
  };
108
- /** 卡片底部一行:左边是说明,右边是按钮。 */
109
- export declare const actions: {
110
- row: string;
111
- note: string;
112
- };
113
108
  export declare const code: {
114
109
  block: string;
115
- diff: string;
116
- diffLine: string;
117
- diffKind: {
118
- same: string;
119
- add: string;
120
- remove: string;
121
- };
122
110
  };
123
111
  export {};
@@ -22,8 +22,6 @@ export interface AgentAvatarProps {
22
22
  /** 像素边长。 */
23
23
  size?: number;
24
24
  mood?: AvatarMood;
25
- /** 右下角的小圆点:在跑是强调色、会一圈圈往外扩;出错是红的。 */
26
- badge?: "running" | "failed" | null;
27
25
  /**
28
26
  * 悬停时眼睛跟着指针、身子弹一下。默认开。
29
27
  *
@@ -39,4 +37,4 @@ export interface AgentAvatarProps {
39
37
  cheer?: string | number | null;
40
38
  className?: string;
41
39
  }
42
- export declare function AgentAvatar({ avatar, size, mood, badge, interactive, host, seed, label, cheer, className, }: AgentAvatarProps): import("react").JSX.Element;
40
+ export declare function AgentAvatar({ avatar, size, mood, interactive, host, seed, label, cheer, className }: AgentAvatarProps): import("react").JSX.Element;
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * 一摞脸:派出去不止一个时,叠在一起说「有好几个」。
3
3
  *
4
- * 和 `AvatarStack` 是两件事。那一排并排摆开,每张脸都看得清表情,适合「这一批都是谁」的场合;
5
- * 这一摞叠着放,占地方小,说的是「这里有好几个,点开看」——放在一行字旁边、一个切换器上这类挤的地方。
4
+ * 叠着放,占地方小,说的是「这里有好几个,点开看」——放在一行字旁边、一个切换器上这类挤的地方。
6
5
  * 表情仍然在:在跑的还在颠,只是被邻居盖住半张脸。
7
6
  *
8
7
  * 叠放有先后:后一张压在前一张上面,像一张一张往右放下去的牌,最后放下的那张完整露出来,「+N」
@@ -73,6 +73,7 @@ export declare const en: {
73
73
  "settings.models": string;
74
74
  "settings.about": string;
75
75
  "settings.personalization": string;
76
+ "settings.saveFailed": string;
76
77
  "settings.agents": string;
77
78
  "agents.title": string;
78
79
  "agents.intro": string;
@@ -185,6 +186,12 @@ export declare const en: {
185
186
  "projectList.projects": string;
186
187
  "projectList.recent": string;
187
188
  "projectList.newLooseChat": string;
189
+ "welcome.lateNight": string;
190
+ "welcome.morning": string;
191
+ "welcome.forenoon": string;
192
+ "welcome.noon": string;
193
+ "welcome.afternoon": string;
194
+ "welcome.evening": string;
188
195
  "empty.chat": string;
189
196
  "empty.projectQuestion": string;
190
197
  "empty.explore": string;
@@ -337,6 +344,9 @@ export declare const en: {
337
344
  "appearance.chatBackgroundErrorFailed": string;
338
345
  "appearance.chatBackgroundEmpty": string;
339
346
  "appearance.chatBackgroundShow": string;
347
+ "appearance.chatBackgroundTexture": string;
348
+ "appearance.chatBackgroundTextureNone": string;
349
+ "appearance.chatBackgroundTextureGrain": string;
340
350
  "appearance.chatBackgroundOpacityStep": string;
341
351
  "appearance.chatBackgroundFit": string;
342
352
  "appearance.chatBackgroundFitCover": string;
@@ -1123,14 +1133,11 @@ export declare const en: {
1123
1133
  "update.download": string;
1124
1134
  "update.downloadingPercent": string;
1125
1135
  "update.failed": string;
1126
- "update.goInstall": string;
1127
1136
  "update.newVersion": string;
1128
1137
  "update.pausedPercent": string;
1129
1138
  "update.preparing": string;
1130
1139
  "update.reading": string;
1131
- "update.readyInstaller": string;
1132
1140
  "update.readyRestart": string;
1133
- "update.reopenInstaller": string;
1134
1141
  "update.restartNow": string;
1135
1142
  "update.restartToUpdate": string;
1136
1143
  "update.resume": string;
@@ -1141,7 +1148,6 @@ export declare const en: {
1141
1148
  "updateDialog.downloadedSoFar": string;
1142
1149
  "updateDialog.downloading": string;
1143
1150
  "updateDialog.failedTitle": string;
1144
- "updateDialog.installerMissing": string;
1145
1151
  "updateDialog.notReady": string;
1146
1152
  "updateDialog.pause": string;
1147
1153
  "updateDialog.paused": string;
@@ -1387,6 +1393,7 @@ export declare const en: {
1387
1393
  "storage.exportDetail": string;
1388
1394
  "storage.exportDialogTitle": string;
1389
1395
  "storage.exportFailed": string;
1396
+ "cleanup.failed": string;
1390
1397
  "storage.exportTitle": string;
1391
1398
  "storage.exported": string;
1392
1399
  "storage.exporting": string;
@@ -73,6 +73,7 @@ export declare const zhCN: {
73
73
  readonly "settings.models": "模型设置";
74
74
  readonly "settings.about": "关于";
75
75
  readonly "settings.personalization": "个性化";
76
+ readonly "settings.saveFailed": "设置没能保存:{reason}";
76
77
  readonly "settings.agents": "智能体";
77
78
  readonly "agents.title": "智能体";
78
79
  readonly "agents.intro": "把一类任务交给单独配置了模型、工具和指令的子智能体。";
@@ -159,6 +160,12 @@ export declare const zhCN: {
159
160
  readonly "projectList.projects": "项目";
160
161
  readonly "projectList.recent": "聊天";
161
162
  readonly "projectList.newLooseChat": "新对话(不在项目中)";
163
+ readonly "welcome.lateNight": "夜深了";
164
+ readonly "welcome.morning": "早上好";
165
+ readonly "welcome.forenoon": "上午好";
166
+ readonly "welcome.noon": "中午好";
167
+ readonly "welcome.afternoon": "下午好";
168
+ readonly "welcome.evening": "晚上好";
162
169
  readonly "empty.chat": "想聊点什么?";
163
170
  readonly "empty.projectQuestion": "要在 {project} 内开发什么?";
164
171
  readonly "empty.explore": "理解代码";
@@ -319,6 +326,9 @@ export declare const zhCN: {
319
326
  readonly "appearance.chatBackgroundErrorFailed": "图片导入失败,原背景图未改变。";
320
327
  readonly "appearance.chatBackgroundEmpty": "尚未设置背景图";
321
328
  readonly "appearance.chatBackgroundShow": "显示位置";
329
+ readonly "appearance.chatBackgroundTexture": "质感";
330
+ readonly "appearance.chatBackgroundTextureNone": "无";
331
+ readonly "appearance.chatBackgroundTextureGrain": "磨砂";
322
332
  readonly "appearance.chatBackgroundOpacityStep": "浓度 {percent}%";
323
333
  readonly "appearance.chatBackgroundFit": "铺放方式";
324
334
  readonly "appearance.chatBackgroundFitCover": "铺满";
@@ -471,7 +481,7 @@ export declare const zhCN: {
471
481
  readonly "market.unreadable": "{n} 个插件有问题,去设置里看看";
472
482
  readonly "market.notInCatalog": "插件目录里已经没有它了";
473
483
  readonly "market.unreachable": "连不上插件市场({reason}),列出的是已装的和上次读到的";
474
- readonly "market.localOwned": "这是本机直接放进目录的插件,到它的目录里删";
484
+ readonly "market.localOwned": "本地上传了同名的插件,先卸掉那一份";
475
485
  readonly "market.builtinOwned": "这是随应用发的插件,不能卸载、停用";
476
486
  readonly "market.notAPlugin": "这个文件夹不是构建好的插件:文件夹名只能用小写字母、数字和连字符,里面要有 dist/index.mjs";
477
487
  readonly "market.pickPluginFolder": "选择插件文件夹";
@@ -979,14 +989,11 @@ export declare const zhCN: {
979
989
  readonly "update.download": "下载安装";
980
990
  readonly "update.downloadingPercent": "下载中 {percent}%";
981
991
  readonly "update.failed": "下载失败";
982
- readonly "update.goInstall": "去安装";
983
992
  readonly "update.newVersion": "新版本 {version}";
984
993
  readonly "update.pausedPercent": "已暂停 {percent}%";
985
994
  readonly "update.preparing": "准备中";
986
995
  readonly "update.reading": "正在读取版本…";
987
- readonly "update.readyInstaller": "已下载完成,安装程序已经打开。按它的提示装完,再重新启动 Plume 就是新版本了。";
988
996
  readonly "update.readyRestart": "已下载完成。重启 Plume 即可用上新版本,正在进行的对话会中断。";
989
- readonly "update.reopenInstaller": "重新打开安装包";
990
997
  readonly "update.restartNow": "立即重启";
991
998
  readonly "update.restartToUpdate": "重启更新";
992
999
  readonly "update.resume": "继续下载";
@@ -997,7 +1004,6 @@ export declare const zhCN: {
997
1004
  readonly "updateDialog.downloadedSoFar": "(已下 {size})";
998
1005
  readonly "updateDialog.downloading": "正在下载";
999
1006
  readonly "updateDialog.failedTitle": "这次没能写完";
1000
- readonly "updateDialog.installerMissing": "没找到下载好的安装包,重新下载一次";
1001
1007
  readonly "updateDialog.notReady": "更新没有准备好,重新下载一次再试";
1002
1008
  readonly "updateDialog.pause": "暂停";
1003
1009
  readonly "updateDialog.paused": "已暂停";
@@ -1207,6 +1213,7 @@ export declare const zhCN: {
1207
1213
  readonly "storage.exportDetail": "把所有会话另存为一个 SQLite 文件(.db),任何 SQLite 工具都能打开";
1208
1214
  readonly "storage.exportDialogTitle": "导出会话记录";
1209
1215
  readonly "storage.exportFailed": "导出失败:{reason}";
1216
+ readonly "cleanup.failed": "清除失败:{reason}";
1210
1217
  readonly "storage.exportTitle": "导出会话记录";
1211
1218
  readonly "storage.exported": "已导出到 {path}";
1212
1219
  readonly "storage.exporting": "正在导出…";
@@ -13,7 +13,6 @@ export type { MessageKey, ResolvedUiLocale } from "./i18n/messages/index.ts";
13
13
  export { activeLocale, translate } from "./i18n/translate.ts";
14
14
  export * from "./avatar/AgentAvatar.tsx";
15
15
  export * from "./avatar/AvatarPile.tsx";
16
- export * from "./avatar/AvatarStack.tsx";
17
16
  export * from "./avatar/art.ts";
18
17
  export * from "./fileIcon.tsx";
19
18
  export * from "./hooks/useCopied.ts";
@@ -53,10 +52,8 @@ export * from "./lib/hour-style.ts";
53
52
  export * from "./lib/number-draft.ts";
54
53
  export * from "./lib/relative-time.ts";
55
54
  export * from "./lib/tiling.ts";
56
- export * from "./lib/typing.ts";
57
55
  export * from "./motion/RollWords.tsx";
58
56
  export * from "./motion/RollingText.tsx";
59
- export * from "./motion/TypedText.tsx";
60
57
  export * from "./motion/freeze.ts";
61
58
  export * from "./motion/held.ts";
62
59
  export * from "./motion/loaders.tsx";
@@ -82,10 +79,8 @@ export * from "./primitives/Caret.tsx";
82
79
  export * from "./primitives/ChoiceMark.tsx";
83
80
  export * from "./primitives/ClosableTab.tsx";
84
81
  export * from "./primitives/CountUp.tsx";
85
- export * from "./primitives/GitHubMark.tsx";
86
82
  export * from "./primitives/IconButton.tsx";
87
83
  export * from "./primitives/MarkNumber.tsx";
88
- export * from "./primitives/RowDeleteButton.tsx";
89
84
  export * from "./primitives/Skeleton.tsx";
90
85
  export * from "./primitives/TabStrip.tsx";
91
86
  export * from "./primitives/Text.tsx";
@@ -126,15 +121,5 @@ export declare function Card({ title, subtitle, status, tone, children }: CardPr
126
121
  export declare function CodeBlock({ children }: {
127
122
  children: string;
128
123
  }): import("react").JSX.Element;
129
- type DiffLine = {
130
- kind: "same" | "add" | "remove";
131
- text: string;
132
- };
133
- /** 按行比较。用 LCS,够看小段改动;大文件的 diff 不在这里做。 */
134
- export declare function diffLines(before: string, after: string): DiffLine[];
135
- export declare function DiffView({ before, after }: {
136
- before: string;
137
- after: string;
138
- }): import("react").JSX.Element;
139
124
  export * from "./inputs/ColorField.tsx";
140
125
  export * from "./inputs/Slider.tsx";
@@ -17,14 +17,13 @@ import { type ComponentPropsWithoutRef } from "react";
17
17
  * 主输入框、消息编辑框这类框里还站着别的东西(工具栏、附件条、那排按钮)的不走这里,外壳是各自的。
18
18
  * 共享的是同一个 `useFieldFade` 和同一条遮罩。
19
19
  */
20
- export declare function TextArea({ value, onChange, shell, className, fieldRef, resizable, ...rest }: {
20
+ export declare function TextArea({ value, onChange, shell, className, resizable, ...rest }: {
21
21
  value: string;
22
22
  onChange: (value: string) => void;
23
23
  /** 外壳上的额外类。高度、外边距、宽度——和这个框在页面上待在哪儿有关的事,调用方说了算。 */
24
24
  shell?: string;
25
25
  /** 字那一层的额外类:等宽、字号、`h-[180px]` 这类只跟可写区域有关的。 */
26
26
  className?: string;
27
- fieldRef?: React.RefObject<HTMLTextAreaElement | null>;
28
27
  /** 能不能用鼠标拉高。只给写长文的地方,比如一份系统提示词。 */
29
28
  resizable?: boolean;
30
29
  } & Omit<ComponentPropsWithoutRef<"textarea">, "value" | "onChange" | "className" | "ref">): import("react").JSX.Element;
@@ -53,5 +53,3 @@ export declare function isInterior(day: string, range: DayRange): boolean;
53
53
  * 前面」得到的是一段空范围,而且没有任何提示说刚才发生了什么。
54
54
  */
55
55
  export declare function pickDay(range: DayRange, day: string): DayRange;
56
- /** 这段日子有几天,两头都算。哪一头空着都数不出来。 */
57
- export declare function rangeLength(range: DayRange): number | null;
@@ -1,19 +1,8 @@
1
1
  /** 留住少数几个看过的页面;藏起来的页面暂停 effect,保留局部状态。 */
2
- export declare function RetainedViews<T extends string>({ active, render, limit, pageClassName, slide, }: {
2
+ export declare function RetainedViews<T extends string>({ active, render, limit }: {
3
3
  active: T;
4
4
  render: (key: T) => React.ReactNode;
5
5
  limit?: number;
6
- /** 到达时的类。不同的页面可以换一种更长的淡入加升起。 */
7
- pageClassName?: string;
8
- /**
9
- * 标签,按标签条上的顺序:每次切换,新页面从它所在的那一侧滑进来并淡入,而不是一帧之内换掉。
10
- *
11
- * 用 Web Animations API 在布局 effect 里播,不用类。用类的话——见下面 `seen` 的注释——从 `display: none`
12
- * 回来的页面会先在终点画一帧,动画才接上。布局 effect 在页面显示之后、任何东西画出来之前跑,所以谁看到的
13
- * 第一帧都已经是滑入的第一帧。回到一个页面也播:标签条是来回切的地方,只在第一次有动画的切换,第二次读起来
14
- * 就像坏了。
15
- */
16
- slide?: readonly T[];
17
6
  }): import("react").JSX.Element;
18
7
  /**
19
8
  * 回来的那一页,里面的 CSS 动画不重演。
@@ -26,7 +15,7 @@ export declare function RetainedViews<T extends string>({ active, render, limit,
26
15
  * 跟着一起拆了),离开期间写完的回复要等回来、订阅重新挂上才补进来——那是紧跟着的另一次提交,
27
16
  * 当场处理会漏掉它,而它同样是在补账,不是此刻发生的事。两次提交都落在第一帧之前,逐帧量过。
28
17
  *
29
- * 页面自己那层(`pageClassName`)不算:那是换页本身的到达动画,每次换页都该有。转圈、呼吸这种
18
+ * 页面自己那层(`plume-page-enter`)不算:那是换页本身的到达动画,每次换页都该有。转圈、呼吸这种
30
19
  * 没有终点的照常跑——它们也没法跳到终点,`finish()` 会抛错。
31
20
  */
32
21
  export declare function finishReplayedAnimations(page: HTMLElement): void;
@@ -18,15 +18,6 @@ export interface Box {
18
18
  width: number;
19
19
  height: number;
20
20
  }
21
- /** 两棵树共有的形状:只要分支,叶子长什么样这里不关心。 */
22
- type Tiling = {
23
- type: "leaf";
24
- } | {
25
- type: "split";
26
- dir: Axis;
27
- sizes: readonly number[];
28
- children: readonly Tiling[];
29
- };
30
21
  /**
31
22
  * 两个窗格之间一条能拖的边界。
32
23
  *
@@ -48,7 +39,6 @@ export interface SplitterBox extends Box {
48
39
  */
49
40
  split: Box;
50
41
  }
51
- export declare const FULL: Box;
52
42
  /**
53
43
  * 分隔条的抓取区,跨在两个窗格的边界上。
54
44
  *
@@ -67,16 +57,6 @@ export declare const GRIP_SPAN = 30;
67
57
  * 帧与帧之间字符串不变——这一点要紧:每帧都变的样式字符串会打断它本该在过渡的那个过渡。
68
58
  */
69
59
  export declare const pct: (fraction: number) => string;
70
- /** 给定在轴上从哪开始、占多少,从父框里切出孩子的框。 */
71
- export declare function slice(rect: Box, dir: Axis, offset: number, share: number): Box;
72
- /**
73
- * 所有能拖的边界。
74
- *
75
- * n 个孩子的分支有 n−1 条——分支的外沿是包着它的东西的边,不是两个窗格之间的边界。
76
- *
77
- * 每条都画成零厚度的线;抓取区由组件加,只有组件知道九个像素是多宽。
78
- */
79
- export declare function layoutSplitters(tree: Tiling, within?: Box): SplitterBox[];
80
60
  /**
81
61
  * 把指针位置换算成分隔条近侧窗格该占的份额。
82
62
  *
@@ -86,4 +66,3 @@ export declare function layoutSplitters(tree: Tiling, within?: Box): SplitterBox
86
66
  * `position` 是拖动轴上的客户区坐标:横向分支是 `clientX`,纵向是 `clientY`。
87
67
  */
88
68
  export declare function shareFromPointer(handle: SplitterBox, position: number, container: Box): number;
89
- export {};
@@ -19,4 +19,3 @@
19
19
  * 多少走的折叠组——这里只给它们加标记,样式引擎经类索引就能找到,不用遍历任何东西。
20
20
  */
21
21
  export declare function freezeMotion(): () => void;
22
- export declare function inertPanes(): () => void;
@@ -39,15 +39,10 @@ export declare function StatusSpinner({ size, className }: {
39
39
  *
40
40
  * 轨道是这个记号的重点,不是装饰:亮弧单独转的话,图形的轮廓跟着弧一起走,在按钮那个方寸
41
41
  * 之间会读成「有东西在里面甩」。垫一圈淡的,形状就恒定是一个完整的圆,动的只有亮处。
42
- *
43
- * 轨道浓度走 `--plume-track`,默认 0.18。实心底的按钮(`bg-ink`、`bg-accent`)上 `currentColor`
44
- * 是底色的反色,0.18 基本看不见,那几处传 `onFill`——见 `ui/motion.css` 里那一档的注释。
45
42
  */
46
- export declare function ActionSpinner({ size, className, onFill, }: {
43
+ export declare function ActionSpinner({ size, className }: {
47
44
  size?: number;
48
45
  className?: string;
49
- /** 画在实心底的按钮上(`bg-ink` / `bg-accent`),轨道要更浓才看得见。 */
50
- onFill?: boolean;
51
46
  }): import("react").JSX.Element;
52
47
  /**
53
48
  * 一圈波纹离开静止的中心,颜色一路走过调色板。
@@ -7,10 +7,9 @@ export declare const OverlayDepth: import("react").Context<number>;
7
7
  /** 播完退场,再运行 `after`——没有别的要做时运行 `onClose`。 */
8
8
  export type Dismiss = (after?: () => void) => void;
9
9
  /** Escape、点遮罩、取消和完成共用一套生命周期,嵌套的对话框也一样。 */
10
- export declare function Overlay({ children, onClose, align, width, label, returnFocus, }: {
10
+ export declare function Overlay({ children, onClose, width, label, returnFocus, }: {
11
11
  children: React.ReactNode | ((dismiss: Dismiss) => React.ReactNode);
12
12
  onClose: () => void;
13
- align?: "center" | "bottom";
14
13
  width?: number;
15
14
  label?: string;
16
15
  returnFocus?: HTMLElement;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 应用的悬停提示,写成 JSX 包装,比属性读起来顺。
3
3
  *
4
- * 它只给子元素打标记——气泡本身由 `tooltip.ts` 里那一个文档级监听器画。所以两种写法表现完全一样,
4
+ * 它只给子元素打标记——气泡本身由 `tooltip-attribute.ts` 里那一个文档级监听器画。所以两种写法表现完全一样,
5
5
  * 这很重要,因为第三方库自己建的按钮只能靠属性够到。
6
6
  *
7
7
  * 取代浏览器的 `title`:那个改不了样式,要等一秒半左右,不跟应用的字体和主题,在 macOS 上画成一个
@@ -9,7 +9,7 @@ import type { DataAttributes } from "./Button.tsx";
9
9
  * ✕ 只出现在指着的那个和当前那个标签上。每个标签都带一个的话,三个标签的一条就成了六个目标,关闭
10
10
  * 按钮和名字一样响——而这一条的全部用处就是让人按名字挑。
11
11
  */
12
- export declare function ClosableTab({ children, current, onSelect, onClose, closeLabel, tip, onContextMenu, className, labelClassName, ...data }: DataAttributes & {
12
+ export declare function ClosableTab({ children, current, onSelect, onClose, closeLabel, tip, onContextMenu, className, ...data }: DataAttributes & {
13
13
  children: ReactNode;
14
14
  current: boolean;
15
15
  onSelect: () => void;
@@ -19,5 +19,4 @@ export declare function ClosableTab({ children, current, onSelect, onClose, clos
19
19
  tip?: string;
20
20
  onContextMenu?: (event: MouseEvent<HTMLDivElement>) => void;
21
21
  className?: string;
22
- labelClassName?: string;
23
22
  }): import("react").JSX.Element;
@@ -11,11 +11,8 @@
11
11
  */
12
12
  export declare function CountUp({ value, className,
13
13
  /** 走动中的数怎么写。默认是四舍五入的整数。 */
14
- format, bidirectional, ms, ...rest }: {
14
+ format, ...rest }: {
15
15
  value: number;
16
16
  className?: string;
17
17
  format?: (shown: number) => string;
18
- /** 往下也走——见 `useCountUp`。给按更短的时段重算的读数。 */
19
- bidirectional?: boolean;
20
- ms?: number;
21
18
  } & Omit<React.ComponentProps<"span">, "className" | "children">): import("react").JSX.Element;
@@ -297,13 +297,9 @@ export interface ModelContext {
297
297
  readOnly?: boolean;
298
298
  /** 设置 › 个性化:语气和全局指令。 */
299
299
  personalization: Personalization;
300
- /** 发出去的系统提示词,就是 `sections` 依次用空行连起来。 */
300
+ /** 发出去的系统提示词,就是 `sections` 依次用空行连起来。只读:每层交接时宿主按 `sections` 重拼,改它不作数。 */
301
301
  systemPrompt: string;
302
- /**
303
- * 系统提示词按段拆开。改哪个都行,交给下一层时宿主把另一个跟上:只改了 `sections`,`systemPrompt` 重新拼;
304
- * 只改了 `systemPrompt` 而且是在后面接了一段,接上的那段成为末尾的 `extension` 段,整个换掉则只剩这一段 `extension`。
305
- * 两个都改了以 `sections` 为准。
306
- */
302
+ /** 系统提示词按段拆开。加、换、删都改这里,交给下一层时宿主把 `systemPrompt` 重新拼。 */
307
303
  sections: PromptSection[];
308
304
  messages: Message[];
309
305
  tools: ToolSpec[];
@@ -473,16 +469,16 @@ export interface ToolResultView {
473
469
  }
474
470
  /**
475
471
  * 钩子 context.compact 的结论:不给是不压。最里层不压。
476
- * 给了 `keep`,`history` 里从 `keep` 起原样留着,之前的(含上一次压缩换上的那几条)换成 `head`;不给 `head` 就是 `summary`
477
- * 加一句确认。`keep` 要落在上一次换上的那几条之后、最后一条之前,而且不能是工具结果(会和它回答的调用拆开);`summary`
478
- * 只在按条丢弃(`via: "dropped"`,这时必须给 `head`)时可以是空串;不合规矩的宿主当作压缩失败。
472
+ * 给了 `keep`,`history` 里从 `keep` 起原样留着,之前的(含上一次压缩换上的那几条)换成 `head`。
473
+ * `keep` 要落在上一次换上的那几条之后、最后一条之前,而且不能是工具结果(会和它回答的调用拆开);`summary`
474
+ * 只在按条丢弃(`via: "dropped"`)时可以是空串;不合规矩的宿主当作压缩失败。
479
475
  * `views` 是留下的那截里剪过的工具结果。只剪了工具结果、没有可摘要的(`pruneOnly`)时只给 `views`。
480
476
  */
481
477
  export type CompactSummary = {
482
478
  keep: number;
483
479
  summary: string;
484
480
  /** 代替边界之前那段的消息(以用户消息开头),宿主原样存下、以后每次照发。 */
485
- head?: Message[];
481
+ head: Message[];
486
482
  via?: CompactionVia;
487
483
  views?: ToolResultView[];
488
484
  } | {
@@ -1040,10 +1036,15 @@ export type WebPageEvent = {
1040
1036
  url: string;
1041
1037
  redirect: boolean;
1042
1038
  }
1043
- /** 页面要开新窗口(`window.open`、`target=_blank`):宿主不开,交给插件决定。 */
1039
+ /**
1040
+ * 页面要开新窗口(`window.open`、`target=_blank`):宿主不开,交给插件决定。`window` 是脚本按尺寸开的小窗(第三方登录、
1041
+ * 支付):宿主已经把它开成挂在 Plume 窗口上的小窗口,和开它的页面连着(登录完经 `window.opener` 交回结果、自己关掉),
1042
+ * 插件不用再开;它给人用,插件驱动不了。
1043
+ */
1044
1044
  | {
1045
1045
  type: "popup";
1046
1046
  url: string;
1047
+ window?: true;
1047
1048
  }
1048
1049
  /**
1049
1050
  * 宿主照 `answerDialogs` 替人答了页面的一个对话框。`beforeunload` 是离开页面的提示,没有文字;答 false 时留在原页,
@@ -1088,6 +1089,24 @@ export interface WebInspection {
1088
1089
  mimeType: "image/png";
1089
1090
  };
1090
1091
  }
1092
+ /** 从本机 Chrome 同步登录状态的现状,见 `WebPages.chromeLogins`。 */
1093
+ export interface ChromeLoginSync {
1094
+ /** 开着:应用启动时同步一次,之后 Chrome 的 cookie 一变就跟着同步。 */
1095
+ enabled: boolean;
1096
+ /**
1097
+ * 同步不了的原因:`unsupported` 这个系统还不支持(目前只有 macOS),`no-chrome` 没找到 Chrome 的数据,
1098
+ * `no-access` 系统不让读(macOS 要在「隐私与安全性 › 完全磁盘访问权限」里给 Plume),`keychain-denied` 钥匙串里的 Chrome 密钥没拿到,
1099
+ * `failed` 别的原因(`detail` 里是给人看的说明)。
1100
+ */
1101
+ problem?: "unsupported" | "no-chrome" | "no-access" | "keychain-denied" | "failed";
1102
+ detail?: string;
1103
+ /** 从哪个 Chrome 用户同步(Chrome 里显示的名字)。 */
1104
+ profile?: string;
1105
+ /** 上次同步时 Chrome 里有多少条能用的 cookie。 */
1106
+ cookies?: number;
1107
+ /** 上次成功同步的时间(毫秒)。 */
1108
+ syncedAt?: number;
1109
+ }
1091
1110
  /**
1092
1111
  * 网页:插件界面入口自己画的 `<webview>`,各自一个进程,逻辑入口经这里驱动它。页面只有主进程碰得到,所以加载、执行脚本、
1093
1112
  * 原生输入、截图、缩放和视口、开发者工具、对话框由宿主做;标签、地址栏、给模型的工具都是插件自己的。
@@ -1167,6 +1186,12 @@ export interface WebPages {
1167
1186
  * 设上时,已经弹着、等人点的那一个也当场按它答掉。每答一个报一条 `dialog` 事件。`prompt` 不在其中:页面调用时直接拿到 null。
1168
1187
  */
1169
1188
  answerDialogs(id: string, accept: boolean | null): Promise<void>;
1189
+ /**
1190
+ * 从本机 Chrome 同步登录状态(全部 cookie,单向:同一条以 Chrome 为准,Chrome 里没了这边也删)进网页的分区,各插件共用。
1191
+ * `status` 看现状;`on` 先由宿主弹系统对话框请人确认,人不同意就照旧关着;`off` 关掉并删掉同步进来的 cookie。
1192
+ * cookie 的值只在主进程里,不交给插件。
1193
+ */
1194
+ chromeLogins(command: "status" | "on" | "off"): Promise<ChromeLoginSync>;
1170
1195
  /** 订阅本插件全部网页的事件,返回取消订阅的函数;停用时自动撤下。 */
1171
1196
  onEvent(listener: (event: WebPageEvent) => void): () => void;
1172
1197
  }
@@ -1364,7 +1389,7 @@ export interface SandboxProcess {
1364
1389
  /** 连它起的子进程一起停。 */
1365
1390
  kill(signal?: "SIGTERM" | "SIGKILL"): void;
1366
1391
  }
1367
- /** 服务 `sandbox`:在沙箱里跑 shell 命令。内置插件 plume.sandbox 提供(macOS 用 seatbelt,Linux 用 bubblewrap,Windows 用受限令牌)。 */
1392
+ /** 服务 `sandbox`:在沙箱里跑 shell 命令。内置插件 plume.sandbox 提供(macOS 用 seatbelt,Windows 用受限令牌;Linux 没有后端)。 */
1368
1393
  export interface SandboxService {
1369
1394
  /**
1370
1395
  * 用用户的 shell 起一条命令,环境向登录 shell 要(从图标启动时也找得到用户装的工具)。`mode` 不给就不进沙箱;
@@ -468,11 +468,15 @@ export type AgentEvent = {
468
468
  type: "projects";
469
469
  projects: ProjectEntry[];
470
470
  }
471
- /** 一条消息写进了会话。回复(assistant)同时顶替正在写的草稿,不另发清空草稿的 draft 事件。 */
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
  | {
@@ -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): Promise<RevertPlan>;
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` 用来核对是不是那一条,对不上时按时间戳重找。正在跑也可以分叉。
@@ -726,8 +735,6 @@ export interface AgentService {
726
735
  origin?: string;
727
736
  })[]>;
728
737
  listDirectory(sessionId: string, path: string): Promise<FileEntry[]>;
729
- /** 在原目录中改名;不覆盖同名目标,不改会话根目录。 */
730
- renameFile(sessionId: string, path: string, name: string): Promise<string>;
731
738
  /**
732
739
  * 在会话工作目录里找一个写得不全的路径:裸文件名(`approvals.ts`)或相对于某个子目录的路径。
733
740
  * 返回找到的绝对路径,按路径排好;跳过依赖和构建产物目录。
@@ -146,7 +146,7 @@ export interface PageOptions {
146
146
  * 前四个是种子:背景、前景、强调色、对比度(0–1,默认 0.6)。其余的面、线、字都由种子推出来,
147
147
  * 皮肤通常只改种子;推出来的哪一档不满意,再单独覆盖那一档。
148
148
  */
149
- export type SkinToken = "bg" | "fg" | "accent" | "contrast" | "shell" | "root" | "accent-dim" | "sidebar" | "card" | "card-hover" | "elevated" | "input" | "float" | "knob" | "shade" | "window-tint" | "line" | "line-soft" | "line-float" | "ink" | "ink-muted" | "ink-faint" | "ok" | "danger" | "warning" | "violet" | "chart-2" | "chart-3" | "chart-4" | "chart-5" | "chart-6" | "on-accent" | "code-bg" | "code-fg" | "code-line-height" | "code-tracking" | "inline-code-bg" | "inline-code-fg" | "inline-code-ring" | "syntax-operator" | "diff-add" | "diff-remove" | "syntax-keyword" | "syntax-string" | "syntax-number" | "syntax-comment" | "syntax-function" | "syntax-type" | "syntax-variable" | "syntax-punctuation" | "syntax-tag" | "syntax-attribute" | "radius-panel" | "radius-field" | "radius-item" | "radius-menu" | "font-sans" | "font-mono" | "ui-size" | "code-size";
149
+ export type SkinToken = "bg" | "fg" | "accent" | "contrast" | "shell" | "root" | "root-image" | "accent-dim" | "sidebar" | "card" | "card-hover" | "elevated" | "input" | "float" | "knob" | "shade" | "window-tint" | "line" | "line-soft" | "line-float" | "ink" | "ink-muted" | "ink-faint" | "ok" | "danger" | "warning" | "violet" | "chart-2" | "chart-3" | "chart-4" | "chart-5" | "chart-6" | "on-accent" | "code-bg" | "code-fg" | "code-line-height" | "code-tracking" | "inline-code-bg" | "inline-code-fg" | "inline-code-ring" | "syntax-operator" | "diff-add" | "diff-remove" | "syntax-keyword" | "syntax-string" | "syntax-number" | "syntax-comment" | "syntax-function" | "syntax-type" | "syntax-variable" | "syntax-punctuation" | "syntax-tag" | "syntax-attribute" | "radius-panel" | "radius-field" | "radius-item" | "radius-menu" | "font-sans" | "font-mono" | "ui-size" | "code-size";
150
150
  /**
151
151
  * 插件给产品显示的一段字:固定的字符串,或者按当前界面语言给出的函数。
152
152
  * 用函数而不是每种语言一份,界面加一种语言时插件的写法不用变;没翻译的语言由插件自己决定退回哪一种。
@@ -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;
@@ -875,6 +881,11 @@ export interface UiContext {
875
881
  sessionId?: string;
876
882
  }) => ComposerMention[] | Promise<ComposerMention[]>): void;
877
883
  composerHint(hint: (session: SessionView) => string | undefined): void;
884
+ /**
885
+ * 换掉欢迎页问候下面的那一行(默认是「要在某个项目里开发什么?」):宿主每次画欢迎页时按界面语言问一次,等答复时那一行先空着,
886
+ * 回来了再淡入。只问最后注册的那一个;返回 undefined、空串或抛错时显示默认的那一句。一个插件只能有一个,再注册换掉上一个。
887
+ */
888
+ welcomeLine(line: (locale: Locale) => WelcomeLine | undefined | PromiseLike<WelcomeLine | undefined>): void;
878
889
  /**
879
890
  * 画回复的正文、复制回复之前,把字过一遍:插件让模型在回复里顺带写的标记(下一步建议的 `:followup[…]`)从这里拿掉。
880
891
  * 只改显示和复制,会话里存的、下一轮发给模型的都不变。正在生成的回复也会过,写了一半的标记也该拿掉。
@@ -1044,7 +1055,7 @@ export interface UiContext {
1044
1055
  }): Promise<ListedCommand[]>;
1045
1056
  /** 产品的对话视图,见 `ConversationProps`。 */
1046
1057
  readonly Conversation: ComponentType<ConversationProps>;
1047
- /** 产品的会话背景设置:选图、拖入图片、预览、铺放、效果、浓淡和显示位置;外观页不再有这一节,背景只经它设置。 */
1058
+ /** 产品的会话背景设置:选图、拖入图片、预览、铺放、效果、浓淡、显示位置,以及整个窗口的磨砂质感(不看皮肤、不要图);外观页不再有这一节,背景只经它设置。 */
1048
1059
  readonly ChatBackgroundSettings: ComponentType<{
1049
1060
  title?: string;
1050
1061
  }>;
@@ -8,7 +8,7 @@
8
8
  import { type RpcChannel } from "./rpc.ts";
9
9
  /** 被调方法收到的最后一个参数。 */
10
10
  export interface CallContext {
11
- /** 调用方取消、超时或通道关闭时中止。 */
11
+ /** 调用方取消或通道关闭时中止。 */
12
12
  signal: AbortSignal;
13
13
  }
14
14
  /**
@@ -3,7 +3,7 @@
3
3
  * 所以 utilityProcess 的 parentPort、渲染进程的 MessagePort、测试里的内存管道都能用同一套。
4
4
  * 不依赖任何第三方库:它会被打进渲染进程。
5
5
  */
6
- export type ErrorCode = "E_NOT_FOUND" | "E_TIMEOUT" | "E_CANCELLED" | "E_CLOSED" | "E_HANDLER";
6
+ export type ErrorCode = "E_NOT_FOUND" | "E_CANCELLED" | "E_CLOSED" | "E_HANDLER";
7
7
  export interface Port {
8
8
  post(message: unknown): void;
9
9
  /** 返回取消监听的函数。 */
@@ -15,8 +15,6 @@ export declare class RpcError extends Error {
15
15
  }
16
16
  export declare function toRpcError(error: unknown, fallback?: ErrorCode): RpcError;
17
17
  export interface RequestOptions {
18
- /** 不填表示不限时。 */
19
- timeoutMs?: number;
20
18
  /** 中止时向对端发取消,本地以 E_CANCELLED 失败。 */
21
19
  signal?: AbortSignal;
22
20
  /** 收到中间值时调用。 */
@@ -24,14 +22,13 @@ export interface RequestOptions {
24
22
  }
25
23
  /** 处理方看到的这次调用。 */
26
24
  export interface IncomingCall {
27
- /** 发起方取消、超时或通道关闭时中止。 */
25
+ /** 发起方取消或通道关闭时中止。 */
28
26
  signal: AbortSignal;
29
27
  /** 推送一个中间值给发起方。 */
30
28
  emit(value: unknown): void;
31
29
  }
32
30
  export interface RpcHandlers {
33
31
  request?(payload: unknown, call: IncomingCall): unknown;
34
- note?(payload: unknown): void;
35
32
  }
36
33
  export declare class RpcChannel {
37
34
  private readonly port;
@@ -43,9 +40,7 @@ export declare class RpcChannel {
43
40
  private closedWith;
44
41
  constructor(port: Port, handlers?: RpcHandlers);
45
42
  setHandlers(handlers: RpcHandlers): void;
46
- get closed(): boolean;
47
43
  request(payload: unknown, options?: RequestOptions): Promise<unknown>;
48
- notify(payload: unknown): void;
49
44
  /** 关闭通道:挂起的请求全部以 E_CLOSED 失败,正在处理的请求被中止,之后的收发都被忽略。 */
50
45
  close(reason?: string): void;
51
46
  private settle;
package/dist/theme.css CHANGED
@@ -42,6 +42,8 @@
42
42
  --plume-shell: var(--plume-bg);
43
43
  /* 窗口最外层,数据皮肤可将它与正文区分开。 */
44
44
  --plume-root: var(--plume-shell);
45
+ /* 盖在 root 上的底纹(CSS 图像,可以是逗号分隔的几层,如渐变),只在数据皮肤接管配色时铺;缺省没有。 */
46
+ --plume-root-image: none;
45
47
  /* 强调色弱底;数据皮肤可直接指定,不再从强调色透明度推导。 */
46
48
  --plume-accent-dim: color-mix(in srgb, var(--plume-accent) 10%, transparent);
47
49
  --plume-sidebar: color-mix(in srgb, var(--plume-fg) calc(4.2% * var(--plume-k) * var(--plume-k-surface)), var(--plume-bg));
@@ -287,7 +289,7 @@
287
289
  --font-mono: var(--plume-font-mono);
288
290
 
289
291
  /*
290
- * 字号七档加代码一档,都是界面字号的 calc,改一个数整套一起动。档与档至少差 1px,各自带行高和字距:
292
+ * 字号八档加代码一档,都是界面字号的 calc,改一个数整套一起动。档与档至少差 1px,各自带行高和字距:
291
293
  * 字越大字距越紧。字重不放进档里,和字号配对写在用的地方(title 用 medium,heading、display 用 semibold)。
292
294
  */
293
295
  --text-caption: calc(var(--plume-ui-size) - 2px);
@@ -309,6 +311,10 @@
309
311
  --text-display: calc(var(--plume-ui-size) + 16px);
310
312
  --text-display--line-height: 1.2;
311
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;
312
318
  /* 代码另起一档:另一种字体,按列读;行高 1.6 比正文紧,缩进和括号的形状不被拉散。 */
313
319
  --text-code: var(--plume-code-size);
314
320
  --text-code--line-height: var(--plume-code-line-height);
package/dist/ui.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** @plume/ui 对插件开放的部分:packages/client/ui/plugin-api.json 里的名字。由 scripts/build-types.mjs 生成。 */
2
- export { Text, Button, IconButton, Input, Textarea, TextArea, Select, InlineSelect, Toggle, ComposerToggle, WorktreeToggle, Segmented, TabStrip, SearchField, NumberField, Slider, ColorField, TimeField, ShortcutRecorder, useFieldFade, ChoiceMark, Popover, ContextMenu, MenuItem, MenuSeparator, MenuLabel, Dialog, DialogAction, Confirm, useConfirmer, useConfirmGate, Tooltip, usePopover, MenuBody, MenuSearch, MENU_MAX_HEIGHT, Overlay, Card, CodeBlock, DiffView, ApprovalFrame, ApprovalExpiry, SettingsSectionTitle, SettingsCard, SettingsRow, SettingsNotice, PanelEmpty, RetainedViews, useNarrow, useLiveRefresh, PageTopRow, ActionSpinner, BreatheLoader, SkeletonList, SkeletonBar, useSlowLoad, HoverRow, HoverRowMark, HoverRowReveal, hoverSlot, Caret, MarkNumber, FlowRow, lookFor, iconColour, Scroller, OverlayScrollbar, ScrollText, Sideways, useI18n, relativeTime, TimeAgo, macKeyboard, composingKey, shortcutLetter, shortcutLabel, acceleratorLabel, motionReduced, CountUp, useRolled, RollingText, useNow, Collapse, Disclosure, AgentAvatar, AvatarPile, assignAvatars, AVATAR_SHAPES, AVATAR_COLORS, COLOR_ART, parseAvatar, formatAvatar, freshAvatar, withHanFallback, useAnnotator, AnnotateCanvas, AnnotateToolbar, useToolbarPlacement, HANDLES, HANDLE_CURSOR, handlePoint, hitHandle, insideRect, rectFromPoints, resizeRect, moveRect, clampRect } from "./client/ui/src/index.d.ts";
2
+ export { Text, Button, IconButton, Input, Textarea, TextArea, Select, InlineSelect, Toggle, ComposerToggle, WorktreeToggle, Segmented, TabStrip, SearchField, NumberField, Slider, ColorField, TimeField, ShortcutRecorder, useFieldFade, ChoiceMark, Popover, ContextMenu, MenuItem, MenuSeparator, MenuLabel, Dialog, DialogAction, Confirm, useConfirmer, useConfirmGate, Tooltip, usePopover, MenuBody, MenuSearch, MENU_MAX_HEIGHT, Overlay, Card, CodeBlock, ApprovalFrame, ApprovalExpiry, SettingsSectionTitle, SettingsCard, SettingsRow, SettingsNotice, PanelEmpty, RetainedViews, useNarrow, useLiveRefresh, PageTopRow, ActionSpinner, BreatheLoader, SkeletonList, SkeletonBar, useSlowLoad, HoverRow, HoverRowMark, HoverRowReveal, hoverSlot, Caret, MarkNumber, FlowRow, lookFor, iconColour, Scroller, OverlayScrollbar, ScrollText, Sideways, useI18n, relativeTime, TimeAgo, macKeyboard, composingKey, shortcutLetter, shortcutLabel, acceleratorLabel, motionReduced, CountUp, useRolled, RollingText, useNow, Collapse, Disclosure, AgentAvatar, AvatarPile, assignAvatars, AVATAR_SHAPES, AVATAR_COLORS, COLOR_ART, parseAvatar, formatAvatar, freshAvatar, withHanFallback, useAnnotator, AnnotateCanvas, AnnotateToolbar, useToolbarPlacement, HANDLES, HANDLE_CURSOR, handlePoint, hitHandle, insideRect, rectFromPoints, resizeRect, moveRect, clampRect } from "./client/ui/src/index.d.ts";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumeai/plugin-sdk",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "写 Plume 插件用的类型、命令(新建、检查、构建、安装)、插件模板和给 agent 的 skill。随 Plume 应用带一份,也发在 npm 上(插件仓库按版本依赖)。",
5
5
  "license": "MIT",
6
6
  "repository": {
package/skill/SKILL.md CHANGED
@@ -46,7 +46,7 @@ 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` 在压缩、撤回或上下文来源变化时作废自己的缓存。
@@ -76,7 +76,7 @@ description: 写、改、排查 Plume 插件:工具、钩子(tool.execute、
76
76
  4. **界面入口**(要画东西时):读 [references/ui.md](references/ui.md)。
77
77
  5. **样式和文案**(有界面或给人看的文字时):读 [references/style-i18n.md](references/style-i18n.md)。
78
78
  6. **构建和加载**:
79
- - 独立项目:`plume-plugin dev` 构建、装进 `~/.plume/local-plugins/<目录名>/`(和市场装的同名时这一份生效,不被市场更新换掉,卸掉就回到市场那份),之后一改源码就重新构建、装上;Plume 开着的话每次都当场加载,不用重启。只装一次用 `plume-plugin build` 再 `plume-plugin install`。也可以把插件的上级目录放进环境变量 `PLUME_PLUGIN_PATHS`,Plume 同样盯着那里。
79
+ - 独立项目:`plume-plugin dev` 构建、装进 `~/.plume/local-plugins/<目录名>/`(和市场装的同名时这一份生效,不被市场更新换掉,卸掉就回到市场那份),之后一改源码就重新构建、装上;Plume 开着的话每次都当场加载,不用重启。只装一次用 `plume-plugin build` 再 `plume-plugin install`。
80
80
  - Plume 的插件仓库里(`plugins/<目录名>`):`pnpm plugin dev plugins/<目录名>`,和独立项目一样构建、装进本机的 Plume。
81
81
  7. **自检**:对照文末清单,再跑检查(独立项目 `plume-plugin check`:类型检查加插件规则;仓库里 `pnpm check`)。
82
82
 
@@ -265,7 +265,6 @@ export function TextArea({
265
265
  onChange,
266
266
  shell = "",
267
267
  className = "",
268
- fieldRef,
269
268
  resizable = false,
270
269
  ...rest
271
270
  }: {
@@ -275,7 +274,6 @@ export function TextArea({
275
274
  shell?: string;
276
275
  /** 字那一层的额外类:等宽、字号、`h-[180px]` 这类只跟可写区域有关的。 */
277
276
  className?: string;
278
- fieldRef?: React.RefObject<HTMLTextAreaElement | null>;
279
277
  /** 能不能用鼠标拉高。只给写长文的地方,比如一份系统提示词。 */
280
278
  resizable?: boolean;
281
279
  } & Omit<ComponentPropsWithoutRef<"textarea">, "value" | "onChange" | "className" | "ref">)
@@ -978,7 +976,7 @@ export function useConfirmGate()
978
976
  /**
979
977
  * 应用的悬停提示,写成 JSX 包装,比属性读起来顺。
980
978
  *
981
- * 它只给子元素打标记——气泡本身由 `tooltip.ts` 里那一个文档级监听器画。所以两种写法表现完全一样,
979
+ * 它只给子元素打标记——气泡本身由 `tooltip-attribute.ts` 里那一个文档级监听器画。所以两种写法表现完全一样,
982
980
  * 这很重要,因为第三方库自己建的按钮只能靠属性够到。
983
981
  *
984
982
  * 取代浏览器的 `title`:那个改不了样式,要等一秒半左右,不跟应用的字体和主题,在 macOS 上画成一个
@@ -1071,14 +1069,12 @@ export const MENU_MAX_HEIGHT = 340;
1071
1069
  export function Overlay({
1072
1070
  children,
1073
1071
  onClose,
1074
- align = "center",
1075
1072
  width = 460,
1076
1073
  label,
1077
1074
  returnFocus,
1078
1075
  }: {
1079
1076
  children: React.ReactNode | ((dismiss: Dismiss) => React.ReactNode);
1080
1077
  onClose: () => void;
1081
- align?: "center" | "bottom";
1082
1078
  width?: number;
1083
1079
  label?: string;
1084
1080
  returnFocus?: HTMLElement;
@@ -1115,14 +1111,6 @@ export interface CardProps {
1115
1111
  export function CodeBlock({ children }: { children: string })
1116
1112
  ```
1117
1113
 
1118
- ### `DiffView`
1119
-
1120
- 源码:`packages/client/ui/src/index.tsx`
1121
-
1122
- ```tsx
1123
- export function DiffView({ before, after }: { before: string; after: string })
1124
- ```
1125
-
1126
1114
  ## 审批卡
1127
1115
 
1128
1116
  ### `ApprovalFrame`
@@ -1276,28 +1264,7 @@ export function PanelEmpty({
1276
1264
 
1277
1265
  ```tsx
1278
1266
  /** 留住少数几个看过的页面;藏起来的页面暂停 effect,保留局部状态。 */
1279
- export function RetainedViews<T extends string>({
1280
- active,
1281
- render,
1282
- limit = 3,
1283
- pageClassName = "plume-page-enter",
1284
- slide,
1285
- }: {
1286
- active: T;
1287
- render: (key: T) => React.ReactNode;
1288
- limit?: number;
1289
- /** 到达时的类。不同的页面可以换一种更长的淡入加升起。 */
1290
- pageClassName?: string;
1291
- /**
1292
- * 标签,按标签条上的顺序:每次切换,新页面从它所在的那一侧滑进来并淡入,而不是一帧之内换掉。
1293
- *
1294
- * 用 Web Animations API 在布局 effect 里播,不用类。用类的话——见下面 `seen` 的注释——从 `display: none`
1295
- * 回来的页面会先在终点画一帧,动画才接上。布局 effect 在页面显示之后、任何东西画出来之前跑,所以谁看到的
1296
- * 第一帧都已经是滑入的第一帧。回到一个页面也播:标签条是来回切的地方,只在第一次有动画的切换,第二次读起来
1297
- * 就像坏了。
1298
- */
1299
- slide?: readonly T[];
1300
- })
1267
+ export function RetainedViews<T extends string>({ active, render, limit = 3 }: { active: T; render: (key: T) => React.ReactNode; limit?: number })
1301
1268
  ```
1302
1269
 
1303
1270
  ### `useNarrow`
@@ -1370,20 +1337,8 @@ export function PageTopRow({
1370
1337
  *
1371
1338
  * 轨道是这个记号的重点,不是装饰:亮弧单独转的话,图形的轮廓跟着弧一起走,在按钮那个方寸
1372
1339
  * 之间会读成「有东西在里面甩」。垫一圈淡的,形状就恒定是一个完整的圆,动的只有亮处。
1373
- *
1374
- * 轨道浓度走 `--plume-track`,默认 0.18。实心底的按钮(`bg-ink`、`bg-accent`)上 `currentColor`
1375
- * 是底色的反色,0.18 基本看不见,那几处传 `onFill`——见 `ui/motion.css` 里那一档的注释。
1376
1340
  */
1377
- export function ActionSpinner({
1378
- size = 14,
1379
- className = "",
1380
- onFill = false,
1381
- }: {
1382
- size?: number;
1383
- className?: string;
1384
- /** 画在实心底的按钮上(`bg-ink` / `bg-accent`),轨道要更浓才看得见。 */
1385
- onFill?: boolean;
1386
- })
1341
+ export function ActionSpinner({ size = 14, className = "" }: { size?: number; className?: string })
1387
1342
  ```
1388
1343
 
1389
1344
  ### `BreatheLoader`
@@ -1871,16 +1826,11 @@ export function CountUp({
1871
1826
  className,
1872
1827
  /** 走动中的数怎么写。默认是四舍五入的整数。 */
1873
1828
  format = plain,
1874
- bidirectional,
1875
- ms,
1876
1829
  ...rest
1877
1830
  }: {
1878
1831
  value: number;
1879
1832
  className?: string;
1880
1833
  format?: (shown: number) => string;
1881
- /** 往下也走——见 `useCountUp`。给按更短的时段重算的读数。 */
1882
- bidirectional?: boolean;
1883
- ms?: number;
1884
1834
  } & Omit<React.ComponentProps<"span">, "className" | "children">)
1885
1835
  ```
1886
1836
 
@@ -2034,26 +1984,13 @@ type Control = { open: boolean; onToggle: () => void; defaultOpen?: never } | {
2034
1984
  源码:`packages/client/ui/src/avatar/AgentAvatar.tsx`
2035
1985
 
2036
1986
  ```tsx
2037
- export function AgentAvatar({
2038
- avatar,
2039
- size = 20,
2040
- mood = "idle",
2041
- badge = null,
2042
- interactive = true,
2043
- host,
2044
- seed = "",
2045
- label,
2046
- cheer = null,
2047
- className = "",
2048
- }: AgentAvatarProps)
1987
+ export function AgentAvatar({ avatar, size = 20, mood = "idle", interactive = true, host, seed = "", label, cheer = null, className = "" }: AgentAvatarProps)
2049
1988
 
2050
1989
  export interface AgentAvatarProps {
2051
1990
  avatar: Avatar;
2052
1991
  /** 像素边长。 */
2053
1992
  size?: number;
2054
1993
  mood?: AvatarMood;
2055
- /** 右下角的小圆点:在跑是强调色、会一圈圈往外扩;出错是红的。 */
2056
- badge?: "running" | "failed" | null;
2057
1994
  /**
2058
1995
  * 悬停时眼睛跟着指针、身子弹一下。默认开。
2059
1996
  *
@@ -2817,23 +2754,10 @@ export const card = {
2817
2754
  };
2818
2755
  ```
2819
2756
 
2820
- ### `actions`
2821
-
2822
- ```ts
2823
- /** 卡片底部一行:左边是说明,右边是按钮。 */
2824
- export const actions = {
2825
- row: "mt-2.5 flex items-center gap-2",
2826
- note: "flex-1 text-caption text-ink-muted",
2827
- };
2828
- ```
2829
-
2830
2757
  ### `code`
2831
2758
 
2832
2759
  ```ts
2833
2760
  export const code = {
2834
2761
  block: `${PRE} px-2.5 py-2 break-words whitespace-pre-wrap`,
2835
- diff: `${PRE} py-1.5`,
2836
- diffLine: "px-2.5 break-words whitespace-pre-wrap",
2837
- diffKind: { same: "", add: "bg-diff-add", remove: "bg-diff-remove" },
2838
2762
  };
2839
2763
  ```
@@ -47,7 +47,7 @@ export default {
47
47
  | `ctx.sessions.steer(id, message)` / `onStopped(listener)` | 给正在运行的自有子会话插话,空闲时拒绝;明确停止会话时通知插件,清理尚未启动的排队工作,监听随插件卸载 |
48
48
  | `ctx.sessions.launch({ project?, cwd?, message, model?, thinking?, spawnedFrom?, title? })` | 开一个新的顶层会话并发出第一句话(`model`、`thinking` 不给用默认模型、默认推理强度;不认识的模型、档位抛错),返回 `{ session, done, idle, reportBack }`(`done` 是第一轮为什么结束,`idle` 在它第一次真闲下来时兑现成 `{ stop?, reply }`(最近一轮的收场、最后一段回复),`reportBack(text, status)` 把结果作为一条运行时送达交回 `spawnedFrom` 那个会话一次,画成「来自新会话」一行,来处删了、归档了返回 false;`spawnedFrom.toolCallId` 不是你的工具在那个会话里的调用、或你的插件停用了时抛错);会话进侧栏、不记在插件名下,人能接着聊,插件之后不能再往里发。只能开新的,碰不到已有会话。见下文「开新会话」 |
49
49
  | `ctx.sessions.models()` | 无凭据的模型目录:`id`、`name`、`contextWindow`、`thinking`;用于插件选择子会话模型,密钥不出宿主 |
50
- | `ctx.pages` | 驱动界面入口画的网页(`<webview>`):`attach` 认下、`load`、`go`、`evaluate`、`input`、`insertText`、`capture`、`setZoom`、`emulate`、`devtools`、`answerDialogs`,`onEvent` 收页面的事件。见下文「网页」 |
50
+ | `ctx.pages` | 驱动界面入口画的网页(`<webview>`):`attach` 认下、`load`、`go`、`evaluate`、`input`、`insertText`、`capture`、`setZoom`、`emulate`、`devtools`、`answerDialogs`、`chromeLogins`,`onEvent` 收页面的事件。见下文「网页」 |
51
51
  | `ctx.commands.provide(provider)` | 给输入框的 `/` 菜单供命令(见下面「`/` 命令」),停用时撤下 |
52
52
  | `ctx.plugins.list()` | 现在启用着、加载好了的插件:`name`、`dir`(插件目录)、`builtin`。给要读别的插件随包文件的插件用(技能插件读各插件的 `skills/`);每次调都是当时的样子 |
53
53
  | `ctx.plugins.onChange(listener)` | 插件装上、卸下、启用、停用以后叫一次(这时 `list()` 已经是新的),停用时撤下。读别的插件随包文件、又要当场跟上的插件用(MCP 插件读各插件的 `.mcp.json`,装上就连) |
@@ -135,7 +135,7 @@ ctx.hook("model.context", (input, next) =>
135
135
  );
136
136
  ```
137
137
 
138
- 只改 `systemPrompt` 也行:宿主在每一层交接时把两边对齐,接在后面的那截成为末尾一段 `extension`,整个换掉的只剩这一段 `extension`;两个都改了以 `sections` 为准。
138
+ `systemPrompt` 只读:宿主在每一层交接时按 `sections` 重拼,直接改它不作数。
139
139
 
140
140
  系统提示词在会话里冻结:plume.prompt 在最外层把里层交回的段和这个会话冻结的那份比对,发出去的始终是冻结的那份,服务商的前缀缓存才接得上。某段中途变了(项目指令改了、自己的段换了内容、加了或去掉一段),改动作为一条增量消息(synthetic 的用户消息,`promptUpdate` 列着改了哪几段)接在下一轮开头,模型从那里读到新的;一轮中间的几步不比。压缩以后(或撤回到第一条消息)按那时的段重新冻结。所以段的内容要只随该变的东西变:每次请求都不一样的东西(时间、计数)放进段里,每一轮都会多出一条增量。
141
141
 
@@ -175,7 +175,7 @@ next({ ...input, sections: [...input.sections, { id: "projectMemory", text, file
175
175
  - 有工具结果时 `continue: false` 不起作用(结果总得交给模型),`say` 照说。
176
176
  - 本来要结束时要接着做,必须给 `say`,否则按结束处理。
177
177
  - 先 `await next(input)` 看自然结论和里层的意见,再决定要不要改;不关心的原样返回。
178
- - 这个钩子抛错时,循环按自然结论走,并在界面上提示出错的插件;它只能让循环多做,退回默认不会放过什么。
178
+ - 这个钩子抛错时,循环按自然结论走,并在界面上提示出错的插件;交回认不得的结论(调了 `next` 却没 `return`、`say` 不是一句话、`reason` 不是上面两种)也一样按自然结论走并提示。它只能让循环多做,退回默认不会放过什么。
179
179
 
180
180
  ```ts
181
181
  // 换掉本体的续写:撞上输出上限就停下,不接着写。
@@ -236,7 +236,7 @@ ctx.hook("prompt.submit", async (input, next) => {
236
236
  | `signal` | 用户停止这一轮时中止 |
237
237
  | `progress(event)` | 走到哪一步了(定下要压、开始写摘要时先报一次 `summarizing`,界面显示「正在生成摘要」):`{ phase: "summarizing" }`、`{ phase: "retrying", delayMs }`、`{ phase: "fallback" }`(摘要没拿到、本地整理),转录里那一行跟着变 |
238
238
 
239
- 输出 `{ keep, summary, head?, via?, views? }`、`{ pruneOnly: true, views }` 或 `undefined`(不压)。`history` 里从 `keep` 起原样留着,之前的(含上一次换上的那几条)换成 `head`(以用户消息开头的几条,宿主原样存下、以后每次照发);不给 `head` 就是 `summary` 加一句确认。`keep` 要大于 `previous.head`、在最后一条之前,而且不能是工具结果(会和它回答的调用拆开);`summary` 只在 `via: "dropped"`(按条丢弃,必须给 `head`)时可以是空串,不合规矩的当作压缩失败。`via` 说摘要怎么来的(`fork`、`independent`、`fallback`、`dropped`)。`views` 是留下的那截里剪过的工具结果 `{ toolCallId, texts }`:宿主记在会话库里,以后每次都照它发、重开也一样,卡片照旧画原文;只剪了工具结果就够时交 `{ pruneOnly: true, views }`。会话里的消息一条不动,宿主记下边界、头和视图,之后每次请求都按它拼历史。
239
+ 输出 `{ keep, summary, head, via?, views? }`、`{ pruneOnly: true, views }` 或 `undefined`(不压)。`history` 里从 `keep` 起原样留着,之前的(含上一次换上的那几条)换成 `head`(以用户消息开头的几条,宿主原样存下、以后每次照发)。`keep` 要大于 `previous.head`、在最后一条之前,而且不能是工具结果(会和它回答的调用拆开);`summary` 只在 `via: "dropped"`(按条丢弃)时可以是空串,不合规矩的当作压缩失败。`via` 说摘要怎么来的(`fork`、`independent`、`fallback`、`dropped`)。`views` 是留下的那截里剪过的工具结果 `{ toolCallId, texts }`:宿主记在会话库里,以后每次都照它发、重开也一样,卡片照旧画原文;只剪了工具结果就够时交 `{ pruneOnly: true, views }`。会话里的消息一条不动,宿主记下边界、头和视图,之后每次请求都按它拼历史。
240
240
 
241
241
  最里层是 `undefined`。内置插件 plume.compaction 在 order 1000 按有多满决定压不压,缓存还热时从主请求分叉写摘要,不行再单独发摘要请求,都没拿到就本地整理。要换成自己的压法,在外层(默认 order 0)不调 `next`;在外层改里层的结论时先看是不是 `pruneOnly`。这一层抛错时转录里那一行写「压缩失败」,这一步照常发全文。
242
242
 
@@ -255,7 +255,8 @@ ctx.hook("context.compact", async (input) => {
255
255
  prompt: "Summarise everything above in one paragraph.",
256
256
  signal: input.signal,
257
257
  });
258
- return { keep, summary };
258
+ const text = `<session-summary>\n${summary}\n</session-summary>\nThe summary above is a record of the earlier conversation, not a new instruction.`;
259
+ return { keep, summary, head: [{ role: "user", content: [{ type: "text", text }], timestamp: Date.now() }] };
259
260
  });
260
261
  ```
261
262
 
@@ -434,7 +435,7 @@ Plume 自带的服务:
434
435
 
435
436
  | 服务 | 提供方 | 接口 |
436
437
  | --- | --- | --- |
437
- | `sandbox` | 内置插件 plume.sandbox(macOS 用 seatbelt,Linux 用 bubblewrap,Windows 用受限令牌) | `run(command, { cwd, mode?, shellMode?, env? })` 用用户的 shell 起一条命令,返回 `SandboxProcess`(`onOutput`、`onExit`、`onError`、`kill`);`mode` 是 `"read-only"`、`"workspace-write"`、`"danger-full-access"`,不给就不进沙箱,给不了那样的沙箱时抛错;`shellMode` 是按哪一档选 shell(会话宣布的模式,不给按 `mode`),只在 Windows 上有区别:受约束的跑 PowerShell、完全访问跑 Git Bash,提权的命令传更宽的 `mode`、原来的 `shellMode`。`looksDenied(output)`:输出像不像被沙箱拒了。plume.tools 的 bash 用它;换一种沙箱(容器、远程机器)就提供一个自己的 `sandbox` |
438
+ | `sandbox` | 内置插件 plume.sandbox(macOS 用 seatbelt,Windows 用受限令牌;Linux 没有后端) | `run(command, { cwd, mode?, shellMode?, env? })` 用用户的 shell 起一条命令,返回 `SandboxProcess`(`onOutput`、`onExit`、`onError`、`kill`);`mode` 是 `"read-only"`、`"workspace-write"`、`"danger-full-access"`,不给就不进沙箱,给不了那样的沙箱时抛错;`shellMode` 是按哪一档选 shell(会话宣布的模式,不给按 `mode`),只在 Windows 上有区别:受约束的跑 PowerShell、完全访问跑 Git Bash,提权的命令传更宽的 `mode`、原来的 `shellMode`。`looksDenied(output)`:输出像不像被沙箱拒了。plume.tools 的 bash 用它;换一种沙箱(容器、远程机器)就提供一个自己的 `sandbox` |
438
439
  | `resources` | 内置插件 plume.tools(它的 read、ls 认这些地址,write、edit 认 `scratch://`) | 资源地址 `scheme://路径[:行-行]`,类型在 `@plume/contract` 的 `ResourceHandler` 等。`register(handler)` 加一个只读的 scheme(`describe` 写进系统提示词的地址清单,`resolve` 读一个地址、找不到时抛错,错误信息直接交给模型;可选 `list` 给 `read scheme://` 列目录、`locate` 给 ls 一个磁盘位置),返回撤下的函数,用 `ctx.effect` 撤;内容不是你写的(第三方文档、外部服务)时给 `origin`,read 把它包进 `<resource origin="…">`。路径的越界(`..`、软链出去)由 scheme 自己挡。`keepArtifact(sessionId, tool, content)` 存一大块原文换回 `artifact://<id>`(按内容算,同一段每次同一个地址;按会话在内存里,最多 30 份)。plume.tools 自己有 `scratch://`(唯一可写)、`artifact://`、`session://`(别的会话的转录)、`plugin://`(插件的 README 和目录里的文件),技能插件加了 `skill://` |
439
440
  | `file.read` | 内置插件 plume.tools | `authorize(run, path)` 与文件工具共用读取授权,返回 `{ ok: true, absolute }` 或 `{ ok: false, message }`。消费方声明 `inject: ["file.read"]`,用当前工具的 `ToolRun` 调用,成功后实际读取必须使用返回的 `absolute`;详见下文 |
440
441
  | `project.files` | 内置插件 plume.tools(它的 glob、grep 也用这一份) | `list(root)`:`root` 之下 git 认为该看的文件(已跟踪的加上未跟踪且没被忽略的),绝对路径;不在 git 仓库里时是 null,自己走目录。问 git 时不执行仓库能控制的程序,插件要列项目文件就用它,不要自己跑 `git ls-files` |
@@ -467,11 +468,12 @@ const content = await readFile(access.absolute, "utf8");
467
468
  浏览器这类插件自己画网页:界面入口在面板里放一个 `<webview>`,页面怎么走、读什么、点哪里都由逻辑入口经 `ctx.pages` 驱动。宿主只管主进程才做得到的事,不认识地址栏、标签、书签这些。
468
469
 
469
470
  - **认下一页**:`<webview>` 挂上(`did-attach`)以后,界面入口读 `getWebContentsId()`,经 `ui.remote()` 交给逻辑入口,逻辑入口调 `ctx.pages.attach(id, contentsId)`;`id` 是插件自己起的页面名(浏览器用面板标签的 id)。之后的方法都按这个 `id` 找页面,只找得到自己认下的。一个 `<webview>` 只能认作一页;它被卸掉(标签关了、组件卸了)就收到 `detached`,页面没了。同一个 `id` 再认另一个 `<webview>` 时换成新的(标签弹到独立窗口、接回来时就是这样),旧的不再算、卸掉时不报 `detached`。
470
- - **宿主管着的**:主窗口和弹出去的面板窗口能挂 `<webview>`(面板 `detachable` 时弹出去的那边画一个新的,按地址重新加载、重新 `attach`);页面一律沙箱、不开 Node、不带 preload,存在单独的一份浏览数据(`persist:plume-web`)里;只放 http、https 和 `about:blank`,跳去别的协议的被拦下,报 `blocked`;页面要开新窗口不开,报 `popup`(插件自己决定开成一个标签还是不管);页面要的权限(摄像头、通知……)一律拒。
471
+ - **宿主管着的**:主窗口和弹出去的面板窗口能挂 `<webview>`(面板 `detachable` 时弹出去的那边画一个新的,按地址重新加载、重新 `attach`);页面一律沙箱、不开 Node、不带 preload,存在单独的一份浏览数据(`persist:plume-web`)里;只放 http、https 和 `about:blank`,跳去别的协议的被拦下,报 `blocked`;页面要开新窗口不开,报 `popup`(插件自己决定开成一个标签还是不管),脚本按尺寸开的小窗(第三方登录、支付)例外:宿主开成挂在 Plume 窗口上的小窗口、和开它的页面连着,报 `popup` 时带 `window: true`,插件不用再开、也驱动不了它;页面要的权限(摄像头、通知……)一律拒。
471
472
  - **事件**:`ctx.pages.onEvent(listener)` 收自己全部页面的事件,每条带着页面此刻的 `state`(地址、标题、加载中、能不能前进后退、缩放):`attached`、`detached`、`navigation`(主框架开始换文档)、`dom-ready`、`loading`、`navigated`、`title`、`failed`、`gone`(页面进程没了)、`blocked`、`popup`、`dialog`。认下之前就发生的(第一跳)在认下时补发。停用时自动撤下。
472
473
  - **读页面、找元素**:`evaluate(id, code, { isolated: true })` 在插件自己的隔离 world 里跑——和页面共用 DOM,页面的脚本看不到它记下的东西,换文档时一起丢掉;不给 `isolated` 是在页面自己的 world 里跑(要读页面的全局变量时)。页面卡住时 `evaluate` 不会自己超时,自己加时限。
473
474
  - **点按、输入**:`input(id, event)` 发原生的指针、滚轮、按键事件,坐标是页面视口里的像素(CSS 像素乘上缩放);`insertText` 往有焦点的输入框里打字;之前先 `focus`。
474
475
  - **对话框**:`answerDialogs(id, true | false)` 之后,这一页的 alert、confirm、离开页面提示由宿主直接答(alert 总是确定),答了什么经 `dialog` 事件报回来,连同之前就开着、没人答的那一个;`null` 交回系统对话框。只在 agent 操作网页期间接管:人自己浏览时页面照旧弹给人。
476
+ - **从 Chrome 同步登录状态**:`chromeLogins("status" | "on" | "off")` 把本机 Chrome 的 cookie 单向同步进网页的分区(只有 macOS),各插件共用一份。`on` 由宿主弹系统对话框请人确认,人不同意时交回的仍是关着;`off` 删掉同步进来的。交回 `ChromeLoginSync`:开没开、`problem`(`no-access` 要人到完全磁盘访问权限里打开 Plume,`keychain-denied` 再调一次 `on` 重试)、Chrome 用户名、条数、上次同步时间,没有 cookie 的值。
475
477
  - **截图**:`capture(id, { rect?, points? })` 交回 PNG 的 data URL,页面没设背景的地方垫成白的;`rect` 截视口里的一块(CSS 像素乘缩放);`points: true` 按屏幕缩放比缩回点,给模型看的图用它(高分屏上不白占一倍的上下文)。截出来交给模型时去掉 `data:` 前缀放进工具结果的 `images`。
476
478
  - **缩放、视口**:`setZoom(id, factor)`(0.25–3),`emulate(id, { width, height, scale })` 仿真一个视口、`scale` 是缩小到面板里的比例,`null` 撤掉。页面进程停了的时候这两样不生效,等 `dom-ready` 再设一次。
477
479
  - 不在桌面应用的主窗口里(测试、命令行)时,`ctx.pages` 的方法抛错。完整的例子是 `browser` 插件。
@@ -31,7 +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.ChatBackgroundSettings` | 自包含的背景设置组件,可传 `title`;管理全局的图片、显示位置、铺满/平铺、效果和浓淡(外观页不再有背景设置),支持选图和拖入图片,不向插件暴露磁盘路径 |
34
+ | `ui.ChatBackgroundSettings` | 自包含的背景设置组件,可传 `title`;管理全局的图片、显示位置、铺满/平铺、效果、浓淡,以及整个窗口的磨砂质感(外圈、停靠侧栏和对话区一起,不看皮肤、不要图;外观页不再有背景设置),支持选图和拖入图片,不向插件暴露磁盘路径 |
35
35
  | `ui.onSessionsChanged(listener)` | 订阅所有会话的已保存消息、摘要和删除变化,包含当前未打开的会话;返回取消订阅函数,组件 effect 结束时调用,插件停用时也会撤下。事件 `SessionChange` 的字段见下文 |
36
36
  | `ui.effect(dispose)` | 界面入口的生命周期清理,停用、重载或 apply 失败时逆序执行 |
37
37
  | `ui.composerItem(id, Component, { side, order })` | 输入框底下那排控件加一格:左边,或右边模型选择前面(右边的也画进 `ui.Conversation` 的输入框) |
@@ -41,6 +41,7 @@ export default {
41
41
  | `ui.environmentSection(id, Component, { label, order, attention? })` | 往会话的环境卡片(`plume.diagnostics` 画)里加一节,收到 `{ session }`;返回 null 不占位、不画分隔线;`label` 是卡片「显示哪些内容」菜单里的名字;`attention(session)` 是 hook,有事等人决定时返回一句话,卡片收着时开关上挂圆点 |
42
42
  | `ui.mentions(provider)` | 每次打开 `@` 时传 `{ cwd?, sessionId? }`,返回 `ComposerMention[]` 或 Promise:`id/title/kind/token` 必填,kind 是 subagent 或 plugin,可带 description/origin/avatar;只插入 token,用户自行发送,停用自动撤销 |
43
43
  | `ui.composerHint(session => text)` | 输入框空着时的灰字建议,按 Tab 填进去 |
44
+ | `ui.welcomeLine(locale => ({ text, source }))` | 换掉欢迎页问候下面的那一行(默认问要在哪个项目里做什么),只问最后注册的那一个 |
44
45
  | `ui.replyText(text => text)` | 画回复正文、复制回复前过一遍,拿掉插件让模型顺带写的标记 |
45
46
  | `ui.sessionMark(Component)` | 侧栏会话行首空闲时的记号,收到 `{ session, fallback }`,不是自己的会话返回 `fallback` |
46
47
  | `ui.deleteNote((sessionId, locale) => text)` | 删除确认框里补一句:删掉这个会话会连带什么、会留下什么 |
@@ -180,7 +181,7 @@ ui.toolCard("my_tool", MyCard);
180
181
  - **只补东西的卡片**:登记了卡片的调用默认单独一行,不和前后的调用合成一段活。卡片只是在通用卡片下面补一点(认不出时直接返回 `fallback`)、调用本身该和同类的合在一起时,加 `inline: true`:调用照常按归类并进一段活,单独一次时那一行、展开一段活后的那一行由这张卡画。例子是 `plume.mcp` 的文档预览。
181
182
  - **崩溃让位**:每张卡片外面有错误边界,渲染出错时让给同一工具的下一个注册者,都不行用通用卡片。组件里不用自己 try/catch。
182
183
  - **后注册的生效**:同一工具 `order` 小的在前;`order` 相同时后注册的在前,它撤下后前一个恢复。卡片的 `order` 默认是 0,写更小的 `order` 也能排到前面。
183
- - 外框用 `@plume/ui` 的 `Card`(`title`、`subtitle` 放命令或路径、`status`);输出用 `CodeBlock`,改动用 `DiffView`。
184
+ - 外框用 `@plume/ui` 的 `Card`(`title`、`subtitle` 放命令或路径、`status`);输出用 `CodeBlock`,改动用 `ui.FileDiff`。
184
185
  - 卡片只占一行时(参数还在写、事情已经交出去了)用 `@plume/ui` 的 `FlowRow`:和转录里思考、工具活、送达那几行同一个骨架(前置图标、标题、摘要、尾部、在跑时摘要扫光、`onToggle` 给了才可点),例子是 `plume.spawn-session` 的卡片写参数时那一行和开始以后那一行。
185
186
  - 审批卡压在输入框正上方,不在工具卡片里:等待确认时工具卡片收到 `status: "approval"`,卡片里不放允许、拒绝。要给自己的工具换一种审批卡,见下面「审批卡」。
186
187
 
@@ -314,6 +315,18 @@ ui.replyText((text) => withoutFollowups(text));
314
315
  - `ui.replyText(transform)`:回复正文画出来、复制出去之前过一遍,只改显示和复制,会话里存的、发给模型的都不变。正在生成的回复也会过:写了一半的标记也要拿掉。拿完只剩空白的一段整段不画。
315
316
  - 两样每个插件各只能有一个,再注册换掉上一个;停用时撤下。
316
317
 
318
+ ## 欢迎页的那一行
319
+
320
+ ```ts
321
+ ui.welcomeLine((locale) => api.today(locale));
322
+ ```
323
+
324
+ 欢迎页问候(早上好、下午好……)下面的那一行字,默认是「要在某个项目里开发什么?」(不在项目里时「想聊点什么?」)。宿主每次画欢迎页时按界面语言问一次,可以返回 Promise:等答复时那一行先空着,回来了再淡入,不会先闪一下默认的那句;`text` 是这句话,`source` 是出处(可不给),跟在后面、小一号、更淡。
325
+
326
+ - 只问最后注册的那一个;它停用后还给前一个,都没有就是默认的那句。
327
+ - 返回 undefined、空串或抛错时显示默认的那句(抛错记日志)。每个插件只能有一个,再注册换掉上一个。
328
+ - 要花时间的事(调模型、读网络)放在逻辑入口里做好缓存,界面入口经 `remote()` 取;欢迎页常开,别每次打开都调一次模型。
329
+
317
330
  ## 侧栏的记号、删除确认框
318
331
 
319
332
  ```tsx
@@ -506,6 +519,8 @@ function ModelSetting() {
506
519
 
507
520
  `root` 是窗口最外层与透明停靠侧栏共用的底色,默认跟随 `shell`;`sidebar` 只给 compact 浮出侧栏铺底,不给停靠侧栏另铺一层;`accent-dim` 是强调弱底,默认是强调色的 10% 透明底。数据皮肤可直接给出完整的面、线、字 token;若一份配色只适用于一个模式,给 `light`、`dark` 同一组 token,并用 `mode` 指定深浅模式。
508
521
 
522
+ `root-image` 是盖在 `root` 上的底纹,值是 CSS 图像,可以逗号分隔叠几层(例如 `linear-gradient(160deg, #…, #…)`,先写的在上面),默认 `none`。颗粒、噪点这类质感不写进皮肤:磨砂是用户在背景设置里开关的,宿主叠在底纹上面,哪套皮肤都一样。只在管理配色的数据皮肤下铺在窗口外圈和透明的停靠侧栏上,正文卡片仍是 `shell` 的实底。宿主把它固定在视口上(`background-attachment: fixed`),外圈、侧栏吸顶标题和 Windows 顶带画的是同一片;平铺的小图按自身尺寸重复,渐变铺满整个窗口。侧栏的字直接压在它上面,渐变两端的颜色都要和 `root` 一样满足文字对比度。
523
+
509
524
  在界面入口的 `apply` 中开启远端 watch 时,用 `ui.effect(() => call.cancel())` 登记取消。清理按后登记先执行,一项抛错不妨碍其余清理;宿主先让旧入口失效再清理,因此停用、重载或 apply 失败后,旧异步回调的 `ui.skin` 不会复活皮肤。组件内的订阅仍用 React effect 清理。
510
525
 
511
526
  ## remote:调自己的逻辑入口
@@ -1,26 +0,0 @@
1
- /**
2
- * 一排小脸:这一批派出去的是谁、各自在干什么。
3
- *
4
- * 并排而不是叠着:叠起来的头像组说的是「有这么些人」,而这里每张脸自己有话说——谁在颠着干活、
5
- * 谁眯着眼在排队、谁已经交差——被邻居盖住半张脸,表情就读不出来了。
6
- *
7
- * 放不下的收成一个「+N」,数字本身就是提示:想看全的点开面板。
8
- */
9
- import type { Avatar } from "../lib/agent-avatar.ts";
10
- import { type AvatarMood } from "./AgentAvatar.tsx";
11
- export interface StackFace {
12
- key: string;
13
- avatar: Avatar;
14
- mood?: AvatarMood;
15
- /** 名字,决定眨眼的节奏。 */
16
- seed: string;
17
- /** 悬停时的那句话,同时是读屏念的名字。 */
18
- tip: string;
19
- onClick?: () => void;
20
- }
21
- export declare function AvatarStack({ faces, size, max, className }: {
22
- faces: StackFace[];
23
- size?: number;
24
- max?: number;
25
- className?: string;
26
- }): import("react").JSX.Element;
@@ -1,19 +0,0 @@
1
- /**
2
- * 一个字符串一个字一个字地改写成另一个。
3
- *
4
- * 一段对话在运行时读完第一条消息之前,标题是「新对话」,之后真标题才落下来。一帧之内直接替换,是侧栏里
5
- * 唯一一处「行变了,却和你刚做的事无关」的时刻;硬切的话读起来像列表在指针底下自己重排了一次。改写一遍
6
- * 传达的是同一件事,只是成了一个看得见过程的事件。
7
- *
8
- * 共同前缀保留不删,于是「查一下依赖」→「查一下依赖为什么装不上」不会先退回到空,而是只打出尾巴。
9
- * 这种情况很常见:存下的标题是从提示词里取的,它和替换它的那个往往开头一样。
10
- *
11
- * 按码点而不是 UTF-16 单元:`[...text]` 让 emoji 和其他星际字符保持完整,`slice` 会把代理对切成两半,
12
- * 有一帧画出替换字形。
13
- */
14
- /**
15
- * `from` 到 `to` 之间的每个中间字符串,按顺序,最后一个是 `to`。
16
- *
17
- * 没什么可演的时候是空的——两个字符串相同,或者原来没有值、第一个值刚到——调用方读作「不演,直接画」。
18
- */
19
- export declare function typingFrames(from: string, to: string, maxFrames?: number): string[];
@@ -1,9 +0,0 @@
1
- /**
2
- * 一个字符串一个字一个字地改写成下一个。
3
- *
4
- * 返回这一帧该显示的东西。第一个值不演——原来那里什么都没有,谈不上替换——关掉动效时的任何变化也不演。
5
- *
6
- * 做成 hook 而不是组件,因为这段字通常另有去处:侧栏把它交给 `ScrollText`,由它量字符串决定这一行悬停时
7
- * 滚不滚。交给它一个渲染好的元素,就是在量一个正在动的目标。
8
- */
9
- export declare function useTypedText(text: string): string;
@@ -1,12 +0,0 @@
1
- /**
2
- * GitHub 的标志,每个仓库页上的那个。
3
- *
4
- * 16px 的 `mark-github` octicon,和 https://github.com 自己导航里画的一模一样,而不是 lucide 的图形:
5
- * lucide 去掉了品牌图标,而仓库链接旁边放一个通用的「代码」或「分支」符号,得读了提示才知道它通向哪——
6
- * 这个标志在读任何字之前就说了是 GitHub。
7
- * 用 `currentColor`,在哪种主题下都取它所在位置的字色。
8
- */
9
- export declare function GitHubMark({ size, className }: {
10
- size?: number;
11
- className?: string;
12
- }): import("react").JSX.Element;
@@ -1,6 +0,0 @@
1
- /** 动作的位置一直占着,包括只有悬停或键盘焦点才让它露出来的时候。 */
2
- export declare function RowDeleteButton({ label, onClick, pending }: {
3
- label: string;
4
- onClick: () => void;
5
- pending?: boolean;
6
- }): import("react").JSX.Element;