@plumeai/plugin-sdk 0.1.8 → 0.2.1

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.
@@ -5,7 +5,7 @@ import type { Remote } from "../../infra/rpc/src/index.d.ts";
5
5
  import type { ComponentType, MouseEvent, ReactNode } from "react";
6
6
  import type { ChangeHunk } from "./changes.ts";
7
7
  import type { Locale, SlashCommand, ToolCall, ToolOutput } from "./plugin.ts";
8
- import type { ApprovalDecision, ApprovalRequest, ContextBreakdown, FileContents, OutgoingMessage, PermissionMode, SessionSummary, SessionTarget, SessionView, ThinkingLevel } from "./service.ts";
8
+ import type { ApprovalDecision, ApprovalRequest, ChildTask, FileContents, HumanRequest, OutgoingMessage, PermissionMode, SessionSummary, SessionTarget, SessionView, ThinkingLevel } from "./service.ts";
9
9
  import type { AssistantMessage, DeliveredReport, UserMessage } from "../../core/llm/types.d.ts";
10
10
  export type { Remote, SessionView, ToolCall, ToolOutput };
11
11
  /** 回复正文中的围栏代码块;语法与内容由插件校验,宿主不执行其中的代码。 */
@@ -47,27 +47,66 @@ export interface ToolCardProps {
47
47
  /** 通用卡片。认不出 details 时直接返回它。 */
48
48
  fallback: ReactNode;
49
49
  }
