@capybara-hq/plugin-sdk 0.1.0-rc.2 → 0.1.0-rc.3
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/README.md +1 -1
- package/agent-docs/agent-templates.md +47 -0
- package/agent-docs/concepts.md +58 -0
- package/agent-docs/connector-templates.md +47 -0
- package/agent-docs/extension-points/channel.md +15 -0
- package/agent-docs/extension-points/config-source.md +15 -0
- package/agent-docs/extension-points/connector.md +15 -0
- package/agent-docs/extension-points/counter.md +15 -0
- package/agent-docs/extension-points/directory.md +15 -0
- package/agent-docs/extension-points/file-preview.md +15 -0
- package/agent-docs/extension-points/identity-source.md +15 -0
- package/agent-docs/extension-points/model-provider.md +15 -0
- package/agent-docs/extension-points/object-store.md +15 -0
- package/agent-docs/extension-points/sandbox.md +15 -0
- package/agent-docs/extension-points/secret-store.md +15 -0
- package/agent-docs/extension-points/web-fetch.md +15 -0
- package/agent-docs/extension-points/web-search.md +15 -0
- package/agent-docs/extensions.md +63 -0
- package/agent-docs/plugin-format.md +60 -0
- package/agent-docs/skills.md +44 -0
- package/agent-docs/tool-naming.md +21 -0
- package/dist/agent-template.d.ts +85 -0
- package/dist/agent-template.js +361 -2
- package/dist/config-ui.d.ts +2 -1
- package/dist/config-ui.js +25 -7
- package/dist/ctx.d.ts +3 -1
- package/dist/directory.d.ts +11 -2
- package/dist/extension-point.d.ts +4 -0
- package/dist/extension-point.js +16 -14
- package/dist/file-preview.d.ts +8 -0
- package/dist/hooks.d.ts +11 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/marketplace.d.ts +29 -2
- package/dist/marketplace.js +30 -2
- package/dist/org-access.d.ts +27 -0
- package/dist/org-access.js +35 -0
- package/dist/plugin-manifest.d.ts +15 -2
- package/dist/plugin-manifest.js +11 -1
- package/dist/sandbox.d.ts +140 -60
- package/dist/sandbox.js +30 -6
- package/dist/skill-invocation.d.ts +8 -5
- package/dist/skill-invocation.js +17 -8
- package/dist/skill.d.ts +20 -3
- package/dist/skill.js +28 -6
- package/dist/tool.d.ts +8 -0
- package/dist/ui.d.ts +8 -0
- package/dist/web.d.ts +30 -5
- package/package.json +4 -2
- package/schemas/marketplace.v1.schema.json +36 -0
- package/schemas/plugin.v1.schema.json +29 -4
package/dist/file-preview.d.ts
CHANGED
|
@@ -53,6 +53,14 @@ export interface FilePreviewArgs {
|
|
|
53
53
|
* rejects a file over the previewer's `maxBytes`. The extension never gets a storage key or URL.
|
|
54
54
|
*/
|
|
55
55
|
read(): Promise<Uint8Array>;
|
|
56
|
+
/**
|
|
57
|
+
* The extension's effective deployment config for this org: the platform's deployment config
|
|
58
|
+
* with the org's copy applied to the overridable fields (`orgAccess(…, "override")`), or the
|
|
59
|
+
* platform's config (equal to `ctx.config`) while the org follows it. Use it, not `ctx.config`,
|
|
60
|
+
* for work done for this org. Optional in the type so older extensions compile; the kernel
|
|
61
|
+
* always passes it.
|
|
62
|
+
*/
|
|
63
|
+
config?: unknown;
|
|
56
64
|
}
|
|
57
65
|
/** Checks a `file-preview@1` provider; returns the problems (empty = valid). */
|
|
58
66
|
export declare function validateFilePreviewProvider(p: unknown): string[];
|
package/dist/hooks.d.ts
CHANGED
|
@@ -169,9 +169,18 @@ export type HookVerdict = void | {
|
|
|
169
169
|
};
|
|
170
170
|
/**
|
|
171
171
|
* A hook handler. The payload is a deep-frozen copy (mutating it throws). `signal` aborts when the
|
|
172
|
-
* hook runs past `hooks.timeoutMs` or the call it observes is aborted.
|
|
172
|
+
* hook runs past `hooks.timeoutMs` or the call it observes is aborted. `call.config` is the
|
|
173
|
+
* extension's effective deployment config for the payload's org (the platform's config with the
|
|
174
|
+
* org's copy applied to the overridable fields, or `ctx.config` while the org follows it); the
|
|
175
|
+
* kernel always passes it, and never calls a hook for an org where that config is not valid
|
|
176
|
+
* (observe hooks are skipped, policy hooks deny).
|
|
173
177
|
*/
|
|
174
|
-
export type HookHandler<K extends AvailableHook> = (payload: HookPayloads[K], signal: AbortSignal) => Promise<HookVerdict> | HookVerdict;
|
|
178
|
+
export type HookHandler<K extends AvailableHook> = (payload: HookPayloads[K], signal: AbortSignal, call?: HookCall) => Promise<HookVerdict> | HookVerdict;
|
|
179
|
+
/** The per-call context of a hook handler (see {@link HookHandler}). */
|
|
180
|
+
export interface HookCall {
|
|
181
|
+
/** The extension's effective deployment config for the payload's org. */
|
|
182
|
+
config: unknown;
|
|
183
|
+
}
|
|
175
184
|
/**
|
|
176
185
|
* - `policy` (only on {@link POLICY_HOOKS}): policy hooks run serially by plugin name (registration
|
|
177
186
|
* order within a plugin) before the call; the first `{ deny }`, error or timeout blocks the call and
|
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.3";
|
|
2
2
|
export * from "./agent-template.js";
|
|
3
3
|
export * from "./bootstrap.js";
|
|
4
4
|
export * from "./cards.js";
|
|
@@ -16,6 +16,7 @@ export * from "./identity.js";
|
|
|
16
16
|
export * from "./marketplace.js";
|
|
17
17
|
export * from "./model.js";
|
|
18
18
|
export * from "./model-params.js";
|
|
19
|
+
export * from "./org-access.js";
|
|
19
20
|
export * from "./permissions.js";
|
|
20
21
|
export * from "./plugin-manifest.js";
|
|
21
22
|
export * from "./plugin-package.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.3";
|
|
2
2
|
export * from "./agent-template.js";
|
|
3
3
|
export * from "./bootstrap.js";
|
|
4
4
|
export * from "./cards.js";
|
|
@@ -16,6 +16,7 @@ export * from "./identity.js";
|
|
|
16
16
|
export * from "./marketplace.js";
|
|
17
17
|
export * from "./model.js";
|
|
18
18
|
export * from "./model-params.js";
|
|
19
|
+
export * from "./org-access.js";
|
|
19
20
|
export * from "./permissions.js";
|
|
20
21
|
export * from "./plugin-manifest.js";
|
|
21
22
|
export * from "./plugin-package.js";
|
package/dist/marketplace.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
/**
|
|
3
3
|
* The marketplace manifest `marketplace.json` (package ecosystem spec §5.2): a list of plugins,
|
|
4
|
-
* each with one source. One entry is one version
|
|
5
|
-
*
|
|
4
|
+
* each with one source. One entry is one version. `capybara-plugin marketplace --write` lists one
|
|
5
|
+
* entry per plugin, its current version (plugin component management spec §13.2); readers still
|
|
6
|
+
* accept several versions of a plugin (marketplaces written in the older form), so
|
|
7
|
+
* `(name, marketplaceEntryVersion)` is unique in a manifest.
|
|
6
8
|
*
|
|
7
9
|
* Sources: a same-repo `path`, a `git` repo, an `npm` package (exact version + sha512, downloads
|
|
8
10
|
* are verified against it, never npm metadata) or an `archive` URL (sha512 or sha256 SRI).
|
|
@@ -19,6 +21,10 @@ export declare const MARKETPLACE_MANIFEST_SCHEMA_VERSION = 1;
|
|
|
19
21
|
export declare const MARKETPLACE_LIMITS: {
|
|
20
22
|
readonly nameChars: 100;
|
|
21
23
|
readonly summaryChars: 280;
|
|
24
|
+
/** Characters of each `displayName` language (as `plugin.json`'s). */
|
|
25
|
+
readonly displayNameChars: 100;
|
|
26
|
+
/** Characters of a `deprecated.reason`. */
|
|
27
|
+
readonly deprecationReasonChars: 280;
|
|
22
28
|
readonly plugins: 5000;
|
|
23
29
|
readonly permissionKeys: 500;
|
|
24
30
|
readonly urlChars: 2000;
|
|
@@ -53,6 +59,11 @@ export interface MarketplaceEntry {
|
|
|
53
59
|
en: string;
|
|
54
60
|
zh?: string;
|
|
55
61
|
};
|
|
62
|
+
/** Display name (the plugin's `plugin.json` `displayName`); lists show it, else the name. */
|
|
63
|
+
displayName?: {
|
|
64
|
+
zh: string;
|
|
65
|
+
en: string;
|
|
66
|
+
};
|
|
56
67
|
/** Exact semver; defaults to the npm source's version, otherwise the plugin's own. */
|
|
57
68
|
version?: string;
|
|
58
69
|
/** Exact semver. */
|
|
@@ -64,6 +75,15 @@ export interface MarketplaceEntry {
|
|
|
64
75
|
};
|
|
65
76
|
/** https URL. */
|
|
66
77
|
homepage?: string;
|
|
78
|
+
/**
|
|
79
|
+
* This version is deprecated (`true`, or with a reason shown to admins): browsing lists it under
|
|
80
|
+
* the plugin's other versions, never as the one to import, and the kernel refuses importing it
|
|
81
|
+
* (`marketplace.entry_deprecated`). Set by hand on the entry by the marketplace maintainer;
|
|
82
|
+
* `capybara-plugin marketplace --write` keeps it. `false` is the same as absent.
|
|
83
|
+
*/
|
|
84
|
+
deprecated?: boolean | {
|
|
85
|
+
reason?: string;
|
|
86
|
+
};
|
|
67
87
|
source: MarketplaceSource;
|
|
68
88
|
}
|
|
69
89
|
/** `marketplace.json`. */
|
|
@@ -97,3 +117,10 @@ export declare function parseMarketplaceManifest(json: unknown, opts?: Marketpla
|
|
|
97
117
|
};
|
|
98
118
|
/** The entry's version: `version`, else the npm source's version, else `null` (the plugin's own). */
|
|
99
119
|
export declare function marketplaceEntryVersion(e: MarketplaceEntry): string | null;
|
|
120
|
+
/**
|
|
121
|
+
* The entry's deprecation (see {@link MarketplaceEntry.deprecated}): `null` when it is not
|
|
122
|
+
* deprecated (absent or `false`), else its reason (`null` without one).
|
|
123
|
+
*/
|
|
124
|
+
export declare function marketplaceEntryDeprecation(e: MarketplaceEntry): {
|
|
125
|
+
reason: string | null;
|
|
126
|
+
} | null;
|
package/dist/marketplace.js
CHANGED
|
@@ -2,8 +2,10 @@ import { z } from "zod";
|
|
|
2
2
|
import { CATALOG_ITEM_NAME_RE, isSafePackagePath, NPM_PACKAGE_NAME_RE, SEMVER_VERSION_RE, SHA512_INTEGRITY_RE, } from "./plugin-package.js";
|
|
3
3
|
/**
|
|
4
4
|
* The marketplace manifest `marketplace.json` (package ecosystem spec §5.2): a list of plugins,
|
|
5
|
-
* each with one source. One entry is one version
|
|
6
|
-
*
|
|
5
|
+
* each with one source. One entry is one version. `capybara-plugin marketplace --write` lists one
|
|
6
|
+
* entry per plugin, its current version (plugin component management spec §13.2); readers still
|
|
7
|
+
* accept several versions of a plugin (marketplaces written in the older form), so
|
|
8
|
+
* `(name, marketplaceEntryVersion)` is unique in a manifest.
|
|
7
9
|
*
|
|
8
10
|
* Sources: a same-repo `path`, a `git` repo, an `npm` package (exact version + sha512, downloads
|
|
9
11
|
* are verified against it, never npm metadata) or an `archive` URL (sha512 or sha256 SRI).
|
|
@@ -20,6 +22,10 @@ export const MARKETPLACE_MANIFEST_SCHEMA_VERSION = 1;
|
|
|
20
22
|
export const MARKETPLACE_LIMITS = {
|
|
21
23
|
nameChars: 100,
|
|
22
24
|
summaryChars: 280,
|
|
25
|
+
/** Characters of each `displayName` language (as `plugin.json`'s). */
|
|
26
|
+
displayNameChars: 100,
|
|
27
|
+
/** Characters of a `deprecated.reason`. */
|
|
28
|
+
deprecationReasonChars: 280,
|
|
23
29
|
plugins: 5000,
|
|
24
30
|
permissionKeys: 500,
|
|
25
31
|
urlChars: 2000,
|
|
@@ -123,6 +129,12 @@ function buildSchema(opts) {
|
|
|
123
129
|
en: z.string().min(1).max(M.summaryChars),
|
|
124
130
|
zh: z.string().min(1).max(M.summaryChars).optional(),
|
|
125
131
|
}),
|
|
132
|
+
displayName: z
|
|
133
|
+
.object({
|
|
134
|
+
zh: z.string().min(1).max(M.displayNameChars),
|
|
135
|
+
en: z.string().min(1).max(M.displayNameChars),
|
|
136
|
+
})
|
|
137
|
+
.optional(),
|
|
126
138
|
version: exactVersion("version").optional(),
|
|
127
139
|
minKernel: exactVersion("minKernel").optional(),
|
|
128
140
|
permissions: z
|
|
@@ -132,6 +144,12 @@ function buildSchema(opts) {
|
|
|
132
144
|
})
|
|
133
145
|
.optional(),
|
|
134
146
|
homepage: urlString(HTTPS_URL_RE).optional(),
|
|
147
|
+
deprecated: z
|
|
148
|
+
.union([
|
|
149
|
+
z.boolean(),
|
|
150
|
+
z.object({ reason: z.string().min(1).max(M.deprecationReasonChars).optional() }),
|
|
151
|
+
])
|
|
152
|
+
.optional(),
|
|
135
153
|
source,
|
|
136
154
|
});
|
|
137
155
|
return z
|
|
@@ -254,3 +272,13 @@ export function marketplaceEntryVersion(e) {
|
|
|
254
272
|
return e.source.npm.version;
|
|
255
273
|
return null;
|
|
256
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* The entry's deprecation (see {@link MarketplaceEntry.deprecated}): `null` when it is not
|
|
277
|
+
* deprecated (absent or `false`), else its reason (`null` without one).
|
|
278
|
+
*/
|
|
279
|
+
export function marketplaceEntryDeprecation(e) {
|
|
280
|
+
const d = e.deprecated;
|
|
281
|
+
if (d === undefined || d === false)
|
|
282
|
+
return null;
|
|
283
|
+
return { reason: d === true ? null : (d.reason ?? null) };
|
|
284
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* How an org may treat a deployment config field: `readonly` (the org sees it, never sets it) or
|
|
4
|
+
* `override` (an org may set its own value). A field without a level is deployment-only.
|
|
5
|
+
*/
|
|
6
|
+
export type OrgAccess = "readonly" | "override";
|
|
7
|
+
/**
|
|
8
|
+
* Marks a deployment config field with an org access level (Zod 4 meta `{ org: access }`), merging
|
|
9
|
+
* with existing meta (`secret`, `hot`, `ui`), so `orgAccess(secret(x), …)` and
|
|
10
|
+
* `secret(orgAccess(x, …))` are the same. `configJsonSchema` publishes it as `"x-org"`.
|
|
11
|
+
*
|
|
12
|
+
* The author's promise for `override`: the extension reads the field from the per-call `config`
|
|
13
|
+
* the kernel passes with each request, not from `ctx.config`, and the field may be absent from
|
|
14
|
+
* `ctx.config` (the deployment value is the default, which an org may replace per call).
|
|
15
|
+
*
|
|
16
|
+
* The per-call config of an org that has its own copy still contains the platform's hidden
|
|
17
|
+
* (platform-only) secrets, which an org cannot set; it never contains an overridable platform
|
|
18
|
+
* secret. Never send a hidden secret to an endpoint taken from an overridable field: an org could
|
|
19
|
+
* point that endpoint at a server it controls.
|
|
20
|
+
*/
|
|
21
|
+
export declare function orgAccess<T extends z.ZodTypeAny>(schema: T, access: OrgAccess): T;
|
|
22
|
+
/**
|
|
23
|
+
* The org access level of a schema's meta, or undefined when it has none. It sees through
|
|
24
|
+
* `.optional()`, `.default()`, `.nullable()`, `.prefault()`, `.catch()` and `.readonly()` (the
|
|
25
|
+
* outermost level wins), so it agrees with the `"x-org"` that `configJsonSchema` publishes.
|
|
26
|
+
*/
|
|
27
|
+
export declare function orgAccessOf(schema: z.ZodTypeAny): OrgAccess | undefined;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marks a deployment config field with an org access level (Zod 4 meta `{ org: access }`), merging
|
|
3
|
+
* with existing meta (`secret`, `hot`, `ui`), so `orgAccess(secret(x), …)` and
|
|
4
|
+
* `secret(orgAccess(x, …))` are the same. `configJsonSchema` publishes it as `"x-org"`.
|
|
5
|
+
*
|
|
6
|
+
* The author's promise for `override`: the extension reads the field from the per-call `config`
|
|
7
|
+
* the kernel passes with each request, not from `ctx.config`, and the field may be absent from
|
|
8
|
+
* `ctx.config` (the deployment value is the default, which an org may replace per call).
|
|
9
|
+
*
|
|
10
|
+
* The per-call config of an org that has its own copy still contains the platform's hidden
|
|
11
|
+
* (platform-only) secrets, which an org cannot set; it never contains an overridable platform
|
|
12
|
+
* secret. Never send a hidden secret to an endpoint taken from an overridable field: an org could
|
|
13
|
+
* point that endpoint at a server it controls.
|
|
14
|
+
*/
|
|
15
|
+
export function orgAccess(schema, access) {
|
|
16
|
+
return schema.meta({ ...(schema.meta() ?? {}), org: access });
|
|
17
|
+
}
|
|
18
|
+
/** The wrappers a field's org level is read through, as `configJsonSchema` inherits meta. */
|
|
19
|
+
const WRAPPERS = new Set(["default", "prefault", "optional", "nullable", "readonly", "catch"]);
|
|
20
|
+
/**
|
|
21
|
+
* The org access level of a schema's meta, or undefined when it has none. It sees through
|
|
22
|
+
* `.optional()`, `.default()`, `.nullable()`, `.prefault()`, `.catch()` and `.readonly()` (the
|
|
23
|
+
* outermost level wins), so it agrees with the `"x-org"` that `configJsonSchema` publishes.
|
|
24
|
+
*/
|
|
25
|
+
export function orgAccessOf(schema) {
|
|
26
|
+
let node = schema;
|
|
27
|
+
while (node) {
|
|
28
|
+
const v = node.meta()?.org;
|
|
29
|
+
if (v === "readonly" || v === "override")
|
|
30
|
+
return v;
|
|
31
|
+
const def = node._zod.def;
|
|
32
|
+
node = WRAPPERS.has(def.type) ? def.innerType : undefined;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
@@ -85,6 +85,19 @@ export interface PluginExtensionSection {
|
|
|
85
85
|
org?: JsonSchema;
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* A plugin's description: a string (English) or `{ en, zh? }`, each ≤ 1024 characters. Read it
|
|
90
|
+
* through {@link pluginDescription}.
|
|
91
|
+
*/
|
|
92
|
+
export type PluginDescription = string | {
|
|
93
|
+
en: string;
|
|
94
|
+
zh?: string;
|
|
95
|
+
};
|
|
96
|
+
/** A plugin description as `{ en, zh? }` (a plain string is the English text). */
|
|
97
|
+
export declare function pluginDescription(d: PluginDescription): {
|
|
98
|
+
en: string;
|
|
99
|
+
zh?: string;
|
|
100
|
+
};
|
|
88
101
|
/** `.capybara-plugin/plugin.json`. */
|
|
89
102
|
export interface PluginManifest {
|
|
90
103
|
$schema?: string;
|
|
@@ -94,8 +107,8 @@ export interface PluginManifest {
|
|
|
94
107
|
/** Exact semver version. */
|
|
95
108
|
version: string;
|
|
96
109
|
publisher: string;
|
|
97
|
-
/** ≤ 1024 characters. */
|
|
98
|
-
description:
|
|
110
|
+
/** A string (English) or `{ en, zh? }`, each ≤ 1024 characters; see {@link pluginDescription}. */
|
|
111
|
+
description: PluginDescription;
|
|
99
112
|
displayName?: LocalizedName;
|
|
100
113
|
/** `./…` .svg / .png inside the plugin. */
|
|
101
114
|
icon?: string;
|
package/dist/plugin-manifest.js
CHANGED
|
@@ -48,6 +48,10 @@ export const PLUGIN_MANIFEST_LIMITS = {
|
|
|
48
48
|
displayNameChars: 100,
|
|
49
49
|
pathChars: 512,
|
|
50
50
|
};
|
|
51
|
+
/** A plugin description as `{ en, zh? }` (a plain string is the English text). */
|
|
52
|
+
export function pluginDescription(d) {
|
|
53
|
+
return typeof d === "string" ? { en: d } : { en: d.en, ...(d.zh !== undefined && { zh: d.zh }) };
|
|
54
|
+
}
|
|
51
55
|
const L = PLUGIN_MANIFEST_LIMITS;
|
|
52
56
|
const MAX_ERRORS = 50;
|
|
53
57
|
const ICON_RE = /\.(?:svg|png)$/i;
|
|
@@ -116,7 +120,13 @@ const schema = z
|
|
|
116
120
|
.max(256)
|
|
117
121
|
.regex(SEMVER_VERSION_RE, { message: "version must be an exact semver version" }),
|
|
118
122
|
publisher: text(L.publisherChars, "publisher"),
|
|
119
|
-
description:
|
|
123
|
+
description: z.union([
|
|
124
|
+
text(L.descriptionChars, "description"),
|
|
125
|
+
z.strictObject({
|
|
126
|
+
en: text(L.descriptionChars, "description.en"),
|
|
127
|
+
zh: text(L.descriptionChars, "description.zh").optional(),
|
|
128
|
+
}),
|
|
129
|
+
]),
|
|
120
130
|
displayName: localized.optional(),
|
|
121
131
|
icon: dotPath("icon")
|
|
122
132
|
.regex(ICON_RE, { message: "icon must be a .svg or .png file" })
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -1,30 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sandbox contract `sandbox@
|
|
2
|
+
* Sandbox contract `sandbox@2`: a single-select, brokered extension point (extensions may not
|
|
3
3
|
* `requires` it; the kernel's `SandboxService` consumes it). The provider implements only the
|
|
4
4
|
* primitives below; the kernel builds `glob` / `grep` / `edit` from `exec` plus `readFile` /
|
|
5
5
|
* `writeFile`.
|
|
6
|
+
*
|
|
7
|
+
* `@2` separates an environment's **file system** from its **instance**: the file system
|
|
8
|
+
* (`createFileSystem`) lives until the environment is deleted (`destroyFileSystem`); instances
|
|
9
|
+
* (`create`) attach to it and come and go (`destroy` drops the instance and keeps the files).
|
|
6
10
|
*/
|
|
7
11
|
/** Sandbox extension point ref. */
|
|
8
|
-
export declare const SANDBOX_EXTENSION_POINT = "sandbox@
|
|
9
|
-
/**
|
|
12
|
+
export declare const SANDBOX_EXTENSION_POINT = "sandbox@2";
|
|
13
|
+
/** The roots inside the sandbox the model may access. */
|
|
10
14
|
export declare const SANDBOX_ROOTS: readonly ["/workspace", "/skills", "/tmp"];
|
|
11
|
-
/**
|
|
15
|
+
/** Sandbox resource and egress limits. */
|
|
12
16
|
export interface SandboxLimits {
|
|
13
17
|
cpu: number;
|
|
14
18
|
memoryMb: number;
|
|
15
19
|
diskMb: number;
|
|
16
20
|
egress: "none" | "allowlist" | "open";
|
|
17
|
-
/**
|
|
21
|
+
/** Egress allowlist (used when `egress` is `allowlist`); each entry passes {@link validateAllowHost}. */
|
|
18
22
|
allowHosts: string[];
|
|
19
23
|
}
|
|
20
|
-
/**
|
|
24
|
+
/** The provider's instance handle. */
|
|
21
25
|
export interface SandboxHandle {
|
|
22
|
-
/**
|
|
26
|
+
/** The provider's instance id. */
|
|
27
|
+
id: string;
|
|
28
|
+
}
|
|
29
|
+
/** The provider's file system handle (an environment's files, independent of any instance). */
|
|
30
|
+
export interface FileSystemHandle {
|
|
31
|
+
/** The provider's file system id. */
|
|
23
32
|
id: string;
|
|
24
33
|
}
|
|
25
|
-
/**
|
|
34
|
+
/** Who runs an `exec` (informational in phase 1: audit and attribution). */
|
|
35
|
+
export interface SandboxActor {
|
|
36
|
+
accountId: string;
|
|
37
|
+
conversationId: string;
|
|
38
|
+
}
|
|
39
|
+
/** Result of a command. */
|
|
26
40
|
export interface ExecResult {
|
|
27
|
-
/**
|
|
41
|
+
/** null on timeout or when aborted while running (`timedOut` is then true / false). */
|
|
28
42
|
exitCode: number | null;
|
|
29
43
|
stdout: string;
|
|
30
44
|
stderr: string;
|
|
@@ -32,8 +46,10 @@ export interface ExecResult {
|
|
|
32
46
|
timedOut: boolean;
|
|
33
47
|
}
|
|
34
48
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
49
|
+
* The instance is definitely gone on the provider's side (container removed, E2B sandbox expired).
|
|
50
|
+
* `exec` / `readFile` / `writeFile` / `stat` must throw it (not a plain Error) when they find the
|
|
51
|
+
* instance gone; the kernel then rebuilds the instance and retries once. A transient connection
|
|
52
|
+
* failure throws a plain error.
|
|
37
53
|
*/
|
|
38
54
|
export declare class SandboxGoneError extends Error {
|
|
39
55
|
readonly sandboxGone: true;
|
|
@@ -41,8 +57,21 @@ export declare class SandboxGoneError extends Error {
|
|
|
41
57
|
cause?: unknown;
|
|
42
58
|
});
|
|
43
59
|
}
|
|
44
|
-
/**
|
|
60
|
+
/** Duck-typed check (not instanceof), so a plugin bundling another plugin-sdk copy still matches. */
|
|
45
61
|
export declare function isSandboxGoneError(e: unknown): e is SandboxGoneError;
|
|
62
|
+
/**
|
|
63
|
+
* The file system is definitely gone on the provider's side (volume removed, E2B sandbox killed).
|
|
64
|
+
* `create` throws it instead of attaching the instance to a new, empty file system; the kernel then
|
|
65
|
+
* reports the environment's files as lost rather than handing out a silently empty workspace.
|
|
66
|
+
*/
|
|
67
|
+
export declare class FileSystemGoneError extends Error {
|
|
68
|
+
readonly fileSystemGone: true;
|
|
69
|
+
constructor(message: string, opts?: {
|
|
70
|
+
cause?: unknown;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** Duck-typed check (not instanceof), so a plugin bundling another plugin-sdk copy still matches. */
|
|
74
|
+
export declare function isFileSystemGoneError(e: unknown): e is FileSystemGoneError;
|
|
46
75
|
/**
|
|
47
76
|
* A holder tag (`exec` argument `tag`, {@link SandboxProvider.killTagged}): the kernel passes its
|
|
48
77
|
* lease tokens (UUIDs). Providers check tags against it before putting them in a command line or a
|
|
@@ -50,73 +79,119 @@ export declare function isSandboxGoneError(e: unknown): e is SandboxGoneError;
|
|
|
50
79
|
*/
|
|
51
80
|
export declare const SANDBOX_TAG_RE: RegExp;
|
|
52
81
|
/**
|
|
53
|
-
* `sandbox@
|
|
54
|
-
*
|
|
55
|
-
*
|
|
82
|
+
* The `sandbox@2` implementation (provided by a sandbox plugin such as Docker or E2B).
|
|
83
|
+
*
|
|
84
|
+
* **File systems and instances.** `createFileSystem` makes an environment's files; they live until
|
|
85
|
+
* `destroyFileSystem` (the environment is deleted). `create` makes an instance attached to an
|
|
86
|
+
* existing file system; `destroy` / `destroyByName` drop the instance only and keep the files, and
|
|
87
|
+
* `suspend` keeps them too. When the file system is gone, `create` must throw
|
|
88
|
+
* {@link FileSystemGoneError} and never create an empty one in its place (a Docker provider checks
|
|
89
|
+
* its volumes exist before mounting them, because Docker would otherwise create empty volumes).
|
|
90
|
+
*
|
|
91
|
+
* **Gone instances.** When the instance is definitely gone: `resume` returns `gone`; `exec` /
|
|
92
|
+
* `readFile` / `writeFile` / `stat` / `killAll` / `killTagged` throw {@link SandboxGoneError};
|
|
93
|
+
* `suspend` / `destroy` succeed as usual.
|
|
56
94
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* `
|
|
95
|
+
* **Auto-start**: when the instance still exists but is stopped or paused (for a reason outside the
|
|
96
|
+
* kernel: host or daemon restart, a process in the sandbox stopping its init, an idle sweep that
|
|
97
|
+
* stopped the instance before recording `suspended`...), `exec` / `readFile` / `writeFile` /
|
|
98
|
+
* `stat` / `killAll` / `killTagged` must start it transparently first (like `resume`, files kept)
|
|
99
|
+
* and then run, never fail: the kernel trusts only the `running` it recorded itself. Auto-start
|
|
100
|
+
* must be idempotent under concurrent calls: two calls that both find the instance stopped both
|
|
101
|
+
* start it without failing.
|
|
60
102
|
*
|
|
61
|
-
*
|
|
62
|
-
* `
|
|
63
|
-
*
|
|
103
|
+
* **Time limits apply inside the sandbox too**: besides stopping a command after `timeoutMs` on the
|
|
104
|
+
* provider's side, `exec` must make the command in the sandbox kill itself shortly (a few seconds)
|
|
105
|
+
* after `timeoutMs`. When a kernel replica crashes, the provider-side timer goes with it, and a
|
|
106
|
+
* dropped connection does not end the command; the in-sandbox limit keeps it from running
|
|
107
|
+
* indefinitely next to the next holder's commands.
|
|
64
108
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
109
|
+
* **Abort**: every method must respond to `signal` promptly (fail at once when already aborted, end
|
|
110
|
+
* with the abort reason as soon as possible when aborted mid-way): the kernel relies on it to stop
|
|
111
|
+
* on lock loss, run abort or timeout, and ignoring it may let two holders operate on one instance.
|
|
112
|
+
* An aborted `exec` must kill its running command (and the processes it spawned), not just stop
|
|
113
|
+
* waiting for its output; an `exec` aborted after its command started resolves normally with
|
|
114
|
+
* `exitCode: null`, `timedOut: false` (and the output collected so far), it does not reject. An
|
|
115
|
+
* `exec` called with an already aborted signal rejects at once.
|
|
69
116
|
*
|
|
70
|
-
* **Concurrent use (
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* calls**: two calls that both find the instance stopped both start it without failing. Each
|
|
75
|
-
* `exec` labels its command with its `tag`, and an aborted `exec` kills only its own command.
|
|
76
|
-
* `concurrentExec` requires {@link SandboxProvider.killTagged}; a provider that declares one
|
|
77
|
-
* without the other, or neither, is used one call at a time (the behavior without them).
|
|
117
|
+
* **Concurrent use** (required in `@2`): the sessions of one environment share its instance, so the
|
|
118
|
+
* kernel calls `exec`, `readFile`, `writeFile` and `stat` concurrently on one instance. Those four
|
|
119
|
+
* methods must be safe to call concurrently. Each `exec` labels its command with its `tag`, and an
|
|
120
|
+
* aborted `exec` kills only its own command.
|
|
78
121
|
*/
|
|
79
122
|
export interface SandboxProvider {
|
|
123
|
+
/** `exec` / `readFile` / `writeFile` / `stat` may run concurrently on one instance (required). */
|
|
124
|
+
readonly concurrentExec: true;
|
|
80
125
|
/**
|
|
81
|
-
*
|
|
82
|
-
* (
|
|
126
|
+
* Whether the provider isolates the sessions of one environment from each other inside the
|
|
127
|
+
* instance (a declared capability; absent means false).
|
|
128
|
+
*/
|
|
129
|
+
readonly sessionIsolation?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Creates an environment's file system. `name` looks like `cap-env-<environmentId>`. If a file
|
|
132
|
+
* system with this name already exists, return it.
|
|
133
|
+
*/
|
|
134
|
+
createFileSystem(args: {
|
|
135
|
+
name: string;
|
|
136
|
+
limits: SandboxLimits;
|
|
137
|
+
signal: AbortSignal;
|
|
138
|
+
}): Promise<FileSystemHandle>;
|
|
139
|
+
/** Deletes the file system and its files. Succeeds when it does not exist. */
|
|
140
|
+
destroyFileSystem(fs: FileSystemHandle, signal: AbortSignal): Promise<void>;
|
|
141
|
+
/**
|
|
142
|
+
* Deletes the file system made under `name` by `createFileSystem`; succeeds when there is none.
|
|
143
|
+
* The kernel uses it when it has no handle (`createFileSystem` finished on the provider's side
|
|
144
|
+
* but the kernel stopped before recording the handle), and in its one-off cleanup of
|
|
145
|
+
* per-conversation sandboxes with `cap-<conversationId>` names that a `sandbox@1` provider's
|
|
146
|
+
* `create` made together with their files (Docker: the `cap-<conversationId>-workspace` and
|
|
147
|
+
* `-skills` volumes); a provider removes those under the same call.
|
|
148
|
+
*/
|
|
149
|
+
destroyFileSystemByName(name: string, signal: AbortSignal): Promise<void>;
|
|
150
|
+
/**
|
|
151
|
+
* Creates an instance attached to `fileSystem`. Without `image` the provider uses its own
|
|
152
|
+
* published image. Throws {@link FileSystemGoneError} when the file system is gone.
|
|
83
153
|
*/
|
|
84
|
-
readonly concurrentExec?: boolean;
|
|
85
|
-
/** `name` 形如 `cap-<conversationId>`。 */
|
|
86
154
|
create(args: {
|
|
87
155
|
name: string;
|
|
88
|
-
image
|
|
156
|
+
image?: string;
|
|
89
157
|
limits: SandboxLimits;
|
|
158
|
+
fileSystem: FileSystemHandle;
|
|
90
159
|
signal: AbortSignal;
|
|
91
160
|
}): Promise<SandboxHandle>;
|
|
92
|
-
/** `gone`
|
|
161
|
+
/** `gone` means the provider no longer has this instance. */
|
|
93
162
|
resume(h: SandboxHandle, signal: AbortSignal): Promise<"resumed" | "gone">;
|
|
94
|
-
/**
|
|
163
|
+
/** Files must be kept. Succeeds when the instance does not exist. */
|
|
95
164
|
suspend(h: SandboxHandle, signal: AbortSignal): Promise<void>;
|
|
96
|
-
/**
|
|
165
|
+
/** Drops the instance only; the file system and its files are kept. Succeeds when it does not exist. */
|
|
97
166
|
destroy(h: SandboxHandle, signal: AbortSignal): Promise<void>;
|
|
98
167
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
168
|
+
* Drops the instance made under `name` by `create` (instance only, files kept); succeeds when
|
|
169
|
+
* there is none. The kernel uses it when it has no handle (`create` finished on the provider's
|
|
170
|
+
* side but the kernel stopped before recording the handle), and in its one-off cleanup of
|
|
171
|
+
* per-conversation sandboxes with `cap-<conversationId>` names made by a `sandbox@1` provider's
|
|
172
|
+
* `create`.
|
|
101
173
|
*/
|
|
102
|
-
destroyByName
|
|
174
|
+
destroyByName(name: string, signal: AbortSignal): Promise<void>;
|
|
103
175
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
176
|
+
* Kills every running command started through `exec` in this instance (and the processes they
|
|
177
|
+
* spawned that did not start their own session). The kernel calls it only when no other holder
|
|
178
|
+
* can be running commands (an instance just resumed from `suspended`, or under the exclusive
|
|
179
|
+
* lease), never while another session may be; to clean up after one crashed holder it uses
|
|
180
|
+
* {@link SandboxProvider.killTagged}. Processes a command put in the background in their own
|
|
181
|
+
* session are not guaranteed to be killed.
|
|
107
182
|
*/
|
|
108
|
-
killAll
|
|
183
|
+
killAll(h: SandboxHandle, signal: AbortSignal): Promise<void>;
|
|
109
184
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* when the instance is gone.
|
|
185
|
+
* Kills every running `exec` command labelled with one of `tags` (and its process tree), leaving
|
|
186
|
+
* other commands running. The kernel calls it for the tags of holders that crashed while live
|
|
187
|
+
* holders keep running commands. Tags match {@link SANDBOX_TAG_RE}. Match whole tags: one tag
|
|
188
|
+
* may be a prefix of another (`ab` of `ab-cd`), and killing `ab` must not touch `ab-cd`'s
|
|
189
|
+
* commands.
|
|
116
190
|
*/
|
|
117
|
-
killTagged
|
|
191
|
+
killTagged(h: SandboxHandle, tags: string[], signal: AbortSignal): Promise<void>;
|
|
118
192
|
/**
|
|
119
|
-
*
|
|
193
|
+
* Runs a command. `maxOutputBytes` caps stdout and stderr **combined**; the rest is dropped and
|
|
194
|
+
* `truncated` is true.
|
|
120
195
|
*/
|
|
121
196
|
exec(h: SandboxHandle, args: {
|
|
122
197
|
command: string;
|
|
@@ -127,11 +202,16 @@ export interface SandboxProvider {
|
|
|
127
202
|
signal: AbortSignal;
|
|
128
203
|
/**
|
|
129
204
|
* The holder's tag ({@link SANDBOX_TAG_RE}); label the command with it so
|
|
130
|
-
* {@link SandboxProvider.killTagged} can find it.
|
|
205
|
+
* {@link SandboxProvider.killTagged} can find it.
|
|
131
206
|
*/
|
|
132
|
-
tag
|
|
207
|
+
tag: string;
|
|
208
|
+
/** Who runs the command; informational in phase 1 (record it, do not act on it). */
|
|
209
|
+
actor: SandboxActor;
|
|
133
210
|
}): Promise<ExecResult>;
|
|
134
|
-
/**
|
|
211
|
+
/**
|
|
212
|
+
* Reads a file (at most `maxBytes`; longer files are truncated and `truncated` is true). Returns
|
|
213
|
+
* null when it does not exist or is a directory.
|
|
214
|
+
*/
|
|
135
215
|
readFile(h: SandboxHandle, path: string, opts: {
|
|
136
216
|
maxBytes: number;
|
|
137
217
|
signal: AbortSignal;
|
|
@@ -139,7 +219,7 @@ export interface SandboxProvider {
|
|
|
139
219
|
data: Uint8Array;
|
|
140
220
|
truncated: boolean;
|
|
141
221
|
} | null>;
|
|
142
|
-
/**
|
|
222
|
+
/** Parent directories are created. */
|
|
143
223
|
writeFile(h: SandboxHandle, path: string, data: Uint8Array, signal: AbortSignal): Promise<void>;
|
|
144
224
|
stat(h: SandboxHandle, path: string, signal: AbortSignal): Promise<{
|
|
145
225
|
size: number;
|