@plumeai/plugin-sdk 0.0.0-stage → 0.1.2

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 (210) hide show
  1. package/LICENSE +21 -0
  2. package/bin/plume-plugin.mjs +70 -0
  3. package/dist/client/styles/src/index.d.ts +123 -0
  4. package/dist/client/ui/src/avatar/AgentAvatar.d.ts +42 -0
  5. package/dist/client/ui/src/avatar/AvatarPile.d.ts +36 -0
  6. package/dist/client/ui/src/avatar/AvatarStack.d.ts +26 -0
  7. package/dist/client/ui/src/avatar/art.d.ts +32 -0
  8. package/dist/client/ui/src/fileIcon.d.ts +33 -0
  9. package/dist/client/ui/src/hooks/useCopied.d.ts +16 -0
  10. package/dist/client/ui/src/hooks/useLiveRefresh.d.ts +22 -0
  11. package/dist/client/ui/src/hooks/useNarrow.d.ts +16 -0
  12. package/dist/client/ui/src/i18n/context.d.ts +17 -0
  13. package/dist/client/ui/src/i18n/index.d.ts +2 -0
  14. package/dist/client/ui/src/i18n/list.d.ts +20 -0
  15. package/dist/client/ui/src/i18n/locales.d.ts +10 -0
  16. package/dist/client/ui/src/i18n/messages/en.d.ts +1418 -0
  17. package/dist/client/ui/src/i18n/messages/index.d.ts +6 -0
  18. package/dist/client/ui/src/i18n/messages/zh-CN.d.ts +1263 -0
  19. package/dist/client/ui/src/i18n/plural.d.ts +16 -0
  20. package/dist/client/ui/src/i18n/translate.d.ts +43 -0
  21. package/dist/client/ui/src/icons/index.d.ts +5 -0
  22. package/dist/client/ui/src/image/AnnotateCanvas.d.ts +29 -0
  23. package/dist/client/ui/src/image/AnnotateToolbar.d.ts +73 -0
  24. package/dist/client/ui/src/image/Annotator.d.ts +154 -0
  25. package/dist/client/ui/src/image/annotate.d.ts +227 -0
  26. package/dist/client/ui/src/image/paint.d.ts +35 -0
  27. package/dist/client/ui/src/image/screenshot-geometry.d.ts +112 -0
  28. package/dist/client/ui/src/image/useBarDrag.d.ts +31 -0
  29. package/dist/client/ui/src/image/useMovableBar.d.ts +26 -0
  30. package/dist/client/ui/src/image/useToolbarPlacement.d.ts +42 -0
  31. package/dist/client/ui/src/index.d.ts +140 -0
  32. package/dist/client/ui/src/inputs/ColorField.d.ts +9 -0
  33. package/dist/client/ui/src/inputs/ComposerToggle.d.ts +23 -0
  34. package/dist/client/ui/src/inputs/DateRangeField.d.ts +25 -0
  35. package/dist/client/ui/src/inputs/NativeField.d.ts +5 -0
  36. package/dist/client/ui/src/inputs/SearchField.d.ts +21 -0
  37. package/dist/client/ui/src/inputs/Segmented.d.ts +23 -0
  38. package/dist/client/ui/src/inputs/Select.d.ts +32 -0
  39. package/dist/client/ui/src/inputs/ShortcutRecorder.d.ts +8 -0
  40. package/dist/client/ui/src/inputs/Slider.d.ts +13 -0
  41. package/dist/client/ui/src/inputs/TextArea.d.ts +30 -0
  42. package/dist/client/ui/src/inputs/Toggle.d.ts +10 -0
  43. package/dist/client/ui/src/inputs/WorktreeToggle.d.ts +19 -0
  44. package/dist/client/ui/src/inputs/day-range.d.ts +57 -0
  45. package/dist/client/ui/src/inputs/pickers.d.ts +40 -0
  46. package/dist/client/ui/src/inputs/useFieldFade.d.ts +1 -0
  47. package/dist/client/ui/src/keyboard.d.ts +21 -0
  48. package/dist/client/ui/src/layout/ApprovalFrame.d.ts +33 -0
  49. package/dist/client/ui/src/layout/Collapse.d.ts +9 -0
  50. package/dist/client/ui/src/layout/CopyMark.d.ts +19 -0
  51. package/dist/client/ui/src/layout/Disclosure.d.ts +66 -0
  52. package/dist/client/ui/src/layout/ErrorBoundary.d.ts +26 -0
  53. package/dist/client/ui/src/layout/PageTopRow.d.ts +19 -0
  54. package/dist/client/ui/src/layout/PanelEmpty.d.ts +17 -0
  55. package/dist/client/ui/src/layout/ResizeHandle.d.ts +24 -0
  56. package/dist/client/ui/src/layout/RetainedViews.d.ts +32 -0
  57. package/dist/client/ui/src/layout/Settings.d.ts +41 -0
  58. package/dist/client/ui/src/layout/Splitter.d.ts +25 -0
  59. package/dist/client/ui/src/lib/agent-avatar.d.ts +58 -0
  60. package/dist/client/ui/src/lib/code/font-stack.d.ts +12 -0
  61. package/dist/client/ui/src/lib/code/themes.d.ts +35 -0
  62. package/dist/client/ui/src/lib/date-format.d.ts +8 -0
  63. package/dist/client/ui/src/lib/dispatches.d.ts +8 -0
  64. package/dist/client/ui/src/lib/hour-style.d.ts +13 -0
  65. package/dist/client/ui/src/lib/number-draft.d.ts +24 -0
  66. package/dist/client/ui/src/lib/relative-time.d.ts +18 -0
  67. package/dist/client/ui/src/lib/tiling.d.ts +89 -0
  68. package/dist/client/ui/src/lib/typing.d.ts +19 -0
  69. package/dist/client/ui/src/motion/RollWords.d.ts +26 -0
  70. package/dist/client/ui/src/motion/RollingText.d.ts +43 -0
  71. package/dist/client/ui/src/motion/TypedText.d.ts +9 -0
  72. package/dist/client/ui/src/motion/freeze.d.ts +22 -0
  73. package/dist/client/ui/src/motion/held.d.ts +17 -0
  74. package/dist/client/ui/src/motion/loaders.d.ts +65 -0
  75. package/dist/client/ui/src/motion/reduced.d.ts +13 -0
  76. package/dist/client/ui/src/motion/ticked.d.ts +24 -0
  77. package/dist/client/ui/src/motion/ticker.d.ts +31 -0
  78. package/dist/client/ui/src/motion/tokens.d.ts +29 -0
  79. package/dist/client/ui/src/motion/useNow.d.ts +6 -0
  80. package/dist/client/ui/src/overlay/Confirm.d.ts +96 -0
  81. package/dist/client/ui/src/overlay/ContextMenu.d.ts +40 -0
  82. package/dist/client/ui/src/overlay/Dialog.d.ts +60 -0
  83. package/dist/client/ui/src/overlay/Menu.d.ts +90 -0
  84. package/dist/client/ui/src/overlay/Overlay.d.ts +17 -0
  85. package/dist/client/ui/src/overlay/Popover.d.ts +127 -0
  86. package/dist/client/ui/src/overlay/Tooltip.d.ts +14 -0
  87. package/dist/client/ui/src/overlay/afterimage.d.ts +20 -0
  88. package/dist/client/ui/src/overlay/chrome-top.d.ts +8 -0
  89. package/dist/client/ui/src/overlay/hover-layers.d.ts +41 -0
  90. package/dist/client/ui/src/overlay/portal.d.ts +21 -0
  91. package/dist/client/ui/src/overlay/scrims.d.ts +20 -0
  92. package/dist/client/ui/src/overlay/tooltip-attribute.d.ts +62 -0
  93. package/dist/client/ui/src/primitives/Button.d.ts +64 -0
  94. package/dist/client/ui/src/primitives/Caret.d.ts +25 -0
  95. package/dist/client/ui/src/primitives/ChoiceMark.d.ts +12 -0
  96. package/dist/client/ui/src/primitives/ClosableTab.d.ts +23 -0
  97. package/dist/client/ui/src/primitives/CountUp.d.ts +21 -0
  98. package/dist/client/ui/src/primitives/GitHubMark.d.ts +12 -0
  99. package/dist/client/ui/src/primitives/IconButton.d.ts +55 -0
  100. package/dist/client/ui/src/primitives/MarkNumber.d.ts +12 -0
  101. package/dist/client/ui/src/primitives/RowDeleteButton.d.ts +6 -0
  102. package/dist/client/ui/src/primitives/Skeleton.d.ts +36 -0
  103. package/dist/client/ui/src/primitives/TabStrip.d.ts +29 -0
  104. package/dist/client/ui/src/primitives/Text.d.ts +341 -0
  105. package/dist/client/ui/src/primitives/TimeAgo.d.ts +13 -0
  106. package/dist/client/ui/src/primitives/useCountUp.d.ts +23 -0
  107. package/dist/client/ui/src/row/FlowRow.d.ts +31 -0
  108. package/dist/client/ui/src/row/HoverRow.d.ts +28 -0
  109. package/dist/client/ui/src/scroll/BackToLatest.d.ts +19 -0
  110. package/dist/client/ui/src/scroll/OverlayScrollbar.d.ts +9 -0
  111. package/dist/client/ui/src/scroll/ScrollText.d.ts +24 -0
  112. package/dist/client/ui/src/scroll/Scroller.d.ts +66 -0
  113. package/dist/client/ui/src/scroll/Sideways.d.ts +30 -0
  114. package/dist/client/ui/src/scroll/fade.d.ts +2 -0
  115. package/dist/client/ui/src/scroll/follow.d.ts +157 -0
  116. package/dist/client/ui/src/scroll/measure-batch.d.ts +13 -0
  117. package/dist/client/ui/src/scroll/memory.d.ts +22 -0
  118. package/dist/client/ui/src/scroll/signature.d.ts +24 -0
  119. package/dist/client/ui/src/scroll/useFollowBottom.d.ts +80 -0
  120. package/dist/client/ui/src/scroll/useSideways.d.ts +29 -0
  121. package/dist/contract/src/app.d.ts +16 -0
  122. package/dist/contract/src/changes.d.ts +50 -0
  123. package/dist/contract/src/diagnostics.d.ts +86 -0
  124. package/dist/contract/src/index.d.ts +18 -0
  125. package/dist/contract/src/plugin.d.ts +1391 -0
  126. package/dist/contract/src/resources.d.ts +79 -0
  127. package/dist/contract/src/service.d.ts +806 -0
  128. package/dist/contract/src/ui.d.ts +1039 -0
  129. package/dist/contract/src/usage.d.ts +102 -0
  130. package/dist/core/llm/ai/anthropic-messages-request.d.ts +82 -0
  131. package/dist/core/llm/ai/anthropic-messages.d.ts +43 -0
  132. package/dist/core/llm/ai/cache-routing.d.ts +53 -0
  133. package/dist/core/llm/ai/chat-delta.d.ts +107 -0
  134. package/dist/core/llm/ai/compat-key.d.ts +19 -0
  135. package/dist/core/llm/ai/endpoint.d.ts +57 -0
  136. package/dist/core/llm/ai/failure.d.ts +181 -0
  137. package/dist/core/llm/ai/index.d.ts +16 -0
  138. package/dist/core/llm/ai/openai-chat-completions-request.d.ts +49 -0
  139. package/dist/core/llm/ai/openai-chat-completions.d.ts +37 -0
  140. package/dist/core/llm/ai/openai-responses-request.d.ts +63 -0
  141. package/dist/core/llm/ai/openai-responses.d.ts +11 -0
  142. package/dist/core/llm/ai/prefix-fingerprint.d.ts +20 -0
  143. package/dist/core/llm/ai/reasoning-compat.d.ts +69 -0
  144. package/dist/core/llm/ai/request-params-compat.d.ts +39 -0
  145. package/dist/core/llm/ai/retry.d.ts +130 -0
  146. package/dist/core/llm/ai/sanitize-history.d.ts +23 -0
  147. package/dist/core/llm/ai/think-tag.d.ts +37 -0
  148. package/dist/core/llm/ai/thinking-options.d.ts +35 -0
  149. package/dist/core/llm/ai/tool-names.d.ts +17 -0
  150. package/dist/core/llm/ai/tool-pairing-compat.d.ts +53 -0
  151. package/dist/core/llm/ai/tool-result-images-compat.d.ts +24 -0
  152. package/dist/core/llm/ai/tool-schema.d.ts +20 -0
  153. package/dist/core/llm/ai/usage-fields.d.ts +30 -0
  154. package/dist/core/llm/ai/vendors/index.d.ts +20 -0
  155. package/dist/core/llm/ai/vendors/thinking.d.ts +39 -0
  156. package/dist/core/llm/ai/vendors/types.d.ts +47 -0
  157. package/dist/core/llm/config/image-models.d.ts +34 -0
  158. package/dist/core/llm/config/retry-policy.d.ts +35 -0
  159. package/dist/core/llm/images/chat-images.d.ts +11 -0
  160. package/dist/core/llm/images/dimensions.d.ts +15 -0
  161. package/dist/core/llm/images/generate.d.ts +15 -0
  162. package/dist/core/llm/images/openai-images.d.ts +11 -0
  163. package/dist/core/llm/images/quota.d.ts +9 -0
  164. package/dist/core/llm/images/shape.d.ts +44 -0
  165. package/dist/core/llm/images/types.d.ts +118 -0
  166. package/dist/core/llm/model-catalog-format.d.ts +50 -0
  167. package/dist/core/llm/model-catalog.d.ts +51 -0
  168. package/dist/core/llm/types/message.d.ts +450 -0
  169. package/dist/core/llm/types/provider.d.ts +169 -0
  170. package/dist/core/llm/types/tool.d.ts +23 -0
  171. package/dist/core/llm/types.d.ts +10 -0
  172. package/dist/core/llm/utils/pricing.d.ts +18 -0
  173. package/dist/core/llm/utils/record.d.ts +2 -0
  174. package/dist/core/llm/utils/sse.d.ts +69 -0
  175. package/dist/infra/rpc/src/endpoint.d.ts +36 -0
  176. package/dist/infra/rpc/src/index.d.ts +2 -0
  177. package/dist/infra/rpc/src/rpc.d.ts +54 -0
  178. package/dist/theme.css +336 -0
  179. package/dist/ui.d.ts +2 -0
  180. package/lib/build.mjs +143 -0
  181. package/lib/check.mjs +23 -0
  182. package/lib/dev.mjs +52 -0
  183. package/lib/icons.mjs +2 -0
  184. package/lib/init.mjs +109 -0
  185. package/lib/install.mjs +80 -0
  186. package/lib/link.mjs +44 -0
  187. package/lib/lint/i18n.mjs +149 -0
  188. package/lib/lint/imports.mjs +83 -0
  189. package/lib/lint/index.mjs +54 -0
  190. package/lib/lint/styles.mjs +160 -0
  191. package/lib/manifest.d.mts +9 -0
  192. package/lib/manifest.mjs +99 -0
  193. package/lib/targets.mjs +60 -0
  194. package/package.json +34 -4
  195. package/skill/SKILL.md +150 -0
  196. package/skill/references/components.md +2830 -0
  197. package/skill/references/logic.md +526 -0
  198. package/skill/references/style-i18n.md +70 -0
  199. package/skill/references/ui.md +532 -0
  200. package/template/AGENTS.md +17 -0
  201. package/template/CLAUDE.md +1 -0
  202. package/template/README.md +12 -0
  203. package/template/gitignore +2 -0
  204. package/template/src/index.ts +28 -0
  205. package/template/src/messages.ts +16 -0
  206. package/template/src/types.ts +8 -0
  207. package/template/src/ui.tsx +30 -0
  208. package/template/tsconfig.json +7 -0
  209. package/tsconfig.json +25 -0
  210. package/README.md +0 -3