50
- /** 内置工具里做同一类事的那个,见 `ToolLine.like`。 */
51
- export type ToolLike = "read" | "grep" | "edit" | "write" | "bash";
52
- /** `ui.toolLine`:转录里怎么说一个工具。 */
50
+ /** `ToolLine.file`:一次调用对着的本机文件,列目录(`ToolDefinition.kind` 是 `list`)的是目录。 */
51
+ export interface ToolFile {
52
+ /** 模型给的样子:绝对路径,或相对会话的工作目录。列目录的空串是工作目录本身。 */
53
+ path: string;
54
+ /** 只读了其中几行:从第 `from` 行起,到第 `to` 行(不给是读到末尾)。 */
55
+ lines?: {
56
+ from: number;
57
+ to?: number;
58
+ };
59
+ }
60
+ /**
61
+ * `ui.toolLine`:转录里怎么说一个工具。
62
+ *
63
+ * 做的是哪种活不在这里说,在工具定义上(`ToolDefinition.kind`,见 `@plume/contract` 的 `ToolKind`),本体从工具表里读:
64
+ * 本体按它给默认的说法、图标、运行行,并归类——`view`、`list`、`find`、`search` 归「探索」,`create`、`modify` 归「改文件」,
65
+ * `command`、`watch`、`job` 归「跑命令」,这几类连着做时并进同一段活。没声明种类的工具自成一类:只有连着调这个工具才合成一段,图标是终端那枚。
66
+ * 是不是记进度清单也在工具定义上说(`ToolDefinition.category` 是 `checklist`)。
67
+ * 这里只补种类说不出来的:自己的说法和图标,以及从参数里取哪个值。
68
+ *
69
+ * 下面几个从参数里取值的函数(`subject`、`file`、`note`、`command`、`job`)拿到的参数是模型给的、没校验过:取不出就返回 undefined。
70
+ */
53
71
  export interface ToolLine {
54
72
  /**
55
73
  * 做的事,一个动词短语(「查找符号」)。一次调用说成「动作 对象」(「查找符号 runAgent」),一段活里几次合在一起数个数
56
- * (「查找符号 3 个」)。
74
+ * (「查找符号 3 个」)。不给就照种类的说法说(「读取文件」);也没有种类时一次说工具名,合在一起说「使用工具」,只用这里的归类和图标。
57
75
  */
58
- verb: LocalizedText;
59
- /** 这一次要点名的对象,从参数里取;参数是模型给的、没校验过,取不出就返回 undefined,只说动作。 */
76
+ verb?: LocalizedText;
77
+ /** 这一次要点名的对象,从参数里取;取不出就只说动作。 */
60
78
  subject?(args: Record<string, unknown>): string | undefined;
61
79
  /**
62
- * 做的事像哪个内置工具:和它归成一类(读、搜索归「探索」,改、写归「改文件」,`bash` 归「跑命令」),连着做时并进同一段活;
63
- * 运行行用同一种说法;没给 `icon` 时用它的图标。不给就自成一类:只有连着调这个工具才合成一段,图标是终端那枚。
80
+ * 这次调用对着的本机文件(列目录的是目录)。没给 `verb` 时,读文件、列目录的那一行说它相对工作区的路径(读了几行的带上行号,
81
+ * 目录带 `/`),别的说文件名,几次合在一起时说名字;不是列目录、调用成功了的,回复里写到这个文件时直接画成文件签,不用再去磁盘上找。
82
+ * 种类是 `create` 的调用参数要生成好一阵(整个文件的正文):还在生成时,那一行先带上已经写出来的路径,行尾说已经写了几行。
64
83
  */
65
- like?: ToolLike;
84
+ file?(args: Record<string, unknown>): ToolFile | undefined;
85
+ /** 模型在参数里写给人看的一句说明(命令、正则一眼读不出在干什么):有就整行说它,不再说动作和对象。 */
86
+ note?(args: Record<string, unknown>): string | undefined;
87
+ /**
88
+ * 这次跑的命令原文(种类是 `command`、`watch` 的)。只是在读的命令(`rg`、`sed -n`、`git log`)归「探索」;参数还在生成、命令还没写出来时,
89
+ * 这次调用先不画,命令到齐时直接出现在它该在的那一段里。
90
+ */
91
+ command?(args: Record<string, unknown>): string | undefined;
92
+ /**
93
+ * 种类是 `job` 的这次调用查看或停掉的是哪个后台任务:`id` 对得上起它那次结果的 `details.job.id`(见 `@plume/contract` 的 `BackgroundJobDetails`),
94
+ * `stop` 是停掉它。查看的那一行说是哪个任务的输出(起它那次调用的说明),连着查同一个任务的几次合成一行、展开是这几次读到的全部输出;
95
+ * 停掉的那一行说停止命令。一段活里有命令时不另数,只有查看时一个任务记一次。不给或取不出时仍是这类调用,只是不知道是哪个任务。
96
+ */
97
+ job?(args: Record<string, unknown>): {
98
+ id?: string;
99
+ stop?: boolean;
100
+ } | undefined;
66
101
  /**
67
102
  * 归哪一类活,名字插件自己起(`"browser"`):同一类的几个工具连着做时合成一段,和读、跑命令、改文件那几类也接成一段——
68
- * 边改边在页面上验是一件事。给了就不按 `like` 归类,`like` 照旧管运行行和图标。
103
+ * 边改边在页面上验是一件事(`apart` 时不接)。给了就不按种类归类,种类照旧管说法、运行行和图标。
69
104
  */
70
105
  group?: string;
106
+ /**
107
+ * `group` 这一类自成一段:只和同一类的接,不和读、跑命令、改文件那几类接(MCP 服务的工具:不管哪台服务都是一类,和本地的活分开)。
108
+ */
109
+ apart?: true;
71
110
  /** 几次合在一起时数次数,不数个数:「操作网页 4 次」,不是读着像四张网页的「操作网页 4 个」。 */
72
111
  times?: true;
73
112
  /**
@@ -204,7 +243,7 @@ export interface PanelOptions {
204
243
  }>;
205
244
  /**
206
245
  * 打开这个面板的快捷键,Electron accelerator 的写法(`Control+\``、`CommandOrControl+Shift+P`),卡片上会显示它。
207
- * 作用于当前会话:面板区没开或在看别的就切到它(没开着就新开一个标签),正在看它就收起。和别的面板重了归后注册的,它撤下后还给前一个;Plume 自己的快捷键(⌘B、后退前进、Esc)和它自己面板的快捷键拿不走。
246
+ * 作用于当前会话:面板区没开或在看别的就切到它(没开着就新开一个标签),正在看它就收起。和别的面板重了归后注册的,它撤下后还给前一个;Plume 自己的快捷键(⌘B、后退前进、Esc,本体文件树的 ⌘P、文件面板的 ⌥⌘P)拿不走。
208
247
  */
209
248
  shortcut?: string;
210
249
  order?: number;
@@ -266,7 +305,7 @@ export interface ConversationProps {
266
305
  /**
267
306
  * 发送按钮左边的模型和思考档位,量的是这个会话(子会话没选的跟着父会话,独立会话没选的用默认模型);会话还没建时只有给了 `blank` 才画。
268
307
  * `choose`:和主输入框同一枚按钮,能换,子会话的模型菜单最前面多一项「跟随主会话」;`show`:只读的模型名和档位,悬停说明末尾接 `modelTip`。
269
- * 不给就不画。插件经 `ui.composerItem(…, { side: "end" })` 加的格子(上下文用量)不用声明,会话在就画。
308
+ * 不给就不画。插件经 `ui.composerItem(…, { side: "end" })` 加的格子和宿主的上下文用量不用声明,会话在就画。
270
309
  */
271
310
  model?: "choose" | "show";
272
311
  /**
@@ -295,11 +334,33 @@ export interface ConversationProps {
295
334
  replyText?(text: string, message: AssistantMessage): string;
296
335
  /** 还没有消息时画在转录的位置上(`PanelEmpty` 之类)。不给就是空白。 */
297
336
  empty?: ReactNode;
337
+ /**
338
+ * 这个子会话交给父会话的那几件事(`SessionView.tasks`)画成什么样,画在转录和输入框之间;状态是宿主给的,留几件、
339
+ * 摆哪些按钮由插件定。不给就不画。组件出错时只丢这一块。
340
+ */
341
+ tasks?: ComponentType<ChildTasksProps>;
342
+ }
343
+ /** `ConversationProps.tasks` 的组件收到的:子会话交给父会话的那几件事,和能对它们做的几样。出错时宿主弹提示条。 */
344
+ export interface ChildTasksProps {
345
+ /** 交事的子会话。 */
346
+ sessionId: string;
347
+ /** 收事的父会话。 */
348
+ parentId: string;
349
+ /** 先交的在前,见 `ChildTask`。 */
350
+ tasks: ChildTask[];
351
+ /** 还排着的那件现在就发,和父会话排队条上的「现在就发」一样。 */
352
+ runNow(taskId: string): void;
353
+ /** 还排着的那件从父会话的队里收回,那句话放回这个视图的输入框。视图没有能放回去的输入框(没给 `composer.draftKey`)时不给。 */
354
+ withdraw?(taskId: string): void;
355
+ /** 失败的、随父会话停下的那件重新排进父会话队尾,见 `AgentService.retryTask`。 */
356
+ retry(taskId: string): void;
357
+ /** 收了场的那件从列表里拿掉,见 `AgentService.dismissTask`。 */
358
+ dismiss(taskId: string): void;
298
359
  }
299
360
  /**
300
361
  * 插件给自己的工具画的审批卡(`ui.approvalCard`)。卡片压在输入框正上方的那一格由产品摆,排队、停下这一轮时撤卡也归产品;
301
- * 组件画整张卡(外框、正文、按钮),人拍板时调 `respond`。要连同回答交给工具的别的话(修改意见、评论),先经自己的
302
- * `remote()` 交给逻辑入口,再 `respond`:工具从 `run.ask` 返回以后去取。
362
+ * 组件画整张卡(外框、正文、按钮),人拍板时调 `respond`。要人给答案(提问、计划、表单)的不是审批,用 `run.request` 和
363
+ * `ui.requestCard`(`RequestCardProps`)。
303
364
  */
304
365
  export interface ApprovalCardProps {
305
366
  request: ApprovalRequest;
@@ -323,11 +384,36 @@ export interface ApprovalCardProps {
323
384
  /** 产品的通用审批卡。认不出这次请求时直接返回它。 */
324
385
  fallback: ReactNode;
325
386
  }
387
+ /**
388
+ * 插件给 `run.request` 画的卡(`ui.requestCard`):问题、计划、表单。摆在哪、排队、过期、停下这一轮时撤卡归产品,
389
+ * 组件画整张卡(外框用 `@plume/ui` 的 `ApprovalFrame`),人答了调 `answer`,不答调 `decline`。
390
+ */
391
+ export interface RequestCardProps {
392
+ request: HumanRequest;
393
+ /** 在问的会话;子会话(侧边聊天、子 Agent)问时是子会话的 id。 */
394
+ sessionId: string;
395
+ /** 问的是子会话时它的标题,卡上要写明是谁在请求。 */
396
+ from?: string;
397
+ /** 排在这张后面还在等的张数,卡上写 +N(`ApprovalFrame` 替你写)。到期时刻在 `request.expiresAt`。 */
398
+ queued: number;
399
+ /** 交回人给的数据,工具从 `run.request` 拿到 `{ answered: true, value }`。没送到时抛错,卡片自己把错误摆出来、让人再答。 */
400
+ answer(value: unknown): Promise<void>;
401
+ /** 人不答,工具拿到 `{ answered: false, reason: "declined" }`。 */
402
+ decline(): Promise<void>;
403
+ /** 设置里现在的权限模式。 */
404
+ permission: PermissionMode;
405
+ /** 同 `ApprovalCardProps.allowAuto`:「接受并开启自动模式」这样的按钮先调它,再 `answer`。 */
406
+ allowAuto(): Promise<void>;
407
+ /** 窄窗口:面板区一次只摆一格,打开面板会盖住这张卡,要读的东西画在卡里。 */
408
+ compact: boolean;
409
+ /** 产品的通用卡(标题和一个「不答」)。认不出 `payload` 时直接返回它。 */
410
+ fallback: ReactNode;
411
+ }
326
412
  /** 始终位于主输入框上方的一整行,不随转录滚动或浮到侧边。 */
327
413
  export interface ComposerAboveProps {
328
414
  session: SessionView;
329
415
  }
330
- /** `ui.agentDelivery` 的组件收到的:送达所在的会话,和这一次送达里本插件的子会话交回的几份。 */
416
+ /** `ui.agentDelivery` 的组件收到的:送达所在的会话,和这一次送达里本插件交的几份(连着的几次只带 `inline` 的送达并成一次)。 */
331
417
  export interface AgentDeliveryProps {
332
418
  sessionId: string;
333
419
  reports: DeliveredReport[];
@@ -388,7 +474,7 @@ export interface EnvironmentSectionEntry {
388
474
  /** 已经包好错误边界:一节出错只撤下这一节。 */
389
475
  component: ComponentType<EnvironmentSectionProps>;
390
476
  /**
391
- * 登记时给了 `attention` 才有:画环境卡片的插件把它挂在开关旁边(它什么都不画),这一节等人处理的那句话变了就调 `onChange`。
477
+ * 登记时给了 `attention` 才有:画环境卡片的插件把它挂在开关旁边(它什么都不画),这一节要提醒人的那句话变了就调 `onChange`。
392
478
  * 已经包好错误边界,出错时当作没有。
393
479
  */
394
480
  attention?: ComponentType<EnvironmentAttentionProps>;
@@ -396,7 +482,7 @@ export interface EnvironmentSectionEntry {
396
482
  /** `EnvironmentSectionEntry.attention` 拿到的。 */
397
483
  export interface EnvironmentAttentionProps {
398
484
  session: SessionView;
399
- /** 这一节等人处理的那句话,见 `UiContext.environmentSection` 的 `attention`;没有时是 undefined。 */
485
+ /** 这一节要提醒人的那句话,见 `UiContext.environmentSection` 的 `attention`;没有时是 undefined。 */
400
486
  onChange(text: string | undefined): void;
401
487
  }
402
488
  export interface ConversationCardProps {
@@ -761,11 +847,20 @@ export interface UiContext {
761
847
  }>;
762
848
  run(session: SessionSummary): void | Promise<void>;
763
849
  }): void;
764
- /** 运行时送达行的查看入口;后注册者生效,停用时恢复前一个。 */
765
- deliveryAction(kind: "job", run: (sessionId: string, id: string) => void): void;
766
850
  /**
767
- * 本插件的子会话交回的结果(`ctx.sessions.report` 带了 `delivery` 的)在对话里画成哪一行:一次送达里本插件交的几份一起交给它,
768
- * 画成一行。只画自己交的那几份,别的插件交的由它们自己登记的组件画;同一插件再登记换掉前一个,停用时撤下,没登记时不画。
851
+ * 提供一项界面侧的服务(见 `UiServices`)给别的插件的界面入口用,停用时撤下。同一项服务有几个插件提供时后提供的生效,
852
+ * 它撤下后前一个恢复;同一插件再提供同名的换掉自己前一个。
853
+ */
854
+ provide<K extends keyof UiServices>(name: K, service: UiServices[K]): void;
855
+ /**
856
+ * React hook:一项界面侧服务现在生效的实现,没人提供时是 `undefined`(用的一方据此不给那个入口);提供方换了、撤了跟着重画。
857
+ * 拿到的就是提供方给的那一份,不用声明依赖。只在组件里调,规则同 React 的 hook。
858
+ */
859
+ useService<K extends keyof UiServices>(name: K): UiServices[K] | undefined;
860
+ /**
861
+ * 本插件交回的结果(`ctx.sessions.report`、`BackgroundChannel.report`、`LaunchedSession.reportBack` 带了 `delivery` 的)在对话里
862
+ * 画成哪一行:一次送达里本插件交的几份一起交给它。只画自己交的那几份,别的插件交的由它们自己登记的组件画;同一插件再登记换掉前一个,
863
+ * 停用时撤下。没登记(或停用了)时宿主按 `DeliveredReport` 的通用字段画默认的一行。
769
864
  */
770
865
  agentDelivery(component: ComponentType<AgentDeliveryProps>): void;
771
866
  /** 当前项目的系统打开方式;复用文件预览的默认目标和菜单。 */
@@ -788,27 +883,36 @@ export interface UiContext {
788
883
  * order 小的生效,默认 0;同一 order 后注册的生效。精确工具名优先于 match: "prefix" 的前缀匹配,撤下后恢复前一个。
789
884
  * 登记了卡片的调用在转录里单独一行;`inline: true` 的卡片只在通用卡片上补东西(多半直接返回 `fallback`),调用照常按归类
790
885
  * 并进一段活,它那一行(单独一次时、展开一段活后)由这张卡画。
886
+ *
887
+ * 卡片只对一部分调用画大东西、其余调用只画 `fallback` 时,`inline` 传函数逐次判断:返回 true 的那次并进一段活。
888
+ * 参数还在写时 `args` 是空对象,只有 `argumentsText` 在长;只看这一次调用自己的参数,参数写完后答案不能再变——一次调用从头到尾是转录里的同一行。
791
889
  */
792
890
  toolCard(tool: string, component: ComponentType<ToolCardProps>, options?: {
793
891
  order?: number;
794
892
  match?: "tool" | "prefix";
795
- inline?: boolean;
893
+ inline?: boolean | ((args: Record<string, unknown>, argumentsText: string | undefined) => boolean);
796
894
  }): void;
797
895
  /**
798
896
  * 转录里怎么说自己的工具:通用卡片那一行、合成一段活的那一行、运行行,见 `ToolLine`。只改说法和归类,画法照通用卡片;
799
897
  * 同时登记了 `toolCard` 的,卡片单独一行(`inline` 的照常并进一段活),`fallback` 用这里的说法。同一工具后登记的生效,它撤下后前一个恢复;插件登记的
800
- * 优先于产品给内置工具的说法。
898
+ * 优先于产品给内置工具的说法。工具名不固定(MCP 的 `mcp__服务__工具`)时用 `match: "prefix"` 按前缀登记,精确工具名的优先于前缀的。
801
899
  */
802
- toolLine(tool: string, line: ToolLine): void;
900
+ toolLine(tool: string, line: ToolLine, options?: {
901
+ match?: "tool" | "prefix";
902
+ }): void;
803
903
  /**
804
904
  * 给自己的工具画审批卡,见 `ApprovalCardProps`。`tool` 是 `ApprovalRequest.tool`;order 和让位规则同 `toolCard`:
805
905
  * 渲染出错时让给下一个,都不行用产品的通用卡。
806
- * `match: "kind"` 时第一个参数对的是 `ApprovalRequest.kind`:工具名不固定、一类问题从几个工具里问出来时用(MCP 服务
807
- * 中途要人填的表单,问它的是哪个 `mcp__…` 工具都有可能)。同一次请求两种都对得上时,按工具名登记的在前。
808
906
  */
809
907
  approvalCard(tool: string, component: ComponentType<ApprovalCardProps>, options?: {
810
908
  order?: number;
811
- match?: "tool" | "kind";
909
+ }): void;
910
+ /**
911
+ * 给 `run.request` 画卡,见 `RequestCardProps`。`kind` 是 `HumanRequest.kind`;order 和让位规则同 `toolCard`:渲染出错时让给下一个,
912
+ * 都不行用产品的通用卡。
913
+ */
914
+ requestCard(kind: string, component: ComponentType<RequestCardProps>, options?: {
915
+ order?: number;
812
916
  }): void;
813
917
  /**
814
918
  * 往工作区加一个面板,标签按会话保留,宽度和收起偏好在窗口内共享。
@@ -834,8 +938,8 @@ export interface UiContext {
834
938
  /** 登记界面入口的订阅清理;停用、重载或加载失败时执行,旧入口不能再注册皮肤。 */
835
939
  effect(dispose: () => void): void;
836
940
  /**
837
- * 输入框底下那排控件加一格:`start` 在左边,排在产品自己的「添加附件」和权限后面;`end` 在右边,排在模型选择前面
838
- * (上下文用量这类读数)。各自按 order 从小到大(默认 0,相同时按注册先后)。
941
+ * 输入框底下那排控件加一格:`start` 在左边,排在产品自己的「添加附件」和权限后面;`end` 在右边,排在产品自己的上下文用量和模型选择前面
942
+ * (跟下一轮的模型有关的读数)。各自按 order 从小到大(默认 0,相同时按注册先后)。
839
943
  * 组件自己画按钮(`@plume/ui` 的 `IconButton`,`size="composer"`),这一格不该显示时返回 null。id 在插件内唯一。
840
944
  */
841
945
  composerItem(id: string, component: ComponentType<ComposerItemProps>, options?: ComposerItemOptions): void;
@@ -862,13 +966,15 @@ export interface UiContext {
862
966
  lane?: boolean;
863
967
  }): void;
864
968
  /**
865
- * 往会话的环境卡片里加一节(卡片由运行诊断插件画):和这个会话相关的一件周边的事,比如当前分支的拉取请求,每一行都只是入口,
969
+ * 往会话的环境卡片里加一节(卡片由随应用提供的会话环境插件画,它经 `useEnvironmentSections` 拿到各节、不认是谁登记的):和这个会话相关的一件周边的事,比如当前分支的拉取请求,每一行都只是入口,
866
970
  * 点了去插件自己的页面或面板。没什么可说时返回 null,卡片不留空位,也不画分隔线。`label` 是卡片「显示哪些内容」菜单里的名字。
867
971
  * 按 order 从小到大排进卡片自己的几节之间(默认 0)。id 在插件内唯一,停用时撤下。
868
972
  *
869
- * `attention` 是一个 React hook:这一节此刻有没有等人处理的事(建议的新会话还没人决定),有就返回一句给人看的话(按界面语言,
870
- * 「2 条建议的新会话等你决定」),没有返回 undefined。卡片收着、这一节没被藏起来时,标题栏上开关卡片的那颗挂一个圆点,这句话
871
- * 接在它的说明后面。只为提醒人回来做决定,不为「有东西在跑」——那样圆点会一直亮着。规则同 React 的 hook。
973
+ * `attention` 是一个 React hook:这一节此刻有没有要提醒人的事,有就返回一句给人看的话(按界面语言,「2 条建议的新会话等你决定」
974
+ * 「1 个定时唤醒待触发」),没有返回 undefined。卡片收着、这一节没被藏起来时,标题栏上开关卡片的那颗挂一个圆点,这句话按节的
975
+ * order 接在它的说明后面;只在这时调,所以它要读的数据可以只在被调时拉。要提醒的是两种事:等人决定的(建议的新会话还没人点开始),
976
+ * 和会自己叫醒这个会话的(待触发的定时唤醒、在跑的监视:人不回来,会话也会自己开一轮)。普通的「有东西在跑」不算(开着一个
977
+ * 开发服务器是常态),算进去圆点就一直亮着。规则同 React 的 hook。
872
978
  */
873
979
  environmentSection(id: string, component: ComponentType<EnvironmentSectionProps>, options: {
874
980
  label: LocalizedText;
@@ -1025,6 +1131,17 @@ export interface UiContext {
1025
1131
  } | null): void;
1026
1132
  /** 用户点击时在系统浏览器打开 HTTP(S) 链接,不允许凭据或其他协议。 */
1027
1133
  openExternal(url: string): Promise<void>;
1134
+ /**
1135
+ * 用户点击时把本机的一个文件或文件夹交给系统:文件用默认程序打开,文件夹在文件管理器里打开。直接打开会运行它的
1136
+ * (程序、脚本、应用)和网络共享上的,改成在所在文件夹里指出来并提示一句。打不开(不存在、没有程序认它)时抛错,话是给人看的。
1137
+ */
1138
+ openPath(path: string): Promise<void>;
1139
+ /**
1140
+ * 用户点击时在系统的文件管理器里打开 `path` 的上级目录,并把它选中(「在访达中显示」)。和 `openPath` 的区别:
1141
+ * `openPath` 打开文件夹是进到它里面、打开文件是交给默认程序;这里不打开它本身,文件、文件夹都只是在上级目录里指出来,
1142
+ * 所以也不会运行它。不存在时抛错,话是给人看的。
1143
+ */
1144
+ revealPath(path: string): Promise<void>;
1028
1145
  /**
1029
1146
  * 像人点了回复里的网址那样打开 HTTP(S) 链接:有插件接手点击(`LinkOpener.takesClicks`,比如内置浏览器)时开在它那里,否则系统浏览器。
1030
1147
  * `sessionId` 是链接所在的会话;规则同 `openExternal`,只在用户点击时调。
@@ -1048,11 +1165,6 @@ export interface UiContext {
1048
1165
  * 只在内存里,重开应用后都算看过。只在组件里调,规则同 React 的 hook。
1049
1166
  */
1050
1167
  useSessionActivity(sessionId: string): SessionActivity | undefined;
1051
- /**
1052
- * 这个会话的上下文窗口用到哪儿了:对话消息、工具表、系统提示词、项目指令、项目记忆各占多少,项目指令和项目记忆来自哪几个文件,见 `ContextBreakdown`。
1053
- * 各段按最近一次发出的请求估,本次启动后这个会话还没发过请求时是 null。每次调用现算,在要看的时候调(比如点开读数时),别每次渲染都调。
1054
- */
1055
- contextBreakdown(sessionId: string): Promise<ContextBreakdown | null>;
1056
1168
  /**
1057
1169
  * 输入框 `/` 菜单此刻有的命令,同名去重后的样子:产品自己的(`/clear`,没有 `plugin`)在前,再是各插件经 `ctx.commands`
1058
1170
  * 供的。`cwd` 是按哪个目录列(项目里的命令文件、技能跟着变)。只读,展开、发送仍走输入框。每次调用现问,要看时再调。
@@ -1076,12 +1188,29 @@ export interface UiContext {
1076
1188
  useModelDefaults(): ModelDefaults;
1077
1189
  /** React hook:登记过的项目,按侧栏的顺序;增删改跟着变。只在组件里调。 */
1078
1190
  useProjects(): readonly ProjectInfo[];
1191
+ /**
1192
+ * React hook:返回一个按插件名(`SessionSummary.owner`、`UserMessage.fromPlugin` 里的那种)查显示名的函数,和侧栏会话卡片、
1193
+ * 「来自<插件名>」用的是同一份;插件停用、卸载了或还没读到时查不到(undefined),自己退回一个通用的词,别把插件名露给人。
1194
+ * 装、停用插件时跟着变。只在组件里调。
1195
+ */
1196
+ usePluginDisplayName(): (plugin: string) => string | undefined;
1079
1197
  /**
1080
1198
  * 自己逻辑入口经 `ctx.serve` 开出来的对象。apply 时拿到,存起来给组件用;逻辑入口没开或停用了,调用以错误结束。
1081
1199
  * T 写逻辑入口那边的接口,末尾的 CallContext 参数这边不传。
1082
1200
  */
1083
1201
  remote<T extends object>(): Remote<T>;
1084
1202
  }
1203
+ /**
1204
+ * 界面侧的服务名 → 服务的接口:一个插件的界面入口经 `ui.provide` 给出实现,别的插件的界面入口经 `ui.useService` 拿到。
1205
+ * 和逻辑侧的 `Services` 一样依赖的是名字、不是某个插件;本体要用的、按种类登记的(`ui.documentViewer`、`ui.linkOpener`……)不走这里。
1206
+ * 仓库外的插件给自己的服务用声明合并加一项:`declare module "@plume/contract/ui" { interface UiServices { … } }`。
1207
+ */
1208
+ export interface UiServices {
1209
+ /** 打开一个后台任务(`ctx.sessions.jobs` 列出的那些)给人看:人点了对话里那个后台任务那一行时调,不改变送达或命令状态。 */
1210
+ "background.jobs.view": {
1211
+ open(sessionId: string, jobId: string): void;
1212
+ };
1213
+ }
1085
1214
  /** `ui.draftInSession` 放进输入框的东西。 */
1086
1215
  export interface SessionDraft {
1087
1216
  text: string;
@@ -3,8 +3,6 @@
3
3
  */
4
4
  import type { RetryPolicySource } from "../config/retry-policy.ts";
5
5
  import type { RequestOptions } from "../types/provider.ts";
6
- /** The tool's name, in one place: the transcript is searched for its results by it. */
7
- export declare const IMAGE_TOOL_NAME = "image_generate";
8
6
  /** An image going in: something to edit, or to draw after. */
9
7
  export interface ImageInput {
10
8
  /** base64, no `data:` prefix. */
@@ -56,63 +54,3 @@ export interface ImagePointer {
56
54
  media?: string;
57
55
  mimeType: string;
58
56
  }
59
- /**
60
- * What `image_generate` hands the window: once as the call starts, so the place can be held at the
61
- * right shape before anything arrives, and again in the result.
62
- */
63
- export interface ImageGenerateDetails {
64
- kind: typeof IMAGE_TOOL_NAME;
65
- /** As asked, `"16:9"`; absent for `auto`. */
66
- aspectRatio?: string;
67
- count: number;
68
- /**
69
- * The pictures that went in, by `session-media` name, in the order they were given.
70
- *
71
- * What makes a picture's history drawable in the gallery: the dog, the dog in a hat, the girl
72
- * holding it. Absent on results written before it existed; those are traced back from the call's
73
- * own `#1`/path arguments instead (`lineage.ts`).
74
- */
75
- sources?: string[];
76
- /**
77
- * The shape the picture will most likely come back in, when none was asked for: an edit keeps the
78
- * shape of the picture it starts from. Width over height.
79
- *
80
- * Without it the place was a square, and an edit of a wide picture landed as a square snapping to
81
- * its real shape — the card, and everything under it, jumping on the frame the picture arrived.
82
- */
83
- shapeHint?: number;
84
- /** Which model, by id and by the name the card shows; with the two below, what the estimate is timed against. */
85
- model?: string;
86
- modelName?: string;
87
- quality?: string;
88
- /** Pictures went in: an edit, which takes a little longer. */
89
- edit?: boolean;
90
- startedAt?: number;
91
- /** The wait the call is in, while it is in one: the card says so instead of a stage that is not happening. */
92
- retry?: {
93
- attempt: number;
94
- delayMs: number;
95
- reason: string;
96
- };
97
- /** Why it failed, for the card: a spent quota or a key without the model is something the user acts on. */
98
- error?: {
99
- summary: string;
100
- detail?: string;
101
- hint?: string;
102
- retryAfterMs?: number;
103
- };
104
- /** The rest is there once it has drawn. */
105
- /** Where each picture was saved, in the order of the pictures. */
106
- paths?: string[];
107
- /** The same pictures by `session-media` name, with the size read from their headers: what `#N` and the card read. */
108
- images?: {
109
- media: string;
110
- mimeType: string;
111
- width?: number;
112
- height?: number;
113
- }[];
114
- references?: number;
115
- route?: ImageRoute;
116
- revisedPrompt?: string;
117
- failures?: string[];
118
- }
@@ -180,28 +180,6 @@ export interface CompactionContext {
180
180
  content: string;
181
181
  status: "pending" | "in_progress" | "completed";
182
182
  }[];
183
- /** 人批准过的计划;摘要可以忘,它不能。见 `runtime/plan/plan-mode.ts`。 */
184
- plan?: ApprovedPlan;
185
- }
186
- /**
187
- * 计划模式里交上去、被人批准的那份计划:一段 Markdown。
188
- *
189
- * 不拆成字段。拆过一次(现状、方案、改动、验证……),每份计划都被套进同一张表,三行的小改动也得
190
- * 填满六栏;该写什么由提示词说,怎么写由模型按这件事的大小定。计划也不是清单:怎么拆步是执行时
191
- * 模型自己用 `todo_write` 记的账。
192
- */
193
- export interface ApprovedPlan {
194
- /** 计划全文,Markdown。 */
195
- plan: string;
196
- /** 人批准时附上的注意事项(多是在计划上划线留的评论):不改计划,执行时照着做。 */
197
- notes?: string;
198
- /** 批准时写出的计划文件,绝对路径,写完不再改:交给别的会话的就是这一份。宿主没有临时目录时没有。 */
199
- file?: string;
200
- /** 人选了「在新会话里执行」:实施在另一个会话里,这个会话不再执行它。 */
201
- handoff?: true;
202
- /** 交接时人给那个新会话挑的模型。没挑就没有,宿主用默认模型开。 */
203
- modelId?: string;
204
- approvedAt: number;
205
183
  }
206
184
  export interface UserMessage {
207
185
  role: "user";
@@ -237,23 +215,15 @@ export interface UserMessage {
237
215
  *
238
216
  * `parent` 是子智能体转录里「派它出去的那个 Agent 说的话」——开头那份任务、续跑时补的那一句。
239
217
  * 面板上它不该画成人发的气泡:那不是看着面板的人说的。
218
+ *
219
+ * `plugin` 是代管这个会话的插件转进来的话(Plume 的 `SessionAccess.post`):外面的人经插件发来的,不是人在输入框里打的。
240
220
  */
241
- origin?: "child" | "parent";
221
+ origin?: "child" | "parent" | "plugin";
242
222
  /**
243
- * `origin` 是 `child` 时,交来这件事的子会话是哪个插件建的(子会话的 `owner`,比如 `plume.side-chat`),界面按它标出插件的显示名。
244
- * 宿主在这件事从队列里发出去时照子会话填上;那时子会话已经删掉了就不带,界面改用通用的说法。
223
+ * `origin` 是 `child` 时,交来这件事的子会话是哪个插件建的(子会话的 `owner`);是 `plugin` 时,转来它的代管插件。界面按它标出插件的显示名。
224
+ * 宿主在这件事从队列里发出去时填上;交来它的子会话那时已经删掉了就不带,界面改用通用的说法。
245
225
  */
246
226
  fromPlugin?: string;
247
- /**
248
- * 侧边聊天每次提问前附上的主会话进展,读到了哪里。
249
- *
250
- * 只附上次之后新增的那一段,所以下一次要知道从哪接着读;`lastAt` 是读到的最后一条的时间戳,
251
- * 主会话被撤回、同一位置换成了别的消息时对不上,就整段重读。见 `runtime/side-chat/side-session.ts`。
252
- */
253
- mainChat?: {
254
- seen: number;
255
- lastAt: number;
256
- };
257
227
  /** Clean user input text for UI display, excluding injected skill or session instructions. */
258
228
  displayText?: string;
259
229
  /** Skill triggered by this prompt, along with its filesystem path for viewing. */
@@ -280,7 +250,7 @@ export interface UserMessage {
280
250
  * 另一个会话的模型发来的消息——不是看着这个面板的人说的话。
281
251
  *
282
252
  * `content` 是给模型读的那份(外面套着 `<session-message>`);这一项给界面画成「来自 X」,
283
- * 并让回信的那一方接上同一条链(见 `runtime/agent-session/peer-letters.ts`)。
253
+ * 本体代答时据它的 `id` 找回读到请求的那条消息(插件经 `ToolRun.letter` 寄,配对和代答见 `@plume/contract` 的 `LetterReceipt`)。
284
254
  */
285
255
  peer?: PeerLetter[];
286
256
  /**
@@ -313,48 +283,45 @@ export interface UserMessage {
313
283
  */
314
284
  attachments?: MessageAttachment[];
315
285
  }
316
- /** 送达消息里的一份报告是谁的、怎么收场的。 */
286
+ /**
287
+ * 送达消息里的一份:谁交的、说的是什么事、怎么收场的。界面画成对话里的一行,不是一个人发的气泡;
288
+ * 怎么画由交它的插件定,宿主只认这几个通用字段。
289
+ */
317
290
  export interface DeliveredReport {
318
- /** 登记簿里的 id;后台命令是它的任务 id。 */
291
+ /** 交它的那一方定的 id(子代理的任务 id、后台命令的任务 id……),界面拿它找回自己那一份。 */
319
292
  id: string;
320
- /**
321
- * 后台命令结束了,而不是子代理跑完了。这时 `agent` 为空,`description` 是模型起任务时写的那句
322
- * 说明(没写就是命令本身),`command` 是完整命令,`exitCode` 为 null 表示被信号终止。
323
- */
324
- kind?: "job" | "session" | "wakeup";
325
- command?: string;
326
- exitCode?: number | null;
327
- agent: string;
328
- description: string;
293
+ /** 交这份的插件,宿主照实填:界面交给它登记的那一行画,导出时按 `title` 写。 */
294
+ plugin: string;
295
+ /** 一句话说是什么事;没人接管时界面的默认那一行、导出的 Markdown 都写它。 */
296
+ title: string;
329
297
  status: "done" | "failed" | "aborted";
330
298
  /** 没做完就停下了(检查点、原地打转、上游出错)——报告是阶段性的。 */
331
299
  incomplete?: boolean;
332
- /** 不带 `kind` 时:交回这份的插件,宿主照实填,界面交给这个插件登记的那一行(`ui.agentDelivery`)画。 */
333
- plugin?: string;
300
+ /** 点了去哪个会话。只有宿主写得出:新会话交回开出它的会话时(`LaunchedSession.reportBack`)填它的 id。 */
301
+ sessionId?: string;
334
302
  /**
335
- * `kind` 为 `job` 时,这不是命令结束,是 `monitor` 送来的一批新输出:`lines` 行,`exitCode` 不设。
336
- * 达到上限自己停了的,最后一批带着 `stopped`。
303
+ * 插在一轮中间、并进正在做的那段活里,挨着的几份并成一行:后台命令的结果——放到后台的那次调用当场就跑完了,
304
+ * 结果单独一行等于把同一件事说两遍。不带的照旧自己一行:接下来那段话是就着它说的。
337
305
  */
338
- monitor?: {
339
- lines: number;
340
- stopped?: "max_events" | "idle";
341
- };
306
+ inline?: boolean;
307
+ /** 交它的插件自己的数据,宿主不读,原样留着给它画那一行。 */
308
+ details?: unknown;
342
309
  }
343
310
  /**
344
311
  * 另一个会话发来的一条消息。
345
312
  *
346
313
  * 请求、通知、答复走同一条路:对方在跑就在两步之间读到,闲着就开一轮。问是发出即返回,请求的答复
347
- * 由收件方用 `reply_session` 交回,作为一封 `answer` 信送到发件方;通知没有答复。
314
+ * 由收件方交回,作为一封 `answer` 信送到发件方,收件方没答时由本体代答;通知没有答复。
348
315
  */
349
316
  export interface PeerLetter {
350
- /** 这一封的 id:发件方据此认出交回的回答、或兜底时它进了哪一次运行,见桌面端的 `session-peers.ts`。旧记录里没有。 */
317
+ /** 这一封的 id:本体据此认出读到它的那条消息,代答时从那里往后找回复。 */
351
318
  id?: string;
352
319
  /** 请求:发件方要一个答复。没有就是通知(或答复)。 */
353
320
  request?: true;
354
321
  /**
355
- * 这是对我问过的请求的答复:`replied` 是对方用 `reply_session` 交回的;`unreplied` 是对方读到请求的那一轮
322
+ * 这是对我问过的请求的答复:`replied` 是对方交回的;`unreplied` 是对方读到请求的那一轮
356
323
  * 收场了却没回,正文是那一轮最后说的话;`dropped` 是请求没被读到就被对方的人停下了;`closed` 是对方会话关了。
357
- * 后三种由路由器代写,见桌面端的 `session-peers.ts`。
324
+ * 后三种由本体代写,见 `@plume/contract` 的 `LetterReceipt`。
358
325
  */
359
326
  answer?: "replied" | "unreplied" | "dropped" | "closed";
360
327
  /** 答复对着我发过的哪几封:每封开头一截,按发出的顺序。 */
@@ -366,8 +333,6 @@ export interface PeerLetter {
366
333
  text: string;
367
334
  /** 对方的工作目录,跨项目时路径的意思会变,模型要知道是从哪儿说的。 */
368
335
  cwd?: string;
369
- /** 这封信所在的往来链,按经手的会话 id 排;跳数上限在桌面端的 `peer-policy.ts`。 */
370
- chain?: string[];
371
336
  }
372
337
  export interface RequestPrefix {
373
338
  /** 这次请求体切成了几段(工具、系统提示词、逐条消息)。 */
@@ -443,6 +408,8 @@ export interface ToolResultMessage {
443
408
  isError: boolean;
444
409
  /** See `ToolResult.uneventful`. Carried on the message so compaction can see it. */
445
410
  uneventful?: boolean;
411
+ /** See `ToolOutput.instructions`. Carried on the message so compaction can see it. */
412
+ instructions?: boolean;
446
413
  /**
447
414
  * See `ToolResult.terminate`. Carried on the message so the loop can see it.
448
415
  *
@@ -450,6 +417,25 @@ export interface ToolResultMessage {
450
417
  * 看自己这一轮刚拿到的结果,重放历史时没人会再读它。
451
418
  */
452
419
  terminate?: boolean;
420
+ /**
421
+ * 这次调用弹的审批卡或等人给数据的卡,是代管这个会话的插件替人答的(Plume 的 `SessionAccess.respond`、`answer`):那个插件的名字。
422
+ * 人自己在卡上答的不带。界面据此在这次调用旁边写明是谁代答的;不进请求体,模型读到的说明写在结果正文里。
423
+ */
424
+ answeredBy?: string;
425
+ /**
426
+ * 这次调用改了哪些文件(Plume 的 `FileChanges`,本体经 `ToolRun.files` 写文件、`ToolRun.exec` 观测命令时记下,工具自己填的不算)。
427
+ * 不进请求体;撤回、交付卡、压缩按它认改过的文件,不认是哪个工具、参数叫什么。
428
+ */
429
+ changes?: FileChanges;
453
430
  timestamp: number;
454
431
  }
432
+ /**
433
+ * 一次工具调用改了的文件。`ids` 是改动快照的标识(撤回、`ctx.changes` 汇总和撤销用),`paths` 是改了的文件的绝对路径,
434
+ * `warning` 是有改动没记全时给人看的话(命令影响的文件太多、命令还在后台跑)。
435
+ */
436
+ export interface FileChanges {
437
+ ids: string[];
438
+ paths: string[];
439
+ warning?: string;
440
+ }
455
441
  export type Message = UserMessage | AssistantMessage | ToolResultMessage;
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, 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, StatusSpinner, 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";