@plumeai/plugin-sdk 0.1.4 → 0.1.6

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 (34) 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 +19 -5
  5. package/dist/client/ui/src/i18n/messages/zh-CN.d.ts +17 -6
  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 +39 -12
  18. package/dist/contract/src/service.d.ts +0 -2
  19. package/dist/contract/src/ui.d.ts +52 -8
  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 +2 -0
  23. package/dist/ui.d.ts +1 -1
  24. package/lib/manifest.mjs +1 -0
  25. package/package.json +1 -1
  26. package/skill/SKILL.md +1 -1
  27. package/skill/references/components.md +4 -80
  28. package/skill/references/logic.md +9 -7
  29. package/skill/references/ui.md +15 -5
  30. package/dist/client/ui/src/avatar/AvatarStack.d.ts +0 -26
  31. package/dist/client/ui/src/lib/typing.d.ts +0 -19
  32. package/dist/client/ui/src/motion/TypedText.d.ts +0 -9
  33. package/dist/client/ui/src/primitives/GitHubMark.d.ts +0 -12
  34. 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;
@@ -337,6 +338,9 @@ export declare const en: {
337
338
  "appearance.chatBackgroundErrorFailed": string;
338
339
  "appearance.chatBackgroundEmpty": string;
339
340
  "appearance.chatBackgroundShow": string;
341
+ "appearance.chatBackgroundTexture": string;
342
+ "appearance.chatBackgroundTextureNone": string;
343
+ "appearance.chatBackgroundTextureGrain": string;
340
344
  "appearance.chatBackgroundOpacityStep": string;
341
345
  "appearance.chatBackgroundFit": string;
342
346
  "appearance.chatBackgroundFitCover": string;
@@ -372,7 +376,6 @@ export declare const en: {
372
376
  "market.title": string;
373
377
  "market.skills": string;
374
378
  "market.connectors": string;
375
- "market.fromMarket": string;
376
379
  "market.autoUpdate": string;
377
380
  "market.updatesN": string;
378
381
  "market.updatingN": string;
@@ -472,6 +475,20 @@ export declare const en: {
472
475
  "common.other": string;
473
476
  "common.openFolder": string;
474
477
  "market.reload": string;
478
+ "market.moreShelves": string;
479
+ "market.skillsN": {
480
+ one: string;
481
+ other: string;
482
+ };
483
+ "market.addSkill": string;
484
+ "market.addConnector": string;
485
+ "market.writeSkill": string;
486
+ "market.writeSkillDetail": string;
487
+ "market.writeSkillPrompt": string;
488
+ "market.personalSkills": string;
489
+ "market.projects": string;
490
+ "market.projectShown": string;
491
+ "market.refreshInstalled": string;
475
492
  "market.readingPlugins": string;
476
493
  "pluginDetail.settings": string;
477
494
  "pluginDetail.settingsAt": string;
@@ -1110,14 +1127,11 @@ export declare const en: {
1110
1127
  "update.download": string;
1111
1128
  "update.downloadingPercent": string;
1112
1129
  "update.failed": string;
1113
- "update.goInstall": string;
1114
1130
  "update.newVersion": string;
1115
1131
  "update.pausedPercent": string;
1116
1132
  "update.preparing": string;
1117
1133
  "update.reading": string;
1118
- "update.readyInstaller": string;
1119
1134
  "update.readyRestart": string;
1120
- "update.reopenInstaller": string;
1121
1135
  "update.restartNow": string;
1122
1136
  "update.restartToUpdate": string;
1123
1137
  "update.resume": string;
@@ -1128,7 +1142,6 @@ export declare const en: {
1128
1142
  "updateDialog.downloadedSoFar": string;
1129
1143
  "updateDialog.downloading": string;
1130
1144
  "updateDialog.failedTitle": string;
1131
- "updateDialog.installerMissing": string;
1132
1145
  "updateDialog.notReady": string;
1133
1146
  "updateDialog.pause": string;
1134
1147
  "updateDialog.paused": string;
@@ -1374,6 +1387,7 @@ export declare const en: {
1374
1387
  "storage.exportDetail": string;
1375
1388
  "storage.exportDialogTitle": string;
1376
1389
  "storage.exportFailed": string;
1390
+ "cleanup.failed": string;
1377
1391
  "storage.exportTitle": string;
1378
1392
  "storage.exported": string;
1379
1393
  "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": "把一类任务交给单独配置了模型、工具和指令的子智能体。";
@@ -319,6 +320,9 @@ export declare const zhCN: {
319
320
  readonly "appearance.chatBackgroundErrorFailed": "图片导入失败,原背景图未改变。";
320
321
  readonly "appearance.chatBackgroundEmpty": "尚未设置背景图";
321
322
  readonly "appearance.chatBackgroundShow": "显示位置";
323
+ readonly "appearance.chatBackgroundTexture": "质感";
324
+ readonly "appearance.chatBackgroundTextureNone": "无";
325
+ readonly "appearance.chatBackgroundTextureGrain": "磨砂";
322
326
  readonly "appearance.chatBackgroundOpacityStep": "浓度 {percent}%";
323
327
  readonly "appearance.chatBackgroundFit": "铺放方式";
324
328
  readonly "appearance.chatBackgroundFitCover": "铺满";
@@ -351,7 +355,6 @@ export declare const zhCN: {
351
355
  readonly "market.title": "插件市场";
352
356
  readonly "market.skills": "技能";
353
357
  readonly "market.connectors": "连接器";
354
- readonly "market.fromMarket": "从市场添加";
355
358
  readonly "market.autoUpdate": "自动更新";
356
359
  readonly "market.updatesN": "{n} 个有新版本";
357
360
  readonly "market.updatingN": "正在更新 {n} 个…";
@@ -442,6 +445,17 @@ export declare const zhCN: {
442
445
  readonly "common.other": "其他";
443
446
  readonly "common.openFolder": "打开目录";
444
447
  readonly "market.reload": "重新读取";
448
+ readonly "market.moreShelves": "更多";
449
+ readonly "market.skillsN": "{n} 个技能";
450
+ readonly "market.addSkill": "添加技能";
451
+ readonly "market.addConnector": "添加连接器";
452
+ readonly "market.writeSkill": "让 agent 写一个";
453
+ readonly "market.writeSkillDetail": "开新对话";
454
+ readonly "market.writeSkillPrompt": "帮我写一个新技能,放在 ~/.plume/skills 下(一个目录,里面的 SKILL.md 开头写 name 和 description):";
455
+ readonly "market.personalSkills": "个人";
456
+ readonly "market.projects": "项目";
457
+ readonly "market.projectShown": "查看的项目";
458
+ readonly "market.refreshInstalled": "刷新";
445
459
  readonly "market.readingPlugins": "正在读取插件目录";
446
460
  readonly "pluginDetail.settings": "设置";
447
461
  readonly "pluginDetail.settingsAt": "去 设置 › {place}";
@@ -461,7 +475,7 @@ export declare const zhCN: {
461
475
  readonly "market.unreadable": "{n} 个插件有问题,去设置里看看";
462
476
  readonly "market.notInCatalog": "插件目录里已经没有它了";
463
477
  readonly "market.unreachable": "连不上插件市场({reason}),列出的是已装的和上次读到的";
464
- readonly "market.localOwned": "这是本机直接放进目录的插件,到它的目录里删";
478
+ readonly "market.localOwned": "本地上传了同名的插件,先卸掉那一份";
465
479
  readonly "market.builtinOwned": "这是随应用发的插件,不能卸载、停用";
466
480
  readonly "market.notAPlugin": "这个文件夹不是构建好的插件:文件夹名只能用小写字母、数字和连字符,里面要有 dist/index.mjs";
467
481
  readonly "market.pickPluginFolder": "选择插件文件夹";
@@ -969,14 +983,11 @@ export declare const zhCN: {
969
983
  readonly "update.download": "下载安装";
970
984
  readonly "update.downloadingPercent": "下载中 {percent}%";
971
985
  readonly "update.failed": "下载失败";
972
- readonly "update.goInstall": "去安装";
973
986
  readonly "update.newVersion": "新版本 {version}";
974
987
  readonly "update.pausedPercent": "已暂停 {percent}%";
975
988
  readonly "update.preparing": "准备中";
976
989
  readonly "update.reading": "正在读取版本…";
977
- readonly "update.readyInstaller": "已下载完成,安装程序已经打开。按它的提示装完,再重新启动 Plume 就是新版本了。";
978
990
  readonly "update.readyRestart": "已下载完成。重启 Plume 即可用上新版本,正在进行的对话会中断。";
979
- readonly "update.reopenInstaller": "重新打开安装包";
980
991
  readonly "update.restartNow": "立即重启";
981
992
  readonly "update.restartToUpdate": "重启更新";
982
993
  readonly "update.resume": "继续下载";
@@ -987,7 +998,6 @@ export declare const zhCN: {
987
998
  readonly "updateDialog.downloadedSoFar": "(已下 {size})";
988
999
  readonly "updateDialog.downloading": "正在下载";
989
1000
  readonly "updateDialog.failedTitle": "这次没能写完";
990
- readonly "updateDialog.installerMissing": "没找到下载好的安装包,重新下载一次";
991
1001
  readonly "updateDialog.notReady": "更新没有准备好,重新下载一次再试";
992
1002
  readonly "updateDialog.pause": "暂停";
993
1003
  readonly "updateDialog.paused": "已暂停";
@@ -1197,6 +1207,7 @@ export declare const zhCN: {
1197
1207
  readonly "storage.exportDetail": "把所有会话另存为一个 SQLite 文件(.db),任何 SQLite 工具都能打开";
1198
1208
  readonly "storage.exportDialogTitle": "导出会话记录";
1199
1209
  readonly "storage.exportFailed": "导出失败:{reason}";
1210
+ readonly "cleanup.failed": "清除失败:{reason}";
1200
1211
  readonly "storage.exportTitle": "导出会话记录";
1201
1212
  readonly "storage.exported": "已导出到 {path}";
1202
1213
  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
  }
@@ -1268,6 +1293,8 @@ export interface PluginManifest {
1268
1293
  license?: string;
1269
1294
  /** 详情页「试试这样问」,`plume.defaultPrompt`。 */
1270
1295
  prompts?: string[];
1296
+ /** 技能合集带几个技能:不在 `package.json` 里,发市场时数 `skills/` 下的目录写进市场目录,只有 `readCatalogInfo` 读得到。 */
1297
+ skills?: number;
1271
1298
  }
1272
1299
  /** 一个启用着的插件。 */
1273
1300
  export interface InstalledPlugin {
@@ -1362,7 +1389,7 @@ export interface SandboxProcess {
1362
1389
  /** 连它起的子进程一起停。 */
1363
1390
  kill(signal?: "SIGTERM" | "SIGKILL"): void;
1364
1391
  }
1365
- /** 服务 `sandbox`:在沙箱里跑 shell 命令。内置插件 plume.sandbox 提供(macOS 用 seatbelt,Linux 用 bubblewrap,Windows 用受限令牌)。 */
1392
+ /** 服务 `sandbox`:在沙箱里跑 shell 命令。内置插件 plume.sandbox 提供(macOS 用 seatbelt,Windows 用受限令牌;Linux 没有后端)。 */
1366
1393
  export interface SandboxService {
1367
1394
  /**
1368
1395
  * 用用户的 shell 起一条命令,环境向登录 shell 要(从图标启动时也找得到用户装的工具)。`mode` 不给就不进沙箱;
@@ -726,8 +726,6 @@ export interface AgentService {
726
726
  origin?: string;
727
727
  })[]>;
728
728
  listDirectory(sessionId: string, path: string): Promise<FileEntry[]>;
729
- /** 在原目录中改名;不覆盖同名目标,不改会话根目录。 */
730
- renameFile(sessionId: string, path: string, name: string): Promise<string>;
731
729
  /**
732
730
  * 在会话工作目录里找一个写得不全的路径:裸文件名(`approvals.ts`)或相对于某个子目录的路径。
733
731
  * 返回找到的绝对路径,按路径排好;跳过依赖和构建产物目录。
@@ -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
  * 用函数而不是每种语言一份,界面加一种语言时插件的写法不用变;没翻译的语言由插件自己决定退回哪一种。
@@ -235,6 +235,11 @@ export interface PanelAvailabilityContext {
235
235
  /** 有没有打开的会话:新对话还没发消息时是 false。只围着一个会话转的面板(侧边聊天、子 Agent)没有会话就不列。 */
236
236
  session: boolean;
237
237
  }
238
+ /** 会话建好之前人在输入框里选的模型(`SessionModel.id`)和思考档位;没有 `model` 是跟随父会话(独立会话用默认模型)。 */
239
+ export interface SessionChoice {
240
+ model?: string;
241
+ thinking?: ThinkingLevel;
242
+ }
238
243
  /**
239
244
  * 产品的对话视图画一个会话(插件自己的子会话:侧边聊天、子 Agent;或插件的独立会话:页面上的助手),和主列是同一套转录:
240
245
  * 工具卡片、思考、运行行都一样。子会话等人批准时,审批卡压在主输入框上方,标明是哪个子会话在请求;独立会话不挂在哪个对话下,
@@ -251,18 +256,27 @@ export interface ConversationProps {
251
256
  /** 输入框跨窗口保留文字、附件和引用时传 PanelProps.tab;页面上的助手传一个自己固定的名字。 */
252
257
  draftKey?: string;
253
258
  placeholder?: LocalizedText;
254
- send(text: string): void | Promise<void>;
255
- /** 支持附件时接收完整消息;不提供时保持纯文字输入。 */
256
- sendMessage?(message: OutgoingMessage): void | Promise<void>;
259
+ /** `choice`:会话还没建、给了 `blank` 时,人在第一句之前选的模型和档位,插件建会话时照它给。 */
260
+ send(text: string, choice?: SessionChoice): void | Promise<void>;
261
+ /** 支持附件时接收完整消息;不提供时保持纯文字输入。`choice` 同 `send`。 */
262
+ sendMessage?(message: OutgoingMessage, choice?: SessionChoice): void | Promise<void>;
257
263
  /** steer 运行时也能发,发送键不变停止键。 */
258
264
  mode?: "turn" | "steer";
259
265
  disabled?: boolean;
260
266
  /**
261
- * 发送按钮左边的模型和思考档位,量的是这个会话(子会话没选的跟着父会话,独立会话没选的用默认模型);会话还没建时不画。
267
+ * 发送按钮左边的模型和思考档位,量的是这个会话(子会话没选的跟着父会话,独立会话没选的用默认模型);会话还没建时只有给了 `blank` 才画。
262
268
  * `choose`:和主输入框同一枚按钮,能换,子会话的模型菜单最前面多一项「跟随主会话」;`show`:只读的模型名和档位,悬停说明末尾接 `modelTip`。
263
269
  * 不给就不画。插件经 `ui.composerItem(…, { side: "end" })` 加的格子(上下文用量)不用声明,会话在就画。
264
270
  */
265
271
  model?: "choose" | "show";
272
+ /**
273
+ * 会话还没建(第一句还没发)时 `model: "choose"` 也画,人先选好,第一句发出去时选的经 `send`、`sendMessage` 的 `choice` 交回。
274
+ * `parentId`:第一句会开在哪个会话下面,菜单最前面的「跟随主会话」跟它,不给是独立会话;`model`:人没动过时插件要用的模型,不给就跟随父会话(独立会话用默认模型)。
275
+ */
276
+ blank?: {
277
+ parentId?: string;
278
+ model?: string;
279
+ };
266
280
  /** `model: "show"` 时悬停说明的最后一句:模型是怎么定下的、去哪里改。 */
267
281
  modelTip?: LocalizedText;
268
282
  /** 发送按钮左边、用量和模型前面,插件自己的东西。 */
@@ -421,6 +435,29 @@ export type NotifyLevel = "info" | "warn" | "error";
421
435
  * (出错、跑到上限、卡住)而人还没打开看。人按停的、看着跑完的不留记号;在跑看摘要的 `running`。
422
436
  */
423
437
  export type SessionActivity = "waiting" | "done" | "failed";
438
+ /**
439
+ * `page: "extensions-skills"`、`"extensions-connectors"` 的组件收到的:页头的刷新(技能还有项目选择)由「插件市场」画,
440
+ * 组件照着读、照着画列表。别的位置的组件什么也不收。
441
+ */
442
+ export interface ExtensionsSectionProps {
443
+ /** 只给技能:选中的项目目录,null 是只看个人的(和插件带来的)。 */
444
+ project?: string | null;
445
+ /** 人点了刷新就变一次,组件跟着重读。 */
446
+ refreshKey?: number;
447
+ /** 打开一个插件的详情(「插件市场」里点开它看到的那一页)。`key` 是插件的目录名(`InstalledPlugin.dir` 的最后一段)。 */
448
+ openPlugin?(key: string): void;
449
+ }
450
+ /** 「插件市场」那一类页顶「添加」菜单里的一项,见 `SettingsSectionOptions.actions`。 */
451
+ export interface SectionAction {
452
+ label: LocalizedText;
453
+ /** 名字下面的一句小字。 */
454
+ detail?: LocalizedText;
455
+ icon?: ComponentType<{
456
+ size?: number;
457
+ strokeWidth?: number;
458
+ }>;
459
+ run(): void | Promise<void>;
460
+ }
424
461
  export interface SettingsSectionOptions {
425
462
  /**
426
463
  * 这一节的标题:插件详情里「设置」下面的小标题;单独占导航一行时是那一行的字和页头标题。放进产品的页面里时只当无障碍名,
@@ -447,7 +484,8 @@ export interface SettingsSectionOptions {
447
484
  * 画在哪。不给时画进这个插件的详情(「插件市场」里点开它看到的那一页)的「设置」一节:只和这个插件有关的
448
485
  * 选项放这里,不占设置导航。用户不管装了哪个插件、都会到设置里直接找的功能才另给位置:
449
486
  * - `nav` 单独占设置导航的一行(使用统计这类),页头由产品按 `title`、`detail` 画;
450
- * - `extensions-skills`、`extensions-connectors` 是「插件市场」页的「技能」「连接器」两类,画在分类标签下面,整节由组件自己画。
487
+ * - `extensions-skills`、`extensions-connectors` 是「插件市场」页的「技能」「连接器」两类,画在筛选条的「已安装」里;
488
+ * 刷新(技能还有看哪个项目)由市场画在页头,经 `ExtensionsSectionProps` 交给组件。
451
489
  * 给的是管技能、MCP 连接器的插件:装了哪些、开着没有、配好没有,和还能装什么放在一处看。
452
490
  * - `general` 是「常规」页「常规」那张卡里的一行,排在「显示思考过程」后面:名字和说明由产品按 `title`、`detail` 画,
453
491
  * 组件只画右边的控件(通常是一个 `Toggle`)。给的是随应用发、不能停用的插件自己的开关(智能标题总结)。
@@ -458,6 +496,12 @@ export interface SettingsSectionOptions {
458
496
  * 「智能体」页只在有插件往里画东西时出现在导航里。给了位置的,插件详情里有一个去那里的入口。
459
497
  */
460
498
  page?: "nav" | "general" | "personalization" | "agents" | "session-models" | "extensions-skills" | "extensions-connectors";
499
+ /**
500
+ * `page: "extensions-skills"`、`"extensions-connectors"` 时放进页顶「添加技能」「添加连接器」的条目,技能的排在产品自己那一项
501
+ * (让 agent 写一个)后面;只有一项时按钮直接做它。给的是只有这个插件做得到的添加方式(打开个人技能目录、手动添加连接器)。
502
+ * 跑完市场换到「已安装」,这一节接着画(比如弹出添加的对话框)。
503
+ */
504
+ actions?: SectionAction[];
461
505
  }
462
506
  /**
463
507
  * 产品的模型下拉:和输入框旁的模型菜单是同一份列表(常用、按供应商分组、搜索),最前面多一项「跟随」。
@@ -969,7 +1013,7 @@ export interface UiContext {
969
1013
  */
970
1014
  openLink(url: string, sessionId: string | undefined): Promise<void>;
971
1015
  /** 加一节设置,默认画进本插件的详情,`options.page` 改放到设置导航或产品的某一页;同一处按 order 从小到大。id 在插件内唯一。 */
972
- settingsSection(id: string, component: ComponentType, options: SettingsSectionOptions): void;
1016
+ settingsSection(id: string, component: ComponentType<ExtensionsSectionProps>, options: SettingsSectionOptions): void;
973
1017
  /** 截屏用的宿主能力,见 `ScreenCapture`。 */
974
1018
  readonly screen: ScreenCapture;
975
1019
  /** 截屏浮层里画的组件。同一时间只有一个,后注册的生效,它撤下后前一个恢复。 */
@@ -1000,7 +1044,7 @@ export interface UiContext {
1000
1044
  }): Promise<ListedCommand[]>;
1001
1045
  /** 产品的对话视图,见 `ConversationProps`。 */
1002
1046
  readonly Conversation: ComponentType<ConversationProps>;
1003
- /** 产品的会话背景设置:选图、拖入图片、预览、铺放、效果、浓淡和显示位置;外观页不再有这一节,背景只经它设置。 */
1047
+ /** 产品的会话背景设置:选图、拖入图片、预览、铺放、效果、浓淡、显示位置,以及整个窗口的磨砂质感(不看皮肤、不要图);外观页不再有这一节,背景只经它设置。 */
1004
1048
  readonly ChatBackgroundSettings: ComponentType<{
1005
1049
  title?: string;
1006
1050
  }>;
@@ -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));
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/lib/manifest.mjs CHANGED
@@ -58,6 +58,7 @@ export function readCatalogInfo(raw) {
58
58
  homepage: text(entry.homepage),
59
59
  license: text(entry.license),
60
60
  prompts: texts(entry.prompts),
61
+ skills: Number.isSafeInteger(entry.skills) && entry.skills >= 0 ? entry.skills : undefined,
61
62
  }),
62
63
  };
63
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumeai/plugin-sdk",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "写 Plume 插件用的类型、命令(新建、检查、构建、安装)、插件模板和给 agent 的 skill。随 Plume 应用带一份,也发在 npm 上(插件仓库按版本依赖)。",
5
5
  "license": "MIT",
6
6
  "repository": {
package/skill/SKILL.md CHANGED
@@ -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` 的输入框) |
@@ -180,7 +180,7 @@ ui.toolCard("my_tool", MyCard);
180
180
  - **只补东西的卡片**:登记了卡片的调用默认单独一行,不和前后的调用合成一段活。卡片只是在通用卡片下面补一点(认不出时直接返回 `fallback`)、调用本身该和同类的合在一起时,加 `inline: true`:调用照常按归类并进一段活,单独一次时那一行、展开一段活后的那一行由这张卡画。例子是 `plume.mcp` 的文档预览。
181
181
  - **崩溃让位**:每张卡片外面有错误边界,渲染出错时让给同一工具的下一个注册者,都不行用通用卡片。组件里不用自己 try/catch。
182
182
  - **后注册的生效**:同一工具 `order` 小的在前;`order` 相同时后注册的在前,它撤下后前一个恢复。卡片的 `order` 默认是 0,写更小的 `order` 也能排到前面。
183
- - 外框用 `@plume/ui` 的 `Card`(`title`、`subtitle` 放命令或路径、`status`);输出用 `CodeBlock`,改动用 `DiffView`。
183
+ - 外框用 `@plume/ui` 的 `Card`(`title`、`subtitle` 放命令或路径、`status`);输出用 `CodeBlock`,改动用 `ui.FileDiff`。
184
184
  - 卡片只占一行时(参数还在写、事情已经交出去了)用 `@plume/ui` 的 `FlowRow`:和转录里思考、工具活、送达那几行同一个骨架(前置图标、标题、摘要、尾部、在跑时摘要扫光、`onToggle` 给了才可点),例子是 `plume.spawn-session` 的卡片写参数时那一行和开始以后那一行。
185
185
  - 审批卡压在输入框正上方,不在工具卡片里:等待确认时工具卡片收到 `status: "approval"`,卡片里不放允许、拒绝。要给自己的工具换一种审批卡,见下面「审批卡」。
186
186
 
@@ -405,7 +405,7 @@ function SidePanel({ tab }: PanelProps) {
405
405
  ```
406
406
 
407
407
  - 不给 `composer` 就只读。默认按发送调 `send`,会话跑着时发送键换成停止键、按了调 `stop`,保持侧边聊天行为。`mode: "steer"` 时运行中也能发送,发送键不变停止键;`disabled` 保留但禁用操控区。停止可放在面板头部。
408
- - 输入框右下和主输入框一样:插件经 `ui.composerItem(…, { side: "end" })` 加的格子(上下文用量)会话在就画,拿到的是子会话;`model: "choose"` 再画能换的模型和思考档位(菜单最前面是「跟随主会话」,选了就清掉子会话自己的),`model: "show"` 画只读的模型和档位、悬停说明末尾接 `modelTip`。量的都是下一轮实际会用的:子会话没选的跟着父会话。会话还没建(第一句没发)时这些都不画。`footer` 放插件自己的东西,排在它们前面。
408
+ - 输入框右下和主输入框一样:插件经 `ui.composerItem(…, { side: "end" })` 加的格子(上下文用量)会话在就画,拿到的是子会话;`model: "choose"` 再画能换的模型和思考档位(菜单最前面是「跟随主会话」,选了就清掉子会话自己的),`model: "show"` 画只读的模型和档位、悬停说明末尾接 `modelTip`。量的都是下一轮实际会用的:子会话没选的跟着父会话。会话还没建(第一句没发)时这些都不画;要让人在第一句之前就选,给 `blank: { parentId, model }`(第一句开在哪个会话下面、人没动过时插件要用的模型),`model: "choose"` 的模型和档位照画(没有用量格子),选的经 `send(text, choice)`、`sendMessage(message, choice)` 的 `choice`(`SessionChoice`:`model` 没有是跟随父会话)交回,建会话时照它给 `ctx.sessions.create({ parentId, ...choice })`。`footer` 放插件自己的东西,排在它们前面。
409
409
  - 提供 `sendMessage` 时输入框接入附件,交回完整 `OutgoingMessage`;不提供仍是纯文字。草稿与附件按子会话隔离,异步发送失败不会把文字还到另一个子会话。
410
410
  - 需要保留草稿、接收划词引用或弹出窗口时设置 `composer.draftKey: tab`(`PanelProps.tab`)。未发送文字、附件和引用按标签保存,支持 `sendMessage`;弹出、收回先确认存储成功,失败阻止交接并保留内存草稿。发送时引用附在消息文字前面,发送失败保留草稿,关闭标签后清理草稿;是否允许关闭仍由面板的 `onClose` 决定。
411
411
  - `renderUserMessage(message, fallback)` 只改当前视图用户消息的样子,可按 `origin === "parent"` 画任务卡,其余返回 fallback。`replyText(text, message)` 只过滤当前视图正文与复制,回报卡去重时只拿掉该条最终正文,不动工具和思考,也不改存储。回报卡复用 `ui.turnFooter`。
@@ -447,10 +447,18 @@ ui.settingsSection("usage", UsagePage, { title: (locale) => MESSAGES[locale].tit
447
447
  ui.settingsSection("memory", MemorySettings, { title: (locale) => MESSAGES[locale].title, page: "personalization" });
448
448
  ```
449
449
 
450
- 管技能、MCP 连接器的插件给 `page: "extensions-skills"`、`"extensions-connectors"`:画进「插件市场」页的「技能」「连接器」两类,在分类标签下面、宽度和设置页一样,整节(介绍、列表、添加按钮)由组件自己画;插件详情里是「去 插件市场 › 技能」这样的入口。内置的 `plume.skills`、`plume.mcp` 就是这样放的:
450
+ 管技能、MCP 连接器的插件给 `page: "extensions-skills"`、`"extensions-connectors"`:画进「插件市场」页「技能」「连接器」两类筛选条的「已安装」里(「全部」和各个货架是市场里的技能合集、连接器,由宿主画),宽度和设置页一样;插件详情里是「去 插件市场 › 技能」这样的入口,落在「已安装」。内置的 `plume.skills`、`plume.mcp` 就是这样放的。
451
+
452
+ - 刷新(技能还有看哪个项目)由宿主画在页头,组件经参数(`ExtensionsSectionProps`)拿到 `refreshKey`(人点了刷新就变)、`openPlugin(key)`(打开一个插件的详情,`key` 是插件目录名),技能的组件还拿到 `project`(选中的项目目录,null 是只看个人的),照着读、照着画列表。组件只画列表,不画自己的工具条。
453
+ - 只有这个插件做得到的添加方式经 `actions`(`SectionAction`:`label`、`detail`、`icon`、`run`)放进页顶「添加技能」「添加连接器」:技能的排在宿主自己那一项(让 agent 写一个)后面;只有一项时按钮直接做它。`run` 跑完宿主换到「已安装」,要弹对话框的,在 `run` 里记下、等那一节画出来再开(`plume.mcp` 的「手动添加」就是这样)。
451
454
 
452
455
  ```tsx
453
- ui.settingsSection("skills", SkillsSettings, { title: (locale) => MESSAGES[locale].settingsTitle, icon: WandSparkles, page: "extensions-skills" });
456
+ ui.settingsSection("skills", (props) => <SkillsSettings {...props} />, {
457
+ title: (locale) => MESSAGES[locale].settingsTitle,
458
+ icon: WandSparkles,
459
+ page: "extensions-skills",
460
+ actions: [{ label: (locale) => MESSAGES[locale].openPersonalDir, icon: FolderOpen, run: () => api.reveal(null, null) }],
461
+ });
454
462
  ```
455
463
 
456
464
  画进产品页的 `page` 有四种,宿主都不画这一节的页头,`title` 只当无障碍名(`general`、`session-models` 例外,见下);同一页的几节按 `order` 排:
@@ -498,6 +506,8 @@ function ModelSetting() {
498
506
 
499
507
  `root` 是窗口最外层与透明停靠侧栏共用的底色,默认跟随 `shell`;`sidebar` 只给 compact 浮出侧栏铺底,不给停靠侧栏另铺一层;`accent-dim` 是强调弱底,默认是强调色的 10% 透明底。数据皮肤可直接给出完整的面、线、字 token;若一份配色只适用于一个模式,给 `light`、`dark` 同一组 token,并用 `mode` 指定深浅模式。
500
508
 
509
+ `root-image` 是盖在 `root` 上的底纹,值是 CSS 图像,可以逗号分隔叠几层(例如 `linear-gradient(160deg, #…, #…)`,先写的在上面),默认 `none`。颗粒、噪点这类质感不写进皮肤:磨砂是用户在背景设置里开关的,宿主叠在底纹上面,哪套皮肤都一样。只在管理配色的数据皮肤下铺在窗口外圈和透明的停靠侧栏上,正文卡片仍是 `shell` 的实底。宿主把它固定在视口上(`background-attachment: fixed`),外圈、侧栏吸顶标题和 Windows 顶带画的是同一片;平铺的小图按自身尺寸重复,渐变铺满整个窗口。侧栏的字直接压在它上面,渐变两端的颜色都要和 `root` 一样满足文字对比度。
510
+
501
511
  在界面入口的 `apply` 中开启远端 watch 时,用 `ui.effect(() => call.cancel())` 登记取消。清理按后登记先执行,一项抛错不妨碍其余清理;宿主先让旧入口失效再清理,因此停用、重载或 apply 失败后,旧异步回调的 `ui.skin` 不会复活皮肤。组件内的订阅仍用 React effect 清理。
502
512
 
503
513
  ## 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;