@@ -0,0 +1,1391 @@
1
+ /**
2
+ * 插件逻辑入口的 API:插件默认导出 `Plugin`,在 apply 里通过 ctx 注册工具、往钩子里加层、登记清理。
3
+ * 界面入口的 API 在 `ui.ts`(`@plume/contract/ui`)。
4
+ */
5
+ import type { FileChangesService, RevertPlan } from "./changes.ts";
6
+ import type { BackgroundJobsService, SessionObservation, SessionWakeupsService } from "./diagnostics.ts";
7
+ import type { ResourcesService } from "./resources.ts";
8
+ import type { AssistantMessage, DeliveredReport, Message, PeerLetter, ThinkingOption, ToolResultMessage, ToolSpec } from "../../core/llm/types.d.ts";
9
+ import type { ApprovalDecision, ApprovalRequest, CompactionCode, NoticeLevel, PermissionMode, Personalization, OutgoingMessage, SessionSummary, ThinkingLevel, TurnStop } from "./service.ts";
10
+ import type { UsageScan } from "./usage.ts";
11
+ /** 界面语言。只有这两种;跟随系统时先解析成其中一种再告诉 agent 进程。 */
12
+ export type Locale = "zh-CN" | "en";
13
+ export interface ToolCall {
14
+ id: string;
15
+ name: string;
16
+ args: Record<string, unknown>;
17
+ }
18
+ export interface ToolOutput {
19
+ /** 交给模型的结果。 */
20
+ output: string;
21
+ /**
22
+ * 跟在 `output` 后面一起交给模型的图(base64,不带 `data:` 前缀)。大图由本体存进会话的媒体目录,不撑大会话文件。
23
+ * 模型只收 PNG、JPEG、WebP、GIF:BMP 照样存下、画给人看,发给模型时换成一句 `[unsupported image: image/bmp]`。
24
+ */
25
+ images?: {
26
+ data: string;
27
+ mimeType: "image/png" | "image/jpeg" | "image/webp" | "image/gif" | "image/bmp";
28
+ }[];
29
+ isError?: boolean;
30
+ /**
31
+ * 只给人看、不交给模型的图,形状同 `images`:太大、不值得每轮重发给模型的图放这里,模型在 `output` 里拿到一句说明和存放的路径。
32
+ * 本体照 `images` 把大图挪进媒体目录,通用卡片把它画在 `images` 后面。
33
+ */
34
+ shownImages?: ToolOutput["images"];
35
+ /** 给工具卡片和插件自己用的结构化数据,存进会话,不发给模型。形状由插件定,本体不看里面。 */
36
+ details?: unknown;
37
+ /**
38
+ * 这一步的结果都写进会话以后,这一轮就此结束,不再把结果交给模型:`done` 是完成,`stalled` 是卡住了。
39
+ * 一步里有几个结果都带了它时,有 `stalled` 就按 `stalled`。结果本身照常存,下一轮模型照样看得到。
40
+ */
41
+ terminate?: "done" | "stalled";
42
+ /**
43
+ * 这条结果之后不会再有人要读(没搜到、空目录):记在会话里的结果上,压缩剪枝时它的正文换成一句「无结果」,
44
+ * 不占后面每一轮的上下文。给人看的卡片照旧是原文。
45
+ */
46
+ uneventful?: boolean;
47
+ }
48
+ /** 审批卡上写什么,见 `ApprovalRequest`。`expiresAt` 由本体弹卡时定。 */
49
+ export type ApprovalContent = Omit<ApprovalRequest, "toolCallId" | "tool" | "expiresAt">;
50
+ /** 工具执行时拿到的环境。 */
51
+ export interface ToolRun {
52
+ sessionId: string;
53
+ /** 这次调用的 id(和 `calls` 里的、工具结果的 `toolCallId` 是同一个):从这次调用开出去的会话记在 `SessionLaunch.spawnedFrom` 里。 */
54
+ toolCallId: string;
55
+ cwd: string;
56
+ /** 当前项目的源文件夹;只授予读取,工作树隔离后为空。 */
57
+ projectRoots?: readonly string[];
58
+ /**
59
+ * 人在这个会话里(子会话连同它的上级会话)拖进来的文件的绝对路径,取自用户消息的附件:拖进来就是「看看这个」,
60
+ * 只授予读取这几个文件,不授予改动,也不扩大到所在目录。没有附件时不给。
61
+ */
62
+ allowedPaths?: readonly string[];
63
+ /** 这个会话的临时目录,见 `ModelContext.scratchDir`。 */
64
+ scratchDir: string;
65
+ /**
66
+ * 和这个会话同一脉的会话的临时目录,含 `scratchDir`:沿子会话(`SessionSummary.parentId`:子智能体、侧边聊天)、分叉
67
+ * (`forkedFrom`)、建议开出的新会话(`spawnedFrom`)的来处连在一起的那些,两头都算。它们互相交接草稿——子智能体把报告写进
68
+ * 父会话的临时目录,分叉接着改来源会话里的那份——往这些里写、删不用问人;别的会话的临时目录不在里面,那是别的对话的草稿。
69
+ * 只管写:读哪个会话的临时目录都不问。宿主每次调用时现算(子会话、分叉随时会多);不给时只算 `scratchDir`。
70
+ */
71
+ scratchDirs?: readonly string[];
72
+ /** 用户停止这一轮时中止。 */
73
+ signal: AbortSignal;
74
+ /** 执行中的输出,推给界面。 */
75
+ progress(chunk: string): void;
76
+ /**
77
+ * 动手之前问一句(写了 `asks` 的工具用)。经钩子 tool.approve 决定,要不要真的弹给人由那里的层定(内置插件 plume.approval:
78
+ * 完全访问、外层放行的、答过「以后不再问」的直接放行,提权总是问人)。参数校验完、注定失败的已经报掉之后再问:
79
+ * 不先问人一个没法成立的问题。用户停止这一轮时得到 `"reject"`。
80
+ */
81
+ ask(content: ApprovalContent): Promise<ApprovalDecision>;
82
+ /** 设置里现在的权限模式,用到才读:中途改了,下一个调用就照新的来。 */
83
+ permissionMode(): Promise<PermissionMode>;
84
+ /** 现在工具表里有哪些工具、各是哪个插件注册的(同名的只列生效的那个)。 */
85
+ tools(): {
86
+ name: string;
87
+ plugin: string;
88
+ }[];
89
+ /** 这一步回复里的全部调用(含这一次),按回复里的顺序。工具要知道这一步是不是只做了它这一件事时看它(todo 清单收尾)。 */
90
+ calls: readonly ToolCall[];
91
+ /** 钩子 tool.execute 的外层要求这次调用不改动工作区(见 `ToolExecution.readOnly`):跑命令的工具只在只读沙箱里跑。 */
92
+ readOnly?: boolean;
93
+ /**
94
+ * 调用答完以后还在后台接着跑的事(后台命令、监视)把结果送回这个会话的渠道,见 `BackgroundChannel`。
95
+ * 只有顶层会话有:插件的子会话(侧边聊天、子 Agent)没人等它们的后台结果,拿不到就只能让模型自己去读。
96
+ */
97
+ background?(): BackgroundChannel;
98
+ /**
99
+ * 给另一个顶层会话寄一封信(`to` 是它的 id),见 `LetterReceipt`。只有顶层会话有,也只寄得到别的顶层会话:
100
+ * 子会话不是谁的通信对象。发件方的 id、工作目录由本体照实填上。对方不在、是子会话、是自己时抛错。
101
+ * 只在这次调用里用:调用答完以后再调就抛错(回执的 `answer` 不受影响)。
102
+ */
103
+ letter?(to: string, letter: LetterInput): LetterReceipt;
104
+ /**
105
+ * 等这一轮以完成(`done`)收场后,归档这次调用所在的会话,和人在侧栏上点归档一样(界面离开它,`onArchived`、`onEnded` 照常)。
106
+ * 不当场归档:这一轮还在等这次调用的结果,工具一般同时返回 `terminate: "done"`,让这一轮就停在这里。
107
+ * 这一轮被人停下、出错、卡住收场时不归档、请求作废:对话留在人看得见的地方;一次请求只管这一轮。
108
+ * 只有顶层会话有,也只够得着这次调用所在的会话:碰不到别的会话,子会话归档不了它的上级会话。
109
+ */
110
+ archiveAfterTurn?(): void;
111
+ /**
112
+ * 这次调用所在的会话是注册这个工具的插件建的子会话时才有:够得着它的父会话(侧边聊天把活交给主会话、让主会话停下)。
113
+ * 别的会话里的调用、别的插件的子会话里都没有。只在这次调用里用:调用答完以后再调它的方法就抛错。
114
+ */
115
+ parent?: ParentSession;
116
+ }
117
+ /**
118
+ * 工具的后台结果回到会话的路。送出的每段和同一时刻别的后台结果(子会话交回的、别的后台命令的)聚合一下,作为一条运行时送达
119
+ * 进会话:会话闲着时开一轮,忙着时在下一个步骤边界(全部工具结果之后、下一次请求之前)读入。送达经钩子 prompt.submit(`synthetic`)。
120
+ * 渠道在人按停止、删会话、退出应用时作废(`signal` 触发):之后送的不再到,攒着没送的也丢掉——人按停是不想再被它叫醒。
121
+ * 撤回、编辑重发时只作废截掉的那段里的调用拿到的渠道:说起它的那次调用已经不在对话里了;留下那段里拿到的照常送达。
122
+ * 作废只管送达,后台的事本身停不停由工具定(plume.tools 的后台命令照跑,监视跟着停)。
123
+ */
124
+ export interface BackgroundChannel {
125
+ /**
126
+ * 送一段给模型读的话,可以送多次。给函数时到真要送出那一刻才取:返回 `undefined` 就不送了(这期间模型已经自己读过)。
127
+ * `delivery` 给界面把这条送达画成对话里的一行(「后台命令完成」),不是一个人发的气泡。渠道已作废时返回 false。
128
+ */
129
+ report(text: string | (() => string | undefined), delivery?: ChannelDelivery): boolean;
130
+ /** 渠道作废时触发。 */
131
+ readonly signal: AbortSignal;
132
+ }
133
+ /**
134
+ * `BackgroundChannel.report` 在对话里画的那一行:后台命令结束、监视送来一批输出(`job`),定时唤醒到点(`wakeup`)。
135
+ * 宿主只留这几个字段、拷一份;`session` 那一行只由宿主在交回时写,插件写不了。
136
+ */
137
+ export type ChannelDelivery = Omit<DeliveredReport, "kind" | "plugin" | "incomplete"> & {
138
+ kind: "job" | "wakeup";
139
+ };
140
+ /**
141
+ * 一封信写什么(`ToolRun.letter`)。字段的意思见 `PeerLetter`:`from` 是发件方的地址,收件方回信时原样当作收件人;
142
+ * 是请求、答复还是通知,答复对着哪几问,所在的往来链,都由寄信的插件定。
143
+ * `guide` 是给收件方模型的处理说明(「标着 reply="expected" 的用 reply_session 回」),一次送达里一样的只写一遍。
144
+ */
145
+ export type LetterInput = Pick<PeerLetter, "from" | "text" | "request" | "answer" | "re" | "chain"> & {
146
+ guide?: string;
147
+ };
148
+ /**
149
+ * 信的下落。`read`:写进了收件方的对话;`opened` 是这封信(和同批的)叫起了收件方的一轮,不是插进一轮已经在跑的。
150
+ * `dropped`:没读到——请求还没读到收件方的人就按了停(通知和答复不丢,留到它下一轮开头读,这期间没有着落),或钩子 prompt.submit
151
+ * 拦下了这次送达。`closed`:收件方删掉了,或应用退出了。
152
+ */
153
+ export type LetterFate = {
154
+ status: "read";
155
+ opened: boolean;
156
+ } | {
157
+ status: "dropped";
158
+ } | {
159
+ status: "closed";
160
+ };
161
+ /**
162
+ * 寄出的一封信。信和后台结果走同一条路:收件方忙着时在下一个步骤边界读到,闲着时为它开一轮(停下过的也开:一封信是
163
+ * 一件新的事),送达经钩子 prompt.submit(`synthetic`)。前面带一句本体的话:这不是用户说的,不构成用户的授权。
164
+ */
165
+ export interface LetterReceipt {
166
+ /** 这封信的 id,记在收件方对话里那条消息的 `peer` 上。 */
167
+ id: string;
168
+ /** 信有了着落时兑现,见 `LetterFate`。 */
169
+ read: Promise<LetterFate>;
170
+ /**
171
+ * 替收件方回发件方一封信(收件方没用工具回、没读到、关了时插件代答):从收件方寄回给发件方,只能用一次。
172
+ * 发件方已经不在时返回 `undefined`。
173
+ */
174
+ answer(letter: LetterInput): LetterReceipt | undefined;
175
+ }
176
+ /** 父会话此刻在做什么,见 `ParentSession.status`。 */
177
+ export interface ParentStatus {
178
+ /** `running` 正在跑一轮;`background` 这一轮收了尾,它派出去的后台运行还没都回来;`idle` 真闲着。 */
179
+ state: "running" | "background" | "idle";
180
+ /** 队里还排着几条(人排的和交过来的都算)。 */
181
+ queued: number;
182
+ }
183
+ /**
184
+ * 子会话的一次工具调用够得着的那一面父会话(`ToolRun.parent`):往它的队里排一件事、立即停下它、让它接着做、看它在不在忙。
185
+ * 改不了文件、读不了别的会话,作用对象只有这一个父会话。写进父会话的话都标着运行时来处,不当成用户新的授权。
186
+ */
187
+ export interface ParentSession {
188
+ id: string;
189
+ /**
190
+ * 把一件事排进父会话的队列(和人在输入框里排的同一条,落盘),父会话真闲着就马上开始。发出去的消息标着来自侧边聊天
191
+ * (`UserMessage.origin`),前面带一句给模型的来处说明。`started` 是排上那一刻它就开始了。指令为空时抛错。
192
+ */
193
+ enqueue(instruction: string): Promise<{
194
+ started: boolean;
195
+ }>;
196
+ /**
197
+ * 立即停下父会话,和人按停止一样(连同它派出去的后台运行);返回停之前的状态。`discardPlan` 是人明确不要旧目标了:
198
+ * 等它停稳,删掉队里交过来的任务,再记一句(界面不画成人的气泡)告诉模型旧清单作废、等新的指令。
199
+ */
200
+ stop(options?: {
201
+ discardPlan?: boolean;
202
+ }): Promise<ParentStatus>;
203
+ /**
204
+ * 让父会话接着做:真闲着时以界面的名义(synthetic)发 `text`,和转录底下的「继续」一样,不等这一轮做完就返回;
205
+ * 正在跑、后台还有没回来的不发。返回发之前的状态。
206
+ */
207
+ carryOn(text: string): Promise<ParentStatus>;
208
+ status(): ParentStatus;
209
+ }
210
+ export interface ToolDefinition {
211
+ name: string;
212
+ description: string;
213
+ /** JSON Schema。 */
214
+ parameters: Record<string, unknown>;
215
+ /** 会改动工作区(写文件、执行命令):不和相邻的调用同时跑;没写 `asks` 的,宿主在执行前按参数问用户。 */
216
+ mutating?: boolean;
217
+ /** `mutating` 的工具里,这一次调用能证明什么都不改(只读的命令):可以和相邻的只读调用同时跑。 */
218
+ readsOnly?(args: Record<string, unknown>): boolean;
219
+ /** 工具自己在 execute 里经 `run.ask` 问(能给出 diff、命令原文),宿主不在执行前替它问。 */
220
+ asks?: boolean;
221
+ execute(args: Record<string, unknown>, run: ToolRun): Promise<ToolOutput>;
222
+ }
223
+ /** 钩子 tool.execute 的输入。 */
224
+ export interface ToolExecution {
225
+ sessionId: string;
226
+ cwd: string;
227
+ call: ToolCall;
228
+ /** `plugin` 是注册它的插件(read、write、edit、ls、glob、grep、bash 是内置插件 plume.tools 的)。 */
229
+ tool: {
230
+ name: string;
231
+ mutating: boolean;
232
+ plugin: string;
233
+ };
234
+ /** 外层已经放行的理由。设了它,本体就不再问人(提权、`userOnly` 和层自己问的除外),也不经钩子 tool.approve。 */
235
+ approved?: string;
236
+ /**
237
+ * 外层要求这次调用不改动工作区(计划模式)。交给工具的 `ToolRun.readOnly`:plume.tools 的 bash 不按权限模式选沙箱,
238
+ * 一律在只读沙箱里跑,机器上给不了只读沙箱时不跑。只能收紧:里层不该把它去掉。
239
+ */
240
+ readOnly?: boolean;
241
+ signal: AbortSignal;
242
+ /**
243
+ * 问一次要不要放行,卡上是这次调用的参数和 `reason`。经钩子 tool.approve(标着 `byLayer`),由那里的层定要不要弹给人
244
+ * (完全访问下 plume.approval 直接放行)。返回是否放行,用户停止这一轮时得到 false。
245
+ */
246
+ ask(reason: string): Promise<boolean>;
247
+ progress(chunk: string): void;
248
+ }
249
+ /**
250
+ * 系统提示词的一段。plume.prompt 给的段(按顺序):`identity`(身份)、`guidelines`(做事的准则)、`boundaries`(边界)、
251
+ * `environment`(平台、shell、是不是 git 仓库、模型)、`tone`(语气)、`userInstructions`(全局指令)、
252
+ * `projectInstructions`(项目里的 AGENTS.md 等)、`workspace`(工作目录和会话的临时目录)。plume.tools 再加一段 `resources`(资源地址的清单)。
253
+ */
254
+ export interface PromptSection {
255
+ /** 段的名字,插件按它替换、删除,或把自己的段插在它前后。插件自己的段用自己的名字。 */
256
+ id: string;
257
+ text: string;
258
+ /**
259
+ * 这段里哪几截来自哪个文件:`chars` 是那一截在 `text` 里的字数(连同包着它的标签)。宿主把 `projectInstructions`、
260
+ * `projectMemory` 两段的来源按文件列进上下文用量的「记忆文件」,这些文件在会话边界外也能在文件面板里只读打开(`path` 给绝对路径);
261
+ * 别的段带了不显示。改了 `text` 的层要么跟着改它,要么去掉它。
262
+ */
263
+ files?: Array<{
264
+ path: string;
265
+ chars: number;
266
+ }>;
267
+ }
268
+ /**
269
+ * 钩子 model.context 的输入和输出。只作用于这一次请求,会话里存的不变。
270
+ * 本体给的 `sections` 是空的、`messages` 是会话的原样历史(压缩过的是摘要加之后的原文);系统提示词各段、
271
+ * 消息里的日期和插话、打断说明由内置插件 plume.prompt 在最外层(order -1000)补上,所以其他层拿到的已经是完整的一份。
272
+ * plume.prompt 还把系统提示词按会话冻结:里层交回的段和冻结的那份不一样时,发出去的仍是冻结的那份,改动作为一条增量消息
273
+ * (`UserMessage.promptUpdate`)接在这一轮开头。
274
+ */
275
+ export interface ModelContext {
276
+ sessionId: string;
277
+ cwd: string;
278
+ /** 当前项目的源文件夹;只授予读取,工作树隔离后为空。 */
279
+ projectRoots?: readonly string[];
280
+ /**
281
+ * 这个会话的临时目录(`<appDataDir>/scratch/<sessionId>`,子会话各有各的):只为得出这次答案而写的文件放这里——验证想法的脚本、
282
+ * 下载的样例、只看一次的完整输出(太大没交给模型的图、截断前的全文)。宿主不预先建它,谁先写谁建;会话删掉时宿主连目录一起删。
283
+ * 内置插件 plume.prompt 把它写进系统提示词,plume.tools 给它开了 `scratch://` 地址。
284
+ */
285
+ scratchDir: string;
286
+ /** 这一次请求用的模型。 */
287
+ model: {
288
+ id: string;
289
+ name: string;
290
+ };
291
+ /** 这个会话现在的权限模式(含项目覆盖)。Windows 上受约束的命令跑在 PowerShell、完全访问跑在 Git Bash,系统提示词按它点名 shell。 */
292
+ permissionMode: PermissionMode;
293
+ /**
294
+ * 外层要求这一轮的命令都在只读沙箱里跑(计划模式,和 `ToolExecution.readOnly` 同一件事)。宿主不给;要设就挂在
295
+ * plume.prompt(order -1000)外面,它按这一档点名 shell:Windows 上只读的命令跑 PowerShell,权限模式是完全访问也一样。
296
+ */
297
+ readOnly?: boolean;
298
+ /** 设置 › 个性化:语气和全局指令。 */
299
+ personalization: Personalization;
300
+ /** 发出去的系统提示词,就是 `sections` 依次用空行连起来。 */
301
+ systemPrompt: string;
302
+ /**
303
+ * 系统提示词按段拆开。改哪个都行,交给下一层时宿主把另一个跟上:只改了 `sections`,`systemPrompt` 重新拼;
304
+ * 只改了 `systemPrompt` 而且是在后面接了一段,接上的那段成为末尾的 `extension` 段,整个换掉则只剩这一段 `extension`。
305
+ * 两个都改了以 `sections` 为准。
306
+ */
307
+ sections: PromptSection[];
308
+ messages: Message[];
309
+ tools: ToolSpec[];
310
+ }
311
+ /** 钩子 step.end 的输入:一步刚收尾,回复和它要的工具的结果都已写进会话。给的是副本,改了不影响会话。 */
312
+ export interface StepEnd {
313
+ /** 清单插件提供的汇报判定与提醒;未提供时由本体按正文判断。 */
314
+ progress?: {
315
+ reported: boolean;
316
+ reminder: string;
317
+ };
318
+ /**
319
+ * 派出去还没回来、结论要等它的后台工作(子智能体之类),由外层插件经 `next({ ...input, awaiting })` 给出。
320
+ * 有它时里层不逼模型交结论:清单插件收尾不追问清单,本体的空回复催促换成 `nudge` 这句(催它说一句在等谁)。
321
+ */
322
+ awaiting?: {
323
+ nudge: string;
324
+ };
325
+ sessionId: string;
326
+ cwd: string;
327
+ /** 这一轮的第几步,从 1 数。 */
328
+ step: number;
329
+ /** 后面还能跑几步;是 0 时要接着做,这一轮也会以「用满步数」结束。 */
330
+ stepsLeft: number;
331
+ reply: AssistantMessage;
332
+ /** 这一步工具的结果;回复没调工具时是空的。 */
333
+ toolResults: ToolResultMessage[];
334
+ signal: AbortSignal;
335
+ }
336
+ /**
337
+ * 钩子 step.end 的结论。最里层是自然结论:有工具结果就 `{ continue: true }`,否则 `{ continue: false }`(完成)。
338
+ * 插件只能让循环多做、给结束换个原因:有工具结果时 `continue: false` 不起作用;本来要结束时要接着做,必须给 `say`。
339
+ * 回复出错、用户停止的那一步不调这个钩子;钩子抛错时按自然结论走。
340
+ */
341
+ export type StepVerdict = {
342
+ continue: true;
343
+ /** 以用户的身份对模型说这句话再接着做。会话里标成 synthetic,界面不画成人的消息,也不当会话标题。 */
344
+ say?: string;
345
+ } | {
346
+ continue: false;
347
+ /** 不给是完成。 */
348
+ reason?: "error" | "stalled";
349
+ /** reason 是 error 时给人看的原因。 */
350
+ error?: string;
351
+ };
352
+ /** 钩子 prompt.submit 的输入:一轮开始时,人发来的这条消息还没写进会话。给的是副本,改了不影响会话。 */
353
+ export interface PromptSubmit {
354
+ sessionId: string;
355
+ cwd: string;
356
+ /** 这条消息的文字(命令发出去的是展开后的那段)。 */
357
+ text: string;
358
+ /** 附件的名字,按顺序。 */
359
+ attachments: string[];
360
+ /** 界面替人说的话(「继续」按钮发的那句)。 */
361
+ synthetic: boolean;
362
+ /** 会话里在它之前一条消息都还没有:这是会话的第一轮。 */
363
+ fresh: boolean;
364
+ /**
365
+ * 这句话是插件经 `ctx.sessions.launch` 发的(它开出来的会话的第一句),不是人在输入框里发的:人为自己正在写的那条消息
366
+ * 选的东西(输入框上的开关)不算到它头上。
367
+ */
368
+ launched: boolean;
369
+ signal: AbortSignal;
370
+ }
371
+ /**
372
+ * 钩子 prompt.submit 的结论。最里层是 `{ block: false }`。
373
+ * 拦下时这条消息不写进会话、这一轮不跑,`reason` 弹成提示条(给人看的话,按界面语言写)。
374
+ * 放行时可以带 `context`:跟在这条消息后面以用户身份写进会话(标成 synthetic,界面不画成人的消息),模型看得到。
375
+ * 放行时还可以带 `cwd`:这个会话从这一轮起改在这个目录里跑,它的子会话跟着(工作树插件给对话开了自己的检出)。
376
+ * 只对一轮开头那条消息生效,插话、后台回报和子会话的不算;不是已有目录的绝对路径时不换,记日志。
377
+ */
378
+ export type PromptVerdict = {
379
+ block: true;
380
+ reason: string;
381
+ } | {
382
+ block: false;
383
+ context?: string;
384
+ cwd?: string;
385
+ };
386
+ /**
387
+ * 钩子 context.compact 的输入:组好一次请求、发出去之前,问要不要把较早的历史换成一份摘要。
388
+ * 内置插件 plume.compaction 在里层(order 1000)按有多满决定压不压、从哪切、写摘要;换一种压法就在外层不调 `next`。
389
+ * 服务商说超长时 `force` 为真,这时压不了,这一轮以出错结束。人敲了 `/compact` 时 `force` 也为真,另有 `manual`;
390
+ * 一轮干净收尾、缓存还热时宿主也问一次(`idle`)。
391
+ */
392
+ export interface ContextCompact {
393
+ sessionId: string;
394
+ cwd: string;
395
+ /**
396
+ * 这个会话现在用的模型。`tierLines` 是它的价格分档线(一次请求的输入越过这么多 token,整次按高一档计),从低到高;
397
+ * 没有分档时不给。
398
+ */
399
+ model: {
400
+ id: string;
401
+ name: string;
402
+ contextWindow: number;
403
+ maxOutputTokens: number;
404
+ tierLines?: number[];
405
+ };
406
+ /**
407
+ * 现在发给模型的历史(插件改之前的那份),副本:压缩过的,前面是上一次压缩换上的那几条(摘要和一句确认,或按条丢弃时的
408
+ * 一条说明),之后是边界以后的原文;剪过的工具结果(压缩时剪的、服务商拒收时剪成一行的)这里也是剪过的那份。
409
+ */
410
+ history: Message[];
411
+ /**
412
+ * 上一次压缩换上的那几条占了 `history` 开头几条(新的 `keep` 要大于它)。没压缩过不给。上一份摘要在 `history` 里。
413
+ */
414
+ previous?: {
415
+ head: number;
416
+ };
417
+ /**
418
+ * 历史上一次被改写(压缩、只剪枝)的时刻:在这之前落定的回复,用量量的是改写前的那份,不能拿来判断现在有多满。
419
+ * 没改写过不给。
420
+ */
421
+ rewrittenAt?: number;
422
+ /** 这一次真正要发出去的请求(经过 model.context),量有多满用它。就是要发的那一份,只读,别改。 */
423
+ request: {
424
+ systemPrompt: string;
425
+ messages: Message[];
426
+ tools: ToolSpec[];
427
+ };
428
+ /** 服务商说超长了,或人敲了 `/compact`:不管量出来多满都该压。 */
429
+ force: boolean;
430
+ /** 人敲的 `/compact`:`instructions` 是命令后面的话(希望摘要保留的内容,可以是空串)。 */
431
+ manual?: {
432
+ instructions: string;
433
+ };
434
+ /**
435
+ * 一轮干净收尾以后、人还没开口时问的:只在能从主请求分叉(`lineage`)时值得压,否则留给下一轮开头。`request` 是宿主为它
436
+ * 再经一遍 model.context 组出来的下一轮开头那份请求,所以 model.context 的层在一轮里可能多走一次。
437
+ */
438
+ idle?: boolean;
439
+ /**
440
+ * 这个会话上一次发出去的主请求:用的哪个模型(`SessionModel.id`)、什么时候开始发的、这家服务商的缓存大概能留多久
441
+ * (毫秒,不知道时不给)。有它,摘要可以经 `ModelCompletion.fork` 从那次请求分叉、吃到它的缓存。没发过、超长恢复时不给。
442
+ */
443
+ lineage?: {
444
+ model: string;
445
+ at: number;
446
+ cacheHotMs?: number;
447
+ };
448
+ signal: AbortSignal;
449
+ /** 压缩走到哪一步了,界面上那一行跟着变:写摘要、等重试、摘要没拿到正在本地整理。 */
450
+ progress(event: CompactionProgress): void;
451
+ }
452
+ /** `ContextCompact.progress` 的一步。 */
453
+ export type CompactionProgress = {
454
+ phase: "summarizing";
455
+ } | {
456
+ phase: "retrying";
457
+ delayMs: number;
458
+ } | {
459
+ phase: "fallback";
460
+ };
461
+ /**
462
+ * 摘要是怎么来的:`fork` 从主请求分叉,`independent` 单独发的摘要请求,`fallback` 两样都没拿到、本地机械整理,
463
+ * `dropped` 连本地整理也放不下、直接丢了最老的几轮。
464
+ */
465
+ export type CompactionVia = "fork" | "independent" | "fallback" | "dropped";
466
+ /**
467
+ * 一条工具结果发给模型时的样子:结果里的文字块依次换成 `texts`,多出来的文字块去掉,图片原样。会话里存的仍是原文,
468
+ * 卡片照旧画原文。宿主把它记在会话库里,以后每一次发请求都照它发,重开应用也一样:发出去的前缀不变,缓存接得上。
469
+ */
470
+ export interface ToolResultView {
471
+ toolCallId: string;
472
+ texts: string[];
473
+ }
474
+ /**
475
+ * 钩子 context.compact 的结论:不给是不压。最里层不压。
476
+ * 给了 `keep`,`history` 里从 `keep` 起原样留着,之前的(含上一次压缩换上的那几条)换成 `head`;不给 `head` 就是 `summary`
477
+ * 加一句确认。`keep` 要落在上一次换上的那几条之后、最后一条之前,而且不能是工具结果(会和它回答的调用拆开);`summary`
478
+ * 只在按条丢弃(`via: "dropped"`,这时必须给 `head`)时可以是空串;不合规矩的宿主当作压缩失败。
479
+ * `views` 是留下的那截里剪过的工具结果。只剪了工具结果、没有可摘要的(`pruneOnly`)时只给 `views`。
480
+ */
481
+ export type CompactSummary = {
482
+ keep: number;
483
+ summary: string;
484
+ /** 代替边界之前那段的消息(以用户消息开头),宿主原样存下、以后每次照发。 */
485
+ head?: Message[];
486
+ via?: CompactionVia;
487
+ views?: ToolResultView[];
488
+ } | {
489
+ pruneOnly: true;
490
+ views: ToolResultView[];
491
+ } | undefined;
492
+ /**
493
+ * 钩子 tool.approve 的输入:一个工具调用要动手了,问要不要放行。工具自己经 `run.ask` 问的(带 diff、命令原文)、
494
+ * 会改动工作区却没写 `asks` 的工具(执行前按参数问)、tool.execute 的层自己问的(`byLayer`)都可能到这里。
495
+ * 最里层照着传进来的 `request` 把审批卡弹给人、等人答。
496
+ *
497
+ * 权限模式的含义在本体,到不了这里:完全访问、tool.execute 外层设了 `approved`、`subject` 答过「以后不再问」的,本体直接放行。
498
+ * 提权(`request.escalation`)和 `request.userOnly` 只能由人答:它们在完全访问下也到这里,层答放行不算数、本体照样弹卡,层答拒绝照算。
499
+ * 内置插件 plume.approval 在这里按帮我批准(`auto`)的风险规则放行;没有任何层时每一次都问人。
500
+ */
501
+ export interface ToolApproval {
502
+ sessionId: string;
503
+ cwd: string;
504
+ /** 这个会话能当草稿区用的临时目录,同 `ToolRun.scratchDirs`;帮我批准把写进、删掉这些里面的东西算作正当干活。 */
505
+ scratchDirs?: readonly string[];
506
+ /** 模型发起的调用,`args` 是原始参数。 */
507
+ call: ToolCall;
508
+ tool: {
509
+ name: string;
510
+ mutating: boolean;
511
+ plugin: string;
512
+ };
513
+ /** 弹给人的话,审批卡上画的就是它。`escalation` 是申请更宽的沙箱。 */
514
+ request: ApprovalContent;
515
+ /** 是钩子 tool.execute 的某一层自己要问(`ToolExecution.ask`),不是工具问的:没有 `subject`,只能答这一次。 */
516
+ byLayer?: boolean;
517
+ /** 这次调用所在项目现在的权限模式。完全访问只有提权和 `userOnly` 会带到这里。 */
518
+ permissionMode: PermissionMode;
519
+ /** 用户停止这一轮时中止。 */
520
+ signal: AbortSignal;
521
+ }
522
+ /**
523
+ * 钩子 session.rewind 的输入:人撤回或编辑重发,要截掉从某条人发的消息起的那段对话。截断由宿主做,这里问的是那段里
524
+ * 插件记着的改动(文件改动等)能不能、要不要一起退回到截断点之前。
525
+ *
526
+ * 先以 `restore: false` 问一次,只演算:答那段改过哪些文件、各自能不能安全退回,界面据此问人。人选了连文件一起退,宿主在截断
527
+ * 之前以 `restore: true` 再问一次:重新校验、真的写回去;写不回去就抛错(message 原样给人看,按界面语言写),这时宿主不截对话。
528
+ * 写回要全有或全无:一个文件不能安全退回,就一个都不写,已经写了的退回原样再抛错。
529
+ * 只截对话、不动文件时不问。最里层答 `{ files: [], unrecorded: false }`;每一层把自己的结论并进 `next` 的结论里。
530
+ */
531
+ export interface SessionRewind {
532
+ sessionId: string;
533
+ cwd: string;
534
+ /** 要截掉的那段:从被撤回、编辑的那条人发的消息起到最后,副本。 */
535
+ messages: Message[];
536
+ /** 真的写回去,还是只演算。 */
537
+ restore: boolean;
538
+ }
539
+ /** 钩子名 → [输入, 输出]。 */
540
+ export interface Hooks {
541
+ "tool.execute": [ToolExecution, ToolOutput];
542
+ "model.context": [ModelContext, ModelContext];
543
+ "step.end": [StepEnd, StepVerdict];
544
+ "prompt.submit": [PromptSubmit, PromptVerdict];
545
+ "context.compact": [ContextCompact, CompactSummary];
546
+ "tool.approve": [ToolApproval, ApprovalDecision];
547
+ "session.rewind": [SessionRewind, RevertPlan];
548
+ }
549
+ /** 输入框 `/` 菜单里的一条命令。 */
550
+ export interface SlashCommand {
551
+ /** 敲在 `/` 后面的名字:小写字母、数字、连字符,可以用 `:` 分段(`git:commit`)。 */
552
+ name: string;
553
+ /** 菜单里名字后面那句说明。 */
554
+ description: string;
555
+ /** 命令后面接什么,还没写参数时以灰字画在输入框里。 */
556
+ argumentHint?: string;
557
+ /** 菜单最右边那一栏:它从哪来(「项目」「个人」)。给人看的话,按界面语言写。 */
558
+ source?: string;
559
+ }
560
+ /** 列命令、展开命令时的环境。 */
561
+ export interface CommandContext {
562
+ /** 下一轮在哪个目录里跑;不属于任何项目的新聊天为空。 */
563
+ cwd?: string;
564
+ /** 输入框所在的会话;还没发出第一条消息的新对话为空。 */
565
+ sessionId?: string;
566
+ }
567
+ /** 一条命令展开成的消息。 */
568
+ export interface CommandExpansion {
569
+ /** 发给模型的话。 */
570
+ text: string;
571
+ /** 对话里画在人的气泡里的字;不给就画 `text`。 */
572
+ displayText?: string;
573
+ /**
574
+ * 会话正忙时怎么送。`steer`:插进正在跑的这一轮,模型做完手上这一步就读到(「只看 src/」这类纠偏,等这一轮做完再说就晚了)。
575
+ * 不给就和平常发的话一样排到这一轮后面。会话空闲时都是开新的一轮。
576
+ */
577
+ deliver?: "steer";
578
+ }
579
+ /**
580
+ * 命令不发消息、插件自己办完了(`/btw` 把话问到侧边聊天里):输入框清空,当前对话里什么都不留。
581
+ * 给了 `openPanel` 就打开这个插件自己注册的那个面板;能开好几个标签的面板新开一个标签。
582
+ * 给了 `openSettings` 就打开这个插件自己注册的、单独占设置导航一行(`page: "nav"`)的那节设置(`/commands` 打开命令管理)。
583
+ */
584
+ export interface CommandHandled {
585
+ handled: true;
586
+ openPanel?: string;
587
+ openSettings?: string;
588
+ }
589
+ /** `expand` 的结果:要发的话,或者已经办完了。 */
590
+ export type CommandResult = CommandExpansion | CommandHandled;
591
+ /**
592
+ * 插件给 `/` 菜单供的命令。宿主每次打开菜单、每次发送命令前都现问一遍,命令文件改了不用重载插件。
593
+ * 几个插件给了同名的命令时,先加载的插件的那条生效。
594
+ */
595
+ export interface CommandProvider {
596
+ list(context: CommandContext): SlashCommand[] | Promise<SlashCommand[]>;
597
+ /**
598
+ * 把 `/name rest` 展开成要发的消息,或者自己办掉它(`CommandHandled`)。`name` 是 `list` 给过的名字,`rest` 是名字后面的原文
599
+ * (去掉首尾空白)。抛错时不发、输入框里的字留着,message 写在输入框上面(给人看的话,按界面语言写)。
600
+ */
601
+ expand(name: string, rest: string, context: CommandContext): CommandResult | Promise<CommandResult>;
602
+ }
603
+ export type HookName = keyof Hooks;
604
+ /** 洋葱的一层:调 next 交给里一层,不调就是拦下。 */
605
+ export type HookLayer<N extends HookName> = (input: Hooks[N][0], next: (input: Hooks[N][0]) => Promise<Hooks[N][1]>) => Promise<Hooks[N][1]>;
606
+ export interface Logger {
607
+ info(message: string): void;
608
+ warn(message: string): void;
609
+ error(message: string): void;
610
+ }
611
+ /** 一次性的模型调用:一段系统提示词、一句话,要回一段字。 */
612
+ export interface ModelCompletion {
613
+ /** 结果将覆盖持久化内容时要求完整收尾;截断等非正常完成仍计费,但调用拒绝。 */
614
+ requireComplete?: boolean;
615
+ systemPrompt: string;
616
+ /** 交给模型的话,作为最后一条用户消息。 */
617
+ prompt: string;
618
+ /** 在 `prompt` 前面的一段对话(比如要总结的历史)。不给就只有 `prompt` 这一条。 */
619
+ messages?: Message[];
620
+ /** 替这个会话做:用它现在的模型,花销记在它名下。不给用默认模型,花销不归哪个会话。 */
621
+ sessionId?: string;
622
+ /** 指定模型(`SessionModel.id`),优先于 `sessionId` 的模型,花销仍记在 `sessionId` 名下。不在目录里时抛错,不悄悄换成别的模型。 */
623
+ model?: string;
624
+ /** 回复最多多少 token;不给用模型自己的上限。 */
625
+ maxTokens?: number;
626
+ /** 中止时请求跟着断开,调用以中止的原因结束。 */
627
+ signal?: AbortSignal;
628
+ /** 请求出了岔子、等一会儿再试时调(压缩那一行说「等待第 N 次重试」)。 */
629
+ onRetry?(info: {
630
+ delayMs: number;
631
+ }): void;
632
+ /**
633
+ * 从 `sessionId` 上一次发出去的主请求分叉:系统提示词、工具表、思考档位、缓存键、输出上限都照那一次原样,`messages`
634
+ * 要是这个会话下一次要发的历史(`ContextCompact.request.messages`),末尾接上 `prompt`;`systemPrompt` 和 `model` 不用。
635
+ * 前缀和那一次相同,服务商的缓存照常命中。发出前宿主核对请求体:那一次不是这一次的前缀就不发、抛错;
636
+ * 没发过主请求、换了模型、回复里调了工具时也抛错。必须给 `sessionId`。
637
+ */
638
+ fork?: boolean;
639
+ }
640
+ /** 一个配好的生图模型,不含供应商凭据。 */
641
+ export interface ImageModel {
642
+ /** `供应商 id/模型 id`,`ModelImageRequest.model` 传它。 */
643
+ id: string;
644
+ /** 界面上的名字。 */
645
+ name: string;
646
+ /** 发给供应商的模型 id。 */
647
+ modelId: string;
648
+ /** 供应商的名字。 */
649
+ provider: string;
650
+ }
651
+ /**
652
+ * 找生图模型的结果:找到了就是 `model`;否则 `reason` 说为什么,`candidates` 是可以点名的那些。
653
+ * `none` 一个生图模型都没配;`unknown` 点的名字对不上;`ambiguous` 对上了好几个。
654
+ */
655
+ export type ImageModelMatch = {
656
+ model: ImageModel;
657
+ } | {
658
+ model?: undefined;
659
+ reason: "none" | "unknown" | "ambiguous";
660
+ candidates: ImageModel[];
661
+ };
662
+ /** 交给生图模型的一张图:要改的,或者照着画的。 */
663
+ export interface ImageInput {
664
+ /** base64,不带 `data:` 前缀。 */
665
+ data: string;
666
+ mimeType: string;
667
+ /** 上传时的文件名;有的端点不给名字就拒收。 */
668
+ name?: string;
669
+ }
670
+ /** 画一次图或改一次图。 */
671
+ export interface ModelImageRequest {
672
+ /** 用哪个生图模型(`ImageModel.id`),先经 `findImageModel` 找到。 */
673
+ model: string;
674
+ prompt: string;
675
+ /** 有图就是改图:提示词说的是这些图。 */
676
+ images?: ImageInput[];
677
+ /** `"16:9"` 这类;不给由模型定(改图时多半照原图的形状)。 */
678
+ aspectRatio?: string;
679
+ /** `max` 是模型给得出的最大一档(支持的话是 4K),慢、也贵。 */
680
+ quality?: "low" | "medium" | "high" | "max";
681
+ /** 画几张,1–4。 */
682
+ count?: number;
683
+ background?: "transparent" | "opaque";
684
+ /** 中止时请求跟着断开,调用以中止的原因结束(抛错)。 */
685
+ signal?: AbortSignal;
686
+ /** 要重试了:第几次、等多久、为什么。等完了不另外通知。 */
687
+ onRetry?: (retry: {
688
+ attempt: number;
689
+ delayMs: number;
690
+ reason: string;
691
+ }) => void;
692
+ }
693
+ /** 生图失败的原因。`hint` 是 `check-billing`(额度用完)、`check-key`(这个 key 不让用这个模型)、`blocked`(内容被拒)时,人要去做点什么。 */
694
+ export interface ImageFailure {
695
+ summary: string;
696
+ detail?: string;
697
+ hint?: string;
698
+ /** 额度什么时候恢复,服务商说了的话。 */
699
+ retryAfterMs?: number;
700
+ }
701
+ /** 画出来的图,或者为什么没画出来。 */
702
+ export type ModelImageResult = {
703
+ ok: true;
704
+ /** 按图片本身的字节认的类型,宽高从图片头读;读不出来时没有宽高。 */
705
+ images: {
706
+ data: string;
707
+ mimeType: string;
708
+ width?: number;
709
+ height?: number;
710
+ }[];
711
+ /** 服务商改写了提示词时,它说自己照着画的那段。 */
712
+ revisedPrompt?: string;
713
+ /** 走的是 Images API 还是 chat completions。 */
714
+ route: "images" | "chat";
715
+ /** 画几张只到了一部分时,没到的那几张为什么没到。 */
716
+ failures?: string[];
717
+ } | {
718
+ ok: false;
719
+ failure: ImageFailure;
720
+ };
721
+ /** 插件调模型、读模型调用的花销。 */
722
+ export interface ModelAccess {
723
+ /**
724
+ * 调一次模型做一件小事(写一句提交说明、起个标题、写压缩摘要):不带工具、不思考(`fork` 的除外,见那里)、不写进任何会话,也不经 `model.context`。
725
+ * 没配默认模型、没有 key、服务商报错时抛错,message 是给人看的话(按界面语言)。
726
+ */
727
+ complete(request: ModelCompletion): Promise<string>;
728
+ /**
729
+ * 找生图模型(启用的供应商下、模型设置里标成生图模型的)。不给 `name` 是设置里选的默认生图模型,没选或已不可用时是第一个;
730
+ * 给了按人的说法认:本地 id、模型 id、显示名,再是唯一的部分匹配。对不上、对上几个都不悄悄换成默认的。每次调用按当时的设置。
731
+ * 给了 `sessionId` 时默认生图模型按那个会话所在项目的配置(`.plume/config.json` 的 `imageModel`)取;会话不存在时抛错。
732
+ */
733
+ findImageModel(name?: string, sessionId?: string): Promise<ImageModelMatch>;
734
+ /**
735
+ * 用生图模型画图、改图:用模型所在供应商的地址和 key(key 不出宿主),先试这个端点更可能有的那条路(Images API 或 chat),
736
+ * 端点说没有就换另一条,记住走通的。网络抖动重试两次;额度用完、key 不让用、内容被拒这类不会变的直接交回失败。
737
+ * 模型不是生图模型、不在了、供应商讲 Anthropic 协议(画不了)时抛错。
738
+ */
739
+ image(request: ModelImageRequest): Promise<ModelImageResult>;
740
+ /**
741
+ * 每一次计费的模型调用(回复、压缩摘要、插件的 `complete`),按小时、供应商、模型合好,最早的在前;删会话不影响这些数。
742
+ * 只读,给画使用统计的插件用。读会话库失败时抛错。
743
+ */
744
+ usage(): Promise<UsageScan>;
745
+ }
746
+ /**
747
+ * 会话的媒体目录:会话里的图片挪出消息后按内容(sha1)存在这里,消息里只留名字(`ImageContent.media`)。
748
+ * 存进去的名字和会话存图时起的一样,同一张图不会存两份。
749
+ */
750
+ export interface MediaAccess {
751
+ /** 存一张图,交回名字和磁盘上的绝对路径。只认 PNG、JPEG、WebP、GIF、BMP,别的抛错。 */
752
+ save(image: {
753
+ data: string;
754
+ mimeType: string;
755
+ }): Promise<{
756
+ name: string;
757
+ path: string;
758
+ }>;
759
+ /** 按名字读回一张图;名字不是这个目录的、文件不在时是 null。 */
760
+ read(name: string): Promise<{
761
+ data: string;
762
+ mimeType: string;
763
+ } | null>;
764
+ /** 一个绝对路径是不是这个目录里的图:是的话交回名字,不是是 null。 */
765
+ nameOf(path: string): string | null;
766
+ }
767
+ /** 插件在自己的子会话中发送的内容;parent 表示派发方交代的任务,不是用户插话。 */
768
+ export type SessionMessage = Omit<OutgoingMessage, "interrupt" | "synthetic"> & {
769
+ origin?: "parent";
770
+ };
771
+ /** 一次后台运行的句柄。续跑同一子会话时 id 不同,结果只能送达一次。 */
772
+ export interface SessionRun {
773
+ id: string;
774
+ sessionId: string;
775
+ }
776
+ /** `SessionManager.report` 的选项。 */
777
+ export interface ReportOptions {
778
+ /** 和本插件同一次送达里别的几份合成一段,见 `ReportFrame`。 */
779
+ frame?: ReportFrame;
780
+ /**
781
+ * 给界面把这份结果画成对话里的一行(「消息来自 @explore」),不是一个人发的气泡。不带 `kind`:那一行由本插件经
782
+ * `ui.agentDelivery` 画(宿主记下是哪个插件交的)。不给就只有模型看得到。
783
+ */
784
+ delivery?: Omit<DeliveredReport, "kind" | "command" | "exitCode" | "monitor" | "plugin">;
785
+ }
786
+ /**
787
+ * `ReportOptions.frame`。几份结果落在同一次送达里时,同一个插件带 `frame` 的合成一段:`head` 写一次,
788
+ * 接着各份的正文,最后是最后一份的 `tail()`——父会话读到的是「这几份到了、接下来怎么用」,不是每份各说一遍。
789
+ */
790
+ export interface ReportFrame {
791
+ head: string;
792
+ /** 真要送出那一刻才取:那时还在等谁、是不是都回来了,到那一刻才定。返回 undefined 就不写。 */
793
+ tail?(): string | undefined;
794
+ }
795
+ /** 可供子会话选择的模型,不含供应商凭据。 */
796
+ export interface SessionModel {
797
+ id: string;
798
+ name: string;
799
+ contextWindow: number;
800
+ thinking: ThinkingOption[];
801
+ }
802
+ /** `SessionAccess.compact` 压完的结果,`code` 同 `CompactionRun.code`。 */
803
+ export type CompactionOutcome = {
804
+ ok: true;
805
+ before: number;
806
+ after: number;
807
+ } | {
808
+ ok: false;
809
+ code: Exclude<CompactionCode, "done">;
810
+ error?: string;
811
+ };
812
+ /** `SessionAccess.launch` 开新会话要的东西。 */
813
+ export interface SessionLaunch {
814
+ /** 开在哪个已登记的项目里(项目 id),工作目录是项目的目录。和 `cwd` 都不给时是不属于任何项目的聊天。 */
815
+ project?: string;
816
+ /** 不在项目里时的工作目录:已有目录的绝对路径。给了 `project` 时不看它。 */
817
+ cwd?: string;
818
+ /** 第一句话:照常经 `prompt.submit`,画成人的气泡,标题默认取它的开头。 */
819
+ message: string;
820
+ /** 用哪个模型(`SessionModel.id`);不给用默认模型。不在目录里时抛错,不悄悄换成别的。 */
821
+ model?: string;
822
+ /**
823
+ * 从哪个会话开出来的,记进新会话摘要的 `spawnedFrom`;那个会话要在、不能是子会话。不给就不记。
824
+ * 要经 `reportBack` 交回时,`toolCallId` 得是本插件注册的工具在那个会话里的一次调用(提出开新会话的那次)。
825
+ */
826
+ spawnedFrom?: {
827
+ sessionId: string;
828
+ toolCallId?: string;
829
+ };
830
+ /** 标题;不给就取第一句话的开头。 */
831
+ title?: string;
832
+ }
833
+ /** `SessionAccess.launch` 开出来的会话。 */
834
+ export interface LaunchedSession {
835
+ session: SessionSummary;
836
+ /** 第一轮结束时给出它为什么结束。 */
837
+ done: Promise<TurnStop>;
838
+ /** 它第一次真闲下来时兑现,见 `SessionAccess.launch`;带上那时的收场和最后一段回复,交回用它。 */
839
+ idle: Promise<LaunchedIdle>;
840
+ /**
841
+ * 把它做出来的结果交回开出它的那个会话(`spawnedFrom`),只能交一次:`text` 是写给模型的话,作为一条运行时送达写进那个会话
842
+ * (前面带着「不是用户说的话」的抬头),界面上画成「来自新会话 · 标题」一行,点了到这个会话;`status` 是它做成了没有。
843
+ * 那个会话闲着(人按停过也算,这是一件新的事)就为它开一轮,忙着在两步之间读到。
844
+ * 没有 `spawnedFrom`、那个会话删了或归档了、已经交过、正在退出时不送,返回 false。
845
+ * `spawnedFrom.toolCallId` 不是本插件的工具在那个会话里的一次调用(或撤回时被截掉了)、插件已经停用时抛错。
846
+ */
847
+ reportBack(text: string, status: "done" | "failed"): boolean;
848
+ }
849
+ /** `LaunchedSession.idle` 兑现时的样子。 */
850
+ export interface LaunchedIdle {
851
+ /** 最近一轮怎么收场的(第一轮之后还有被唤起的几轮时,是最后那一轮);会话这期间被删掉时不给。 */
852
+ stop?: TurnStop;
853
+ /** 最后一段有字的回复,没有时是空串。 */
854
+ reply: string;
855
+ }
856
+ /** 会话为什么不再活着:删掉了、归档了、换到别的项目(工作目录变了)、退出应用。 */
857
+ export type SessionEndReason = "deleted" | "archived" | "moved" | "quit";
858
+ /**
859
+ * 子会话复用产品的循环、审批、存储和事件,插件只能运行自己拥有的会话。
860
+ * 不开放任意向用户会话发送消息的能力:插件够得着的会话都从宿主给的句柄来,目标不由插件随手填。
861
+ * - 新开的会话:`launch` 开一个新的顶层会话、发出第一句话,之后它就是人的会话,插件不能再往里发;
862
+ * - 开出它的那个会话:`LaunchedSession.reportBack` 交回一次,来处要是本插件的工具在那里的一次调用;
863
+ * - 调用所在的会话:`ToolRun.background` 送后台结果、`ToolRun.archiveAfterTurn` 收尾后归档;
864
+ * - 子会话的父会话:插件自己建的子会话里,它注册的工具的那次调用拿得到 `ToolRun.parent`(排活、停下、接着做),
865
+ * 人在子会话的卡片上点的 `ToolCardProps.handToParent` 同样只到父会话;
866
+ * - 别的顶层会话:只经那次调用的 `ToolRun.letter` 寄信,发件方由本体照实填;
867
+ * - 自己的子会话:`report` 把后台运行的结果交给它的父会话。
868
+ * 写进去的话都标着运行时来处,不当成人的授权;`ToolRun` 上的句柄调用答完就失效。
869
+ * 身份和工具限制由插件经 model.context、tool.execute 管理。
870
+ */
871
+ export interface SessionAccess {
872
+ /** 只读观察请求、工具开始、审批、重试和运行状态;副本不含配置密钥,停用时自动撤下。 */
873
+ observe(listener: (observation: SessionObservation) => void): void;
874
+ /** 全部会话的摘要(含归档的、子会话),最近活动的在前。 */
875
+ list(): Promise<SessionSummary[]>;
876
+ /** 一个会话存下的全部消息,是会话里的原文,不受压缩影响。没有这个会话时抛错。 */
877
+ messages(sessionId: string): Promise<Message[]>;
878
+ /**
879
+ * 现在就压缩这个会话(人敲了 `/compact`):照常经钩子 context.compact(带 `force` 和 `manual`),转录里画一行
880
+ * `/compact` 和它的进度、结果。任何会话都行(压缩不替人说话,只改模型看到的历史)。会话正在跑、正在压、没有可用的模型、
881
+ * 没有这个会话时抛错,message 是给人看的话(按界面语言)。开始了就返回;`done` 在压完时给出结果,停止按钮能停下它。
882
+ */
883
+ compact(sessionId: string, options?: {
884
+ instructions?: string;
885
+ }): Promise<{
886
+ done: Promise<CompactionOutcome>;
887
+ }>;
888
+ /**
889
+ * 改会话的标题(侧栏和标题栏上的那行字)。给起标题的插件用:宿主默认取第一条消息的开头。
890
+ * 空白压成一个空格、去掉首尾空白,最长 80 个字;改成空串不起作用。没有这个会话时抛错。
891
+ * 自动总结传 `automatic: true`:用户手动命名过就不覆盖;不传仍是显式设置标题。
892
+ */
893
+ setTitle(sessionId: string, title: string, options?: {
894
+ automatic?: boolean;
895
+ }): Promise<void>;
896
+ /** 会话被删掉时调(插件按会话记的东西该清了,按会话起的进程该停了)。停用时自动撤下。 */
897
+ onDeleted(listener: (sessionId: string) => void): void;
898
+ /** 成功压缩、撤回或重建历史后刷新该会话的上下文快照;停用时自动撤下。 */
899
+ onContextReset(listener: (sessionId: string) => void): () => void;
900
+ /**
901
+ * 归档、取消归档一个会话,和侧栏上的归档按钮一样(`onArchived` 照常通知各插件)。只在人点了插件界面上的按钮时调
902
+ * (工作树合回去之后的「归档这个对话」),不拿来替人收拾侧栏;模型照人的要求归档它所在的会话走 `ToolRun.archiveAfterTurn`。子会话跟着父会话,不单独归档:传子会话、没有这个会话时抛错。
903
+ */
904
+ setArchived(sessionId: string, archived: boolean): Promise<void>;
905
+ /** 会话归档(`archived` 为真)、取消归档时调:按会话占着的东西该收起、该放回了(工作树插件收回目录、检出回来)。停用时自动撤下。 */
906
+ onArchived(listener: (sessionId: string, archived: boolean) => void): void;
907
+ /**
908
+ * 会话在这次运行里不再活着时调,带上原因(见 `SessionEndReason`),子会话跟着父会话各调一次。删除、归档、换项目时不等监听;
909
+ * 退出应用时等这一轮停稳后再调,最多等 2 秒就接着退出。监听抛错只记日志。停用时自动撤下。
910
+ */
911
+ onEnded(listener: (sessionId: string, reason: SessionEndReason) => void | Promise<void>): void;
912
+ /**
913
+ * 建一个记在这个插件名下(`owner`)的会话,只有这个插件能在里面发起一轮。
914
+ *
915
+ * - 给 `parentId`:子会话。工作目录、项目随父会话(`project`、`cwd` 不能再给);没另选模型时跟着父会话的模型和思考档位;
916
+ * 父会话删掉时一起删。父会话不在、本身是子会话或插件的独立会话时抛错。
917
+ * - 不给 `parentId`:插件的独立会话,给插件自己的页面当一直在的助手用(经 `ui.Conversation` 画)。不挂在任何会话下,
918
+ * 在 `project` 或 `cwd`(已有目录的绝对路径)里跑,都不给就和不属于项目的聊天一样放在聊天目录里;没另选模型时用默认模型。
919
+ * 它不进侧栏、搜索、托盘、`@` 引用和已归档列表,不能归档,不收后台结果、不收发会话间的信,`prompt.submit` 给的 `cwd` 不换它的目录;
920
+ * 由插件经 `delete` 删掉(插件停用、卸载都不删,记录留着,下次启用时经 `list()` 按 `owner` 找回自己的;人在设置 › 数据里
921
+ * 按时间清理记录时和别的会话一起清掉)。
922
+ *
923
+ * 两种都不进侧栏。`title` 不给就取第一条消息的开头。模型、思考档位、项目、目录不对时抛错。
924
+ */
925
+ create(options: {
926
+ parentId?: string;
927
+ title?: string;
928
+ model?: string;
929
+ thinking?: ThinkingLevel;
930
+ /** 独立会话所在的项目(项目 id);和 `cwd` 二选一,不能和 `parentId` 一起给。 */
931
+ project?: string;
932
+ /** 独立会话的工作目录(已有目录的绝对路径);和 `project` 二选一,不能和 `parentId` 一起给。 */
933
+ cwd?: string;
934
+ }): Promise<SessionSummary>;
935
+ /**
936
+ * 开一个新会话并发出第一句话(定时任务到点了、人在插件界面上点了「在新会话里执行」)。新会话和人在侧栏新建的一样:
937
+ * 进侧栏、不记在插件名下(没有 `owner`),人能接着聊,插件之后不能再往里发、也停不了删不了它。第一句话照常经
938
+ * `prompt.submit`,可能被拦下(这一轮按完成收场)。只能开新的;对已有会话只有 `reportBack` 往开出它的那个会话交回一次。
939
+ * 建好就返回:`session` 是新会话的摘要,`done` 在这第一轮结束时给出它为什么结束(之后人接着聊的几轮不算)。
940
+ * `idle` 在它第一次真闲下来时兑现:第一轮结束,它派出去的后台运行都回来了、送回的结果唤起的几轮也做完了,它经 `ToolRun.letter`
941
+ * 问出去的请求(`request: true`)也都有了答复、答复唤起的几轮也做完了。请求有了答复:经回执的 `answer` 回了信,或对方读到以后
942
+ * 寄回一封带 `answer` 的信(答掉它读到的全部);对方删掉、寄信的插件停用了也不再等。
943
+ * 项目、目录、模型、`spawnedFrom` 不对时抛错,message 是给人看的话(按界面语言)。
944
+ */
945
+ launch(options: SessionLaunch): Promise<LaunchedSession>;
946
+ /** 可用模型目录,只含名称、窗口和思考档位。 */
947
+ models(): Promise<SessionModel[]>;
948
+ /** 开始自己的子会话的一轮,立即交回句柄;首次运行固定实际模型与思考档位,续跑沿用。父轮正常结束不取消,明确停止父会话则取消。 */
949
+ start(sessionId: string, message: string | SessionMessage): Promise<SessionRun>;
950
+ /** 等待自己的后台运行结束,失败和停止也正常交回原因。 */
951
+ wait(runId: string): Promise<TurnStop>;
952
+ /** 把已结束运行的结果交给其父会话。忙时在安全步骤边界送入,闲时唤起;已停止、重复或所属会话被删除则返回 false。 */
953
+ report(runId: string, text: string, options?: ReportOptions): Promise<boolean>;
954
+ /** 给自己正在运行的子会话插话,不打断当前工具;空闲时拒绝,需显式 send/start 开新轮。 */
955
+ steer(sessionId: string, message: string | SessionMessage): Promise<void>;
956
+ /** 人明确停止会话时通知,包括父会话;供插件撤销尚未开始的排队工作,停用时撤下。 */
957
+ onStopped(listener: (sessionId: string) => void): void;
958
+ /**
959
+ * 在自己建的子会话里发一句话、跑一轮,等这一轮结束,返回它为什么结束;回复在 `messages` 里。
960
+ * 这句话照常经 `prompt.submit`,界面画成人的气泡。正在跑、不是自己建的会话时抛错。`signal` 中止时这一轮停下。
961
+ */
962
+ send(sessionId: string, text: string | SessionMessage, options?: {
963
+ signal?: AbortSignal;
964
+ }): Promise<TurnStop>;
965
+ /** 停下自己子会话正在跑的那一轮;没在跑什么都不做。 */
966
+ stop(sessionId: string): void;
967
+ /** 删掉自己建的子会话(和它下面的子会话);正在跑的先停下。 */
968
+ delete(sessionId: string): Promise<void>;
969
+ }
970
+ /** 网页此刻的样子,见 `WebPages`。 */
971
+ export interface WebPageState {
972
+ /** 主框架此刻的地址;第一页还没提交时是空串。 */
973
+ url: string;
974
+ title: string;
975
+ /** 还在加载(主框架或子框架)。 */
976
+ loading: boolean;
977
+ canGoBack: boolean;
978
+ canGoForward: boolean;
979
+ /** 页面进程没了(崩溃、内存不够被系统杀掉),还没重新加载。 */
980
+ crashed: boolean;
981
+ /** 缩放倍数。 */
982
+ zoom: number;
983
+ }
984
+ /** 网页上发生的事。每条都带着页面 id 和那一刻的 `state`。 */
985
+ export type WebPageEvent = {
986
+ page: string;
987
+ state: WebPageState;
988
+ } &
989
+ /** `attach` 认下了这一页,从这时起能驱动它;认下之前它已经报过的事(被拦下的跳转之类)紧跟着补报。 */
990
+ ({
991
+ type: "attached";
992
+ }
993
+ /** 页面没了:`<webview>` 卸掉、标签关掉、窗口关掉。之后的调用报错,直到再 `attach`。 */
994
+ | {
995
+ type: "detached";
996
+ }
997
+ /** 主框架开始换一张文档(页内跳转不算)。 */
998
+ | {
999
+ type: "navigation";
1000
+ url: string;
1001
+ }
1002
+ /** 这张文档能读了(DOM 建好,资源可能还在加载)。 */
1003
+ | {
1004
+ type: "dom-ready";
1005
+ }
1006
+ /** 开始、停止加载。 */
1007
+ | {
1008
+ type: "loading";
1009
+ loading: boolean;
1010
+ }
1011
+ /** 主框架换到了这一页;`inPage` 是页内跳转(锚点、`history.pushState`)。 */
1012
+ | {
1013
+ type: "navigated";
1014
+ url: string;
1015
+ inPage: boolean;
1016
+ } | {
1017
+ type: "title";
1018
+ title: string;
1019
+ }
1020
+ /** 主框架没加载出来(被取消的不算)。`description` 是 Chromium 的错误名,如 `ERR_NAME_NOT_RESOLVED`。 */
1021
+ | {
1022
+ type: "failed";
1023
+ url: string;
1024
+ code: number;
1025
+ description: string;
1026
+ }
1027
+ /** 页面进程没了,`reason` 是 Electron 给的原因(`crashed`、`oom`、`killed`……)。宿主不自动重新加载。 */
1028
+ | {
1029
+ type: "gone";
1030
+ reason: string;
1031
+ }
1032
+ /**
1033
+ * 宿主拦下了主框架去非网页地址的一次跳转(`lark://` 这类唤起客户端的):网页只能是 http、https 和 about:blank。
1034
+ * `redirect` 是服务器重定向过去的,不是页面自己跳的。
1035
+ */
1036
+ | {
1037
+ type: "blocked";
1038
+ url: string;
1039
+ redirect: boolean;
1040
+ }
1041
+ /** 页面要开新窗口(`window.open`、`target=_blank`):宿主不开,交给插件决定。 */
1042
+ | {
1043
+ type: "popup";
1044
+ url: string;
1045
+ }
1046
+ /**
1047
+ * 宿主照 `answerDialogs` 替人答了页面的一个对话框。`beforeunload` 是离开页面的提示,没有文字;答 false 时留在原页,
1048
+ * 等着换页的那一方等不到新文档。
1049
+ */
1050
+ | {
1051
+ type: "dialog";
1052
+ dialog: "alert" | "confirm" | "beforeunload";
1053
+ message: string;
1054
+ accepted: boolean;
1055
+ });
1056
+ /** 发给网页的一次原生输入,坐标是页面视口里的 CSS 像素乘上缩放。 */
1057
+ export type WebInputEvent = {
1058
+ type: "mouseMove" | "mouseDown" | "mouseUp";
1059
+ x: number;
1060
+ y: number;
1061
+ button?: "left" | "middle" | "right";
1062
+ clickCount?: number;
1063
+ } | {
1064
+ type: "mouseWheel";
1065
+ x: number;
1066
+ y: number;
1067
+ deltaX: number;
1068
+ deltaY: number;
1069
+ }
1070
+ /** `keyCode` 是 Electron accelerator 的键名(`Enter`、`Tab`、`Up`、`a`)。 */
1071
+ | {
1072
+ type: "keyDown" | "keyUp" | "char";
1073
+ keyCode: string;
1074
+ modifiers?: ("shift" | "control" | "alt" | "meta")[];
1075
+ };
1076
+ /** 隐藏页面检查结果;截图宽高和起点均为 CSS 像素。 */
1077
+ export interface WebInspection {
1078
+ exceptions: string[];
1079
+ errors: string[];
1080
+ width: number;
1081
+ height: number;
1082
+ shotTop?: number;
1083
+ shotHeight?: number;
1084
+ screenshot?: {
1085
+ data: string;
1086
+ mimeType: "image/png";
1087
+ };
1088
+ }
1089
+ /**
1090
+ * 网页:插件界面入口自己画的 `<webview>`,各自一个进程,逻辑入口经这里驱动它。页面只有主进程碰得到,所以加载、执行脚本、
1091
+ * 原生输入、截图、缩放和视口、开发者工具、对话框由宿主做;标签、地址栏、给模型的工具都是插件自己的。
1092
+ *
1093
+ * 界面入口在 `<webview>` 的 `did-attach` 事件里取 `getWebContentsId()`,经自己的 `remote()` 交给逻辑入口,逻辑入口
1094
+ * 用 `attach` 把它认作自己起的页面 id(只在本插件内有效)。不管标签上写了什么,宿主都把页面放进沙箱、拿掉 Node 和预加载脚本,
1095
+ * 分区固定是持久化的 `persist:plume-web`(登录状态跨重启保留,各插件共用);只开 http、https 和 about:blank,摄像头、定位这类权限一律不给。
1096
+ * 主窗口和弹出去的面板窗口里的 `<webview>` 都能挂上;标签弹出去、接回来时 `<webview>` 换了一个,页面按记着的地址重新加载、重新 `attach`。
1097
+ * 页面没挂着时调用报错。
1098
+ */
1099
+ export interface WebPages {
1100
+ /**
1101
+ * 在独立、无持久数据的沙箱里加载 http(s) 页面,返回首屏异常、控制台错误和内容高度,可截取从 from 开始的最多 2160px。
1102
+ * 不挂界面、不共享浏览器登录、不允许导航、弹窗、下载或设备权限,最长 12 秒后销毁。width 默认 720,范围 320–1600。
1103
+ * measure 是插件提供的测高表达式(返回有限正数);省略时使用文档滚动高度。不按用户手势执行。
1104
+ */
1105
+ inspect(url: string, options?: {
1106
+ width?: number;
1107
+ from?: number;
1108
+ screenshot?: boolean;
1109
+ measure?: string;
1110
+ }): Promise<WebInspection>;
1111
+ /**
1112
+ * 把界面入口的一个 `<webview>`(`getWebContentsId()` 拿到的 id)认作页面 `id`,随即报 `attached`。同一个 `<webview>` 再认一次
1113
+ * 什么都不做;这个 id 正连着另一个还活着的 `<webview>` 时换成这一个(旧的不再算,卸掉时不报 `detached`)。
1114
+ * 这个 `<webview>` 已被别的插件或别的 id 认走、或不是 Plume 窗口里的 `<webview>` 时报错。
1115
+ */
1116
+ attach(id: string, contentsId: number): Promise<void>;
1117
+ /** 这一页的现状;没挂着时是 null。 */
1118
+ state(id: string): Promise<WebPageState | null>;
1119
+ /** 加载这个地址,不等它加载完(看事件)。只认 http、https 和 about:blank。 */
1120
+ load(id: string, url: string): Promise<void>;
1121
+ /** 后退、前进、刷新;没有上一页、下一页时什么都不做。页面进程没了时 `reload` 重新起一个。 */
1122
+ go(id: string, step: "back" | "forward" | "reload"): Promise<void>;
1123
+ /**
1124
+ * 在页面里求一个表达式的值(可以是 Promise,等它有结果),结果要能结构化克隆。按用户手势执行(能开全屏、播放声音)。
1125
+ * `isolated` 在只属于这个插件的隔离 world 里跑:和页面共享 DOM,但看不到页面的全局变量,页面也看不到它的。
1126
+ * 页面卡住时这次调用一直不回来,要限时就自己 race。
1127
+ */
1128
+ evaluate(id: string, code: string, options?: {
1129
+ isolated?: boolean;
1130
+ }): Promise<unknown>;
1131
+ /** 一次原生输入(指针、按键),和人在页面上操作一样触发页面的事件。 */
1132
+ input(id: string, event: WebInputEvent): Promise<void>;
1133
+ /** 在焦点处插入一段字,和输入法上屏一样。 */
1134
+ insertText(id: string, text: string): Promise<void>;
1135
+ /** 把键盘焦点给这一页。 */
1136
+ focus(id: string): Promise<void>;
1137
+ /**
1138
+ * 截下这一页此刻画着的样子(或 `rect` 那一块,视口坐标乘缩放),垫上白底(页面没设背景的地方本来是透明的),PNG 的 data URL。
1139
+ * 默认是物理像素;`points` 按屏幕的缩放比缩回点(高分屏上宽高减半),给模型看的图用它,不白占上下文。
1140
+ */
1141
+ capture(id: string, options?: {
1142
+ rect?: {
1143
+ x: number;
1144
+ y: number;
1145
+ width: number;
1146
+ height: number;
1147
+ };
1148
+ points?: boolean;
1149
+ }): Promise<string>;
1150
+ /** 缩放倍数,0.25–3。Chromium 按源共享缩放,换页后要再设一次。 */
1151
+ setZoom(id: string, factor: number): Promise<void>;
1152
+ /**
1153
+ * 把视口仿真成这个尺寸(CSS 像素),再按 `scale` 缩小放进挂着它的地方;null 恢复成挂着它的地方那么大。
1154
+ * 页面进程没了时不起作用,重新加载后要再设一次。
1155
+ */
1156
+ emulate(id: string, viewport: {
1157
+ width: number;
1158
+ height: number;
1159
+ scale: number;
1160
+ } | null): Promise<void>;
1161
+ /** 在独立窗口里开这一页的开发者工具。 */
1162
+ devtools(id: string): Promise<void>;
1163
+ /**
1164
+ * 之后这一页弹的 alert、confirm、离开页面提示由宿主直接答,不弹给人:alert 确定,其余按 `accept`;传 null 恢复成弹给人。
1165
+ * 设上时,已经弹着、等人点的那一个也当场按它答掉。每答一个报一条 `dialog` 事件。`prompt` 不在其中:页面调用时直接拿到 null。
1166
+ */
1167
+ answerDialogs(id: string, accept: boolean | null): Promise<void>;
1168
+ /** 订阅本插件全部网页的事件,返回取消订阅的函数;停用时自动撤下。 */
1169
+ onEvent(listener: (event: WebPageEvent) => void): () => void;
1170
+ }
1171
+ /** 逻辑入口的 HTTP 请求;桌面版使用系统代理,不共享浏览器 cookie,不跟随重定向。 */
1172
+ export type PluginFetch = (url: string, init?: RequestInit) => Promise<Response>;
1173
+ export interface PluginContext {
1174
+ readonly name: string;
1175
+ /** 插件私有的数据目录,加载前已创建。 */
1176
+ readonly dataDir: string;
1177
+ /** Plume 自己的数据目录(设置、会话、插件都在这里)。给模型用的工具不该写这里:写得到设置,就改得了自己的权限。 */
1178
+ readonly appDataDir: string;
1179
+ /** 只接受 HTTP(S) URL。响应兼容标准 fetch;取消信号和停用都会中止请求,超时和响应上限由插件决定。凭据只留在逻辑入口。 */
1180
+ readonly fetch: PluginFetch;
1181
+ /**
1182
+ * 把一个文件或目录移到系统的废纸篓(Windows 上是回收站),人删错了还能从那里拿回来。宿主没有这个能力时抛错
1183
+ * (比如 agent 不在桌面应用里跑)。
1184
+ */
1185
+ trash(path: string): Promise<void>;
1186
+ readonly tools: {
1187
+ /** 停用时自动注销;返回的函数提前撤下这一个(工具来自会断开的外部服务时用),撤下后同名的前一个恢复。 */
1188
+ register(tool: ToolDefinition): () => void;
1189
+ /** 现在登记着的全部工具名(各插件的,同名只算一个),按登记先后。给要让人挑工具的插件用。 */
1190
+ names(): string[];
1191
+ };
1192
+ /** order 小的在外层,默认 0;相同时按注册先后。 */
1193
+ hook<N extends HookName>(name: N, layer: HookLayer<N>, options?: {
1194
+ order?: number;
1195
+ }): void;
1196
+ /** 登记清理,插件停用时执行,后登记的先执行。 */
1197
+ effect(dispose: () => void | Promise<void>): void;
1198
+ /**
1199
+ * 把一个对象开给自己的界面入口,界面入口用 `ui.remote()` 调它的方法;一个插件只能开一个,停用时收回。
1200
+ * 方法的最后一个参数是 CallContext(运行时追加;需要响应取消的方法把参数个数写死);返回异步迭代器的,
1201
+ * 每个值推给界面,界面取消时 signal 中止。参数和返回值要能结构化克隆。
1202
+ */
1203
+ serve(api: object): void;
1204
+ readonly log: Logger;
1205
+ /** 插件自己调一次模型、画图、读花销,见 `ModelAccess`。 */
1206
+ readonly model: ModelAccess;
1207
+ /** 读写会话的媒体目录,见 `MediaAccess`。 */
1208
+ readonly media: MediaAccess;
1209
+ /** 读会话,见 `SessionAccess`。 */
1210
+ readonly sessions: SessionAccess;
1211
+ /** 驱动界面入口画的网页(`<webview>`),见 `WebPages`。 */
1212
+ readonly pages: WebPages;
1213
+ /** 现在的界面语言。插件给人看的文字(审批理由之类)按它选;发给模型的不用。 */
1214
+ locale(): Locale;
1215
+ /** 在界面上弹一条提示(提示条),不管那个会话是不是正开着。level 默认 info。 */
1216
+ notice(sessionId: string, text: string, level?: NoticeLevel): void;
1217
+ /** 给输入框的 `/` 菜单供命令,见 `CommandProvider`。一个插件可以供几份,停用时一起撤下。 */
1218
+ readonly commands: {
1219
+ provide(provider: CommandProvider): void;
1220
+ };
1221
+ /** 装着的插件,见 `InstalledPlugin`。 */
1222
+ readonly plugins: {
1223
+ /**
1224
+ * 现在启用着、已经加载好的插件,按加载先后(自己在 apply 里调时还不在其中)。给要读别的插件随包文件的插件用:技能插件读各插件的 `skills/`。
1225
+ * 每次调用都是当时的样子:插件装上、卸下、停用以后再调就变了。
1226
+ */
1227
+ list(): InstalledPlugin[];
1228
+ /**
1229
+ * 插件装上、卸下、启用、停用以后叫一次,这时 `list()` 已经是新的样子;停用时撤下。给按别的插件随包文件配东西、
1230
+ * 又不能等到下一轮才发现的插件用:MCP 插件读各插件的 `.mcp.json`,装上就连。
1231
+ */
1232
+ onChange(listener: () => void): void;
1233
+ };
1234
+ /**
1235
+ * 提供一项服务(见 `Services`),停用时撤下。同一项服务有几个插件提供时,非内置的优先于内置的,同是非内置的
1236
+ * 先加载的那个生效;生效的那个换了,用它的插件都重新加载一遍。
1237
+ */
1238
+ provide<K extends keyof Services>(name: K, service: Services[K]): void;
1239
+ /**
1240
+ * 用一项服务。只能用 `Plugin.inject` 里写了的。拿到的是宿主包过的一份:只有方法,改不动它。
1241
+ * 在这次加载里一直有效:提供方换了,插件会先停用再重新加载,到时重新拿。
1242
+ */
1243
+ use<K extends keyof Services>(name: K): Services[K];
1244
+ }
1245
+ /**
1246
+ * 插件包是哪一类(`package.json` 的 `plume.kind`),决定它在「插件市场」页的哪个分类里:普通插件;技能合集(只带 `skills/`);
1247
+ * 连接器(只带 `.mcp.json` 里的 MCP 服务)。装法、更新、卸载都一样。没写是普通插件。
1248
+ */
1249
+ export type PluginKind = "plugin" | "skill" | "connector";
1250
+ /**
1251
+ * 插件 `package.json` 里给市场看的说明,`@plumeai/plugin-sdk/manifest` 的 `readManifest` 读出的样子;市场目录 `marketplace.json`
1252
+ * 的一条照抄一份(加上目录名 `name`)。没写或类型不对的字段不出现。
1253
+ */
1254
+ export interface PluginManifest {
1255
+ kind: PluginKind;
1256
+ /** 显示名,`plume.displayName`。 */
1257
+ displayName?: string;
1258
+ description?: string;
1259
+ /** 市场按它判断更新:x.y.z,发出去以后不可变。 */
1260
+ version?: string;
1261
+ author?: string;
1262
+ /** 货架,`plume.category`。 */
1263
+ category?: string;
1264
+ keywords?: string[];
1265
+ homepage?: string;
1266
+ license?: string;
1267
+ /** 详情页「试试这样问」,`plume.defaultPrompt`。 */
1268
+ prompts?: string[];
1269
+ }
1270
+ /** 一个启用着的插件。 */
1271
+ export interface InstalledPlugin {
1272
+ name: string;
1273
+ /** 插件目录:`package.json`、`README.md`、`dist/` 和随包的 `skills/` 在这里。 */
1274
+ dir: string;
1275
+ /** 随应用发的插件。 */
1276
+ builtin: boolean;
1277
+ }
1278
+ /**
1279
+ * 服务名 → 服务的接口。服务是一个插件开给别的插件用的一组方法:提供的插件经 `ctx.provide` 给出实现,用的插件把
1280
+ * 服务名写进 `Plugin.inject`、经 `ctx.use` 拿到。依赖的是名字,不是某个插件,所以谁提供都行,能整个换掉。
1281
+ * 仓库外的插件给自己的服务用声明合并加一项:`declare module "@plume/contract" { interface Services { … } }`。
1282
+ */
1283
+ export interface Services {
1284
+ /** 与内置文件工具共用读取授权、项目根和会话批准记录。 */
1285
+ "file.read": {
1286
+ authorize(run: ToolRun, path: string): Promise<{
1287
+ ok: true;
1288
+ absolute: string;
1289
+ } | {
1290
+ ok: false;
1291
+ message: string;
1292
+ }>;
1293
+ };
1294
+ "background.jobs": BackgroundJobsService;
1295
+ "session.wakeups": SessionWakeupsService;
1296
+ sandbox: SandboxService;
1297
+ "file.changes": FileChangesService;
1298
+ "project.files": ProjectFilesService;
1299
+ resources: ResourcesService;
1300
+ "session.worktree": SessionWorktreeService;
1301
+ secrets: SecretsService;
1302
+ }
1303
+ /**
1304
+ * 服务 `secrets`:把插件要存的密钥(令牌、密码、请求头里的凭据)加密成一段文本,插件把它写回自己的配置文件。
1305
+ * 内置插件 plume.secrets 提供:AES-256-GCM,钥匙在它的数据目录。
1306
+ *
1307
+ * 只管加解密,不替插件存:密文跟着它所属的那条配置走,删掉配置就是删掉密钥,不用另外清。
1308
+ * 挡的是配置文件被同步、拷走、贴进问题报告时带出明文;挡不住同一台机器上同一个用户的程序——插件都跑在同一个进程里,
1309
+ * 谁拿到服务都解得开谁的密文。
1310
+ */
1311
+ export interface SecretsService {
1312
+ /** 加密一个值,返回带固定前缀的文本(`isSealed` 认得出),能放进 JSON 之类的任何文本里。 */
1313
+ seal(value: string): Promise<string>;
1314
+ /**
1315
+ * 解回来。不是 `seal` 出来的、或者这把钥匙打不开(钥匙丢了换了新的、密文来自别的机器)时是 null,当成「要重新填」。
1316
+ * 钥匙文件在却读不出来时抛错:这时不能当成「没了」,否则调用方把空值写回去,等钥匙又读得出来时已经没东西可解了。
1317
+ */
1318
+ unseal(sealed: string): Promise<string | null>;
1319
+ /** 这段文本是不是 `seal` 出来的样子:手写进配置的明文和密文分得开。 */
1320
+ isSealed(value: string): boolean;
1321
+ }
1322
+ /**
1323
+ * 服务 `session.worktree`:让开出来的新会话在它自己的 git 工作树和分支里跑,不动项目的检出。内置插件 plume.git 提供。
1324
+ * 建、收回、合并回去都归提供方;用的一方只说要不要。
1325
+ */
1326
+ export interface SessionWorktreeService {
1327
+ /** 这个目录在不在 git 仓库里:在才开得出工作树。 */
1328
+ available(cwd: string): Promise<boolean>;
1329
+ /**
1330
+ * 从这次工具调用开出来的会话(`SessionLaunch.spawnedFrom` 里的 `sessionId`、`toolCallId`)要自己的工作树:它第一轮开始时开一棵,
1331
+ * 登记只用这一次。先登记再 `ctx.sessions.launch`;返回的函数撤销登记,只在 `launch` 抛错时调(开出来以后登记由第一轮取走)。
1332
+ */
1333
+ requestFor(origin: {
1334
+ sessionId: string;
1335
+ toolCallId: string;
1336
+ }): () => void;
1337
+ }
1338
+ /** 服务 `project.files`:项目里该看哪些文件。内置插件 plume.tools 提供,它的 glob、grep 用的也是这一份。 */
1339
+ export interface ProjectFilesService {
1340
+ /**
1341
+ * `root` 之下 git 认为该看的文件(已跟踪且还在磁盘上的,加上未跟踪且没被忽略的),绝对路径,以 `root` 开头。
1342
+ * 不在 git 仓库里、或 git 答不上来时是 null,由调用方自己走目录。问 git 时不执行仓库能控制的程序(`core.fsmonitor`、过滤器、钩子)。
1343
+ */
1344
+ list(root: string): Promise<string[] | null>;
1345
+ }
1346
+ /** 沙箱的模式:只读、能写项目目录、不设限。 */
1347
+ export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access";
1348
+ /** 一条已经启动的命令。 */
1349
+ export interface SandboxProcess {
1350
+ readonly pid?: number;
1351
+ /** stdout 和 stderr 按到达顺序交错,和 shell 显示的一样。 */
1352
+ onOutput(listener: (chunk: string) => void): void;
1353
+ /**
1354
+ * 命令结束了。`code` 是 null 时 `signal` 说是什么杀了它。
1355
+ * `lingering`:命令退出了,但它自己起的进程还拿着它的输出(没重定向的 `server &`),经这个句柄还能读、能停。
1356
+ */
1357
+ onExit(listener: (code: number | null, signal?: string | null, lingering?: SandboxProcess) => void): void;
1358
+ /** 命令根本没能启动。之后不再报退出。 */
1359
+ onError(listener: (error: Error) => void): void;
1360
+ /** 连它起的子进程一起停。 */
1361
+ kill(signal?: "SIGTERM" | "SIGKILL"): void;
1362
+ }
1363
+ /** 服务 `sandbox`:在沙箱里跑 shell 命令。内置插件 plume.sandbox 提供(macOS 用 seatbelt,Linux 用 bubblewrap,Windows 用受限令牌)。 */
1364
+ export interface SandboxService {
1365
+ /**
1366
+ * 用用户的 shell 起一条命令,环境向登录 shell 要(从图标启动时也找得到用户装的工具)。`mode` 不给就不进沙箱;
1367
+ * 给了而这台机器给不了那样的沙箱时抛错。
1368
+ *
1369
+ * `shellMode` 是按哪一档选 shell:会话宣布的模式,系统提示词里告诉模型的 shell 就是按它选的;不给时按 `mode`。
1370
+ * 只在 Windows 上有区别(受约束的跑 PowerShell,完全访问跑 Git Bash):提权的命令传更宽的 `mode` 和原来的 `shellMode`,
1371
+ * 它仍是照被告知的那个 shell 写的。
1372
+ */
1373
+ run(command: string, options: {
1374
+ cwd: string;
1375
+ mode?: SandboxMode;
1376
+ shellMode?: SandboxMode;
1377
+ env?: Record<string, string>;
1378
+ }): SandboxProcess;
1379
+ /** 这段输出像不像被沙箱拒了(只读的文件系统、没有权限):像的话模型可以申请提权重试。 */
1380
+ looksDenied(output: string): boolean;
1381
+ }
1382
+ /** 逻辑入口 `dist/index.mjs` 的默认导出。 */
1383
+ export interface Plugin {
1384
+ name: string;
1385
+ /**
1386
+ * 要用的服务。都有插件提供了才调 apply,缺的时候插件处于「等待」状态,补上了自动加载;
1387
+ * 提供方换了或停了,插件先停用,再按现在的提供方重新加载(缺了就回到等待)。
1388
+ */
1389
+ inject?: readonly (keyof Services)[];
1390
+ apply(ctx: PluginContext): void | Promise<void>;
1391
+ }