@plumeai/plugin-sdk 0.1.3 → 0.1.5

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.
@@ -6,7 +6,7 @@
6
6
  * plume-plugin init <目录> [--name 插件名] [--display 市场里的名字]
7
7
  * plume-plugin check [目录] 类型检查和插件规则(公开接口、样式、文案),缺省当前目录
8
8
  * plume-plugin build [目录]
9
- * plume-plugin install [目录] [--to 目录] 装进 ~/.plume/plugins,Plume 开着就当场加载
9
+ * plume-plugin install [目录] [--to 目录] 装进 ~/.plume/local-plugins,Plume 开着就当场加载
10
10
  * plume-plugin dev [目录] [--to 目录] 构建、装上,之后一改源码就重新来一遍
11
11
  * plume-plugin link [目录] 重新链到这份 SDK(Plume 挪了位置、npm 删了链接时)
12
12
  */
@@ -372,7 +372,6 @@ export declare const en: {
372
372
  "market.title": string;
373
373
  "market.skills": string;
374
374
  "market.connectors": string;
375
- "market.fromMarket": string;
376
375
  "market.autoUpdate": string;
377
376
  "market.updatesN": string;
378
377
  "market.updatingN": string;
@@ -472,6 +471,20 @@ export declare const en: {
472
471
  "common.other": string;
473
472
  "common.openFolder": string;
474
473
  "market.reload": string;
474
+ "market.moreShelves": string;
475
+ "market.skillsN": {
476
+ one: string;
477
+ other: string;
478
+ };
479
+ "market.addSkill": string;
480
+ "market.addConnector": string;
481
+ "market.writeSkill": string;
482
+ "market.writeSkillDetail": string;
483
+ "market.writeSkillPrompt": string;
484
+ "market.personalSkills": string;
485
+ "market.projects": string;
486
+ "market.projectShown": string;
487
+ "market.refreshInstalled": string;
475
488
  "market.readingPlugins": string;
476
489
  "pluginDetail.settings": string;
477
490
  "pluginDetail.settingsAt": string;
@@ -351,7 +351,6 @@ export declare const zhCN: {
351
351
  readonly "market.title": "插件市场";
352
352
  readonly "market.skills": "技能";
353
353
  readonly "market.connectors": "连接器";
354
- readonly "market.fromMarket": "从市场添加";
355
354
  readonly "market.autoUpdate": "自动更新";
356
355
  readonly "market.updatesN": "{n} 个有新版本";
357
356
  readonly "market.updatingN": "正在更新 {n} 个…";
@@ -442,6 +441,17 @@ export declare const zhCN: {
442
441
  readonly "common.other": "其他";
443
442
  readonly "common.openFolder": "打开目录";
444
443
  readonly "market.reload": "重新读取";
444
+ readonly "market.moreShelves": "更多";
445
+ readonly "market.skillsN": "{n} 个技能";
446
+ readonly "market.addSkill": "添加技能";
447
+ readonly "market.addConnector": "添加连接器";
448
+ readonly "market.writeSkill": "让 agent 写一个";
449
+ readonly "market.writeSkillDetail": "开新对话";
450
+ readonly "market.writeSkillPrompt": "帮我写一个新技能,放在 ~/.plume/skills 下(一个目录,里面的 SKILL.md 开头写 name 和 description):";
451
+ readonly "market.personalSkills": "个人";
452
+ readonly "market.projects": "项目";
453
+ readonly "market.projectShown": "查看的项目";
454
+ readonly "market.refreshInstalled": "刷新";
445
455
  readonly "market.readingPlugins": "正在读取插件目录";
446
456
  readonly "pluginDetail.settings": "设置";
447
457
  readonly "pluginDetail.settingsAt": "去 设置 › {place}";
@@ -7,7 +7,7 @@
7
7
  * 开和关是同一颗按钮、同一个左内边距:左右 6px 让关着时正好是控件边长的方块,一点图标不横跳,颜色也能过渡。
8
8
  */
9
9
  import type { ReactNode } from "react";
10
- export declare function ComposerToggle({ on, onChange, icon, label, tip, size, ...data }: {
10
+ export declare function ComposerToggle({ on, onChange, icon, label, tip, size, iconOnly, ...data }: {
11
11
  on: boolean;
12
12
  onChange: (on: boolean) => void;
13
13
  /** 16px 的图标,颜色跟着文字走。 */
@@ -18,6 +18,11 @@ export declare function ComposerToggle({ on, onChange, icon, label, tip, size, .
18
18
  tip: string;
19
19
  /** `sm` 放在 `Button size="sm"` 旁边:26px;主输入框的控件跟着 `--plume-composer-control`。 */
20
20
  size?: "composer" | "sm";
21
+ /**
22
+ * 开着时也只有图标,开没开只看颜色,名字和点了会怎样都在悬停提示里。给卡片上挨着一颗「开始」的开关:那里开着时带上的字
23
+ * 读起来像又一颗要按的按钮。主输入框里不用它,那一排的字说的是下一轮在什么模式里。
24
+ */
25
+ iconOnly?: boolean;
21
26
  } & {
22
27
  [key: `data-${string}`]: string | undefined;
23
28
  }): import("react").JSX.Element;
@@ -11,9 +11,11 @@
11
11
  * 放在基础组件里而不是某个功能里,因为有两处要问这个问题——主输入框,问正在写的这次对话;建议的
12
12
  * 对话卡片,问它要开始的那一次。
13
13
  */
14
- export declare function WorktreeToggle({ on, onChange, size, }: {
14
+ export declare function WorktreeToggle({ on, onChange, size, iconOnly, }: {
15
15
  on: boolean;
16
16
  onChange: (on: boolean) => void;
17
17
  /** `sm` 放在 `Button size="sm"` 旁边,比如建议卡片上:26px,主输入框的控件是 28px。 */
18
18
  size?: "composer" | "sm";
19
+ /** 开着时也只有图标,见 `ComposerToggle` 的 `iconOnly`。 */
20
+ iconOnly?: boolean;
19
21
  }): import("react").JSX.Element;
@@ -819,6 +819,8 @@ export interface SessionLaunch {
819
819
  message: string;
820
820
  /** 用哪个模型(`SessionModel.id`);不给用默认模型。不在目录里时抛错,不悄悄换成别的。 */
821
821
  model?: string;
822
+ /** 思考档位;不给用设置里的默认推理强度。不认识的档位抛错;模型没有这一档时,跑的时候就近落到它有的那一档(和输入框里选的一样)。 */
823
+ thinking?: ThinkingLevel;
822
824
  /**
823
825
  * 从哪个会话开出来的,记进新会话摘要的 `spawnedFrom`;那个会话要在、不能是子会话。不给就不记。
824
826
  * 要经 `reportBack` 交回时,`toolCallId` 得是本插件注册的工具在那个会话里的一次调用(提出开新会话的那次)。
@@ -940,7 +942,7 @@ export interface SessionAccess {
940
942
  * `idle` 在它第一次真闲下来时兑现:第一轮结束,它派出去的后台运行都回来了、送回的结果唤起的几轮也做完了,它经 `ToolRun.letter`
941
943
  * 问出去的请求(`request: true`)也都有了答复、答复唤起的几轮也做完了。请求有了答复:经回执的 `answer` 回了信,或对方读到以后
942
944
  * 寄回一封带 `answer` 的信(答掉它读到的全部);对方删掉、寄信的插件停用了也不再等。
943
- * 项目、目录、模型、`spawnedFrom` 不对时抛错,message 是给人看的话(按界面语言)。
945
+ * 项目、目录、模型、思考档位、`spawnedFrom` 不对时抛错,message 是给人看的话(按界面语言)。
944
946
  */
945
947
  launch(options: SessionLaunch): Promise<LaunchedSession>;
946
948
  /** 可用模型目录,只含名称、窗口和思考档位。 */
@@ -1266,6 +1268,8 @@ export interface PluginManifest {
1266
1268
  license?: string;
1267
1269
  /** 详情页「试试这样问」,`plume.defaultPrompt`。 */
1268
1270
  prompts?: string[];
1271
+ /** 技能合集带几个技能:不在 `package.json` 里,发市场时数 `skills/` 下的目录写进市场目录,只有 `readCatalogInfo` 读得到。 */
1272
+ skills?: number;
1269
1273
  }
1270
1274
  /** 一个启用着的插件。 */
1271
1275
  export interface InstalledPlugin {
@@ -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
  * 产品的模型下拉:和输入框旁的模型菜单是同一份列表(常用、按供应商分组、搜索),最前面多一项「跟随」。
@@ -483,6 +527,19 @@ export interface ModelSelectProps {
483
527
  */
484
528
  compact?: boolean;
485
529
  }
530
+ /**
531
+ * 主输入框右下角那两枚:模型和思考档位,样子、菜单都和输入框里的一样。给开新会话之前先选好它怎么跑的地方用(建议新会话的卡片)。
532
+ * 不改哪个会话,只把选择交出来。没选时显示默认模型和设置里的默认推理强度,和新会话发第一句之前的输入框一样。
533
+ */
534
+ export interface ModelControlsProps {
535
+ /** 选中的模型 id(`SessionModel.id`);空串是默认模型。 */
536
+ model: string;
537
+ /** 选中的思考档位;不给是默认推理强度。模型没有这一档时显示的是就近落到的那一档,和跑的时候一致。 */
538
+ thinking?: ThinkingLevel;
539
+ onModelChange(modelId: string): void;
540
+ onThinkingChange(thinking: ThinkingLevel): void;
541
+ disabled?: boolean;
542
+ }
486
543
  /** 「跟随」时会落到哪个模型上,给设置页的下拉算继承的那一项。 */
487
544
  export interface ModelDefaults {
488
545
  /** 新会话的默认模型;一个可用的都没有时为空。 */
@@ -956,7 +1013,7 @@ export interface UiContext {
956
1013
  */
957
1014
  openLink(url: string, sessionId: string | undefined): Promise<void>;
958
1015
  /** 加一节设置,默认画进本插件的详情,`options.page` 改放到设置导航或产品的某一页;同一处按 order 从小到大。id 在插件内唯一。 */
959
- settingsSection(id: string, component: ComponentType, options: SettingsSectionOptions): void;
1016
+ settingsSection(id: string, component: ComponentType<ExtensionsSectionProps>, options: SettingsSectionOptions): void;
960
1017
  /** 截屏用的宿主能力,见 `ScreenCapture`。 */
961
1018
  readonly screen: ScreenCapture;
962
1019
  /** 截屏浮层里画的组件。同一时间只有一个,后注册的生效,它撤下后前一个恢复。 */
@@ -995,6 +1052,8 @@ export interface UiContext {
995
1052
  readonly Markdown: ComponentType<MarkdownProps>;
996
1053
  /** 产品的模型下拉,见 `ModelSelectProps`。 */
997
1054
  readonly ModelSelect: ComponentType<ModelSelectProps>;
1055
+ /** 输入框里的模型和思考档位那两枚,见 `ModelControlsProps`。 */
1056
+ readonly ModelControls: ComponentType<ModelControlsProps>;
998
1057
  /** React hook:「跟随」会落到的模型和默认思考档位,见 `ModelDefaults`;设置改了跟着变。只在组件里调。 */
999
1058
  useModelDefaults(): ModelDefaults;
1000
1059
  /** React hook:登记过的项目,按侧栏的顺序;增删改跟着变。只在组件里调。 */
package/lib/install.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * 把构建好的插件装进本机的 Plume:复制 package.json、README.md、images/、icon.png、dist/、随包的 skills/、.mcp.json 和 LICENSE 到 `~/.plume/plugins/<目录名>/`,
3
- * 和插件市场装的放在同一个地方。Plume 监视这个目录,开着的话当场加载(已经装过的就换成新的)。
2
+ * 把构建好的插件装进本机的 Plume:复制 package.json、README.md、images/、icon.png、dist/、随包的 skills/、.mcp.json 和 LICENSE 到 `~/.plume/local-plugins/<目录名>/`,
3
+ * 和 Plume 里「从本地上传」的放在同一个地方:不跟市场更新,和市场装的同名时这一份生效。Plume 监视这个目录,开着的话当场加载(已经装过的就换成新的)。
4
4
  *
5
5
  * 先复制进旁边的暂存目录再整个换上去:Plume 看到的要么是旧的一份,要么是新的一份,不会是复制到一半的。
6
6
  *
@@ -17,10 +17,10 @@ import { DIR_NAME, PLUGIN_ENTRY, SHIPPED } from "./manifest.mjs";
17
17
 
18
18
  /**
19
19
  * @param {string} source 插件目录
20
- * @param {string} [root] 装到哪个目录下,缺省 Plume 数据目录(`PLUME_HOME`,没设是 ~/.plume)下的 plugins
20
+ * @param {string} [root] 装到哪个目录下,缺省 Plume 数据目录(`PLUME_HOME`,没设是 ~/.plume)下的 local-plugins
21
21
  * @returns {string} 装到的目录
22
22
  */
23
- export function installPlugin(source, root = join(process.env.PLUME_HOME || join(homedir(), ".plume"), "plugins")) {
23
+ export function installPlugin(source, root = join(process.env.PLUME_HOME || join(homedir(), ".plume"), "local-plugins")) {
24
24
  const dir = resolve(source);
25
25
  const key = basename(dir);
26
26
  if (!DIR_NAME.test(key)) throw new Error(`目录名 ${key} 不行:只能用小写字母、数字和连字符`);
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.3",
3
+ "version": "0.1.5",
4
4
  "description": "写 Plume 插件用的类型、命令(新建、检查、构建、安装)、插件模板和给 agent 的 skill。随 Plume 应用带一份,也发在 npm 上(插件仓库按版本依赖)。",
5
5
  "license": "MIT",
6
6
  "repository": {
package/skill/SKILL.md CHANGED
@@ -76,8 +76,8 @@ 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/plugins/<目录名>/`,之后一改源码就重新构建、装上;Plume 开着的话每次都当场加载,不用重启。只装一次用 `plume-plugin build` 再 `plume-plugin install`。也可以把插件的上级目录放进环境变量 `PLUME_PLUGIN_PATHS`,Plume 同样盯着那里。
80
- - Plume 的插件仓库里(`plugins/<目录名>`):`pnpm plugin dev plugins/<目录名>`,和独立项目一样构建、装进本机的 Plume;要连市场一起试(安装、更新),`pnpm market` 起一个本机市场,Plume 启动前设它打印的 `PLUME_MARKET_URL`。
79
+ - 独立项目:`plume-plugin dev` 构建、装进 `~/.plume/local-plugins/<目录名>/`(和市场装的同名时这一份生效,不被市场更新换掉,卸掉就回到市场那份),之后一改源码就重新构建、装上;Plume 开着的话每次都当场加载,不用重启。只装一次用 `plume-plugin build` 再 `plume-plugin install`。也可以把插件的上级目录放进环境变量 `PLUME_PLUGIN_PATHS`,Plume 同样盯着那里。
80
+ - Plume 的插件仓库里(`plugins/<目录名>`):`pnpm plugin dev plugins/<目录名>`,和独立项目一样构建、装进本机的 Plume。
81
81
  7. **自检**:对照文末清单,再跑检查(独立项目 `plume-plugin check`:类型检查加插件规则;仓库里 `pnpm check`)。
82
82
 
83
83
  ## 目录骨架
@@ -342,6 +342,7 @@ export function ComposerToggle({
342
342
  label,
343
343
  tip,
344
344
  size = "composer",
345
+ iconOnly = false,
345
346
  ...data
346
347
  }: {
347
348
  on: boolean;
@@ -354,6 +355,11 @@ export function ComposerToggle({
354
355
  tip: string;
355
356
  /** `sm` 放在 `Button size="sm"` 旁边:26px;主输入框的控件跟着 `--plume-composer-control`。 */
356
357
  size?: "composer" | "sm";
358
+ /**
359
+ * 开着时也只有图标,开没开只看颜色,名字和点了会怎样都在悬停提示里。给卡片上挨着一颗「开始」的开关:那里开着时带上的字
360
+ * 读起来像又一颗要按的按钮。主输入框里不用它,那一排的字说的是下一轮在什么模式里。
361
+ */
362
+ iconOnly?: boolean;
357
363
  } & { [key: `data-${string}`]: string | undefined })
358
364
  ```
359
365
 
@@ -366,11 +372,14 @@ export function WorktreeToggle({
366
372
  on,
367
373
  onChange,
368
374
  size = "composer",
375
+ iconOnly = false,
369
376
  }: {
370
377
  on: boolean;
371
378
  onChange: (on: boolean) => void;
372
379
  /** `sm` 放在 `Button size="sm"` 旁边,比如建议卡片上:26px,主输入框的控件是 28px。 */
373
380
  size?: "composer" | "sm";
381
+ /** 开着时也只有图标,见 `ComposerToggle` 的 `iconOnly`。 */
382
+ iconOnly?: boolean;
374
383
  })
375
384
  ```
376
385
 
@@ -45,7 +45,7 @@ export default {
45
45
  | `ctx.sessions.create({ parentId?, title, model?, thinking?, project?, cwd? })` / `send(id, message, { signal })` / `stop(id)` / `delete(id)` | 插件自己的会话(给 `parentId` 是子会话,不给是独立会话,见「子会话」「独立会话」):建、发一句并等结束、停、删。message 可为字符串或 `SessionMessage`(完整内容与附件;派发任务带 `origin: "parent"`),只能动自己建的 |
46
46
  | `ctx.sessions.start(id, message)` / `wait(runId)` / `report(runId, text, { frame?, delivery? })` | 后台运行立即交回 `{ id, sessionId }`;等待该次结束后,把结果送给它的真实父会话。父忙时安全步骤边界读入,闲时唤起;停止、过期、重复的结果返回 false,不能任意向会话发消息。`frame`(`{ head, tail?() }`):同一次送达里本插件带 `frame` 的几份合成一段,`head` 写一次,接着各份正文,最后是最后一份的 `tail()`(送出那一刻才取,比如「还在等谁」)。`delivery`(`{ id, agent, description, status, incomplete? }`)写进送达消息,宿主记下是哪个插件交的,界面把这一次送达里本插件的几份交给本插件经 `ui.agentDelivery` 登记的组件画成一行;不给就只有模型看得到 |
47
47
  | `ctx.sessions.steer(id, message)` / `onStopped(listener)` | 给正在运行的自有子会话插话,空闲时拒绝;明确停止会话时通知插件,清理尚未启动的排队工作,监听随插件卸载 |
48
- | `ctx.sessions.launch({ project?, cwd?, message, model?, spawnedFrom?, title? })` | 开一个新的顶层会话并发出第一句话,返回 `{ session, done, idle, reportBack }`(`done` 是第一轮为什么结束,`idle` 在它第一次真闲下来时兑现成 `{ stop?, reply }`(最近一轮的收场、最后一段回复),`reportBack(text, status)` 把结果作为一条运行时送达交回 `spawnedFrom` 那个会话一次,画成「来自新会话」一行,来处删了、归档了返回 false;`spawnedFrom.toolCallId` 不是你的工具在那个会话里的调用、或你的插件停用了时抛错);会话进侧栏、不记在插件名下,人能接着聊,插件之后不能再往里发。只能开新的,碰不到已有会话。见下文「开新会话」 |
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
50
  | `ctx.pages` | 驱动界面入口画的网页(`<webview>`):`attach` 认下、`load`、`go`、`evaluate`、`input`、`insertText`、`capture`、`setZoom`、`emulate`、`devtools`、`answerDialogs`,`onEvent` 收页面的事件。见下文「网页」 |
51
51
  | `ctx.commands.provide(provider)` | 给输入框的 `/` 菜单供命令(见下面「`/` 命令」),停用时撤下 |
@@ -79,6 +79,7 @@ export default {
79
79
  | `ui.openLink(url, sessionId)` | 用户点击后像回复里的网址那样打开:有插件接手点击(`takesClicks`,内置浏览器)时开在它那里,否则系统浏览器;限制同 `ui.openExternal` |
80
80
  | `ui.settingsSection(id, Component, options)` | 加一节设置:默认画进本插件详情的「设置」一节;`page: "nav"` 时单独占设置导航一行(`group` 选「Agent 能力」「数据」「代码与版本控制」组),给别的 `page` 时画进产品的那一页(「个性化」「智能体」,或「智能体」里「会话」那张卡的一行,或「插件市场」的「技能」「连接器」两类),见下文 |
81
81
  | `ui.ModelSelect` | 产品的模型下拉:`{ value, onChange(modelId), ariaLabel, inheritLabel, inheritDetail?, inheritedModelId?, inheritedSource?, disabled?, compact? }`;`value` 为空串是「跟随」,菜单顶上那一项就是它,选了 `onChange("")`;`compact` 只剩一枚箭头,贴在它管的按钮旁边当分体按钮(计划卡的「在新会话里执行」),选了什么由那颗按钮自己说 |
82
+ | `ui.ModelControls` | 输入框右下角那两枚(模型、思考档位),样子和菜单都和输入框里的一样:`{ model, thinking?, onModelChange(modelId), onThinkingChange(level), disabled? }`;`model` 为空串、`thinking` 不给时显示默认模型和默认推理强度。不改哪个会话,只把选择交出来,给开新会话前先选好怎么跑的地方用(建议新会话的卡片),选的再交给 `ctx.sessions.launch` |
82
83
  | `ui.useModelDefaults()` | React hook:`{ defaultModel?, sessionModel?, thinking }`——新会话的默认模型、当前会话的模型、默认思考档位,「跟随」时写哪个模型名用它 |
83
84
  | `ui.useProjects()` | React hook:侧栏里的项目 `{ id, name, path }[]`,随增删更新;几个项目可能登记在同一个目录上 |
84
85
  | `ui.screen` | 截屏的宿主能力:开截图浮层、全局快捷键、选文件夹 |
@@ -404,7 +405,7 @@ function SidePanel({ tab }: PanelProps) {
404
405
  ```
405
406
 
406
407
  - 不给 `composer` 就只读。默认按发送调 `send`,会话跑着时发送键换成停止键、按了调 `stop`,保持侧边聊天行为。`mode: "steer"` 时运行中也能发送,发送键不变停止键;`disabled` 保留但禁用操控区。停止可放在面板头部。
407
- - 输入框右下和主输入框一样:插件经 `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` 放插件自己的东西,排在它们前面。
408
409
  - 提供 `sendMessage` 时输入框接入附件,交回完整 `OutgoingMessage`;不提供仍是纯文字。草稿与附件按子会话隔离,异步发送失败不会把文字还到另一个子会话。
409
410
  - 需要保留草稿、接收划词引用或弹出窗口时设置 `composer.draftKey: tab`(`PanelProps.tab`)。未发送文字、附件和引用按标签保存,支持 `sendMessage`;弹出、收回先确认存储成功,失败阻止交接并保留内存草稿。发送时引用附在消息文字前面,发送失败保留草稿,关闭标签后清理草稿;是否允许关闭仍由面板的 `onClose` 决定。
410
411
  - `renderUserMessage(message, fallback)` 只改当前视图用户消息的样子,可按 `origin === "parent"` 画任务卡,其余返回 fallback。`replyText(text, message)` 只过滤当前视图正文与复制,回报卡去重时只拿掉该条最终正文,不动工具和思考,也不改存储。回报卡复用 `ui.turnFooter`。
@@ -446,10 +447,18 @@ ui.settingsSection("usage", UsagePage, { title: (locale) => MESSAGES[locale].tit
446
447
  ui.settingsSection("memory", MemorySettings, { title: (locale) => MESSAGES[locale].title, page: "personalization" });
447
448
  ```
448
449
 
449
- 管技能、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` 的「手动添加」就是这样)。
450
454
 
451
455
  ```tsx
452
- 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
+ });
453
462
  ```
454
463
 
455
464
  画进产品页的 `page` 有四种,宿主都不画这一节的页头,`title` 只当无障碍名(`general`、`session-models` 例外,见下);同一页的几节按 `order` 排:
@@ -10,7 +10,7 @@
10
10
  | --- | --- |
11
11
  | `~/.plume/bin/plume-plugin check` | 类型检查,加上插件规则:只引用公开接口、不写死色值和 Tailwind 类、界面文字不写死中文 |
12
12
  | `~/.plume/bin/plume-plugin build` | 打出 `dist/index.mjs`(和 `dist/ui.mjs`) |
13
- | `~/.plume/bin/plume-plugin install` | 把 `package.json`、`README.md`、`dist/` 复制进 `~/.plume/plugins/<目录名>/`,Plume 开着的话当场加载 |
13
+ | `~/.plume/bin/plume-plugin install` | 把 `package.json`、`README.md`、`dist/` 复制进 `~/.plume/local-plugins/<目录名>/`,Plume 开着的话当场加载 |
14
14
  | `~/.plume/bin/plume-plugin dev` | 构建、装上,之后一改源码就重新构建、装上 |
15
15
  | `~/.plume/bin/plume-plugin link` | `node_modules` 里指向 SDK 的链接坏了(Plume 挪了位置、npm 删了链接)时重建 |
16
16