@capybara-hq/plugin-sdk 0.1.0-rc.8 → 0.1.0-rc.9
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/agent-docs/extension-points/sandbox.md +2 -2
- package/agent-docs/extensions.md +1 -1
- package/dist/ctx.d.ts +8 -3
- package/dist/define-extension.d.ts +5 -1
- package/dist/define-extension.js +1 -0
- package/dist/device-protocol.d.ts +1 -1
- package/dist/device-protocol.js +1 -1
- package/dist/extension-point.js +1 -1
- package/dist/file-preview.d.ts +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/permissions.d.ts +9 -4
- package/dist/permissions.js +38 -15
- package/dist/sandbox.d.ts +9 -9
- package/dist/sandbox.js +3 -3
- package/dist/tool.d.ts +3 -3
- package/dist/ui-resolve.d.ts +40 -0
- package/dist/ui-resolve.js +54 -0
- package/dist/ui-slots.d.ts +43 -0
- package/dist/ui-slots.js +168 -0
- package/dist/ui.d.ts +114 -39
- package/dist/ui.js +0 -4
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- generated:extension-point -->
|
|
4
4
|
- Name: `sandbox` (a ref is `sandbox@<major>`)
|
|
5
|
-
- Runs
|
|
5
|
+
- Runs a device's instance and keeps its files for agents' commands (Docker, E2B).
|
|
6
6
|
- Providers: one at a time
|
|
7
7
|
- Enabled per org: no
|
|
8
8
|
- Brokered by the platform: yes (extensions use it only through `ctx`)
|
|
@@ -10,6 +10,6 @@
|
|
|
10
10
|
|
|
11
11
|
Contract: SandboxProvider (`sandbox.ts`)
|
|
12
12
|
|
|
13
|
-
The kernel accepts only `sandbox@2`.
|
|
13
|
+
The kernel accepts only `sandbox@2`. A device is one instance plus a file system that outlives it. Implement the file system (`createFileSystem`, `destroyFileSystem`, `destroyFileSystemByName`), the instance on it (`create`, which throws `FileSystemGoneError` when the file system is gone instead of making an empty one; `resume`, `suspend`; `destroy` and `destroyByName`, which drop the instance and keep the files), and the instance's `exec`, `readFile`, `writeFile`, `stat`, `killAll` and `killTagged` (`concurrentExec` is required: several conversations share one instance). Aborting a command must kill it. Locking, suspension timing and destruction are the kernel's job, not yours. Never publish a docker socket proxy on a host address.
|
|
14
14
|
|
|
15
15
|
See `extensions` for the rules every extension follows.
|
package/agent-docs/extensions.md
CHANGED
|
@@ -44,7 +44,7 @@ look like `channel@1`: name and major version. The registered points:
|
|
|
44
44
|
<!-- generated:extension-points -->
|
|
45
45
|
| Extension point | Providers | Per org | What it is |
|
|
46
46
|
|---|---|---|---|
|
|
47
|
-
| `sandbox` | single | no | Runs
|
|
47
|
+
| `sandbox` | single | no | Runs a device's instance and keeps its files for agents' commands (Docker, E2B). |
|
|
48
48
|
| `config-source` | single | no | Supplies deployment configuration from an external store such as Nacos. |
|
|
49
49
|
| `secret-store` | single | no | Stores and resolves secrets (connector credentials, secret config) outside the database. |
|
|
50
50
|
| `object-store` | single | no | Stores files and artifacts in S3-compatible object storage, with presigned uploads. |
|
package/dist/ctx.d.ts
CHANGED
|
@@ -198,15 +198,20 @@ export interface ExtensionContext {
|
|
|
198
198
|
* 界面贡献的运行时(清单 `ui` + `permissions.ui`):
|
|
199
199
|
* - `view` / `action`:只能在 `setup` 期间注册,id 是清单里 kind view 的贡献 id;
|
|
200
200
|
* `action` 收到视图里按钮或表单提交(`args.actionId` 区分是哪一个);
|
|
201
|
-
* - `invalidate`:告诉内核「这个贡献变了」,打开着它的网页会重新取视图(经 bus,跨副本);
|
|
201
|
+
* - `invalidate`:告诉内核「这个贡献变了」,打开着它的网页会重新取视图(经 bus,跨副本);a
|
|
202
|
+
* `platform.page` contribution takes no `orgId` (it refreshes the admin console), every other
|
|
203
|
+
* contribution requires one;
|
|
202
204
|
* - `verifyEmbedToken`:校验 embed 页面拿到的嵌入令牌,只认签给本插件、未过期的,且此刻插件仍在该组织启用、
|
|
203
|
-
* 调用者仍是 active
|
|
205
|
+
* 调用者仍是 active 成员,角色仍满足位置的 minRole,否则返回 null;返回的 `role` 是此刻的角色。A
|
|
206
|
+
* platform-scope token (`scope: "platform"`, no `orgId`) verifies only for a `platform.page`
|
|
207
|
+
* contribution while the account is still an active super admin.
|
|
204
208
|
*/
|
|
205
209
|
ui: {
|
|
206
210
|
view(id: string, render: (args: UiRenderArgs) => Promise<UiView>): void;
|
|
207
211
|
action(id: string, handler: (args: UiActionArgs) => Promise<UiActionResult>): void;
|
|
208
212
|
invalidate(target: {
|
|
209
|
-
|
|
213
|
+
/** Required, except for a `platform.page` contribution, where it must be omitted. */
|
|
214
|
+
orgId?: string;
|
|
210
215
|
conversationId?: string;
|
|
211
216
|
contribution: string;
|
|
212
217
|
}): Promise<void>;
|
|
@@ -14,7 +14,11 @@ export interface ExtensionManifest {
|
|
|
14
14
|
optional?: ExtensionPointRef[];
|
|
15
15
|
/** 权限清单(治理而非沙箱)。 */
|
|
16
16
|
permissions?: ExtensionPermissions;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* UI contributions, each in a slot of `UI_SLOTS`; every id is also listed in `permissions.ui`.
|
|
19
|
+
* Kept exactly as written: structural problems fail the extension, while a contribution to an
|
|
20
|
+
* unknown slot or version is only dropped when the host resolves the slots (`resolveUiSlots`).
|
|
21
|
+
*/
|
|
18
22
|
ui?: UiContribution[];
|
|
19
23
|
config?: {
|
|
20
24
|
deployment?: z.ZodTypeAny;
|
package/dist/define-extension.js
CHANGED
|
@@ -78,6 +78,7 @@ export function defineExtension(m) {
|
|
|
78
78
|
if (errs.length > 0)
|
|
79
79
|
fail(`invalid permissions: ${errs.join("; ")}`);
|
|
80
80
|
}
|
|
81
|
+
// structural ui problems only; slot resolution never fails the extension (UI slots spec §3.2)
|
|
81
82
|
const uiErrs = validateUiContributions(m.ui, m.permissions);
|
|
82
83
|
if (uiErrs.length > 0)
|
|
83
84
|
fail(`invalid ui: ${uiErrs.join("; ")}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Device protocol v1: the WebSocket frames between the kernel and a `capybara-device` program
|
|
3
|
-
* (a connected machine that serves as
|
|
3
|
+
* (a connected machine that serves as a device). Pure types and hand-written guards.
|
|
4
4
|
*/
|
|
5
5
|
import { type ExecResult, type SandboxActor, type SandboxLimits, type SandboxRoots } from "./sandbox.js";
|
|
6
6
|
/** The protocol version carried in `X-Capybara-Device-Protocol` and the hello frame. */
|
package/dist/device-protocol.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Device protocol v1: the WebSocket frames between the kernel and a `capybara-device` program
|
|
3
|
-
* (a connected machine that serves as
|
|
3
|
+
* (a connected machine that serves as a device). Pure types and hand-written guards.
|
|
4
4
|
*/
|
|
5
5
|
import { validateSandboxPathShape, } from "./sandbox.js";
|
|
6
6
|
/** The protocol version carried in `X-Capybara-Device-Protocol` and the hello frame. */
|
package/dist/extension-point.js
CHANGED
|
@@ -8,7 +8,7 @@ const info = (majors, description, cardinality, brokered = false, orgScoped = fa
|
|
|
8
8
|
});
|
|
9
9
|
/** The registered extension points. */
|
|
10
10
|
export const EXTENSION_POINTS = {
|
|
11
|
-
sandbox: info([2], "Runs
|
|
11
|
+
sandbox: info([2], "Runs a device's instance and keeps its files for agents' commands (Docker, E2B).", "single", true),
|
|
12
12
|
"config-source": info([1], "Supplies deployment configuration from an external store such as Nacos.", "single"),
|
|
13
13
|
"secret-store": info([1], "Stores and resolves secrets (connector credentials, secret config) outside the database.", "single", true),
|
|
14
14
|
"object-store": info([1], "Stores files and artifacts in S3-compatible object storage, with presigned uploads.", "single", true),
|
package/dist/file-preview.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export declare const FILE_PREVIEW_MAX_BYTES: number;
|
|
|
7
7
|
/**
|
|
8
8
|
* One previewer of a `file-preview@1` provider: which files it previews and how. It renders either a
|
|
9
9
|
* view (`render`, drawn by the web app like any `UiView`) or an embed page (`embed`, the id of a ui
|
|
10
|
-
* contribution `{ kind: "embed",
|
|
10
|
+
* contribution `{ kind: "embed", slot: "file.preview" }` of the same extension, which receives the
|
|
11
11
|
* file over the message bridge). Exactly one of the two.
|
|
12
12
|
*/
|
|
13
13
|
export interface FilePreviewer {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "0.1.0-rc.
|
|
1
|
+
export declare const SDK_VERSION = "0.1.0-rc.9";
|
|
2
2
|
export * from "./agent-template.js";
|
|
3
3
|
export * from "./bootstrap.js";
|
|
4
4
|
export * from "./cards.js";
|
|
@@ -34,4 +34,6 @@ export * from "./theme-tokens.generated.js";
|
|
|
34
34
|
export * from "./tool.js";
|
|
35
35
|
export * from "./tool-key.js";
|
|
36
36
|
export * from "./ui.js";
|
|
37
|
+
export * from "./ui-resolve.js";
|
|
38
|
+
export * from "./ui-slots.js";
|
|
37
39
|
export * from "./web.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const SDK_VERSION = "0.1.0-rc.
|
|
1
|
+
export const SDK_VERSION = "0.1.0-rc.9";
|
|
2
2
|
export * from "./agent-template.js";
|
|
3
3
|
export * from "./bootstrap.js";
|
|
4
4
|
export * from "./cards.js";
|
|
@@ -34,4 +34,6 @@ export * from "./theme-tokens.generated.js";
|
|
|
34
34
|
export * from "./tool.js";
|
|
35
35
|
export * from "./tool-key.js";
|
|
36
36
|
export * from "./ui.js";
|
|
37
|
+
export * from "./ui-resolve.js";
|
|
38
|
+
export * from "./ui-slots.js";
|
|
37
39
|
export * from "./web.js";
|
package/dist/permissions.d.ts
CHANGED
|
@@ -62,9 +62,14 @@ export declare class ExtensionPointUnavailableError extends Error {
|
|
|
62
62
|
/** 校验权限清单,返回错误列表(空表示合法)。 */
|
|
63
63
|
export declare function validatePermissions(plugin: string, p: unknown): string[];
|
|
64
64
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
65
|
+
* Checks the structure of the manifest's `ui` and cross-checks it with the permission manifest;
|
|
66
|
+
* returns the problems (empty = valid). Only structural problems (UI slots spec §3.2): each
|
|
67
|
+
* contribution's id is listed in exactly `permissions.ui.views` (kind view) or
|
|
68
|
+
* `permissions.ui.embeds` (kind embed), and `permissions.ui` lists no id the `ui` lacks; an embed's
|
|
69
|
+
* path starts with `/ui/` and is declared in `permissions.http` as GET; exactly one of `slot` (a
|
|
70
|
+
* well-formed slot reference) and `place` (a known place); for a slot the catalog knows at the
|
|
71
|
+
* version named, a kind it accepts and, unless an alias, the icon it requires. An unknown slot id or version is not a
|
|
72
|
+
* problem here: `resolveUiSlots` drops that contribution and the rest of the extension loads.
|
|
73
|
+
* Assumes `permissions` passed {@link validatePermissions}.
|
|
69
74
|
*/
|
|
70
75
|
export declare function validateUiContributions(ui: unknown, permissions: unknown): string[];
|
package/dist/permissions.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { POLICY_HOOKS } from "./hooks.js";
|
|
2
2
|
import { UI_ICONS } from "./ui.js";
|
|
3
|
+
import { PLACE_SLOTS, parseSlotRef, uiSlot } from "./ui-slots.js";
|
|
3
4
|
/** hook 挂载点(封闭列表,见总设计 2.4)。 */
|
|
4
5
|
export const HOOK_MOUNT_POINTS = [
|
|
5
6
|
"session.start",
|
|
@@ -168,20 +169,18 @@ export function validatePermissions(plugin, p) {
|
|
|
168
169
|
}
|
|
169
170
|
const UI_ID_RE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
170
171
|
const UI_TITLE_MAX = 100;
|
|
171
|
-
const UI_PLACES =
|
|
172
|
-
"conversation",
|
|
173
|
-
"panel",
|
|
174
|
-
"section",
|
|
175
|
-
"inline",
|
|
176
|
-
"page",
|
|
177
|
-
"file-preview",
|
|
178
|
-
];
|
|
172
|
+
const UI_PLACES = Object.keys(PLACE_SLOTS);
|
|
179
173
|
const UI_EMBED_PREFIX = "/ui/";
|
|
180
174
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
175
|
+
* Checks the structure of the manifest's `ui` and cross-checks it with the permission manifest;
|
|
176
|
+
* returns the problems (empty = valid). Only structural problems (UI slots spec §3.2): each
|
|
177
|
+
* contribution's id is listed in exactly `permissions.ui.views` (kind view) or
|
|
178
|
+
* `permissions.ui.embeds` (kind embed), and `permissions.ui` lists no id the `ui` lacks; an embed's
|
|
179
|
+
* path starts with `/ui/` and is declared in `permissions.http` as GET; exactly one of `slot` (a
|
|
180
|
+
* well-formed slot reference) and `place` (a known place); for a slot the catalog knows at the
|
|
181
|
+
* version named, a kind it accepts and, unless an alias, the icon it requires. An unknown slot id or version is not a
|
|
182
|
+
* problem here: `resolveUiSlots` drops that contribution and the rest of the extension loads.
|
|
183
|
+
* Assumes `permissions` passed {@link validatePermissions}.
|
|
185
184
|
*/
|
|
186
185
|
export function validateUiContributions(ui, permissions) {
|
|
187
186
|
const perms = isObj(permissions) ? permissions : {};
|
|
@@ -219,22 +218,46 @@ export function validateUiContributions(ui, permissions) {
|
|
|
219
218
|
if (!title ||
|
|
220
219
|
[title.zh, title.en].some((t) => typeof t !== "string" || t === "" || t.length > UI_TITLE_MAX))
|
|
221
220
|
errs.push(`ui "${id}" title needs non-empty zh and en of at most ${UI_TITLE_MAX} characters`);
|
|
222
|
-
|
|
221
|
+
// the slot it names, when the slot / place is well formed (known to the catalog or not)
|
|
222
|
+
let slotRef = null;
|
|
223
|
+
if ((c.slot === undefined) === (c.place === undefined)) {
|
|
224
|
+
errs.push(`ui "${id}" needs exactly one of slot and place`);
|
|
225
|
+
}
|
|
226
|
+
else if (c.slot !== undefined) {
|
|
227
|
+
slotRef = typeof c.slot === "string" ? parseSlotRef(c.slot) : null;
|
|
228
|
+
if (slotRef === null)
|
|
229
|
+
errs.push(`ui "${id}" slot must be a slot reference like "org.page" or "org.page@1"`);
|
|
230
|
+
}
|
|
231
|
+
else if (!UI_PLACES.includes(c.place)) {
|
|
223
232
|
errs.push(`ui "${id}" place must be one of ${UI_PLACES.join(", ")}`);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
slotRef = { id: PLACE_SLOTS[c.place], version: 1 };
|
|
236
|
+
}
|
|
237
|
+
if (c.after !== undefined && typeof c.after !== "string")
|
|
238
|
+
errs.push(`ui "${id}" after must be a string`);
|
|
224
239
|
if (c.kind !== "view" && c.kind !== "embed") {
|
|
225
240
|
errs.push(`ui "${id}" kind must be view or embed`);
|
|
226
241
|
return;
|
|
227
242
|
}
|
|
228
243
|
if (c.icon !== undefined && !UI_ICONS.includes(c.icon))
|
|
229
244
|
errs.push(`ui "${id}" icon must be one of ${UI_ICONS.join(", ")}`);
|
|
245
|
+
// the slot checks describe only the catalog's version: another version is left to
|
|
246
|
+
// resolveUiSlots, which drops it (unsupported-version) without failing the extension
|
|
247
|
+
const known = slotRef === null ? undefined : uiSlot(slotRef.id);
|
|
248
|
+
const spec = known !== undefined && known.version === slotRef?.version ? known : undefined;
|
|
249
|
+
if (spec !== undefined) {
|
|
250
|
+
if (!spec.accepts.includes(c.kind))
|
|
251
|
+
errs.push(`ui "${id}" slot ${spec.id} does not accept kind ${c.kind}`);
|
|
252
|
+
if (spec.icon === "required" && c.icon === undefined && c.aliasOf === undefined)
|
|
253
|
+
errs.push(`ui "${id}" slot ${spec.id} needs an icon`);
|
|
254
|
+
}
|
|
230
255
|
if (c.aliasOf !== undefined)
|
|
231
256
|
aliases.push({ id, kind: c.kind, target: c.aliasOf });
|
|
232
257
|
if (seen.has(id))
|
|
233
258
|
errs.push(`ui has duplicate id "${id}"`);
|
|
234
259
|
seen.set(id, c.kind);
|
|
235
260
|
if (c.kind === "view") {
|
|
236
|
-
if (c.place === "file-preview")
|
|
237
|
-
errs.push(`ui "${id}" place file-preview is only allowed for kind embed`);
|
|
238
261
|
if (c.path !== undefined)
|
|
239
262
|
errs.push(`ui "${id}" path is only allowed for kind embed`);
|
|
240
263
|
if (!views.includes(id))
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* primitives below; the kernel builds `glob` / `grep` / `edit` from `exec` plus `readFile` /
|
|
5
5
|
* `writeFile`.
|
|
6
6
|
*
|
|
7
|
-
* `@2` separates
|
|
8
|
-
* (`createFileSystem`) lives until the
|
|
7
|
+
* `@2` separates a device's **file system** from its **instance**: the file system
|
|
8
|
+
* (`createFileSystem`) lives until the device is deleted (`destroyFileSystem`); instances
|
|
9
9
|
* (`create`) attach to it and come and go (`destroy` drops the instance and keeps the files).
|
|
10
10
|
*/
|
|
11
11
|
/** Sandbox extension point ref. */
|
|
@@ -38,7 +38,7 @@ export interface SandboxHandle {
|
|
|
38
38
|
}
|
|
39
39
|
/** The roots of an instance, falling back to {@link DEFAULT_SANDBOX_ROOTS}. */
|
|
40
40
|
export declare function rootsOf(h: SandboxHandle): SandboxRoots;
|
|
41
|
-
/** The provider's file system handle (
|
|
41
|
+
/** The provider's file system handle (a device's files, independent of any instance). */
|
|
42
42
|
export interface FileSystemHandle {
|
|
43
43
|
/** The provider's file system id. */
|
|
44
44
|
id: string;
|
|
@@ -74,7 +74,7 @@ export declare function isSandboxGoneError(e: unknown): e is SandboxGoneError;
|
|
|
74
74
|
/**
|
|
75
75
|
* The file system is definitely gone on the provider's side (volume removed, E2B sandbox killed).
|
|
76
76
|
* `create` throws it instead of attaching the instance to a new, empty file system; the kernel then
|
|
77
|
-
* reports the
|
|
77
|
+
* reports the device's files as lost rather than handing out a silently empty workspace.
|
|
78
78
|
*/
|
|
79
79
|
export declare class FileSystemGoneError extends Error {
|
|
80
80
|
readonly fileSystemGone: true;
|
|
@@ -93,8 +93,8 @@ export declare const SANDBOX_TAG_RE: RegExp;
|
|
|
93
93
|
/**
|
|
94
94
|
* The `sandbox@2` implementation (provided by a sandbox plugin such as Docker or E2B).
|
|
95
95
|
*
|
|
96
|
-
* **File systems and instances.** `createFileSystem` makes
|
|
97
|
-
* `destroyFileSystem` (the
|
|
96
|
+
* **File systems and instances.** `createFileSystem` makes a device's files; they live until
|
|
97
|
+
* `destroyFileSystem` (the device is deleted). `create` makes an instance attached to an
|
|
98
98
|
* existing file system; `destroy` / `destroyByName` drop the instance only and keep the files, and
|
|
99
99
|
* `suspend` keeps them too. When the file system is gone, `create` must throw
|
|
100
100
|
* {@link FileSystemGoneError} and never create an empty one in its place (a Docker provider checks
|
|
@@ -126,7 +126,7 @@ export declare const SANDBOX_TAG_RE: RegExp;
|
|
|
126
126
|
* `exitCode: null`, `timedOut: false` (and the output collected so far), it does not reject. An
|
|
127
127
|
* `exec` called with an already aborted signal rejects at once.
|
|
128
128
|
*
|
|
129
|
-
* **Concurrent use** (required in `@2`): the sessions of one
|
|
129
|
+
* **Concurrent use** (required in `@2`): the sessions of one device share its instance, so the
|
|
130
130
|
* kernel calls `exec`, `readFile`, `writeFile` and `stat` concurrently on one instance. Those four
|
|
131
131
|
* methods must be safe to call concurrently. Each `exec` labels its command with its `tag`, and an
|
|
132
132
|
* aborted `exec` kills only its own command.
|
|
@@ -135,12 +135,12 @@ export interface SandboxProvider {
|
|
|
135
135
|
/** `exec` / `readFile` / `writeFile` / `stat` may run concurrently on one instance (required). */
|
|
136
136
|
readonly concurrentExec: true;
|
|
137
137
|
/**
|
|
138
|
-
* Whether the provider isolates the sessions of one
|
|
138
|
+
* Whether the provider isolates the sessions of one device from each other inside the
|
|
139
139
|
* instance (a declared capability; absent means false).
|
|
140
140
|
*/
|
|
141
141
|
readonly sessionIsolation?: boolean;
|
|
142
142
|
/**
|
|
143
|
-
* Creates
|
|
143
|
+
* Creates a device's file system. `name` looks like `cap-device-<deviceId>`. If a file
|
|
144
144
|
* system with this name already exists, return it.
|
|
145
145
|
*/
|
|
146
146
|
createFileSystem(args: {
|
package/dist/sandbox.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* primitives below; the kernel builds `glob` / `grep` / `edit` from `exec` plus `readFile` /
|
|
5
5
|
* `writeFile`.
|
|
6
6
|
*
|
|
7
|
-
* `@2` separates
|
|
8
|
-
* (`createFileSystem`) lives until the
|
|
7
|
+
* `@2` separates a device's **file system** from its **instance**: the file system
|
|
8
|
+
* (`createFileSystem`) lives until the device is deleted (`destroyFileSystem`); instances
|
|
9
9
|
* (`create`) attach to it and come and go (`destroy` drops the instance and keeps the files).
|
|
10
10
|
*/
|
|
11
11
|
/** Sandbox extension point ref. */
|
|
@@ -45,7 +45,7 @@ export function isSandboxGoneError(e) {
|
|
|
45
45
|
/**
|
|
46
46
|
* The file system is definitely gone on the provider's side (volume removed, E2B sandbox killed).
|
|
47
47
|
* `create` throws it instead of attaching the instance to a new, empty file system; the kernel then
|
|
48
|
-
* reports the
|
|
48
|
+
* reports the device's files as lost rather than handing out a silently empty workspace.
|
|
49
49
|
*/
|
|
50
50
|
export class FileSystemGoneError extends Error {
|
|
51
51
|
fileSystemGone = true;
|
package/dist/tool.d.ts
CHANGED
|
@@ -72,9 +72,9 @@ export interface ToolResult {
|
|
|
72
72
|
display?: ToolDisplay;
|
|
73
73
|
/**
|
|
74
74
|
* Optional: an inline card under this call in the conversation: `view` is a ui contribution of
|
|
75
|
-
* the same extension
|
|
76
|
-
* (recorded on the `tool_result` event, never in the model's
|
|
77
|
-
* with a warning and the result is kept.
|
|
75
|
+
* the same extension in the `conversation.tool-result.card` slot, rendered with
|
|
76
|
+
* `card: { callId, key }`. Display only (recorded on the `tool_result` event, never in the model's
|
|
77
|
+
* context); an unknown view is dropped with a warning and the result is kept.
|
|
78
78
|
*/
|
|
79
79
|
card?: {
|
|
80
80
|
view: string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { UiContribution } from "./ui.js";
|
|
2
|
+
import { type UiSlotSpec } from "./ui-slots.js";
|
|
3
|
+
/** A contribution whose slot the catalog knows at the version it names (UI slots spec §3.2). */
|
|
4
|
+
export interface ResolvedUiContribution {
|
|
5
|
+
contribution: UiContribution;
|
|
6
|
+
slot: UiSlotSpec;
|
|
7
|
+
version: number;
|
|
8
|
+
/**
|
|
9
|
+
* For a slot with anchors: the anchor it goes after (the contribution's own `after` when it is one
|
|
10
|
+
* of the slot's anchors, else the slot's first anchor). Absent for a slot without anchors.
|
|
11
|
+
*/
|
|
12
|
+
after?: string;
|
|
13
|
+
}
|
|
14
|
+
/** Why a contribution was dropped: its slot is not in the catalog, or not at that version. */
|
|
15
|
+
export type UiDropReason = "unknown-slot" | "unsupported-version";
|
|
16
|
+
/** A contribution left out of the loaded extension; `ref` is the slot reference it names. */
|
|
17
|
+
export interface DroppedUiContribution {
|
|
18
|
+
id: string;
|
|
19
|
+
ref: string;
|
|
20
|
+
reason: UiDropReason;
|
|
21
|
+
}
|
|
22
|
+
/** The slot reference a contribution names: its `slot`, or the slot of its deprecated `place`. */
|
|
23
|
+
export declare function slotRefOf(c: UiContribution): string;
|
|
24
|
+
/**
|
|
25
|
+
* Resolves the slots of a manifest's `ui` against the catalog (UI slots spec §3.2). Assumes
|
|
26
|
+
* `validateUiContributions` passed; never throws. A contribution to an unknown slot or to a version
|
|
27
|
+
* other than the slot's is dropped (an alias whose target is dropped is dropped with the target's
|
|
28
|
+
* reason and its own `ref`); an `after` that is not an anchor of the slot is listed in
|
|
29
|
+
* `unknownAnchors` and the contribution keeps the slot's default position. `deprecatedPlace` lists
|
|
30
|
+
* the ids that still say `place`. Both lists keep manifest order.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveUiSlots(ui: readonly UiContribution[] | undefined): {
|
|
33
|
+
resolved: ResolvedUiContribution[];
|
|
34
|
+
dropped: DroppedUiContribution[];
|
|
35
|
+
unknownAnchors: {
|
|
36
|
+
id: string;
|
|
37
|
+
after: string;
|
|
38
|
+
}[];
|
|
39
|
+
deprecatedPlace: string[];
|
|
40
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { PLACE_SLOTS, parseSlotRef, uiSlot } from "./ui-slots.js";
|
|
2
|
+
/** The slot reference a contribution names: its `slot`, or the slot of its deprecated `place`. */
|
|
3
|
+
export function slotRefOf(c) {
|
|
4
|
+
// biome-ignore lint/style/noNonNullAssertion: validateUiContributions requires slot or place
|
|
5
|
+
return c.slot ?? PLACE_SLOTS[c.place];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Resolves the slots of a manifest's `ui` against the catalog (UI slots spec §3.2). Assumes
|
|
9
|
+
* `validateUiContributions` passed; never throws. A contribution to an unknown slot or to a version
|
|
10
|
+
* other than the slot's is dropped (an alias whose target is dropped is dropped with the target's
|
|
11
|
+
* reason and its own `ref`); an `after` that is not an anchor of the slot is listed in
|
|
12
|
+
* `unknownAnchors` and the contribution keeps the slot's default position. `deprecatedPlace` lists
|
|
13
|
+
* the ids that still say `place`. Both lists keep manifest order.
|
|
14
|
+
*/
|
|
15
|
+
export function resolveUiSlots(ui) {
|
|
16
|
+
const list = ui ?? [];
|
|
17
|
+
const own = new Map();
|
|
18
|
+
for (const c of list) {
|
|
19
|
+
const ref = parseSlotRef(slotRefOf(c));
|
|
20
|
+
const slot = ref === null ? undefined : uiSlot(ref.id);
|
|
21
|
+
own.set(c.id, ref === null || slot === undefined
|
|
22
|
+
? { ok: false, reason: "unknown-slot" }
|
|
23
|
+
: ref.version !== slot.version
|
|
24
|
+
? { ok: false, reason: "unsupported-version" }
|
|
25
|
+
: { ok: true, slot, version: ref.version });
|
|
26
|
+
}
|
|
27
|
+
const resolved = [];
|
|
28
|
+
const dropped = [];
|
|
29
|
+
const unknownAnchors = [];
|
|
30
|
+
const deprecatedPlace = [];
|
|
31
|
+
for (const c of list) {
|
|
32
|
+
if (c.slot === undefined && c.place !== undefined)
|
|
33
|
+
deprecatedPlace.push(c.id);
|
|
34
|
+
const mine = own.get(c.id);
|
|
35
|
+
const target = c.aliasOf === undefined ? undefined : own.get(c.aliasOf);
|
|
36
|
+
const r = mine.ok && target !== undefined && !target.ok ? target : mine;
|
|
37
|
+
if (!r.ok) {
|
|
38
|
+
dropped.push({ id: c.id, ref: slotRefOf(c), reason: r.reason });
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const anchors = r.slot.anchors ?? [];
|
|
42
|
+
if (c.after !== undefined && !anchors.includes(c.after)) {
|
|
43
|
+
unknownAnchors.push({ id: c.id, after: c.after });
|
|
44
|
+
}
|
|
45
|
+
const after = c.after !== undefined && anchors.includes(c.after) ? c.after : anchors[0];
|
|
46
|
+
resolved.push({
|
|
47
|
+
contribution: c,
|
|
48
|
+
slot: r.slot,
|
|
49
|
+
version: r.version,
|
|
50
|
+
...(anchors.length > 0 && { after }),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return { resolved, dropped, unknownAnchors, deprecatedPlace };
|
|
54
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { UiKind, UiPlace } from "./ui.js";
|
|
2
|
+
export type UiSlotScope = "conversation" | "agent" | "org" | "platform" | "file";
|
|
3
|
+
export type UiSlotRole = "guest" | "member" | "admin" | "superAdmin";
|
|
4
|
+
/** Every slot id of the catalog, in catalog order (UI slots spec §3). */
|
|
5
|
+
export declare const UI_SLOT_IDS: readonly ["conversation.panel", "conversation.properties.section", "conversation.tool-result.card", "org.page", "file.preview", "agent.detail.section", "org.settings.section", "platform.page"];
|
|
6
|
+
export type UiSlotId = (typeof UI_SLOT_IDS)[number];
|
|
7
|
+
/** One slot of the catalog: where extension UI may go and what it receives (UI slots spec §3.1). */
|
|
8
|
+
export interface UiSlotSpec {
|
|
9
|
+
id: UiSlotId;
|
|
10
|
+
/** Bumped only on an incompatible change of the slot's contract. */
|
|
11
|
+
version: number;
|
|
12
|
+
/** Path relative to `apps/web/src` of the component that renders the slot. */
|
|
13
|
+
owner: string;
|
|
14
|
+
scope: UiSlotScope;
|
|
15
|
+
accepts: readonly UiKind[];
|
|
16
|
+
cardinality: "list";
|
|
17
|
+
minRole: UiSlotRole;
|
|
18
|
+
/** Context ids passed to render (`conversationId`, `agentId`, …). */
|
|
19
|
+
args: readonly string[];
|
|
20
|
+
density: "dense" | "regular";
|
|
21
|
+
icon: "required" | "optional" | "none";
|
|
22
|
+
/** Built-in parts a contribution may be placed after; the first is the default. */
|
|
23
|
+
anchors?: readonly string[];
|
|
24
|
+
limits?: {
|
|
25
|
+
maxShown?: number;
|
|
26
|
+
};
|
|
27
|
+
description: {
|
|
28
|
+
en: string;
|
|
29
|
+
zh: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export declare const UI_SLOTS: readonly UiSlotSpec[];
|
|
33
|
+
export declare function uiSlot(id: string): UiSlotSpec | undefined;
|
|
34
|
+
/** Reads `<id>` or `<id>@<version>` (version 1 when omitted); does not check the catalog. */
|
|
35
|
+
export declare function parseSlotRef(ref: string): {
|
|
36
|
+
id: string;
|
|
37
|
+
version: number;
|
|
38
|
+
} | null;
|
|
39
|
+
/** @deprecated step A of the UI slots spec §4 */
|
|
40
|
+
export declare const PLACE_SLOTS: Readonly<Record<UiPlace, UiSlotId>>;
|
|
41
|
+
export declare const UI_ARRANGEABLE_SLOTS: readonly UiSlotId[];
|
|
42
|
+
export declare const UI_CONTRIBUTION_KEY_RE: RegExp;
|
|
43
|
+
export declare function uiContributionKey(plugin: string, id: string): string;
|
package/dist/ui-slots.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/** Every slot id of the catalog, in catalog order (UI slots spec §3). */
|
|
2
|
+
export const UI_SLOT_IDS = [
|
|
3
|
+
"conversation.panel",
|
|
4
|
+
"conversation.properties.section",
|
|
5
|
+
"conversation.tool-result.card",
|
|
6
|
+
"org.page",
|
|
7
|
+
"file.preview",
|
|
8
|
+
"agent.detail.section",
|
|
9
|
+
"org.settings.section",
|
|
10
|
+
"platform.page",
|
|
11
|
+
];
|
|
12
|
+
const SLOT_DATA = [
|
|
13
|
+
{
|
|
14
|
+
id: "conversation.panel",
|
|
15
|
+
version: 1,
|
|
16
|
+
owner: "components/conversation/RightArea.tsx",
|
|
17
|
+
scope: "conversation",
|
|
18
|
+
accepts: ["view", "embed"],
|
|
19
|
+
cardinality: "list",
|
|
20
|
+
minRole: "guest",
|
|
21
|
+
args: ["conversationId", "item"],
|
|
22
|
+
density: "regular",
|
|
23
|
+
icon: "optional",
|
|
24
|
+
description: {
|
|
25
|
+
en: "A panel beside the conversation, opened from the right area.",
|
|
26
|
+
zh: "对话旁的面板,从右侧区域打开。",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "conversation.properties.section",
|
|
31
|
+
version: 1,
|
|
32
|
+
owner: "components/conversation/PropertiesSections.tsx",
|
|
33
|
+
scope: "conversation",
|
|
34
|
+
accepts: ["view", "embed"],
|
|
35
|
+
cardinality: "list",
|
|
36
|
+
minRole: "guest",
|
|
37
|
+
args: ["conversationId"],
|
|
38
|
+
density: "dense",
|
|
39
|
+
icon: "optional",
|
|
40
|
+
anchors: ["context", "files", "background"],
|
|
41
|
+
description: {
|
|
42
|
+
en: "A section in the conversation properties list.",
|
|
43
|
+
zh: "对话属性列表中的一个分区。",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "conversation.tool-result.card",
|
|
48
|
+
version: 1,
|
|
49
|
+
owner: "components/conversation/RightArea.tsx",
|
|
50
|
+
scope: "conversation",
|
|
51
|
+
accepts: ["view", "embed"],
|
|
52
|
+
cardinality: "list",
|
|
53
|
+
minRole: "guest",
|
|
54
|
+
args: ["conversationId", "card"],
|
|
55
|
+
density: "regular",
|
|
56
|
+
icon: "optional",
|
|
57
|
+
description: {
|
|
58
|
+
en: "A card a tool result opens in the conversation.",
|
|
59
|
+
zh: "工具结果在对话中打开的卡片。",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "org.page",
|
|
64
|
+
version: 1,
|
|
65
|
+
owner: "app/(app)/p/[plugin]/[id]/view.tsx",
|
|
66
|
+
scope: "org",
|
|
67
|
+
accepts: ["view", "embed"],
|
|
68
|
+
cardinality: "list",
|
|
69
|
+
minRole: "member",
|
|
70
|
+
args: [],
|
|
71
|
+
density: "regular",
|
|
72
|
+
icon: "optional",
|
|
73
|
+
description: {
|
|
74
|
+
en: "A full page in the organization navigation.",
|
|
75
|
+
zh: "组织导航中的完整页面。",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "file.preview",
|
|
80
|
+
version: 1,
|
|
81
|
+
owner: "components/conversation/FilePanel.tsx",
|
|
82
|
+
scope: "file",
|
|
83
|
+
accepts: ["embed"],
|
|
84
|
+
cardinality: "list",
|
|
85
|
+
minRole: "guest",
|
|
86
|
+
args: ["conversationId", "fileId"],
|
|
87
|
+
density: "regular",
|
|
88
|
+
icon: "optional",
|
|
89
|
+
description: {
|
|
90
|
+
en: "A previewer for a file of the conversation.",
|
|
91
|
+
zh: "对话中文件的预览器。",
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "agent.detail.section",
|
|
96
|
+
version: 1,
|
|
97
|
+
owner: "components/agent/AgentPage.tsx",
|
|
98
|
+
scope: "agent",
|
|
99
|
+
accepts: ["view", "embed"],
|
|
100
|
+
cardinality: "list",
|
|
101
|
+
minRole: "guest",
|
|
102
|
+
args: ["agentId"],
|
|
103
|
+
density: "regular",
|
|
104
|
+
icon: "optional",
|
|
105
|
+
description: {
|
|
106
|
+
en: "A section on the detail page of an agent.",
|
|
107
|
+
zh: "智能体详情页中的一个分区。",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "org.settings.section",
|
|
112
|
+
version: 1,
|
|
113
|
+
owner: "app/(app)/settings/organization/view.tsx",
|
|
114
|
+
scope: "org",
|
|
115
|
+
accepts: ["view", "embed"],
|
|
116
|
+
cardinality: "list",
|
|
117
|
+
minRole: "admin",
|
|
118
|
+
args: [],
|
|
119
|
+
density: "regular",
|
|
120
|
+
icon: "optional",
|
|
121
|
+
description: {
|
|
122
|
+
en: "A section on the organization settings page.",
|
|
123
|
+
zh: "组织设置页中的一个分区。",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: "platform.page",
|
|
128
|
+
version: 1,
|
|
129
|
+
owner: "app/(app)/admin/p/[plugin]/[id]/view.tsx",
|
|
130
|
+
scope: "platform",
|
|
131
|
+
accepts: ["view", "embed"],
|
|
132
|
+
cardinality: "list",
|
|
133
|
+
minRole: "superAdmin",
|
|
134
|
+
args: [],
|
|
135
|
+
density: "regular",
|
|
136
|
+
icon: "optional",
|
|
137
|
+
description: {
|
|
138
|
+
en: "A full page in the platform administration.",
|
|
139
|
+
zh: "平台管理中的完整页面。",
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
export const UI_SLOTS = SLOT_DATA;
|
|
144
|
+
export function uiSlot(id) {
|
|
145
|
+
return UI_SLOTS.find((s) => s.id === id);
|
|
146
|
+
}
|
|
147
|
+
const SLOT_REF_RE = /^([a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+)(?:@([1-9][0-9]{0,2}))?$/;
|
|
148
|
+
/** Reads `<id>` or `<id>@<version>` (version 1 when omitted); does not check the catalog. */
|
|
149
|
+
export function parseSlotRef(ref) {
|
|
150
|
+
const m = SLOT_REF_RE.exec(ref);
|
|
151
|
+
if (!m)
|
|
152
|
+
return null;
|
|
153
|
+
return { id: m[1], version: m[2] === undefined ? 1 : Number(m[2]) };
|
|
154
|
+
}
|
|
155
|
+
/** @deprecated step A of the UI slots spec §4 */
|
|
156
|
+
export const PLACE_SLOTS = {
|
|
157
|
+
panel: "conversation.panel",
|
|
158
|
+
conversation: "conversation.panel",
|
|
159
|
+
section: "conversation.properties.section",
|
|
160
|
+
inline: "conversation.tool-result.card",
|
|
161
|
+
page: "org.page",
|
|
162
|
+
"file-preview": "file.preview",
|
|
163
|
+
};
|
|
164
|
+
export const UI_ARRANGEABLE_SLOTS = UI_SLOT_IDS.filter((id) => id !== "file.preview");
|
|
165
|
+
export const UI_CONTRIBUTION_KEY_RE = /^[a-z][a-z0-9-]{1,40}\/[a-z][a-z0-9-]{0,39}$/;
|
|
166
|
+
export function uiContributionKey(plugin, id) {
|
|
167
|
+
return `${plugin}/${id}`;
|
|
168
|
+
}
|
package/dist/ui.d.ts
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
import type { Locale } from "./cards.js";
|
|
2
2
|
import { type FormAnswer, type InputForm } from "./tool.js";
|
|
3
|
+
import type { UiSlotId, UiSlotScope } from "./ui-slots.js";
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* - `inline`: an inline card in the conversation, hanging off a tool result (`ToolResult.card`);
|
|
9
|
-
* - `page`: an org page under "Extensions";
|
|
10
|
-
* - `file-preview`: an embed previewer of `file-preview@1` (embed only, see `FilePreviewer.embed`);
|
|
11
|
-
* - `conversation`: the old right-side tab; it means `panel` (see {@link normalizePlace}).
|
|
5
|
+
* The places of the first UI design, kept as a deprecated input (UI slots spec §4, step A): each maps
|
|
6
|
+
* to a slot through `PLACE_SLOTS` (`panel` and `conversation` → `conversation.panel`, `section` →
|
|
7
|
+
* `conversation.properties.section`, `inline` → `conversation.tool-result.card`, `page` →
|
|
8
|
+
* `org.page`, `file-preview` → `file.preview`).
|
|
12
9
|
*/
|
|
13
10
|
export type UiPlace = "conversation" | "panel" | "section" | "inline" | "page" | "file-preview";
|
|
14
|
-
/** A place with the old `conversation` mapped to `panel`. */
|
|
15
|
-
export declare function normalizePlace(place: UiPlace): Exclude<UiPlace, "conversation">;
|
|
16
11
|
/**
|
|
17
12
|
* Icons a contribution may use (design system icon names; `puzzle` is the default and is drawn as
|
|
18
13
|
* the design `extension` icon). The title-bar button of a panel shows it.
|
|
@@ -20,8 +15,9 @@ export declare function normalizePlace(place: UiPlace): Exclude<UiPlace, "conver
|
|
|
20
15
|
export declare const UI_ICONS: readonly ["list-checks", "layout-grid", "file", "chart", "calendar", "users", "bot", "book", "message-square", "puzzle"];
|
|
21
16
|
export type UiIcon = (typeof UI_ICONS)[number];
|
|
22
17
|
/**
|
|
23
|
-
* An "open" target: a `panel` contribution of the same extension, scrolled to the
|
|
24
|
-
* whose `id` equals `item` (the panel's render receives it as
|
|
18
|
+
* An "open" target: a `conversation.panel` contribution of the same extension, scrolled to the
|
|
19
|
+
* block or item whose `id` equals `item` (the panel's render receives it as
|
|
20
|
+
* {@link UiRenderArgs.item}).
|
|
25
21
|
*/
|
|
26
22
|
export interface UiOpen {
|
|
27
23
|
contribution: string;
|
|
@@ -39,7 +35,19 @@ export interface UiContribution {
|
|
|
39
35
|
zh: string;
|
|
40
36
|
en: string;
|
|
41
37
|
};
|
|
42
|
-
|
|
38
|
+
/**
|
|
39
|
+
* The slot it goes in: `"<id>"` (version 1) or `"<id>@<n>"`, an id of `UI_SLOTS`. Exactly one of
|
|
40
|
+
* `slot` and `place`. An unknown slot or version does not fail the extension: the contribution is
|
|
41
|
+
* dropped (see `resolveUiSlots`).
|
|
42
|
+
*/
|
|
43
|
+
slot?: string;
|
|
44
|
+
/** @deprecated use slot (UI slots spec §4, step A) */
|
|
45
|
+
place?: UiPlace;
|
|
46
|
+
/**
|
|
47
|
+
* One of the slot's `anchors` to be placed after; omitted (or not an anchor of the slot) = the
|
|
48
|
+
* slot's first anchor.
|
|
49
|
+
*/
|
|
50
|
+
after?: string;
|
|
43
51
|
kind: UiKind;
|
|
44
52
|
/** 仅 embed:插件路由下 `/ui/` 开头的路径,须在 `permissions.http` 中声明为 GET。 */
|
|
45
53
|
path?: string;
|
|
@@ -150,20 +158,41 @@ export declare const UI_LIMITS: {
|
|
|
150
158
|
*/
|
|
151
159
|
export declare function validateUiView(v: unknown): string[];
|
|
152
160
|
/**
|
|
153
|
-
*
|
|
154
|
-
*
|
|
161
|
+
* The caller's role in the org (owner / admin / member / guest, highest first). A slot's `minRole`
|
|
162
|
+
* decides who may use its contributions, then its scope's rule (the kernel answers 404 to anyone
|
|
163
|
+
* else, so the extension never sees such a request): `org.page` member and up,
|
|
164
|
+
* `org.settings.section` admin and up; conversation slots any role that can read the
|
|
165
|
+
* conversation; `agent.detail.section` any role that can read the agent (read-only viewers
|
|
166
|
+
* included: tighten on `role` if you must).
|
|
155
167
|
*/
|
|
156
168
|
export type UiRole = "owner" | "admin" | "member" | "guest";
|
|
157
|
-
/**
|
|
158
|
-
export interface
|
|
159
|
-
orgId: string;
|
|
169
|
+
/** The render args every scope shares (identity comes from the kernel's auth, never the caller's credentials). */
|
|
170
|
+
export interface UiRenderArgsBase {
|
|
160
171
|
accountId: string;
|
|
172
|
+
/** The catalog slot the contribution resolved to. */
|
|
173
|
+
slot: UiSlotId;
|
|
174
|
+
locale: Locale;
|
|
175
|
+
signal: AbortSignal;
|
|
176
|
+
/**
|
|
177
|
+
* The extension's effective deployment config: for an org, the platform's deployment config
|
|
178
|
+
* with the org's copy applied to the overridable fields (`orgAccess(…, "override")`), or the
|
|
179
|
+
* platform's config (equal to `ctx.config`) while the org follows it; for the platform scope,
|
|
180
|
+
* the platform's config. Use it, not `ctx.config`, for work done for this org. Optional in the
|
|
181
|
+
* type so older extensions compile; the kernel always passes it.
|
|
182
|
+
*/
|
|
183
|
+
config?: unknown;
|
|
184
|
+
}
|
|
185
|
+
/** 内核调用插件 `render` 时传入的参数,组织内的位置(会话、智能体、组织、文件)。 */
|
|
186
|
+
export interface UiOrgRenderArgs extends UiRenderArgsBase {
|
|
187
|
+
/** The scope of {@link slot}: which of the context ids below are set. */
|
|
188
|
+
scope: Exclude<UiSlotScope, "platform">;
|
|
189
|
+
orgId: string;
|
|
161
190
|
/** 调用者此刻在 `orgId` 的角色(内核按成员关系填,插件可据此收紧按钮等,见 {@link UiRole})。 */
|
|
162
191
|
role: UiRole;
|
|
163
192
|
/** 仅对话视图。 */
|
|
164
193
|
conversationId?: string;
|
|
165
|
-
|
|
166
|
-
|
|
194
|
+
/** Only an agent view (`agent.detail.section`): an agent of `orgId` the caller can read. */
|
|
195
|
+
agentId?: string;
|
|
167
196
|
/** A panel opened from an open target: the block or item `id` to scroll to. */
|
|
168
197
|
item?: string;
|
|
169
198
|
/**
|
|
@@ -175,21 +204,36 @@ export interface UiRenderArgs {
|
|
|
175
204
|
callId: string;
|
|
176
205
|
key?: string;
|
|
177
206
|
};
|
|
178
|
-
/**
|
|
179
|
-
* The extension's effective deployment config for this org: the platform's deployment config
|
|
180
|
-
* with the org's copy applied to the overridable fields (`orgAccess(…, "override")`), or the
|
|
181
|
-
* platform's config (equal to `ctx.config`) while the org follows it. Use it, not `ctx.config`,
|
|
182
|
-
* for work done for this org. Optional in the type so older extensions compile; the kernel
|
|
183
|
-
* always passes it.
|
|
184
|
-
*/
|
|
185
|
-
config?: unknown;
|
|
186
207
|
}
|
|
187
|
-
/**
|
|
188
|
-
|
|
208
|
+
/**
|
|
209
|
+
* The render args of a `platform.page` contribution: rendered for a super admin in the admin
|
|
210
|
+
* console, with no org (and so no org role, conversation or agent).
|
|
211
|
+
*/
|
|
212
|
+
export interface UiPlatformRenderArgs extends UiRenderArgsBase {
|
|
213
|
+
scope: "platform";
|
|
214
|
+
orgId?: undefined;
|
|
215
|
+
role: "superAdmin";
|
|
216
|
+
conversationId?: undefined;
|
|
217
|
+
agentId?: undefined;
|
|
218
|
+
item?: undefined;
|
|
219
|
+
card?: undefined;
|
|
220
|
+
}
|
|
221
|
+
/** 内核调用插件 `render` 时传入的参数:narrow on `scope` (`"platform"` has no `orgId`). */
|
|
222
|
+
export type UiRenderArgs = UiOrgRenderArgs | UiPlatformRenderArgs;
|
|
223
|
+
/** What a button or form submit adds to the render args. */
|
|
224
|
+
export interface UiActionInput {
|
|
189
225
|
actionId: string;
|
|
190
226
|
payload?: unknown;
|
|
191
227
|
answer?: FormAnswer;
|
|
192
228
|
}
|
|
229
|
+
/** An action of an org-scope view. */
|
|
230
|
+
export interface UiOrgActionArgs extends UiOrgRenderArgs, UiActionInput {
|
|
231
|
+
}
|
|
232
|
+
/** An action of a `platform.page` view. */
|
|
233
|
+
export interface UiPlatformActionArgs extends UiPlatformRenderArgs, UiActionInput {
|
|
234
|
+
}
|
|
235
|
+
/** 视图里的按钮或表单提交:`actionId` 来自块;表单提交时带 `answer`。 */
|
|
236
|
+
export type UiActionArgs = UiOrgActionArgs | UiPlatformActionArgs;
|
|
193
237
|
/** 动作结果:一条提示,或一份新的视图。 */
|
|
194
238
|
export type UiActionResult = {
|
|
195
239
|
toast?: {
|
|
@@ -199,21 +243,55 @@ export type UiActionResult = {
|
|
|
199
243
|
} | {
|
|
200
244
|
view: UiView;
|
|
201
245
|
};
|
|
202
|
-
/**
|
|
203
|
-
export interface
|
|
246
|
+
/** The embed claims both scopes share. `exp` 为 Unix 秒。 */
|
|
247
|
+
export interface EmbedClaimsBase {
|
|
204
248
|
plugin: string;
|
|
205
|
-
orgId: string;
|
|
206
249
|
accountId: string;
|
|
250
|
+
contribution: string;
|
|
251
|
+
exp: number;
|
|
252
|
+
}
|
|
253
|
+
/** An org-scope embed token's claims (every slot but `platform.page`). */
|
|
254
|
+
export interface OrgEmbedClaims extends EmbedClaimsBase {
|
|
255
|
+
scope: "org";
|
|
256
|
+
orgId: string;
|
|
207
257
|
/** 调用者的角色:签发时写入;`ctx.ui.verifyEmbedToken` 返回校验时刻的当前角色。 */
|
|
208
258
|
role: UiRole;
|
|
209
259
|
conversationId?: string;
|
|
260
|
+
/** Only an agent embed's token (`agent.detail.section`): the agent it was issued for. */
|
|
261
|
+
agentId?: string;
|
|
210
262
|
/** Only a `file-preview` embed's token: the one file (of `conversationId`) it previews. */
|
|
211
263
|
fileId?: string;
|
|
212
|
-
contribution: string;
|
|
213
|
-
exp: number;
|
|
214
264
|
}
|
|
265
|
+
/** A `platform.page` embed token's claims: a super admin's, with no org, conversation, agent or file. */
|
|
266
|
+
export interface PlatformEmbedClaims extends EmbedClaimsBase {
|
|
267
|
+
scope: "platform";
|
|
268
|
+
orgId?: undefined;
|
|
269
|
+
role: "superAdmin";
|
|
270
|
+
conversationId?: undefined;
|
|
271
|
+
agentId?: undefined;
|
|
272
|
+
fileId?: undefined;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* 嵌入令牌里的声明(经 `ctx.ui.verifyEmbedToken` 校验后可信): narrow on `scope` (`"platform"` has no
|
|
276
|
+
* `orgId` and `role` `"superAdmin"`).
|
|
277
|
+
*/
|
|
278
|
+
export type EmbedClaims = OrgEmbedClaims | PlatformEmbedClaims;
|
|
215
279
|
/** 外层页面与嵌入 iframe 之间 `postMessage` 协议的版本标识。 */
|
|
216
280
|
export declare const EMBED_PROTOCOL = "capybara-embed/1";
|
|
281
|
+
/**
|
|
282
|
+
* What an embed's `init` says about where it is shown, narrowed on `scope` like {@link EmbedClaims}:
|
|
283
|
+
* an org's page, panel, section or previewer (`orgId`, and the conversation when there is one), or
|
|
284
|
+
* a platform page of the admin console (no org).
|
|
285
|
+
*/
|
|
286
|
+
export type EmbedInitContext = {
|
|
287
|
+
scope: "org";
|
|
288
|
+
orgId: string;
|
|
289
|
+
conversationId?: string;
|
|
290
|
+
} | {
|
|
291
|
+
scope: "platform";
|
|
292
|
+
orgId?: undefined;
|
|
293
|
+
conversationId?: undefined;
|
|
294
|
+
};
|
|
217
295
|
/** 外层页面发给 iframe 的消息。 */
|
|
218
296
|
export type EmbedParentMessage = {
|
|
219
297
|
protocol: typeof EMBED_PROTOCOL;
|
|
@@ -222,10 +300,7 @@ export type EmbedParentMessage = {
|
|
|
222
300
|
locale: Locale;
|
|
223
301
|
theme: "dark" | "light";
|
|
224
302
|
tokens: Record<string, string>;
|
|
225
|
-
context:
|
|
226
|
-
orgId: string;
|
|
227
|
-
conversationId?: string;
|
|
228
|
-
};
|
|
303
|
+
context: EmbedInitContext;
|
|
229
304
|
} | {
|
|
230
305
|
protocol: typeof EMBED_PROTOCOL;
|
|
231
306
|
type: "token";
|
package/dist/ui.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { validateInputForm } from "./tool.js";
|
|
2
2
|
import { isHttpUrl } from "./url.js";
|
|
3
|
-
/** A place with the old `conversation` mapped to `panel`. */
|
|
4
|
-
export function normalizePlace(place) {
|
|
5
|
-
return place === "conversation" ? "panel" : place;
|
|
6
|
-
}
|
|
7
3
|
/**
|
|
8
4
|
* Icons a contribution may use (design system icon names; `puzzle` is the default and is drawn as
|
|
9
5
|
* the design `extension` icon). The title-bar button of a panel shows it.
|