@plumeai/plugin-sdk 0.1.7 → 0.2.0
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.
- package/dist/client/ui/src/i18n/messages/en.d.ts +47 -50
- package/dist/client/ui/src/i18n/messages/zh-CN.d.ts +44 -41
- package/dist/contract/src/changes.d.ts +30 -12
- package/dist/contract/src/diagnostics.d.ts +18 -23
- package/dist/contract/src/index.d.ts +2 -2
- package/dist/contract/src/plugin.d.ts +687 -137
- package/dist/contract/src/resources.d.ts +27 -14
- package/dist/contract/src/service.d.ts +138 -40
- package/dist/contract/src/ui.d.ts +175 -39
- package/dist/core/llm/images/types.d.ts +0 -62
- package/dist/core/llm/types/message.d.ts +50 -59
- package/dist/ui.d.ts +1 -1
- package/lib/build.mjs +34 -2
- package/lib/lint/i18n.mjs +41 -3
- package/package.json +1 -1
- package/skill/SKILL.md +20 -17
- package/skill/references/components.md +17 -0
- package/skill/references/logic.md +179 -90
- package/skill/references/ui.md +96 -31
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
* 插件逻辑入口的 API:插件默认导出 `Plugin`,在 apply 里通过 ctx 注册工具、往钩子里加层、登记清理。
|
|
3
3
|
* 界面入口的 API 在 `ui.ts`(`@plume/contract/ui`)。
|
|
4
4
|
*/
|
|
5
|
-
import type {
|
|
6
|
-
import type {
|
|
7
|
-
import type {
|
|
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";
|
|
5
|
+
import type { PluginChanges } from "./changes.ts";
|
|
6
|
+
import type { BackgroundJobInfo, BackgroundJobOutput, SessionObservation, SessionWakeup } from "./diagnostics.ts";
|
|
7
|
+
import type { PluginResources, Resource } from "./resources.ts";
|
|
8
|
+
import type { AssistantMessage, DeliveredReport, FileChanges, Message, PeerLetter, ThinkingOption, ToolResultMessage, ToolSpec } from "../../core/llm/types.d.ts";
|
|
9
|
+
import type { ApprovalDecision, ApprovalRequest, CompactionCode, HumanRequest, NoticeLevel, PermissionMode, Personalization, OutgoingMessage, SessionKind, SessionSummary, ThinkingLevel, TurnStop } from "./service.ts";
|
|
10
10
|
import type { UsageScan } from "./usage.ts";
|
|
11
11
|
/** 界面语言。只有这两种;跟随系统时先解析成其中一种再告诉 agent 进程。 */
|
|
12
12
|
export type Locale = "zh-CN" | "en";
|
|
13
13
|
export interface ToolCall {
|
|
14
14
|
id: string;
|
|
15
15
|
name: string;
|
|
16
|
+
/** 模型给的参数,别名已换成规范名(`ToolDefinition.aliases`);外层改过的照改过的,改成别名的也换回规范名。 */
|
|
16
17
|
args: Record<string, unknown>;
|
|
17
18
|
}
|
|
18
19
|
export interface ToolOutput {
|
|
@@ -32,8 +33,20 @@ export interface ToolOutput {
|
|
|
32
33
|
* 本体照 `images` 把大图挪进媒体目录,通用卡片把它画在 `images` 后面。
|
|
33
34
|
*/
|
|
34
35
|
shownImages?: ToolOutput["images"];
|
|
35
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* 给工具卡片和插件自己用的结构化数据,存进会话,不发给模型。形状由插件定,本体不看里面,只认这几个通用声明:
|
|
38
|
+
* 带 `checklist`(`ChecklistDetails`)是一份进度清单,带 `job`(`BackgroundJobDetails`)是在后台起了一个任务,
|
|
39
|
+
* 带 `path`、`added`、`removed`、`hunks`(`FileDiffDetails`)是改了一个文件、通用卡片画出 diff,
|
|
40
|
+
* 出错的结果带 `cancelled: true`(`CancelledDetails`)是人按了停、不是失败。
|
|
41
|
+
* 插件之间另认一个:让模型看了文件的哪一块(`FileViewDetails`)。改了哪些文件不在这里,见 `changes`。
|
|
42
|
+
*/
|
|
36
43
|
details?: unknown;
|
|
44
|
+
/**
|
|
45
|
+
* 这次调用改了哪些文件(`FileChanges`),由本体填:经 `ToolRun.files.change` 写成的、经 `ToolRun.exec`(`recordChanges`)观测到的命令改的,
|
|
46
|
+
* 和 `ToolRun.files.includeChanges` 算进来的。工具自己填的不算,钩子 tool.execute 的外层读得到、改不了(本体最后照它记下的重填)。
|
|
47
|
+
* 记进会话里的结果(`ToolResultMessage.changes`),撤回连文件一起退、交付卡、压缩都按它认改过的文件。
|
|
48
|
+
*/
|
|
49
|
+
changes?: FileChanges;
|
|
37
50
|
/**
|
|
38
51
|
* 这一步的结果都写进会话以后,这一轮就此结束,不再把结果交给模型:`done` 是完成,`stalled` 是卡住了。
|
|
39
52
|
* 一步里有几个结果都带了它时,有 `stalled` 就按 `stalled`。结果本身照常存,下一轮模型照样看得到。
|
|
@@ -44,12 +57,105 @@ export interface ToolOutput {
|
|
|
44
57
|
* 不占后面每一轮的上下文。给人看的卡片照旧是原文。
|
|
45
58
|
*/
|
|
46
59
|
uneventful?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* 这条结果是要照着做的说明(载入的技能正文这类),不是一次观察:记在会话里的结果上,压缩时原样留着,
|
|
62
|
+
* 不按大小剪,也不当作被后来的结果顶替的旧观察清掉。剪了它就是在改规矩。
|
|
63
|
+
*/
|
|
64
|
+
instructions?: boolean;
|
|
65
|
+
}
|
|
66
|
+
/** 进度清单的一项。`activeForm` 是正在做这一项时的说法(「正在接上路由」)。 */
|
|
67
|
+
export interface ChecklistItem {
|
|
68
|
+
content: string;
|
|
69
|
+
status: "pending" | "in_progress" | "completed";
|
|
70
|
+
activeForm?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 工具结果的 `details` 带上 `checklist`,就是声明「这是这件事最新的一份进度清单」:每次给的都是整张,会话里最新一条成功的那份算数。
|
|
74
|
+
* 宿主按它决定一轮收尾时要不要提醒还有没做完的(转录底下的「继续」);`details` 里别的字段照旧归插件。
|
|
75
|
+
* 哪个工具记清单由工具定义上的 `category: "checklist"` 说,转录据它把这次调用当成一段活的记账(见 `ToolDefinition.category`)。
|
|
76
|
+
*/
|
|
77
|
+
export interface ChecklistDetails {
|
|
78
|
+
checklist: ChecklistItem[];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 工具结果的 `details` 带上 `job`,就是声明「这次调用在后台起了一个任务」:之后查它输出、停下它的调用按 `id` 找回是哪一件,
|
|
82
|
+
* 转录里那一行说的是起它那次调用写的说明。`command` 是它跑的命令。`details` 里别的字段照旧归插件。
|
|
83
|
+
*/
|
|
84
|
+
export interface BackgroundJobDetails {
|
|
85
|
+
job: {
|
|
86
|
+
id: string;
|
|
87
|
+
command?: string;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 工具结果的 `details` 带上 `fileView`,就是声明「这次调用让模型看了这个文件的这一块」:`path` 是调用里写的路径解析成的绝对路径
|
|
92
|
+
* (`~/` 展开、相对路径按工作目录,不追符号链接,同一个文件的读和改才对得上);`lines` 是第几行到第几行(从 1 数,含两头),
|
|
93
|
+
* 不给就是从头到尾;`columns` 是每一行给了第几个到第几个字符,不给就是整行;`verbatim` 说给的是不是这一块的原文——大纲、
|
|
94
|
+
* 截断过的抽取文字只是看过这一块,不能拿来顶替别的读。
|
|
95
|
+
*
|
|
96
|
+
* 压缩据此清掉被后来的观察顶替掉的旧读:之后又原样读到了盖住它的一块,或者这个文件被改过。不带它的读文件结果压缩不动。
|
|
97
|
+
* 谁让模型读的谁填,不认是哪个工具交的;`details` 里别的字段照旧归插件。
|
|
98
|
+
*/
|
|
99
|
+
export interface FileViewDetails {
|
|
100
|
+
fileView: {
|
|
101
|
+
path: string;
|
|
102
|
+
lines?: {
|
|
103
|
+
from: number;
|
|
104
|
+
to: number;
|
|
105
|
+
};
|
|
106
|
+
columns?: {
|
|
107
|
+
from: number;
|
|
108
|
+
to: number;
|
|
109
|
+
};
|
|
110
|
+
verbatim: boolean;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 出错的工具结果(`isError`)的 `details` 带上 `cancelled: true`,就是声明「没做完是因为人按了停,不是失败」:转录画成「已停止」,
|
|
115
|
+
* 不画成出错。工具被停下时自己交结果的(命令跑到一半被杀)自己带上;本体替停下的调用交结果、或停下以后工具交回的错误结果
|
|
116
|
+
* 没有 `details` 时由本体带上。`details` 里别的字段照旧归插件。
|
|
117
|
+
*/
|
|
118
|
+
export interface CancelledDetails {
|
|
119
|
+
cancelled: true;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 钩子输入和 `ToolRun` 上的 `session`:这次调用、这一步、这次请求所在的会话是哪一种、挂在哪,本体照实填。
|
|
123
|
+
* 只想知道「是不是侧栏里的会话、是不是自己的子会话」时看它,不用为此去 `ctx.sessions.list()`。
|
|
124
|
+
* 字段的意思同 `SessionSummary` 上的同名字段;`id` 就是 `sessionId`。
|
|
125
|
+
*/
|
|
126
|
+
export interface SessionScope {
|
|
127
|
+
id: string;
|
|
128
|
+
kind: SessionKind;
|
|
129
|
+
parentId?: string;
|
|
130
|
+
owner?: string;
|
|
131
|
+
spawnedFrom?: {
|
|
132
|
+
sessionId: string;
|
|
133
|
+
toolCallId?: string;
|
|
134
|
+
};
|
|
135
|
+
project?: string;
|
|
136
|
+
/** 代管这个会话的插件,见 `SessionSummary.steward`。插件据此只对自己代管的会话收紧工具、改提示词。 */
|
|
137
|
+
steward?: string;
|
|
47
138
|
}
|
|
48
139
|
/** 审批卡上写什么,见 `ApprovalRequest`。`expiresAt` 由本体弹卡时定。 */
|
|
49
|
-
export type ApprovalContent = Omit<ApprovalRequest, "toolCallId" | "tool" | "expiresAt">;
|
|
140
|
+
export type ApprovalContent = Omit<ApprovalRequest, "type" | "toolCallId" | "tool" | "expiresAt">;
|
|
141
|
+
/** `ToolRun.request` 弹给人的那张卡,见 `HumanRequest`。 */
|
|
142
|
+
export type HumanRequestInput = Pick<HumanRequest, "kind" | "title" | "payload" | "waiting">;
|
|
143
|
+
/**
|
|
144
|
+
* 人怎么回的 `ToolRun.request`:交回了一份数据,或者没有——`declined` 人点了不答,`expired` 到时没人答,`stopped` 这一轮停了
|
|
145
|
+
* (会话删了也算)。`value` 来自渲染进程,类型参数只是工具期望的形状,用之前自己核一遍。
|
|
146
|
+
*/
|
|
147
|
+
export type HumanAnswer<T = unknown> = {
|
|
148
|
+
answered: true;
|
|
149
|
+
value: T;
|
|
150
|
+
} | {
|
|
151
|
+
answered: false;
|
|
152
|
+
reason: "declined" | "expired" | "stopped";
|
|
153
|
+
};
|
|
50
154
|
/** 工具执行时拿到的环境。 */
|
|
51
155
|
export interface ToolRun {
|
|
52
156
|
sessionId: string;
|
|
157
|
+
/** 调用所在的会话,见 `SessionScope`。 */
|
|
158
|
+
session: SessionScope;
|
|
53
159
|
/** 这次调用的 id(和 `calls` 里的、工具结果的 `toolCallId` 是同一个):从这次调用开出去的会话记在 `SessionLaunch.spawnedFrom` 里。 */
|
|
54
160
|
toolCallId: string;
|
|
55
161
|
cwd: string;
|
|
@@ -74,30 +180,102 @@ export interface ToolRun {
|
|
|
74
180
|
/** 执行中的输出,推给界面。 */
|
|
75
181
|
progress(chunk: string): void;
|
|
76
182
|
/**
|
|
77
|
-
* 动手之前问一句(写了 `asks`
|
|
78
|
-
*
|
|
79
|
-
*
|
|
183
|
+
* 动手之前问一句(写了 `asks` 的工具用)。完全访问、外层放行的、答过「以后不再问」的由本体直接放行,提权总是问人;其余经钩子
|
|
184
|
+
* tool.approve,要不要真的弹给人由那里的层定(内置插件 plume.approval 按帮我批准的风险规则放行)。参数校验完、注定失败的已经报掉
|
|
185
|
+
* 之后再问:不先问人一个没法成立的问题。用户停止这一轮时得到 `"reject"`。
|
|
186
|
+
* `subject` 不用加插件名前缀:本体把这里答的「以后不再问」记在注册这件工具的插件名下,只放行这个插件的工具再问同一个 `subject`,
|
|
187
|
+
* 别的插件、本体自己的读边界(`read:<项目>`)都不认它(见 `ApprovalRequest.subject`)。
|
|
80
188
|
*/
|
|
81
189
|
ask(content: ApprovalContent): Promise<ApprovalDecision>;
|
|
190
|
+
/**
|
|
191
|
+
* `ask` 得到 `"reject"` 以后交给模型的话:说这次调用被人拒了,叮嘱别原样重试、别换个工具或命令达到同样效果。原样作为错误结果交回,
|
|
192
|
+
* 工具不自己另写一份。本体自己拒绝时(替没写 `asks` 的工具在执行前问的、`escalate`、`files.read`)用的是同一句叮嘱,模型在哪看到都认得出。
|
|
193
|
+
*/
|
|
194
|
+
rejection: string;
|
|
195
|
+
/**
|
|
196
|
+
* 停下来等人给一份数据:回答一个问题、批准一份计划、填一张表。卡片由界面入口按 `kind` 登记(`ui.requestCard`),
|
|
197
|
+
* 没登记时产品画一张只能「不答」的通用卡。和 `ask` 同排一队、同占输入框上方那一格,子会话问时卡片压在上级会话的输入框上方。
|
|
198
|
+
* 不是权限:不经钩子 tool.approve,完全访问、「以后不再问」都不替人答。等 30 分钟,到时得到 `expired`;这一轮停了得到
|
|
199
|
+
* `stopped`。`payload` 要能结构化克隆(它要跨进程送到界面)。
|
|
200
|
+
*/
|
|
201
|
+
request<T = unknown>(input: HumanRequestInput): Promise<HumanAnswer<T>>;
|
|
82
202
|
/** 设置里现在的权限模式,用到才读:中途改了,下一个调用就照新的来。 */
|
|
83
203
|
permissionMode(): Promise<PermissionMode>;
|
|
84
|
-
/**
|
|
85
|
-
tools():
|
|
86
|
-
name: string;
|
|
87
|
-
plugin: string;
|
|
88
|
-
}[];
|
|
204
|
+
/** 现在工具表里有哪些工具、各是哪个插件注册的、是哪一类(`ToolDefinition.category`;同名的只列生效的那个)。 */
|
|
205
|
+
tools(): RegisteredToolInfo[];
|
|
89
206
|
/** 这一步回复里的全部调用(含这一次),按回复里的顺序。工具要知道这一步是不是只做了它这一件事时看它(todo 清单收尾)。 */
|
|
90
207
|
calls: readonly ToolCall[];
|
|
91
|
-
/** 钩子 tool.execute 的外层要求这次调用不改动工作区(见 `ToolExecution.readOnly
|
|
208
|
+
/** 钩子 tool.execute 的外层要求这次调用不改动工作区(见 `ToolExecution.readOnly`):`exec` 的命令只在只读沙箱里跑,`escalate` 一律不批。 */
|
|
92
209
|
readOnly?: boolean;
|
|
210
|
+
/**
|
|
211
|
+
* 这次调用的命令跑在哪个 shell、哪一档沙箱(见 `ShellInfo`),本体按权限模式和 `readOnly` 算好,和系统提示词里点名的是同一个。
|
|
212
|
+
* 提权不改它:提权的命令仍是照这个 shell 写的。用到才读设置,同 `permissionMode()`。
|
|
213
|
+
*/
|
|
214
|
+
shell(): Promise<ShellInfo>;
|
|
215
|
+
/**
|
|
216
|
+
* 在这次调用的沙箱里用 `shell()` 的那个 shell 起一条命令,环境向登录 shell 要(从图标启动时也找得到用户装的工具)。
|
|
217
|
+
* 沙箱档由本体定,工具选不了:完全访问不设限,帮我批准只能写项目目录和临时目录,请求批准只读,`readOnly` 一律只读;
|
|
218
|
+
* 这次调用经 `escalate` 批过更宽的一档时跑在那一档。`cwd` 不给是这次调用的工作目录。机器上给不了这一档的沙箱时拒绝(Linux 没有后端),
|
|
219
|
+
* 不悄悄不加沙箱就跑。问不问人不归这里:要问的工具自己先经 `ask` 问。只在这次调用里用:调用答完以后再调就抛错,已经起来的进程不受影响。
|
|
220
|
+
*
|
|
221
|
+
* `recordChanges`:这条命令可能改文件,本体在它起来之前、结束以后(连同它留下的进程)各看一次 git 工作区,改了的文件记进改动快照
|
|
222
|
+
* (来源是命令,不能自动撤销,撤回时说清),记进这次调用的结果(`ToolOutput.changes`);这次调用答完时还在跑的,结果里记一句「结束后才记」,
|
|
223
|
+
* 之后读它输出的调用用 `files.includeChanges` 把记下的算进去。观测期间撤销等着它结束。能证明什么都不写的命令(只读的 `git log`)不用带:
|
|
224
|
+
* 前后各起几个 git、读一遍工作区里改动着的文件,不便宜;挂着不结束的命令(监视)不该带:撤销会一直等它。
|
|
225
|
+
*/
|
|
226
|
+
exec(command: string, options?: {
|
|
227
|
+
cwd?: string;
|
|
228
|
+
env?: Record<string, string>;
|
|
229
|
+
recordChanges?: boolean;
|
|
230
|
+
}): Promise<RunProcess>;
|
|
231
|
+
/**
|
|
232
|
+
* 申请这次调用的命令跑在比权限模式更宽的一档(提权)。本体弹提权卡(`ApprovalRequest.escalation`),只认人答,或代管这个会话的插件
|
|
233
|
+
* 经 `ctx.sessions.respond` 转达;完全访问、外层放行、钩子 tool.approve、「以后不再问」都不替人答。批了,这次调用之后的 `exec`
|
|
234
|
+
* 都跑在那一档,调用答完就作废,什么都不累积。`readOnly` 下、没给理由、要的不比当前宽、人拒绝时不批,`message` 是交给模型的说明。
|
|
235
|
+
* `title`、`detail` 是卡上的标题和正文(要跑的命令),`justification` 是模型给的理由,原样给人看。只在这次调用里用。
|
|
236
|
+
*/
|
|
237
|
+
escalate(request: {
|
|
238
|
+
mode: "workspace-write" | "danger-full-access";
|
|
239
|
+
justification: string;
|
|
240
|
+
title: string;
|
|
241
|
+
detail: string;
|
|
242
|
+
}): Promise<{
|
|
243
|
+
granted: true;
|
|
244
|
+
} | {
|
|
245
|
+
granted: false;
|
|
246
|
+
message: string;
|
|
247
|
+
}>;
|
|
248
|
+
/**
|
|
249
|
+
* 这次调用经本体读工作区:读边界和问人都在里面,见 `RunFiles`。读文件的工具、要读用户文件的插件工具都经它,
|
|
250
|
+
* 不自己判断哪里能读。只在这次调用里用:调用答完以后再调就抛错。
|
|
251
|
+
*/
|
|
252
|
+
files: RunFiles;
|
|
93
253
|
/**
|
|
94
254
|
* 调用答完以后还在后台接着跑的事(后台命令、监视)把结果送回这个会话的渠道,见 `BackgroundChannel`。
|
|
95
255
|
* 只有顶层会话有:插件的子会话(侧边聊天、子 Agent)没人等它们的后台结果,拿不到就只能让模型自己去读。
|
|
96
256
|
*/
|
|
97
257
|
background?(): BackgroundChannel;
|
|
258
|
+
/**
|
|
259
|
+
* 登记一项调用答完以后还在跑的后台任务(后台命令、监视),见 `BackgroundJobInput`。本体据此:算会话闲没闲——顶层会话里
|
|
260
|
+
* 登记着、没结束、这次调用的送达渠道(`background`)还没作废的任务,在它结束之前会话都不算闲(`LaunchedSession.idle`、
|
|
261
|
+
* `ParentStatus`、`SessionSummary.background`),挂着的除外(`wakes` 的监视、经 `TrackedJob.stand` 标成长跑服务的);删会话、退出应用、登记它的插件停用时调 `stop(true)` 停掉;
|
|
262
|
+
* 子会话经 `ctx.sessions.start` 派出的那一次运行结束时停掉它还在跑的;经 `ctx.sessions.jobs` 列给界面(环境卡片、任务面板)。
|
|
263
|
+
* 人按停、撤回截掉这次调用时本体不停它(渠道作废,它不再算在等),停不停由工具定。只在这次调用里用:调用答完以后再调就抛错,
|
|
264
|
+
* 交回的 `TrackedJob` 不受影响。
|
|
265
|
+
*/
|
|
266
|
+
track(job: BackgroundJobInput): TrackedJob;
|
|
267
|
+
/**
|
|
268
|
+
* 让这个会话以后再接着做:到点经这次调用的送达渠道送一条运行时送达进来(闲着开一轮,忙着在下一个步骤边界读到),那一行记在
|
|
269
|
+
* 注册这个工具的插件名下(`DeliveredReport.plugin`)。见 `WakeupInput`。没触发的唤醒不挡会话闲下来:它是挂着的,到点照样叫醒会话。
|
|
270
|
+
* 人按停止时全部取消(本体跟人说一声取消了几个),撤回、编辑重发截掉这次调用时取消它约的,删会话、退出应用、注册工具的插件停用时取消。
|
|
271
|
+
* 只有顶层会话有(同 `background`)。cron 表达式写错时抛错(message 说哪一段不对,给模型看);在 `expiresAt` 之前一次也对不上时
|
|
272
|
+
* 不登记,返回 `undefined`。只在这次调用里用。
|
|
273
|
+
*/
|
|
274
|
+
schedule?(wake: WakeupInput): SessionWakeup | undefined;
|
|
98
275
|
/**
|
|
99
276
|
* 给另一个顶层会话寄一封信(`to` 是它的 id),见 `LetterReceipt`。只有顶层会话有,也只寄得到别的顶层会话:
|
|
100
|
-
* 子会话不是谁的通信对象。发件方的 id
|
|
277
|
+
* 子会话不是谁的通信对象。发件方的 id、工作目录由本体照实填上。请求的答复由本体配对、没答时由本体代答,往来链也由本体数,
|
|
278
|
+
* 见 `LetterReceipt`。对方不在、是子会话、是自己时抛错;往来链太长、太绕时抛错,message 是给模型的话(让它停下、回去告诉人)。
|
|
101
279
|
* 只在这次调用里用:调用答完以后再调就抛错(回执的 `answer` 不受影响)。
|
|
102
280
|
*/
|
|
103
281
|
letter?(to: string, letter: LetterInput): LetterReceipt;
|
|
@@ -114,6 +292,102 @@ export interface ToolRun {
|
|
|
114
292
|
*/
|
|
115
293
|
parent?: ParentSession;
|
|
116
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* `ToolRun.files`:读边界是本体的,一个路径能不能读、要不要问人,不管哪个工具来读都是同一个答案(两个答案就有一条绕过去的路:
|
|
297
|
+
* 一个工具拒了的换个工具读得到,模型就学会去绕)。
|
|
298
|
+
*
|
|
299
|
+
* 不问的:工作区、项目的其他源码目录(`ToolRun.projectRoots`)、各会话的临时目录、系统临时目录、工具链要读的系统目录(`/usr`、`/etc`……)、
|
|
300
|
+
* 人拖进来的文件(`ToolRun.allowedPaths`)。要问的:本机的密钥文件(Plume 的设置和凭据、ssh 私钥、云服务凭据、netrc、gh 的登录……,
|
|
301
|
+
* 在工作区里也问,批准只管这一个文件);别的地方(批准覆盖它所属的项目,往上走到仓库根,不到家目录)。问人经过闸(`kind` 是 `view`,
|
|
302
|
+
* `subject` 由本体定、记在本体名下:哪个工具来读都认同一份,插件经 `run.ask` 给同样的字符串也不算),完全访问、「以后不再问」照常生效;
|
|
303
|
+
* 人拒绝时 `message` 是交给模型的话。判定前追符号链接:工作区里一个指向外面的链接按它指向的地方判。
|
|
304
|
+
*/
|
|
305
|
+
export interface RunFiles {
|
|
306
|
+
/**
|
|
307
|
+
* 要读的路径或资源地址。
|
|
308
|
+
* - 路径(相对 `cwd`、`~/`、Windows 上 shell 的几种写法):按上面的边界判,过了交回 `path`,读就读它(判定用的绝对路径,
|
|
309
|
+
* 工作区自己在链接后面时写成工作区下的路径)。不存在的不问(密钥除外),照样交回,由工具自己报「找不到」。
|
|
310
|
+
* - 有人登记了 scheme 的资源地址(见 `PluginResources`):不问人,落点已由 scheme 限在它自己的地方。交回读到的 `resource`
|
|
311
|
+
* (带行范围的已经切好,`scheme://` 是列表);没有这样东西、越界时 `ok: false`,`message` 是 scheme 的说明。
|
|
312
|
+
* `scratch://<路径>`(不带行范围)是临时目录里的一个文件,交回它的 `path`,按文件读。
|
|
313
|
+
* - `directory: true`:要的是一个目录(列目录),总是交回 `path`。地址交回 scheme 给的磁盘位置(`ResourceHandler.locate`),给不出的照路径判。
|
|
314
|
+
* 没人登记的 `foo://` 照路径判。
|
|
315
|
+
*/
|
|
316
|
+
read(target: string, options?: {
|
|
317
|
+
directory?: boolean;
|
|
318
|
+
}): Promise<{
|
|
319
|
+
ok: true;
|
|
320
|
+
path: string;
|
|
321
|
+
resource?: undefined;
|
|
322
|
+
} | {
|
|
323
|
+
ok: true;
|
|
324
|
+
resource: Resource;
|
|
325
|
+
path?: undefined;
|
|
326
|
+
} | {
|
|
327
|
+
ok: false;
|
|
328
|
+
message: string;
|
|
329
|
+
}>;
|
|
330
|
+
/**
|
|
331
|
+
* 一行命令写明要读的路径(照这次调用的 shell 的语法拆,只认字面写的:`$DIR/key`、通配符、替换不猜;重定向的目标、heredoc 的正文不算),
|
|
332
|
+
* 按同一个边界判。同一个批准范围只问一次。全部不用问或都批准了是 `ok: true`;问到第一个被拒的就停,`message` 是交给模型的话。
|
|
333
|
+
* 跑命令的工具在审批、起命令之前调它:命令改不改东西是另一回事,`cat ~/.ssh/id_ed25519` 什么都不改,却是在读密钥。
|
|
334
|
+
*/
|
|
335
|
+
readCommand(command: string): Promise<{
|
|
336
|
+
ok: true;
|
|
337
|
+
} | {
|
|
338
|
+
ok: false;
|
|
339
|
+
message: string;
|
|
340
|
+
}>;
|
|
341
|
+
/**
|
|
342
|
+
* 模型写的路径(相对 `cwd`、`~/`、Windows 上 shell 的几种写法)指的是哪个绝对路径,和读、写边界判定用的是同一个解析。
|
|
343
|
+
* 不判能不能读写、不追符号链接、不认资源地址。给模型看路径、往结果里写 `FileViewDetails.path` 时用,不自己再解析一遍。
|
|
344
|
+
*/
|
|
345
|
+
resolve(path: string): string;
|
|
346
|
+
/**
|
|
347
|
+
* 替模型改一个文本文件:写边界、按文件排队、拿当前内容、写、记改动快照、撤销门闩一次做完,写成的记进这次调用的结果(`ToolOutput.changes`)。
|
|
348
|
+
*
|
|
349
|
+
* 写边界:不写 Plume 的数据目录和 `~/.plume`(工作区、同一脉的会话的临时目录在里面的除外),工作区里一个指向外面的链接不写
|
|
350
|
+
* (审批卡写的是字面路径,改的却是别处);工作区外不在这里拒,审批由工具问。附件(`allowedPaths`)不算能写。
|
|
351
|
+
* `edit` 拿到这个文件现在的样子(`path` 是真实路径,不存在时 `text`、`stamp` 是 null),在同一个队里决定写不写:要写就交回 `text`,
|
|
352
|
+
* 不写就不给;`value` 原样交回。审批由工具在 `edit` 里经 `ask` 问(diff 只有工具写得出来)。写之前本体再核对一遍:等审批时文件被改了、
|
|
353
|
+
* 换成了链接,不写并抛错。只认 UTF-8 文本,二进制、不是普通文件的抛错;撤销进行中抛错。抛的错都是交给模型的话。
|
|
354
|
+
* 交回的 `written` 是写完那一刻的修改时间和大小(没写时不给)。只在这次调用里用。
|
|
355
|
+
*/
|
|
356
|
+
change<T>(path: string, edit: (current: {
|
|
357
|
+
path: string;
|
|
358
|
+
text: string | null;
|
|
359
|
+
stamp: FileStamp | null;
|
|
360
|
+
}) => Promise<{
|
|
361
|
+
text?: string;
|
|
362
|
+
value: T;
|
|
363
|
+
}>): Promise<{
|
|
364
|
+
value: T;
|
|
365
|
+
path: string;
|
|
366
|
+
written?: FileStamp;
|
|
367
|
+
}>;
|
|
368
|
+
/**
|
|
369
|
+
* 写一个资源地址。能写的只有本体的 `scratch://`(会话的临时目录,落点不出目录、软链出不去),别的 scheme 都是只读的:能写
|
|
370
|
+
* `skill://<名字>` 的模型就能改掉它该遵守的说明。不问人、不记改动快照(临时目录是会话自己的草稿)。成了交回地址在磁盘上的位置;
|
|
371
|
+
* 只读、越界、不是登记着的地址时 `ok: false`,`message` 是交给模型的话。只在这次调用里用。
|
|
372
|
+
*/
|
|
373
|
+
write(address: string, content: string): Promise<{
|
|
374
|
+
ok: true;
|
|
375
|
+
path?: string;
|
|
376
|
+
} | {
|
|
377
|
+
ok: false;
|
|
378
|
+
message: string;
|
|
379
|
+
}>;
|
|
380
|
+
/**
|
|
381
|
+
* 这次调用的结果也算上一条命令改的文件:`process` 是经 `exec`(`recordChanges`)起的进程或它留下的进程,可以是之前的调用起的(后台命令)。
|
|
382
|
+
* 命令已经结束就算上它记下的,还在跑就记一句「结束后才记」。读后台命令输出的调用用。不是这个会话里经 `exec` 观测的进程时什么都不做。
|
|
383
|
+
*/
|
|
384
|
+
includeChanges(process: SandboxProcess): void;
|
|
385
|
+
}
|
|
386
|
+
/** 一个文件的修改时间和大小:工具判断读过以后文件变没变用。 */
|
|
387
|
+
export interface FileStamp {
|
|
388
|
+
mtimeMs: number;
|
|
389
|
+
size: number;
|
|
390
|
+
}
|
|
117
391
|
/**
|
|
118
392
|
* 工具的后台结果回到会话的路。送出的每段和同一时刻别的后台结果(子会话交回的、别的后台命令的)聚合一下,作为一条运行时送达
|
|
119
393
|
* 进会话:会话闲着时开一轮,忙着时在下一个步骤边界(全部工具结果之后、下一次请求之前)读入。送达经钩子 prompt.submit(`synthetic`)。
|
|
@@ -124,35 +398,97 @@ export interface ToolRun {
|
|
|
124
398
|
export interface BackgroundChannel {
|
|
125
399
|
/**
|
|
126
400
|
* 送一段给模型读的话,可以送多次。给函数时到真要送出那一刻才取:返回 `undefined` 就不送了(这期间模型已经自己读过)。
|
|
127
|
-
* `delivery`
|
|
401
|
+
* `delivery` 给界面把这条送达画成对话里的一行(「后台命令完成」),不是一个人发的气泡,见 `DeliveryInput`。渠道已作废时返回 false。
|
|
128
402
|
*/
|
|
129
|
-
report(text: string | (() => string | undefined), delivery?:
|
|
403
|
+
report(text: string | (() => string | undefined), delivery?: DeliveryInput): boolean;
|
|
130
404
|
/** 渠道作废时触发。 */
|
|
131
405
|
readonly signal: AbortSignal;
|
|
132
406
|
}
|
|
407
|
+
/** `ToolRun.track` 登记的一项后台任务:给人看的几样,和本体要用时调的两个方法。 */
|
|
408
|
+
export interface BackgroundJobInput {
|
|
409
|
+
command: string;
|
|
410
|
+
/** 这条命令是干什么的,人读的是它。 */
|
|
411
|
+
description?: string;
|
|
412
|
+
pid?: number;
|
|
413
|
+
/**
|
|
414
|
+
* 每有新输出就叫醒会话开一轮(监视命令),界面据此标出来,见 `BackgroundJobInfo.wakes`。这样的任务是挂着的:它的事随每次输出
|
|
415
|
+
* 送回会话,没有「做完」的那一刻,不挡会话闲下来(同 `TrackedJob.stand`)。
|
|
416
|
+
*/
|
|
417
|
+
wakes?: boolean;
|
|
418
|
+
/** 给人看的尾部输出(任务面板、环境卡片找端口):不能算成模型读过,也不能消费它的送达。 */
|
|
419
|
+
output(): Promise<BackgroundJobOutput>;
|
|
420
|
+
/** 停掉它(`force` 是强杀)。停不掉就抛错。 */
|
|
421
|
+
stop(force: boolean): void;
|
|
422
|
+
}
|
|
423
|
+
/** `ToolRun.track` 交回的句柄。 */
|
|
424
|
+
export interface TrackedJob {
|
|
425
|
+
/** 本体给这项任务的 id,在应用里唯一:工具交给模型、界面按它找这项任务都用它。 */
|
|
426
|
+
readonly id: string;
|
|
427
|
+
/** 它结束了(退出,或起不来:`error`);不再算在等。可以再调,以最后一次的退出码为准。 */
|
|
428
|
+
done(exitCode: number | null, error?: string): void;
|
|
429
|
+
/**
|
|
430
|
+
* 它是挂着的:起来以后才认得出是长跑的服务(开发服务器监听上了端口),不会自己做完,不再挡会话闲下来。只能标上、不能撤回;
|
|
431
|
+
* 照旧登记、列出、结束时照样可以送达、照样被本体停掉。
|
|
432
|
+
*/
|
|
433
|
+
stand(): void;
|
|
434
|
+
}
|
|
133
435
|
/**
|
|
134
|
-
* `
|
|
135
|
-
*
|
|
436
|
+
* `ToolRun.schedule` 约的一次「以后再接着做」:`at` 或 `cron` 二选一。
|
|
437
|
+
* 本体管什么时候到点、到点时这件事上一次开的那一轮做完了没有(没做完就跳过这次,记着跳过了几次)、什么时候取消;
|
|
438
|
+
* 到点送进会话的话由约它的插件给(`message`),本体不替它说。
|
|
136
439
|
*/
|
|
137
|
-
export
|
|
138
|
-
|
|
139
|
-
|
|
440
|
+
export interface WakeupInput {
|
|
441
|
+
/** 一次性:到这个时刻(毫秒时间戳)送。 */
|
|
442
|
+
at?: number;
|
|
443
|
+
/** 5 段 cron 表达式,本地时间;第一次至少在一分钟以后。 */
|
|
444
|
+
cron?: string;
|
|
445
|
+
/** cron 到点以后留着等下一次,直到 `expiresAt`;不给是只触发一次。 */
|
|
446
|
+
recurring?: boolean;
|
|
447
|
+
/** cron 过了这个时刻不再触发,第一次也要落在它之前;循环的记进 `SessionWakeup.expiresAt`。不给时不设限。 */
|
|
448
|
+
expiresAt?: number;
|
|
449
|
+
/** 到点时要接着做的事,见 `SessionWakeup.prompt`。 */
|
|
450
|
+
prompt: string;
|
|
451
|
+
reason?: string;
|
|
452
|
+
/**
|
|
453
|
+
* 到点时送进会话的话(运行时送达,给模型读)和对话里那一行(见 `DeliveryInput`)。`wakeup` 是这一刻的样子(`fired` 已经算上这一次),
|
|
454
|
+
* `next` 是下一次触发的时刻,`null` 表示这是最后一次;`skipped` 是上一次送达之后因为那一轮还没做完而跳过的次数。抛错时这次不送。
|
|
455
|
+
*/
|
|
456
|
+
message(wakeup: SessionWakeup, info: {
|
|
457
|
+
next: number | null;
|
|
458
|
+
skipped: number;
|
|
459
|
+
}): {
|
|
460
|
+
text: string;
|
|
461
|
+
delivery?: DeliveryInput;
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* 一份后台结果在对话里画成的那一行,`BackgroundChannel.report`、`ReportOptions.delivery`、`LaunchedSession.reportBack` 给的都是它。
|
|
466
|
+
* 宿主只留这几个字段、拷一份(`details` 要能存成 JSON),记下是哪个插件交的(`DeliveredReport.plugin`),那一行交给这个插件经
|
|
467
|
+
* `ui.agentDelivery` 登记的组件画,没登记就画成宿主默认的一行(`title` 和收场)。`sessionId`(点了去哪个会话)插件写不了,
|
|
468
|
+
* 只有新会话交回时宿主填上。
|
|
469
|
+
*/
|
|
470
|
+
export type DeliveryInput = Pick<DeliveredReport, "id" | "title" | "status" | "incomplete" | "inline" | "details">;
|
|
140
471
|
/**
|
|
141
472
|
* 一封信写什么(`ToolRun.letter`)。字段的意思见 `PeerLetter`:`from` 是发件方的地址,收件方回信时原样当作收件人;
|
|
142
|
-
*
|
|
143
|
-
*
|
|
473
|
+
* 是请求、答复还是通知由寄信的插件定,往来链由本体数(见 `LetterReceipt`)。
|
|
474
|
+
* - 答复(`answer`,插件写的一般是 `replied`)对着哪几封请求由本体认:收件方读到了、还没答的、这封信的收件方问它的请求,一封答复
|
|
475
|
+
* 全部答掉;`re` 不给时本体按这几封请求的开头填。一封也对不上(问的都答过了、根本没问过)时按通知送,`answer`、`re` 都不带。
|
|
476
|
+
* - `recipient` 只对请求有用:收件方的地址,写法和 `from` 一样由寄信的插件定。本体替收件方代答(见 `LetterReceipt`)时拿它当
|
|
477
|
+
* 答复的 `from`;不给就用收件方此刻的标题。
|
|
478
|
+
* - `guide` 是给收件方模型的处理说明(「标着 reply="expected" 的用某件工具回」),一次送达里一样的只写一遍。各个标记本身是什么意思
|
|
479
|
+
* (请求没答会被代答、`answer` 的几种值)本体自己会说,插件只说怎么用它的工具。
|
|
144
480
|
*/
|
|
145
|
-
export type LetterInput = Pick<PeerLetter, "from" | "text" | "request" | "answer" | "re"
|
|
481
|
+
export type LetterInput = Pick<PeerLetter, "from" | "text" | "request" | "answer" | "re"> & {
|
|
482
|
+
recipient?: string;
|
|
146
483
|
guide?: string;
|
|
147
484
|
};
|
|
148
485
|
/**
|
|
149
|
-
* 信的下落。`read
|
|
486
|
+
* 信的下落。`read`:写进了收件方的对话。
|
|
150
487
|
* `dropped`:没读到——请求还没读到收件方的人就按了停(通知和答复不丢,留到它下一轮开头读,这期间没有着落),或钩子 prompt.submit
|
|
151
488
|
* 拦下了这次送达。`closed`:收件方删掉了,或应用退出了。
|
|
152
489
|
*/
|
|
153
490
|
export type LetterFate = {
|
|
154
491
|
status: "read";
|
|
155
|
-
opened: boolean;
|
|
156
492
|
} | {
|
|
157
493
|
status: "dropped";
|
|
158
494
|
} | {
|
|
@@ -161,21 +497,41 @@ export type LetterFate = {
|
|
|
161
497
|
/**
|
|
162
498
|
* 寄出的一封信。信和后台结果走同一条路:收件方忙着时在下一个步骤边界读到,闲着时为它开一轮(停下过的也开:一封信是
|
|
163
499
|
* 一件新的事),送达经钩子 prompt.submit(`synthetic`)。前面带一句本体的话:这不是用户说的,不构成用户的授权。
|
|
500
|
+
*
|
|
501
|
+
* 请求(`request: true`)总会有一封答复,由本体配对,不靠哪个插件记账:
|
|
502
|
+
* - 收件方答了:它寄回一封答复(见 `LetterInput`),或者寄信的插件经 `answer` 替它答了。
|
|
503
|
+
* - 读到它的那一轮收场了还没答(完成、人按停、出错、跑到步数上限、原地打转都算):本体拿那一轮读到它以后最后一段回复代答,
|
|
504
|
+
* `answer` 是 `unreplied`(太长的截掉,没以完成收场的前面注一句是怎么收场的,什么也没说的也说一句)。
|
|
505
|
+
* - 没读到:收件方的人先按了停、或钩子 prompt.submit 拦下了这次送达,本体代答 `dropped`;收件方删掉了(读没读到都算)代答 `closed`。
|
|
506
|
+
* - 发件方删掉了、应用退出了:不再答。
|
|
507
|
+
* 代答从收件方寄回发件方,`from` 是请求的 `recipient`,和收件方自己回的一样走信的路。答过的请求再答一次(收件方答了以后又回、
|
|
508
|
+
* 插件在本体代答以后再代答)什么也不寄:`answer` 返回 `undefined`,寄来的答复按通知送或不寄(见 `answer`)。
|
|
509
|
+
*
|
|
510
|
+
* 往来链防几个会话原地来回,也由本体数,不靠哪个插件记账:一封信叫起收件方的一轮(不是插进一轮已经在跑的),这一轮里经
|
|
511
|
+
* `ToolRun.letter` 寄出的信就接着它的链再加一跳;人在这一轮里开口以后从头算。代答、经回执 `answer` 替收件方答的接着它答的
|
|
512
|
+
* 那封信的链,同样往下数,所以收件方总不正面回答时也照样数到上限。一条链超过 28 跳、或同一个会话经手超过 10 次时
|
|
513
|
+
* `ToolRun.letter` 抛错不寄;代答和回执的 `answer` 一封答一封,不拦。
|
|
164
514
|
*/
|
|
165
515
|
export interface LetterReceipt {
|
|
166
516
|
/** 这封信的 id,记在收件方对话里那条消息的 `peer` 上。 */
|
|
167
517
|
id: string;
|
|
168
518
|
/** 信有了着落时兑现,见 `LetterFate`。 */
|
|
169
519
|
read: Promise<LetterFate>;
|
|
520
|
+
/** 这封信作为答复答掉了几封请求(见 `LetterInput`);不是答复、或一封也没对上时是 0。 */
|
|
521
|
+
answered: number;
|
|
170
522
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
523
|
+
* 替收件方答这一封请求:从收件方寄回给发件方,只答这一封,`answer` 不给就是 `replied`。请求已经有了答复(收件方答了、本体代答了、
|
|
524
|
+
* 这里答过了)、两边有一方不在了、应用在退出时返回 `undefined`,什么也不寄。通知的回执也能用一次,寄回去的是一封通知。
|
|
173
525
|
*/
|
|
174
526
|
answer(letter: LetterInput): LetterReceipt | undefined;
|
|
175
527
|
}
|
|
176
528
|
/** 父会话此刻在做什么,见 `ParentSession.status`。 */
|
|
177
529
|
export interface ParentStatus {
|
|
178
|
-
/**
|
|
530
|
+
/**
|
|
531
|
+
* `running` 正在跑一轮;`background` 这一轮收了尾,还在等会自己结束、结束后要叫醒它的事:派出去的后台运行没都回来、交回的结果还没送进去,
|
|
532
|
+
* 登记着的一次性后台任务(`ToolRun.track`)没结束;`idle` 闲着。挂着的不算:监视、开发服务器这类长跑的任务(`BackgroundJobInput.wakes`、
|
|
533
|
+
* `TrackedJob.stand`),约好的唤醒和 cron(`ToolRun.schedule`),它们到点、有输出照样叫醒会话。被人停掉、撤回而作废的(不会再把结果送回会话的)不算。
|
|
534
|
+
*/
|
|
179
535
|
state: "running" | "background" | "idle";
|
|
180
536
|
/** 队里还排着几条(人排的和交过来的都算)。 */
|
|
181
537
|
queued: number;
|
|
@@ -187,8 +543,8 @@ export interface ParentStatus {
|
|
|
187
543
|
export interface ParentSession {
|
|
188
544
|
id: string;
|
|
189
545
|
/**
|
|
190
|
-
*
|
|
191
|
-
* (`UserMessage.origin
|
|
546
|
+
* 把一件事排进父会话的队列(和人在输入框里排的同一条,落盘),父会话真闲着就马上开始。发出去的消息标着来自哪个插件
|
|
547
|
+
* (`UserMessage.origin` 是 `child`,`fromPlugin` 是建这个子会话的插件),前面带一句给模型的来处说明。`started` 是排上那一刻它就开始了。指令为空时抛错。
|
|
192
548
|
*/
|
|
193
549
|
enqueue(instruction: string): Promise<{
|
|
194
550
|
started: boolean;
|
|
@@ -207,22 +563,91 @@ export interface ParentSession {
|
|
|
207
563
|
carryOn(text: string): Promise<ParentStatus>;
|
|
208
564
|
status(): ParentStatus;
|
|
209
565
|
}
|
|
566
|
+
/** 工具表里生效的一件工具:名字、注册它的插件、它声明的种类和类别。见 `ToolRun.tools()`、`ctx.tools.list()`、`AgentService.listTools()`。 */
|
|
567
|
+
export interface RegisteredToolInfo {
|
|
568
|
+
name: string;
|
|
569
|
+
plugin: string;
|
|
570
|
+
kind?: ToolKind;
|
|
571
|
+
category?: ToolDefinition["category"];
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* 一个工具对本机的文件和命令做的是哪种活,见 `ToolDefinition.kind`:
|
|
575
|
+
* - `view` 读一个文件,`list` 列一个目录,`find` 按名字找文件,`search` 按内容搜文件;
|
|
576
|
+
* - `create` 写出一个文件(整个正文就在参数里),`modify` 改一个已有的文件;
|
|
577
|
+
* - `command` 跑一条命令,`watch` 起一条长跑的命令、边跑边把新输出送回会话,`job` 查看或停掉一个后台任务(起它的那次结果带着
|
|
578
|
+
* `BackgroundJobDetails`)。
|
|
579
|
+
*
|
|
580
|
+
* 不碰本机文件和命令的工具(查网页、搜会话转录、问人)不在这几种里:别的插件按种类挑「能读、能改、能跑命令」的工具时不该挑到它们。
|
|
581
|
+
*/
|
|
582
|
+
export type ToolKind = "view" | "list" | "find" | "search" | "create" | "modify" | "command" | "watch" | "job";
|
|
210
583
|
export interface ToolDefinition {
|
|
211
584
|
name: string;
|
|
212
585
|
description: string;
|
|
213
586
|
/** JSON Schema。 */
|
|
214
587
|
parameters: Record<string, unknown>;
|
|
588
|
+
/**
|
|
589
|
+
* 模型常把参数名写错时,宽容地认哪些:规范名(`parameters` 里的)→ 模型会写成的别名(`{ path: ["file", "filePath"] }`)。
|
|
590
|
+
* 宿主在这一步的回复写进会话之前就换好:规范名没给(或是 null)时取第一个给了的别名,别名一律从参数里删掉;
|
|
591
|
+
* tool.execute 的层改写参数往里交时又写成别名的,交给里层之前再换一次。于是 tool.execute、tool.approve 的各层、审批卡、转录、导出、压缩和工具自己看到、存下的都是规范名:按参数拦调用的守卫
|
|
592
|
+
* 不会因为模型换了个写法就读不到,工具和别处也不用各抄一份别名表。别名不能是 `parameters.properties` 里的名字。
|
|
593
|
+
*/
|
|
594
|
+
aliases?: Record<string, readonly string[]>;
|
|
215
595
|
/** 会改动工作区(写文件、执行命令):不和相邻的调用同时跑;没写 `asks` 的,宿主在执行前按参数问用户。 */
|
|
216
596
|
mutating?: boolean;
|
|
217
597
|
/** `mutating` 的工具里,这一次调用能证明什么都不改(只读的命令):可以和相邻的只读调用同时跑。 */
|
|
218
598
|
readsOnly?(args: Record<string, unknown>): boolean;
|
|
219
599
|
/** 工具自己在 execute 里经 `run.ask` 问(能给出 diff、命令原文),宿主不在执行前替它问。 */
|
|
220
600
|
asks?: boolean;
|
|
601
|
+
/**
|
|
602
|
+
* 导出会话成 Markdown 时这一次调用写成的一行(英文,「Generate image a red fox」)。不给、返回空或抛错时写工具名。
|
|
603
|
+
* 导出在 agent 进程,够不着界面入口的 `ui.toolLine`,所以说法跟着工具定义走。
|
|
604
|
+
*/
|
|
605
|
+
describe?(args: Record<string, unknown>): string | undefined;
|
|
606
|
+
/**
|
|
607
|
+
* 导出会话成 Markdown 时,这次调用的结果里要引出来的那段(子智能体交回的结论):写在调用列表后面,引成一段。
|
|
608
|
+
* 不给、返回空或抛错时不引,结果照旧不导出。
|
|
609
|
+
*/
|
|
610
|
+
exportResult?(result: ToolResultMessage): string | undefined;
|
|
611
|
+
/**
|
|
612
|
+
* 这一次调用是替另一个工具调的(工具多了按需加载时,模型经一个总入口按名字调它们):返回被调的那个工具和交给它的参数。
|
|
613
|
+
* 导出会话成 Markdown 时这一行按被调的那个写(它的名字、它的 `describe`)。不是转调的调用返回 undefined。
|
|
614
|
+
*/
|
|
615
|
+
forwards?(args: Record<string, unknown>): {
|
|
616
|
+
name: string;
|
|
617
|
+
args: Record<string, unknown>;
|
|
618
|
+
} | undefined;
|
|
619
|
+
/**
|
|
620
|
+
* 做的是哪种活(见 `ToolKind`)。一个工具做什么只在这里声明一次,宿主、界面和别的插件都从这里认,不认工具名
|
|
621
|
+
* (`run.tools()`、`ctx.tools.list()`、`AgentService.listTools()` 带着它):
|
|
622
|
+
* - 转录按它给默认的说法、图标、运行行和归类,回复里的文件签按 `view`、`create`、`modify` 的调用认读写过的文件(从参数里取什么见 `ui.toolLine`)。
|
|
623
|
+
* - `create`、`modify` 成功一次就是工作区有了实打实的进展:重复检测从头数,同样的调用之前反复得到同样的结果不再算原地打转。
|
|
624
|
+
* - 给模型一套收窄的工具表的插件按它留下工具(侧边聊天留读、搜、改、跑命令的,只读的子智能体留读和搜的),提示词要点名
|
|
625
|
+
* 「读文件的那个」「跑命令的那个」时按它在工具表里找它叫什么。
|
|
626
|
+
*
|
|
627
|
+
* 不给的不算这几种活:转录照没登记说法的工具说,自成一类。
|
|
628
|
+
*/
|
|
629
|
+
kind?: ToolKind;
|
|
630
|
+
/**
|
|
631
|
+
* 这是哪一类能力,宿主和别的插件按它认,不认工具名(`run.tools()`、`ctx.tools.list()` 带着它):
|
|
632
|
+
* - `mcp`:MCP 服务的工具(连同按需搜索、调用它们的总入口)。上下文用量里单独算「MCP 工具」一段。
|
|
633
|
+
* - `checklist`:记进度清单的工具,结果的 `details` 带 `ChecklistDetails`。转录把它的调用当成一段活的记账:跟旁边那段活接成一段、
|
|
634
|
+
* 不自己占一行,一段里有别的活时总的那句不数它,回答之后被替人追问、只更新了清单的那一步不画,运行行说在排步骤。
|
|
635
|
+
* 别的插件要对模型说「用它记下步骤」时,按这一类在工具表里找它叫什么,找不到就不说。
|
|
636
|
+
* - `question`:停下来问人一个问题、等人回答的工具(经 `run.request`)。别的插件要对模型说「拿不准就问人」时同样按这一类找。
|
|
637
|
+
* - `web`:只读地查网页的工具(搜索网页、把网址读成文字),不碰工作区。给模型一套收窄的工具表的插件按这一类留下它们。
|
|
638
|
+
* - `delegate`:把一件活派给子智能体、等它交回结论的工具。别的插件要对模型说「派子智能体去做」时按这一类找它叫什么,
|
|
639
|
+
* 找不到就换成不派、自己做的说法;怎么填它的参数归它自己的说明。
|
|
640
|
+
*
|
|
641
|
+
* 不给的算普通工具。做的是哪种活(读、改文件、跑命令)用 `kind`,不在这里。
|
|
642
|
+
*/
|
|
643
|
+
category?: "mcp" | "checklist" | "question" | "web" | "delegate";
|
|
221
644
|
execute(args: Record<string, unknown>, run: ToolRun): Promise<ToolOutput>;
|
|
222
645
|
}
|
|
223
646
|
/** 钩子 tool.execute 的输入。 */
|
|
224
647
|
export interface ToolExecution {
|
|
225
648
|
sessionId: string;
|
|
649
|
+
/** 调用所在的会话,见 `SessionScope`。 */
|
|
650
|
+
session: SessionScope;
|
|
226
651
|
cwd: string;
|
|
227
652
|
call: ToolCall;
|
|
228
653
|
/** `plugin` 是注册它的插件(read、write、edit、ls、glob、grep、bash 是内置插件 plume.tools 的)。 */
|
|
@@ -231,13 +656,18 @@ export interface ToolExecution {
|
|
|
231
656
|
mutating: boolean;
|
|
232
657
|
plugin: string;
|
|
233
658
|
};
|
|
234
|
-
/**
|
|
659
|
+
/** 外层已经放行的理由。设了它,本体就不再问人(提权和层自己问的除外),也不经钩子 tool.approve。 */
|
|
235
660
|
approved?: string;
|
|
236
661
|
/**
|
|
237
|
-
*
|
|
238
|
-
*
|
|
662
|
+
* 外层要求这次调用不改动工作区(计划模式)。只能收紧:里层不该把它去掉。本体照它执行,从设上它的那一层往里:
|
|
663
|
+
* - 写、改文件的工具(`ToolDefinition.kind` 是 `create`、`modify`)不再往里交、不执行,这次调用的结果就是 `refusal`
|
|
664
|
+
* (交给模型的话:为什么没跑、接下来怎么办);
|
|
665
|
+
* - 别的工具照常往里交、执行,拿到 `ToolRun.readOnly`:`run.exec` 的命令不按权限模式选沙箱,一律在只读沙箱里跑(机器上给不了时不跑),
|
|
666
|
+
* `run.escalate` 一律不批。
|
|
239
667
|
*/
|
|
240
|
-
readOnly?:
|
|
668
|
+
readOnly?: {
|
|
669
|
+
refusal: string;
|
|
670
|
+
};
|
|
241
671
|
signal: AbortSignal;
|
|
242
672
|
/**
|
|
243
673
|
* 问一次要不要放行,卡上是这次调用的参数和 `reason`。经钩子 tool.approve(标着 `byLayer`),由那里的层定要不要弹给人
|
|
@@ -250,14 +680,28 @@ export interface ToolExecution {
|
|
|
250
680
|
* 系统提示词的一段。plume.prompt 给的段(按顺序):`identity`(身份)、`guidelines`(做事的准则)、`boundaries`(边界)、
|
|
251
681
|
* `environment`(平台、shell、是不是 git 仓库、模型)、`tone`(语气)、`userInstructions`(全局指令)、
|
|
252
682
|
* `projectInstructions`(项目里的 AGENTS.md 等)、`workspace`(工作目录和会话的临时目录)。plume.tools 再加一段 `resources`(资源地址的清单)。
|
|
683
|
+
* 段名是提供它的插件自己的:别的插件要找某一类段(身份、准则)时按 `category` 找,不认段名。
|
|
253
684
|
*/
|
|
254
685
|
export interface PromptSection {
|
|
255
|
-
/**
|
|
686
|
+
/** 段的名字,提供它的插件按它替换、删除自己的段。插件自己的段用自己的名字。 */
|
|
256
687
|
id: string;
|
|
257
688
|
text: string;
|
|
258
689
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
690
|
+
* 这段是什么。宿主按它分段统计,别的插件按它找段,都不认段名:
|
|
691
|
+
* - `instructions`:人写给 agent 的全局指令(设置里填的,不分项目)。上下文用量里算系统提示词,使用统计 Token 构成里的「指令和记忆」。
|
|
692
|
+
* - `conventions`:项目里写给 agent 的指令文件(AGENTS.md 这类)。上下文用量的「项目指令」,Token 构成里的「指令和记忆」。
|
|
693
|
+
* - `memory`:插件记下、每轮带上的记忆(用户的、项目的都算)。上下文用量的「记忆」,Token 构成里的「指令和记忆」。
|
|
694
|
+
* - `skills`:技能目录(有哪些技能、各自什么时候用)。上下文用量的「技能」,Token 构成里的「技能与智能体」。
|
|
695
|
+
* - `catalog`:别的按需取用的能力目录(可以派的智能体、没放进工具表的工具)。上下文用量里算系统提示词,Token 构成里算「技能与智能体」。
|
|
696
|
+
* - `identity`:agent 是谁的那一段。子会话要换一个身份(子智能体、侧边聊天)时换掉带这一类的段,不认段名。宿主算系统提示词。
|
|
697
|
+
* - `guidelines`:做事的通用准则。要换一套准则、或往准则里补几条的插件按这一类找它。宿主算系统提示词。
|
|
698
|
+
*
|
|
699
|
+
* 不给的段算系统提示词。
|
|
700
|
+
*/
|
|
701
|
+
category?: "memory" | "skills" | "catalog" | "identity" | "guidelines" | "instructions" | "conventions";
|
|
702
|
+
/**
|
|
703
|
+
* 这段里哪几截来自哪个文件:`chars` 是那一截在 `text` 里的字数(连同包着它的标签)。宿主把 `conventions` 和 `memory` 两类段的来源
|
|
704
|
+
* 按文件列进上下文用量的「记忆文件」,这些文件在会话边界外也能在文件面板里只读打开(`path` 给绝对路径);
|
|
261
705
|
* 别的段带了不显示。改了 `text` 的层要么跟着改它,要么去掉它。
|
|
262
706
|
*/
|
|
263
707
|
files?: Array<{
|
|
@@ -274,6 +718,18 @@ export interface PromptSection {
|
|
|
274
718
|
*/
|
|
275
719
|
export interface ModelContext {
|
|
276
720
|
sessionId: string;
|
|
721
|
+
/** 这次请求所在的会话,见 `SessionScope`。下面的 `parentId`、`owner` 和它里面的同名字段是同一件事。 */
|
|
722
|
+
session: SessionScope;
|
|
723
|
+
/**
|
|
724
|
+
* 这个会话挂在哪个会话下面,和 `SessionSummary.parentId` 同一件事:只有插件建的子会话(侧边聊天、子 Agent)有,
|
|
725
|
+
* 顶层会话不带这个键。按会话藏工具、改提示词时看它,不用为此去 `ctx.sessions.list()`。
|
|
726
|
+
*/
|
|
727
|
+
parentId?: string;
|
|
728
|
+
/**
|
|
729
|
+
* 建这个会话的插件,和 `SessionSummary.owner` 同一件事:插件的子会话和独立会话(`SessionAccess.create` 不给 `parentId`)有,
|
|
730
|
+
* 侧栏里的会话不带这个键。有 `owner` 没有 `parentId` 的是插件的独立会话。
|
|
731
|
+
*/
|
|
732
|
+
owner?: string;
|
|
277
733
|
cwd: string;
|
|
278
734
|
/** 当前项目的源文件夹;只授予读取,工作树隔离后为空。 */
|
|
279
735
|
projectRoots?: readonly string[];
|
|
@@ -288,13 +744,18 @@ export interface ModelContext {
|
|
|
288
744
|
id: string;
|
|
289
745
|
name: string;
|
|
290
746
|
};
|
|
291
|
-
/** 这个会话现在的权限模式(含项目覆盖)。
|
|
747
|
+
/** 这个会话现在的权限模式(含项目覆盖)。 */
|
|
292
748
|
permissionMode: PermissionMode;
|
|
293
749
|
/**
|
|
294
750
|
* 外层要求这一轮的命令都在只读沙箱里跑(计划模式,和 `ToolExecution.readOnly` 同一件事)。宿主不给;要设就挂在
|
|
295
|
-
* plume.prompt
|
|
751
|
+
* 点名 shell 的那一层(plume.prompt,order -1000)外面:宿主据此重算 `shell`。
|
|
296
752
|
*/
|
|
297
753
|
readOnly?: boolean;
|
|
754
|
+
/**
|
|
755
|
+
* 这一轮的命令跑在哪个 shell、哪一档沙箱(见 `ShellInfo`),和 `ToolRun.shell()` 是同一个,系统提示词按它点名 shell 和写法。
|
|
756
|
+
* 宿主按 `permissionMode` 和 `readOnly` 算,每层交接时重算:改它不作数,要收紧就设 `readOnly`。
|
|
757
|
+
*/
|
|
758
|
+
shell: ShellInfo;
|
|
298
759
|
/** 设置 › 个性化:语气和全局指令。 */
|
|
299
760
|
personalization: Personalization;
|
|
300
761
|
/** 发出去的系统提示词,就是 `sections` 依次用空行连起来。只读:每层交接时宿主按 `sections` 重拼,改它不作数。 */
|
|
@@ -319,6 +780,8 @@ export interface StepEnd {
|
|
|
319
780
|
nudge: string;
|
|
320
781
|
};
|
|
321
782
|
sessionId: string;
|
|
783
|
+
/** 这一步所在的会话,见 `SessionScope`。 */
|
|
784
|
+
session: SessionScope;
|
|
322
785
|
cwd: string;
|
|
323
786
|
/** 这一轮的第几步,从 1 数。 */
|
|
324
787
|
step: number;
|
|
@@ -348,6 +811,8 @@ export type StepVerdict = {
|
|
|
348
811
|
/** 钩子 prompt.submit 的输入:一轮开始时,人发来的这条消息还没写进会话。给的是副本,改了不影响会话。 */
|
|
349
812
|
export interface PromptSubmit {
|
|
350
813
|
sessionId: string;
|
|
814
|
+
/** 这条消息要进的会话,见 `SessionScope`。 */
|
|
815
|
+
session: SessionScope;
|
|
351
816
|
cwd: string;
|
|
352
817
|
/** 这条消息的文字(命令发出去的是展开后的那段)。 */
|
|
353
818
|
text: string;
|
|
@@ -358,8 +823,8 @@ export interface PromptSubmit {
|
|
|
358
823
|
/** 会话里在它之前一条消息都还没有:这是会话的第一轮。 */
|
|
359
824
|
fresh: boolean;
|
|
360
825
|
/**
|
|
361
|
-
*
|
|
362
|
-
*
|
|
826
|
+
* 这句话是插件发的,不是人在输入框里发的:经 `ctx.sessions.launch` 开出来的会话的第一句,或代管插件经 `ctx.sessions.post`
|
|
827
|
+
* 转进来的话。人为自己正在写的那条消息选的东西(输入框上的开关)不算到它头上。
|
|
363
828
|
*/
|
|
364
829
|
launched: boolean;
|
|
365
830
|
signal: AbortSignal;
|
|
@@ -387,6 +852,8 @@ export type PromptVerdict = {
|
|
|
387
852
|
*/
|
|
388
853
|
export interface ContextCompact {
|
|
389
854
|
sessionId: string;
|
|
855
|
+
/** 要压的会话,见 `SessionScope`。 */
|
|
856
|
+
session: SessionScope;
|
|
390
857
|
cwd: string;
|
|
391
858
|
/**
|
|
392
859
|
* 这个会话现在用的模型。`tierLines` 是它的价格分档线(一次请求的输入越过这么多 token,整次按高一档计),从低到高;
|
|
@@ -421,6 +888,17 @@ export interface ContextCompact {
|
|
|
421
888
|
messages: Message[];
|
|
422
889
|
tools: ToolSpec[];
|
|
423
890
|
};
|
|
891
|
+
/**
|
|
892
|
+
* `request` 有多满,本体按和上下文用量圆环同一套算法算好:最近一条落定回复服务商量的数(整份请求,含系统提示词和工具表)加上它之后
|
|
893
|
+
* 的消息的估算;`rewrittenAt` 之前(含)落定的回复不算。没有可用的时 `measured` 为假,`tokens` 是整份请求(系统提示词、工具表、
|
|
894
|
+
* 消息)的估算。
|
|
895
|
+
*/
|
|
896
|
+
usage: {
|
|
897
|
+
tokens: number;
|
|
898
|
+
measured: boolean;
|
|
899
|
+
};
|
|
900
|
+
/** 本体估 token 的那一份算法,量一截历史、一段文字有多大(决定从哪切、摘要请求放不放得下)时用,不要自己再写一份。 */
|
|
901
|
+
estimate: TokenEstimate;
|
|
424
902
|
/** 服务商说超长了,或人敲了 `/compact`:不管量出来多满都该压。 */
|
|
425
903
|
force: boolean;
|
|
426
904
|
/** 人敲的 `/compact`:`instructions` 是命令后面的话(希望摘要保留的内容,可以是空串)。 */
|
|
@@ -445,6 +923,17 @@ export interface ContextCompact {
|
|
|
445
923
|
/** 压缩走到哪一步了,界面上那一行跟着变:写摘要、等重试、摘要没拿到正在本地整理。 */
|
|
446
924
|
progress(event: CompactionProgress): void;
|
|
447
925
|
}
|
|
926
|
+
/**
|
|
927
|
+
* 本体按字数估 token,按文字分别计:ASCII 约 3.5 个字符一个,其余字符(中日韩文字为主)一个一个,图片按定数。各家分词器不同,
|
|
928
|
+
* 一类文本上差个两三成常见(满是数字的命令输出会低估到一半),所以能用服务商量的数(`ContextCompact.usage`)就用它,
|
|
929
|
+
* 估算拿来补位、分比例、按比例校准。估出来的不取整,逐条、逐段估的可以直接相加。
|
|
930
|
+
*/
|
|
931
|
+
export interface TokenEstimate {
|
|
932
|
+
messages(messages: readonly Message[]): number;
|
|
933
|
+
text(text: string): number;
|
|
934
|
+
/** 工具表:名字、说明、参数表。 */
|
|
935
|
+
tools(tools: readonly ToolSpec[]): number;
|
|
936
|
+
}
|
|
448
937
|
/** `ContextCompact.progress` 的一步。 */
|
|
449
938
|
export type CompactionProgress = {
|
|
450
939
|
phase: "summarizing";
|
|
@@ -491,11 +980,17 @@ export type CompactSummary = {
|
|
|
491
980
|
* 最里层照着传进来的 `request` 把审批卡弹给人、等人答。
|
|
492
981
|
*
|
|
493
982
|
* 权限模式的含义在本体,到不了这里:完全访问、tool.execute 外层设了 `approved`、`subject` 答过「以后不再问」的,本体直接放行。
|
|
494
|
-
* 提权(`request.escalation
|
|
495
|
-
*
|
|
983
|
+
* 提权(`request.escalation`)只能由人答:它在完全访问下也到这里,层答放行不算数、本体照样弹卡,层答拒绝照算。
|
|
984
|
+
* 弹出来的卡可以由人点,也可以由代管这个会话的插件经 `ctx.sessions.respond` 转达,那条路不经这里。
|
|
985
|
+
* 工具要人给答案(`ToolRun.request`)不是审批,不到这里。
|
|
986
|
+
* 内置插件 plume.approval 在里层(order 1000)按帮我批准(`auto`)的风险规则放行,命中规则的把理由写进卡片再交给里层;
|
|
987
|
+
* 要在它之前替人答、或整个换掉这套规则的层挂在外面(order 小于 1000);只管真要弹给人的那些(它放行的到不了里面,到了的
|
|
988
|
+
* 卡上带着它写的理由)的层挂在里面(order 大于 1000)。没有任何层时每一次都问人。
|
|
496
989
|
*/
|
|
497
990
|
export interface ToolApproval {
|
|
498
991
|
sessionId: string;
|
|
992
|
+
/** 调用所在的会话,见 `SessionScope`。 */
|
|
993
|
+
session: SessionScope;
|
|
499
994
|
cwd: string;
|
|
500
995
|
/** 这个会话能当草稿区用的临时目录,同 `ToolRun.scratchDirs`;帮我批准把写进、删掉这些里面的东西算作正当干活。 */
|
|
501
996
|
scratchDirs?: readonly string[];
|
|
@@ -510,28 +1005,11 @@ export interface ToolApproval {
|
|
|
510
1005
|
request: ApprovalContent;
|
|
511
1006
|
/** 是钩子 tool.execute 的某一层自己要问(`ToolExecution.ask`),不是工具问的:没有 `subject`,只能答这一次。 */
|
|
512
1007
|
byLayer?: boolean;
|
|
513
|
-
/**
|
|
1008
|
+
/** 这次调用所在项目现在的权限模式。完全访问只有提权会带到这里。 */
|
|
514
1009
|
permissionMode: PermissionMode;
|
|
515
1010
|
/** 用户停止这一轮时中止。 */
|
|
516
1011
|
signal: AbortSignal;
|
|
517
1012
|
}
|
|
518
|
-
/**
|
|
519
|
-
* 钩子 session.rewind 的输入:人撤回或编辑重发,要截掉从某条人发的消息起的那段对话。截断由宿主做,这里问的是那段里
|
|
520
|
-
* 插件记着的改动(文件改动等)能不能、要不要一起退回到截断点之前。
|
|
521
|
-
*
|
|
522
|
-
* 先以 `restore: false` 问一次,只演算:答那段改过哪些文件、各自能不能安全退回,界面据此问人。人选了连文件一起退,宿主在截断
|
|
523
|
-
* 之前以 `restore: true` 再问一次:重新校验、真的写回去;写不回去就抛错(message 原样给人看,按界面语言写),这时宿主不截对话。
|
|
524
|
-
* 写回要全有或全无:一个文件不能安全退回,就一个都不写,已经写了的退回原样再抛错。
|
|
525
|
-
* 只截对话、不动文件时不问。最里层答 `{ files: [], unrecorded: false }`;每一层把自己的结论并进 `next` 的结论里。
|
|
526
|
-
*/
|
|
527
|
-
export interface SessionRewind {
|
|
528
|
-
sessionId: string;
|
|
529
|
-
cwd: string;
|
|
530
|
-
/** 要截掉的那段:从被撤回、编辑的那条人发的消息起到最后,副本。 */
|
|
531
|
-
messages: Message[];
|
|
532
|
-
/** 真的写回去,还是只演算。 */
|
|
533
|
-
restore: boolean;
|
|
534
|
-
}
|
|
535
1013
|
/** 钩子名 → [输入, 输出]。 */
|
|
536
1014
|
export interface Hooks {
|
|
537
1015
|
"tool.execute": [ToolExecution, ToolOutput];
|
|
@@ -540,7 +1018,6 @@ export interface Hooks {
|
|
|
540
1018
|
"prompt.submit": [PromptSubmit, PromptVerdict];
|
|
541
1019
|
"context.compact": [ContextCompact, CompactSummary];
|
|
542
1020
|
"tool.approve": [ToolApproval, ApprovalDecision];
|
|
543
|
-
"session.rewind": [SessionRewind, RevertPlan];
|
|
544
1021
|
}
|
|
545
1022
|
/** 输入框 `/` 菜单里的一条命令。 */
|
|
546
1023
|
export interface SlashCommand {
|
|
@@ -739,6 +1216,25 @@ export interface ModelAccess {
|
|
|
739
1216
|
*/
|
|
740
1217
|
usage(): Promise<UsageScan>;
|
|
741
1218
|
}
|
|
1219
|
+
/**
|
|
1220
|
+
* 插件的 `ctx.secrets`:把要存的密钥(令牌、密码、请求头里的凭据)加密成一段文本,插件把它写回自己的配置文件。
|
|
1221
|
+
* AES-256-GCM,钥匙由本体从它给供应商 key 用的那把(`vault.key`)按插件名派生:各插件的密文互相解不开。
|
|
1222
|
+
*
|
|
1223
|
+
* 只管加解密,不替插件存:密文跟着它所属的那条配置走,删掉配置就是删掉密钥,不用另外清。
|
|
1224
|
+
* 挡的是配置文件被同步、拷走、贴进问题报告时带出明文,以及一个插件的密文被抄进另一个插件的配置后被它解开;
|
|
1225
|
+
* 挡不住同一台机器上同一个用户的程序——钥匙就在数据目录里,插件也都跑在同一个进程里。
|
|
1226
|
+
*/
|
|
1227
|
+
export interface PluginSecrets {
|
|
1228
|
+
/** 加密一个值,返回带固定前缀的文本(`isSealed` 认得出),能放进 JSON 之类的任何文本里。 */
|
|
1229
|
+
seal(value: string): Promise<string>;
|
|
1230
|
+
/**
|
|
1231
|
+
* 解回来。不是 `seal` 出来的、不是这个插件封的、或者这把钥匙打不开(钥匙丢了换了新的、密文来自别的机器)时是 null,当成「要重新填」。
|
|
1232
|
+
* 钥匙文件在却读不出来时抛错:这时不能当成「没了」,否则调用方把空值写回去,等钥匙又读得出来时已经没东西可解了。
|
|
1233
|
+
*/
|
|
1234
|
+
unseal(sealed: string): Promise<string | null>;
|
|
1235
|
+
/** 这段文本是不是 `seal` 出来的样子:手写进配置的明文和密文分得开。 */
|
|
1236
|
+
isSealed(value: string): boolean;
|
|
1237
|
+
}
|
|
742
1238
|
/**
|
|
743
1239
|
* 会话的媒体目录:会话里的图片挪出消息后按内容(sha1)存在这里,消息里只留名字(`ImageContent.media`)。
|
|
744
1240
|
* 存进去的名字和会话存图时起的一样,同一张图不会存两份。
|
|
@@ -773,11 +1269,8 @@ export interface SessionRun {
|
|
|
773
1269
|
export interface ReportOptions {
|
|
774
1270
|
/** 和本插件同一次送达里别的几份合成一段,见 `ReportFrame`。 */
|
|
775
1271
|
frame?: ReportFrame;
|
|
776
|
-
/**
|
|
777
|
-
|
|
778
|
-
* `ui.agentDelivery` 画(宿主记下是哪个插件交的)。不给就只有模型看得到。
|
|
779
|
-
*/
|
|
780
|
-
delivery?: Omit<DeliveredReport, "kind" | "command" | "exitCode" | "monitor" | "plugin">;
|
|
1272
|
+
/** 给界面把这份结果画成对话里的一行(「消息来自 @explore」),不是一个人发的气泡,见 `DeliveryInput`。不给就只有模型看得到。 */
|
|
1273
|
+
delivery?: DeliveryInput;
|
|
781
1274
|
}
|
|
782
1275
|
/**
|
|
783
1276
|
* `ReportOptions.frame`。几份结果落在同一次送达里时,同一个插件带 `frame` 的合成一段:`head` 写一次,
|
|
@@ -827,22 +1320,31 @@ export interface SessionLaunch {
|
|
|
827
1320
|
};
|
|
828
1321
|
/** 标题;不给就取第一句话的开头。 */
|
|
829
1322
|
title?: string;
|
|
1323
|
+
/**
|
|
1324
|
+
* 开出来的会话由本插件代管(记进摘要的 `steward`):之后本插件能经 `post` 往里转话、经 `respond`、`answer` 替人答它和它下面的子会话
|
|
1325
|
+
* 弹的卡,直到人在会话菜单里停止代管。会话照旧是人的顶层会话:进侧栏,人能接着聊,权限照这个会话的设置。
|
|
1326
|
+
*/
|
|
1327
|
+
steward?: boolean;
|
|
830
1328
|
}
|
|
831
1329
|
/** `SessionAccess.launch` 开出来的会话。 */
|
|
832
1330
|
export interface LaunchedSession {
|
|
833
1331
|
session: SessionSummary;
|
|
834
1332
|
/** 第一轮结束时给出它为什么结束。 */
|
|
835
1333
|
done: Promise<TurnStop>;
|
|
836
|
-
/**
|
|
1334
|
+
/**
|
|
1335
|
+
* 它第一次真闲下来时兑现,见 `SessionAccess.launch`;带上那时的收场和最后一段回复,交回用它。还在等一次性的后台命令时不兑现;
|
|
1336
|
+
* 挂着的监视、认出来的开发服务器、约着还没触发的唤醒不挡它(见 `ParentStatus`),之后它们叫醒会话做的几轮不算进这一次。
|
|
1337
|
+
*/
|
|
837
1338
|
idle: Promise<LaunchedIdle>;
|
|
838
1339
|
/**
|
|
839
1340
|
* 把它做出来的结果交回开出它的那个会话(`spawnedFrom`),只能交一次:`text` 是写给模型的话,作为一条运行时送达写进那个会话
|
|
840
|
-
*
|
|
1341
|
+
* (前面带着「不是用户说的话」的抬头);`delivery` 是对话里那一行(见 `DeliveryInput`),宿主给它填上这个会话的 id
|
|
1342
|
+
* (`DeliveredReport.sessionId`),默认画成「来自新会话 · 标题」,点了到这个会话。
|
|
841
1343
|
* 那个会话闲着(人按停过也算,这是一件新的事)就为它开一轮,忙着在两步之间读到。
|
|
842
1344
|
* 没有 `spawnedFrom`、那个会话删了或归档了、已经交过、正在退出时不送,返回 false。
|
|
843
1345
|
* `spawnedFrom.toolCallId` 不是本插件的工具在那个会话里的一次调用(或撤回时被截掉了)、插件已经停用时抛错。
|
|
844
1346
|
*/
|
|
845
|
-
reportBack(text: string,
|
|
1347
|
+
reportBack(text: string, delivery: DeliveryInput): boolean;
|
|
846
1348
|
}
|
|
847
1349
|
/** `LaunchedSession.idle` 兑现时的样子。 */
|
|
848
1350
|
export interface LaunchedIdle {
|
|
@@ -857,6 +1359,8 @@ export type SessionEndReason = "deleted" | "archived" | "moved" | "quit";
|
|
|
857
1359
|
* 子会话复用产品的循环、审批、存储和事件,插件只能运行自己拥有的会话。
|
|
858
1360
|
* 不开放任意向用户会话发送消息的能力:插件够得着的会话都从宿主给的句柄来,目标不由插件随手填。
|
|
859
1361
|
* - 新开的会话:`launch` 开一个新的顶层会话、发出第一句话,之后它就是人的会话,插件不能再往里发;
|
|
1362
|
+
* - 代管的会话:`launch({ steward: true })` 开出来的那些,人没停止代管时,开它的插件能经 `post` 往里转话、经 `respond`、`answer`
|
|
1363
|
+
* 替人答它和挂在它下面的子会话弹的卡;别的会话(人已有的、别的插件代管的)不行,也不能事后认领;
|
|
860
1364
|
* - 开出它的那个会话:`LaunchedSession.reportBack` 交回一次,来处要是本插件的工具在那里的一次调用;
|
|
861
1365
|
* - 调用所在的会话:`ToolRun.background` 送后台结果、`ToolRun.archiveAfterTurn` 收尾后归档;
|
|
862
1366
|
* - 子会话的父会话:插件自己建的子会话里,它注册的工具的那次调用拿得到 `ToolRun.parent`(排活、停下、接着做),
|
|
@@ -867,12 +1371,28 @@ export type SessionEndReason = "deleted" | "archived" | "moved" | "quit";
|
|
|
867
1371
|
* 身份和工具限制由插件经 model.context、tool.execute 管理。
|
|
868
1372
|
*/
|
|
869
1373
|
export interface SessionAccess {
|
|
870
|
-
/**
|
|
1374
|
+
/**
|
|
1375
|
+
* 只读观察请求、工具开始、审批、重试和运行状态;副本不含配置密钥,停用时自动撤下。
|
|
1376
|
+
* 只收到和本插件有关的会话的:本插件建的(`owner` 是它的子会话、独立会话),本插件代管的会话和挂在它下面的子会话,
|
|
1377
|
+
* 本插件经 `launch` 开出、还没兑现 `idle` 的会话。要看全部会话的(诊断轨迹、按每个会话的运行状态做事)在 `Plugin.observe` 声明 `"all"`。
|
|
1378
|
+
*/
|
|
871
1379
|
observe(listener: (observation: SessionObservation) => void): void;
|
|
872
|
-
/**
|
|
1380
|
+
/** 全部会话的摘要(含归档的、子会话),最近活动的在前。只为找一个会话时用 `get`。 */
|
|
873
1381
|
list(): Promise<SessionSummary[]>;
|
|
1382
|
+
/** 一个会话的摘要;没有这个会话(删掉了、id 不对)时是 undefined。给钩子之外拿不到 `SessionScope` 的地方(`serve` 的方法、事件回调)用。 */
|
|
1383
|
+
get(sessionId: string): Promise<SessionSummary | undefined>;
|
|
874
1384
|
/** 一个会话存下的全部消息,是会话里的原文,不受压缩影响。没有这个会话时抛错。 */
|
|
875
1385
|
messages(sessionId: string): Promise<Message[]>;
|
|
1386
|
+
/** 会话里登记过的后台任务(`ToolRun.track`),结束了的也在,按登记的先后;没有这个会话、没登记过时是空数组。 */
|
|
1387
|
+
jobs(sessionId: string): BackgroundJobInfo[];
|
|
1388
|
+
/** 叫一项后台任务停下(`force` 是强杀);没有这一项、已经结束时返回 false,停不掉时抛错。 */
|
|
1389
|
+
stopJob(sessionId: string, jobId: string, force: boolean): boolean;
|
|
1390
|
+
/** 一项后台任务的尾部输出,见 `BackgroundJobOutput`;没有这一项时抛错。 */
|
|
1391
|
+
jobOutput(sessionId: string, jobId: string): Promise<BackgroundJobOutput>;
|
|
1392
|
+
/** 会话里还没触发的唤醒(`ToolRun.schedule`),按下一次触发的先后;没有时是空数组。 */
|
|
1393
|
+
wakeups(sessionId: string): SessionWakeup[];
|
|
1394
|
+
/** 取消一个还没触发的唤醒;已经触发了、不存在时返回 false。 */
|
|
1395
|
+
cancelWakeup(sessionId: string, id: string): boolean;
|
|
876
1396
|
/**
|
|
877
1397
|
* 现在就压缩这个会话(人敲了 `/compact`):照常经钩子 context.compact(带 `force` 和 `manual`),转录里画一行
|
|
878
1398
|
* `/compact` 和它的进度、结果。任何会话都行(压缩不替人说话,只改模型看到的历史)。会话正在跑、正在压、没有可用的模型、
|
|
@@ -932,18 +1452,50 @@ export interface SessionAccess {
|
|
|
932
1452
|
}): Promise<SessionSummary>;
|
|
933
1453
|
/**
|
|
934
1454
|
* 开一个新会话并发出第一句话(定时任务到点了、人在插件界面上点了「在新会话里执行」)。新会话和人在侧栏新建的一样:
|
|
935
|
-
* 进侧栏、不记在插件名下(没有 `owner
|
|
1455
|
+
* 进侧栏、不记在插件名下(没有 `owner`),人能接着聊,插件之后不能再往里发、也停不了删不了它(`steward` 开出来的除外:
|
|
1456
|
+
* 本插件能往里转话、替人答卡,见 `SessionLaunch.steward`)。第一句话照常经
|
|
936
1457
|
* `prompt.submit`,可能被拦下(这一轮按完成收场)。只能开新的;对已有会话只有 `reportBack` 往开出它的那个会话交回一次。
|
|
937
1458
|
* 建好就返回:`session` 是新会话的摘要,`done` 在这第一轮结束时给出它为什么结束(之后人接着聊的几轮不算)。
|
|
938
|
-
* `idle`
|
|
939
|
-
*
|
|
940
|
-
*
|
|
1459
|
+
* `idle` 在它第一次真闲下来时兑现:第一轮结束,它派出去的后台运行都回来了、送回的结果唤起的几轮也做完了,登记着的一次性后台任务
|
|
1460
|
+
* 都结束了(被人停掉、撤回而作废的不算在等;挂着的监视、开发服务器、约好的唤醒和 cron 不等,见 `ParentStatus`),它经 `ToolRun.letter`
|
|
1461
|
+
* 问出去的请求(`request: true`)也都有了答复、答复唤起的几轮也做完了。请求总会有答复(见 `LetterReceipt`):对方答了,
|
|
1462
|
+
* 或者本体在对方没答时代答。
|
|
941
1463
|
* 项目、目录、模型、思考档位、`spawnedFrom` 不对时抛错,message 是给人看的话(按界面语言)。
|
|
942
1464
|
*/
|
|
943
1465
|
launch(options: SessionLaunch): Promise<LaunchedSession>;
|
|
1466
|
+
/**
|
|
1467
|
+
* 往本插件代管的会话里转一句话(外面的人经插件发来的消息):排进这个会话的队(和人在输入框里排的同一条,落盘),
|
|
1468
|
+
* 会话闲着就当场开一轮,正在跑就排着,不插进这一轮。气泡标着来自本插件(`UserMessage.origin` 是 `plugin`,`fromPlugin` 是本插件),
|
|
1469
|
+
* 模型读到的前面多一句本体写的来处说明:这是代管插件转来的,不是人在 Plume 的输入框里打的。权限、审批照这个会话的设置,不因来处而变。
|
|
1470
|
+
* `started` 是排上那一刻它就开始了。会话不在、不是本插件代管的(没代管过、人停止了代管)、是子会话(子会话里的一轮归建它的插件发起)、
|
|
1471
|
+
* 已归档、话为空时抛错。
|
|
1472
|
+
* 回复和收场经 `observe`(`response`、`turnEnd`)和 `messages` 读;哪些转出去由插件自己定。
|
|
1473
|
+
*/
|
|
1474
|
+
post(sessionId: string, message: string): Promise<{
|
|
1475
|
+
started: boolean;
|
|
1476
|
+
}>;
|
|
1477
|
+
/**
|
|
1478
|
+
* 替人答本插件代管的会话里弹着的那张审批卡(`approval` 事件、`SessionView.approval` 里第一张,按 `toolCallId` 认):`once` 放行这一次,
|
|
1479
|
+
* `reject` 拒绝;对等人给数据的卡(`HumanRequest`)`reject` 是不答。和人在桌面上点卡同一条路,提权也算:人和插件谁先答算谁的,
|
|
1480
|
+
* 另一边的卡随之收起。「以后不再问」改的是人的设置,插件答不了。卡上的东西怎么转给外面的人、答的人是不是该答的人,由插件负责确认。
|
|
1481
|
+
* 挂在代管会话下面的子会话(模型派的子智能体、人开的侧边聊天,不管是哪个插件建的)弹的卡也能这样答,`sessionId` 传子会话的 id
|
|
1482
|
+
* (`approval` 事件带的那个);看的是父会话此刻是不是本插件代管着。
|
|
1483
|
+
* 答上了返回 true;那张卡已经不在(答过了、过期了、这一轮停了)、不是这一张时返回 false。会话不是本插件代管的(子会话:父会话不是)时抛错,
|
|
1484
|
+
* 人停止代管以后也抛错:那时还弹着的卡只能人答。
|
|
1485
|
+
* 这次调用的结果里记着是哪个插件代答的(`ToolResultMessage.answeredBy`),模型读到的结果后面也写明这一点。
|
|
1486
|
+
*/
|
|
1487
|
+
respond(sessionId: string, toolCallId: string, decision: "once" | "reject"): Promise<boolean>;
|
|
1488
|
+
/**
|
|
1489
|
+
* 替人交回本插件代管的会话(或它下面的子会话)里弹着的那张等人给数据的卡(`ToolRun.request`:提问、表单、计划)要的数据,同 `AgentService.answer`。
|
|
1490
|
+
* `value` 由工具自己核。能答哪些会话、返回值、抛错和记下代答者同 `respond`。
|
|
1491
|
+
*/
|
|
1492
|
+
answer(sessionId: string, toolCallId: string, value: unknown): Promise<boolean>;
|
|
944
1493
|
/** 可用模型目录,只含名称、窗口和思考档位。 */
|
|
945
1494
|
models(): Promise<SessionModel[]>;
|
|
946
|
-
/**
|
|
1495
|
+
/**
|
|
1496
|
+
* 开始自己的子会话的一轮,立即交回句柄;首次运行固定实际模型与思考档位,续跑沿用。父轮正常结束不取消,明确停止父会话则取消。
|
|
1497
|
+
* 这一次运行结束时,本体停掉这个子会话还在跑的后台任务(`ToolRun.track`):子会话收不到后台送达,留下来就没人管了。
|
|
1498
|
+
*/
|
|
947
1499
|
start(sessionId: string, message: string | SessionMessage): Promise<SessionRun>;
|
|
948
1500
|
/** 等待自己的后台运行结束,失败和停止也正常交回原因。 */
|
|
949
1501
|
wait(runId: string): Promise<TurnStop>;
|
|
@@ -1215,6 +1767,8 @@ export interface PluginContext {
|
|
|
1215
1767
|
register(tool: ToolDefinition): () => void;
|
|
1216
1768
|
/** 现在登记着的全部工具名(各插件的,同名只算一个),按登记先后。给要让人挑工具的插件用。 */
|
|
1217
1769
|
names(): string[];
|
|
1770
|
+
/** 同 `ToolRun.tools()`:生效的工具、注册它的插件、它声明的类别。在钩子里按类别找工具(「记清单的那个叫什么」)时用。 */
|
|
1771
|
+
list(): RegisteredToolInfo[];
|
|
1218
1772
|
};
|
|
1219
1773
|
/** order 小的在外层,默认 0;相同时按注册先后。 */
|
|
1220
1774
|
hook<N extends HookName>(name: N, layer: HookLayer<N>, options?: {
|
|
@@ -1233,6 +1787,14 @@ export interface PluginContext {
|
|
|
1233
1787
|
readonly model: ModelAccess;
|
|
1234
1788
|
/** 读写会话的媒体目录,见 `MediaAccess`。 */
|
|
1235
1789
|
readonly media: MediaAccess;
|
|
1790
|
+
/** 把插件要存的密钥加密成一段文本、再解回来,见 `PluginSecrets`。本体提供,别的插件换不掉。 */
|
|
1791
|
+
readonly secrets: PluginSecrets;
|
|
1792
|
+
/** 资源地址(`scheme://路径`):登记自己的 scheme、存大块原文换地址,见 `PluginResources`。本体提供,别的插件换不掉。 */
|
|
1793
|
+
readonly resources: PluginResources;
|
|
1794
|
+
/** 在宿主上读工作区仓库,不执行仓库能控制的程序,见 `PluginWorkspace`。本体提供,别的插件换不掉。 */
|
|
1795
|
+
readonly workspace: PluginWorkspace;
|
|
1796
|
+
/** 本体记下的改动快照汇总和撤销,见 `PluginChanges`。本体提供,别的插件换不掉。 */
|
|
1797
|
+
readonly changes: PluginChanges;
|
|
1236
1798
|
/** 读会话,见 `SessionAccess`。 */
|
|
1237
1799
|
readonly sessions: SessionAccess;
|
|
1238
1800
|
/** 驱动界面入口画的网页(`<webview>`),见 `WebPages`。 */
|
|
@@ -1310,43 +1872,19 @@ export interface InstalledPlugin {
|
|
|
1310
1872
|
* 仓库外的插件给自己的服务用声明合并加一项:`declare module "@plume/contract" { interface Services { … } }`。
|
|
1311
1873
|
*/
|
|
1312
1874
|
export interface Services {
|
|
1313
|
-
/** 与内置文件工具共用读取授权、项目根和会话批准记录。 */
|
|
1314
|
-
"file.read": {
|
|
1315
|
-
authorize(run: ToolRun, path: string): Promise<{
|
|
1316
|
-
ok: true;
|
|
1317
|
-
absolute: string;
|
|
1318
|
-
} | {
|
|
1319
|
-
ok: false;
|
|
1320
|
-
message: string;
|
|
1321
|
-
}>;
|
|
1322
|
-
};
|
|
1323
|
-
"background.jobs": BackgroundJobsService;
|
|
1324
|
-
"session.wakeups": SessionWakeupsService;
|
|
1325
|
-
sandbox: SandboxService;
|
|
1326
|
-
"file.changes": FileChangesService;
|
|
1327
|
-
"project.files": ProjectFilesService;
|
|
1328
|
-
resources: ResourcesService;
|
|
1329
1875
|
"session.worktree": SessionWorktreeService;
|
|
1330
|
-
|
|
1876
|
+
"session.plan": SessionPlanService;
|
|
1331
1877
|
}
|
|
1332
1878
|
/**
|
|
1333
|
-
* 服务 `
|
|
1334
|
-
*
|
|
1335
|
-
*
|
|
1336
|
-
* 只管加解密,不替插件存:密文跟着它所属的那条配置走,删掉配置就是删掉密钥,不用另外清。
|
|
1337
|
-
* 挡的是配置文件被同步、拷走、贴进问题报告时带出明文;挡不住同一台机器上同一个用户的程序——插件都跑在同一个进程里,
|
|
1338
|
-
* 谁拿到服务都解得开谁的密文。
|
|
1879
|
+
* 服务 `session.plan`:一个会话照着做的、人批准过的计划。内置插件 plume.plan 提供。
|
|
1880
|
+
* 要把「这段活原本批的是什么」带到别处(审阅这个会话的改动)时用它,不读交计划那次调用的结果。
|
|
1339
1881
|
*/
|
|
1340
|
-
export interface
|
|
1341
|
-
/** 加密一个值,返回带固定前缀的文本(`isSealed` 认得出),能放进 JSON 之类的任何文本里。 */
|
|
1342
|
-
seal(value: string): Promise<string>;
|
|
1882
|
+
export interface SessionPlanService {
|
|
1343
1883
|
/**
|
|
1344
|
-
*
|
|
1345
|
-
*
|
|
1884
|
+
* 这个会话照着做的那份计划:它自己最近批准、没交给新会话执行的那份;没有的话,它是计划「在新会话里执行」开出来的,
|
|
1885
|
+
* 就是交给它的那份。正文连同批准时附上的注意事项,和交给模型的一样。没有是 null;没有这个会话时抛错。
|
|
1346
1886
|
*/
|
|
1347
|
-
|
|
1348
|
-
/** 这段文本是不是 `seal` 出来的样子:手写进配置的明文和密文分得开。 */
|
|
1349
|
-
isSealed(value: string): boolean;
|
|
1887
|
+
approved(sessionId: string): Promise<string | null>;
|
|
1350
1888
|
}
|
|
1351
1889
|
/**
|
|
1352
1890
|
* 服务 `session.worktree`:让开出来的新会话在它自己的 git 工作树和分支里跑,不动项目的检出。内置插件 plume.git 提供。
|
|
@@ -1364,16 +1902,41 @@ export interface SessionWorktreeService {
|
|
|
1364
1902
|
toolCallId: string;
|
|
1365
1903
|
}): () => void;
|
|
1366
1904
|
}
|
|
1367
|
-
/**
|
|
1368
|
-
|
|
1905
|
+
/**
|
|
1906
|
+
* `ctx.workspace`:在宿主进程里读工作区仓库。工作区是沙箱可写的,仓库的配置(`.git/config`、`.gitattributes`)也是:直接跑 git 会替沙箱里的命令
|
|
1907
|
+
* 执行它写进去的程序(`core.fsmonitor`、过滤器、钩子),所以本体在一个读不到仓库配置的隔离视图里问 git。插件要列项目文件就用它,不自己跑 `git ls-files`。
|
|
1908
|
+
*/
|
|
1909
|
+
export interface PluginWorkspace {
|
|
1369
1910
|
/**
|
|
1370
|
-
* `root` 之下 git
|
|
1371
|
-
* 不在 git 仓库里、或 git 答不上来时是 null
|
|
1911
|
+
* `root` 之下 git 认为该看的文件(已跟踪且还在磁盘上的,加上未跟踪且没被忽略的;隐藏的也算),绝对路径,以 `root` 开头。
|
|
1912
|
+
* 不在 git 仓库里、或 git 答不上来时是 null,由调用方自己走目录。不问人:要不要读 `root` 由调用方先经 `ToolRun.files.read` 判。
|
|
1372
1913
|
*/
|
|
1373
|
-
|
|
1914
|
+
files(root: string): Promise<string[] | null>;
|
|
1374
1915
|
}
|
|
1375
|
-
/**
|
|
1916
|
+
/**
|
|
1917
|
+
* 沙箱的模式:只读、能写项目目录、不设限。权限模式对应哪一档由本体定:请求批准只读,帮我批准能写项目目录和临时目录,
|
|
1918
|
+
* 完全访问不设限;`ToolExecution.readOnly` 一律只读。macOS 用 seatbelt,Windows 用受限令牌,Linux 没有后端。
|
|
1919
|
+
*/
|
|
1376
1920
|
export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access";
|
|
1921
|
+
/** 命令跑在哪个 shell、哪一档沙箱,见 `ToolRun.shell()`、`ModelContext.shell`。 */
|
|
1922
|
+
export interface ShellInfo {
|
|
1923
|
+
/** 命令行按什么语法写。Git Bash 是 `posix`。 */
|
|
1924
|
+
kind: "posix" | "powershell";
|
|
1925
|
+
/** 告诉模型、告诉人这是哪个 shell 时用的名字(「zsh」「Git Bash」「Windows PowerShell 5.1」)。 */
|
|
1926
|
+
label: string;
|
|
1927
|
+
/** 会话宣布的那一档。Windows 上受约束的命令跑在 PowerShell 里,完全访问跑在 Git Bash 里,别的平台每档都是同一个 shell。 */
|
|
1928
|
+
sandbox: SandboxMode;
|
|
1929
|
+
}
|
|
1930
|
+
/** `ToolRun.exec` 起的一条命令。 */
|
|
1931
|
+
export interface RunProcess extends SandboxProcess {
|
|
1932
|
+
/** 实际跑在哪一档(提权批过的就是更宽的那一档)。 */
|
|
1933
|
+
readonly mode: SandboxMode;
|
|
1934
|
+
/**
|
|
1935
|
+
* 这段输出像不像被沙箱拒了(只读的文件系统、没有权限):像的话交回给模型的那句说明(说出实际跑的那一档、说清这是策略拒绝而不是命令出错),
|
|
1936
|
+
* 原样放进结果,模型可以申请提权重试;不像交回 undefined。`danger-full-access` 下总是 undefined。
|
|
1937
|
+
*/
|
|
1938
|
+
denied(output: string): string | undefined;
|
|
1939
|
+
}
|
|
1377
1940
|
/** 一条已经启动的命令。 */
|
|
1378
1941
|
export interface SandboxProcess {
|
|
1379
1942
|
readonly pid?: number;
|
|
@@ -1389,25 +1952,6 @@ export interface SandboxProcess {
|
|
|
1389
1952
|
/** 连它起的子进程一起停。 */
|
|
1390
1953
|
kill(signal?: "SIGTERM" | "SIGKILL"): void;
|
|
1391
1954
|
}
|
|
1392
|
-
/** 服务 `sandbox`:在沙箱里跑 shell 命令。内置插件 plume.sandbox 提供(macOS 用 seatbelt,Windows 用受限令牌;Linux 没有后端)。 */
|
|
1393
|
-
export interface SandboxService {
|
|
1394
|
-
/**
|
|
1395
|
-
* 用用户的 shell 起一条命令,环境向登录 shell 要(从图标启动时也找得到用户装的工具)。`mode` 不给就不进沙箱;
|
|
1396
|
-
* 给了而这台机器给不了那样的沙箱时抛错。
|
|
1397
|
-
*
|
|
1398
|
-
* `shellMode` 是按哪一档选 shell:会话宣布的模式,系统提示词里告诉模型的 shell 就是按它选的;不给时按 `mode`。
|
|
1399
|
-
* 只在 Windows 上有区别(受约束的跑 PowerShell,完全访问跑 Git Bash):提权的命令传更宽的 `mode` 和原来的 `shellMode`,
|
|
1400
|
-
* 它仍是照被告知的那个 shell 写的。
|
|
1401
|
-
*/
|
|
1402
|
-
run(command: string, options: {
|
|
1403
|
-
cwd: string;
|
|
1404
|
-
mode?: SandboxMode;
|
|
1405
|
-
shellMode?: SandboxMode;
|
|
1406
|
-
env?: Record<string, string>;
|
|
1407
|
-
}): SandboxProcess;
|
|
1408
|
-
/** 这段输出像不像被沙箱拒了(只读的文件系统、没有权限):像的话模型可以申请提权重试。 */
|
|
1409
|
-
looksDenied(output: string): boolean;
|
|
1410
|
-
}
|
|
1411
1955
|
/** 逻辑入口 `dist/index.mjs` 的默认导出。 */
|
|
1412
1956
|
export interface Plugin {
|
|
1413
1957
|
name: string;
|
|
@@ -1416,5 +1960,11 @@ export interface Plugin {
|
|
|
1416
1960
|
* 提供方换了或停了,插件先停用,再按现在的提供方重新加载(缺了就回到等待)。
|
|
1417
1961
|
*/
|
|
1418
1962
|
inject?: readonly (keyof Services)[];
|
|
1963
|
+
/**
|
|
1964
|
+
* `"all"`:`ctx.sessions.observe` 收全部会话的观察(每个会话的模型请求和回复、弹着的卡、运行状态),不只和本插件有关的那些
|
|
1965
|
+
* (见 `SessionAccess.observe`)。诊断轨迹、一轮收场后跑用户的钩子、给每个会话写摘要这类插件要它;插件市场的详情页对装着的插件标出这一条。
|
|
1966
|
+
* 不写就是只看和自己有关的会话。内置插件也一样要写。
|
|
1967
|
+
*/
|
|
1968
|
+
observe?: "all";
|
|
1419
1969
|
apply(ctx: PluginContext): void | Promise<void>;
|
|
1420
1970
|
}
|