@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/agent-template.d.ts
CHANGED
|
@@ -21,6 +21,62 @@ export interface AgentTemplate {
|
|
|
21
21
|
maxSteps?: number;
|
|
22
22
|
/** Skill names preloaded (pinned) in the sub-agent. */
|
|
23
23
|
skills?: string[];
|
|
24
|
+
/** Further skill names the agent uses (not preloaded; P4-1). */
|
|
25
|
+
selectedSkills?: string[];
|
|
26
|
+
/** Connectors the agent needs: a connector template of the plugin, or a described need. */
|
|
27
|
+
connectors?: AgentTemplateConnector[];
|
|
28
|
+
/** The sub-agents the agent gets in its roster. */
|
|
29
|
+
subagents?: AgentTemplateSubagents;
|
|
30
|
+
/** `provider/model`: the concrete model an org template was saved with. */
|
|
31
|
+
modelRef?: string;
|
|
32
|
+
/** The agent profile's quick actions (same shape and limits). */
|
|
33
|
+
quickActions?: AgentTemplateQuickAction[];
|
|
34
|
+
/** The agent's instruction files. */
|
|
35
|
+
instructionFiles?: AgentTemplateInstructionFile[];
|
|
36
|
+
}
|
|
37
|
+
/** A quick action of the agent profile. */
|
|
38
|
+
export interface AgentTemplateQuickAction {
|
|
39
|
+
title: string;
|
|
40
|
+
body: string;
|
|
41
|
+
}
|
|
42
|
+
/** An instruction file of the agent. */
|
|
43
|
+
export interface AgentTemplateInstructionFile {
|
|
44
|
+
name: string;
|
|
45
|
+
content: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A connector entry of a template: a connector template id (`<id>` or `<plugin>:<id>`) or a need.
|
|
49
|
+
* `policies` tighten the connector's tools by tool name (`ask` / `deny`), applied once it is bound.
|
|
50
|
+
*/
|
|
51
|
+
export type AgentTemplateConnector = {
|
|
52
|
+
template: string;
|
|
53
|
+
tools?: string[];
|
|
54
|
+
policies?: Record<string, "ask" | "deny">;
|
|
55
|
+
} | {
|
|
56
|
+
needs: {
|
|
57
|
+
protocol: "mcp" | "openapi";
|
|
58
|
+
endpoint: string;
|
|
59
|
+
tools: string[];
|
|
60
|
+
label: string;
|
|
61
|
+
};
|
|
62
|
+
policies?: Record<string, "ask" | "deny">;
|
|
63
|
+
};
|
|
64
|
+
/** A sub-agent type described inside a template. */
|
|
65
|
+
export interface AgentTemplateInlineSubagent {
|
|
66
|
+
name: string;
|
|
67
|
+
description: string;
|
|
68
|
+
instructions: string;
|
|
69
|
+
/** Tool keys allowed (an allowlist; `[]` = no tools); absent = all of the agent's tools. */
|
|
70
|
+
tools?: string[];
|
|
71
|
+
/** Skill names. */
|
|
72
|
+
skills: string[];
|
|
73
|
+
model: AgentTemplateTier;
|
|
74
|
+
}
|
|
75
|
+
export interface AgentTemplateSubagents {
|
|
76
|
+
general?: boolean;
|
|
77
|
+
/** Template types as the roster writes them (`<plugin>:<name>` or `<name>`). */
|
|
78
|
+
types?: string[];
|
|
79
|
+
inline?: AgentTemplateInlineSubagent[];
|
|
24
80
|
}
|
|
25
81
|
/** Limits of agent templates. */
|
|
26
82
|
export declare const AGENT_TEMPLATE_LIMITS: {
|
|
@@ -33,9 +89,38 @@ export declare const AGENT_TEMPLATE_LIMITS: {
|
|
|
33
89
|
readonly skills: 3;
|
|
34
90
|
/** Entries of `tools` and of `disallowedTools`. */
|
|
35
91
|
readonly tools: 200;
|
|
92
|
+
/** Entries of `selectedSkills`. */
|
|
93
|
+
readonly selectedSkills: 32;
|
|
94
|
+
/** Entries of `connectors`. */
|
|
95
|
+
readonly connectors: 16;
|
|
96
|
+
/** `subagents.types` plus `subagents.inline`. */
|
|
97
|
+
readonly subagentTypes: 16;
|
|
98
|
+
/** Characters of one inline sub-agent's instructions. */
|
|
99
|
+
readonly inlineInstructionsChars: 100000;
|
|
100
|
+
/** Tools named by a connector entry (its `tools`, and the keys of its `policies`). */
|
|
101
|
+
readonly connectorTools: 200;
|
|
102
|
+
/** Quick actions, as the agent profile limits them. */
|
|
103
|
+
readonly quickActions: 9;
|
|
104
|
+
readonly quickActionTitleChars: 40;
|
|
105
|
+
readonly quickActionBodyChars: 20000;
|
|
106
|
+
/** Instruction files and their joined size, as the agent limits them. */
|
|
107
|
+
readonly instructionFiles: 20;
|
|
108
|
+
readonly instructionFilesChars: 100000;
|
|
36
109
|
};
|
|
110
|
+
/** One frontmatter key of an agent template, for documentation (served to agents by `Platform`). */
|
|
111
|
+
export interface AgentTemplateField {
|
|
112
|
+
name: string;
|
|
113
|
+
type: string;
|
|
114
|
+
required: boolean;
|
|
115
|
+
/** What it does, with its limit. */
|
|
116
|
+
description: string;
|
|
117
|
+
}
|
|
118
|
+
/** The frontmatter keys `parseAgentTemplate` reads (the body is the instructions). */
|
|
119
|
+
export declare const AGENT_TEMPLATE_FIELDS: readonly AgentTemplateField[];
|
|
37
120
|
/** The name rule of a template (the catalog item name rule). */
|
|
38
121
|
export declare const AGENT_TEMPLATE_NAME_RE: RegExp;
|
|
122
|
+
/** The name rule of an instruction file (the agent's rule, mirrored). */
|
|
123
|
+
export declare const AGENT_TEMPLATE_INSTRUCTION_FILE_NAME_RE: RegExp;
|
|
39
124
|
/**
|
|
40
125
|
* Parses an agent template file. Returns the template, or every problem found (English, for the
|
|
41
126
|
* plugin author or an admin). Keys the format does not define are ignored. Never throws.
|
package/dist/agent-template.js
CHANGED
|
@@ -17,14 +17,130 @@ export const AGENT_TEMPLATE_LIMITS = {
|
|
|
17
17
|
skills: 3,
|
|
18
18
|
/** Entries of `tools` and of `disallowedTools`. */
|
|
19
19
|
tools: 200,
|
|
20
|
+
/** Entries of `selectedSkills`. */
|
|
21
|
+
selectedSkills: 32,
|
|
22
|
+
/** Entries of `connectors`. */
|
|
23
|
+
connectors: 16,
|
|
24
|
+
/** `subagents.types` plus `subagents.inline`. */
|
|
25
|
+
subagentTypes: 16,
|
|
26
|
+
/** Characters of one inline sub-agent's instructions. */
|
|
27
|
+
inlineInstructionsChars: 100_000,
|
|
28
|
+
/** Tools named by a connector entry (its `tools`, and the keys of its `policies`). */
|
|
29
|
+
connectorTools: 200,
|
|
30
|
+
/** Quick actions, as the agent profile limits them. */
|
|
31
|
+
quickActions: 9,
|
|
32
|
+
quickActionTitleChars: 40,
|
|
33
|
+
quickActionBodyChars: 20_000,
|
|
34
|
+
/** Instruction files and their joined size, as the agent limits them. */
|
|
35
|
+
instructionFiles: 20,
|
|
36
|
+
instructionFilesChars: 100_000,
|
|
20
37
|
};
|
|
38
|
+
/** The frontmatter keys `parseAgentTemplate` reads (the body is the instructions). */
|
|
39
|
+
export const AGENT_TEMPLATE_FIELDS = [
|
|
40
|
+
{
|
|
41
|
+
name: "name",
|
|
42
|
+
type: "string",
|
|
43
|
+
required: true,
|
|
44
|
+
description: "`^[a-z0-9][a-z0-9-]{0,63}$`; the type name is `<plugin>:<name>`.",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "description",
|
|
48
|
+
type: "string",
|
|
49
|
+
required: true,
|
|
50
|
+
description: "When to use it; at most 4096 characters.",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "tools",
|
|
54
|
+
type: "array of tool keys",
|
|
55
|
+
required: false,
|
|
56
|
+
description: "Allowlist (at most 200); omitted = the parent's tools, `[]` = none.",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "disallowedTools",
|
|
60
|
+
type: "array of tool keys",
|
|
61
|
+
required: false,
|
|
62
|
+
description: "Tool keys removed (at most 200).",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "toolPolicies",
|
|
66
|
+
type: "object",
|
|
67
|
+
required: false,
|
|
68
|
+
description: "Tool key to `ask` or `deny`; can only tighten.",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "model",
|
|
72
|
+
type: "`inherit` \\| `fast` \\| `standard` \\| `capable`",
|
|
73
|
+
required: false,
|
|
74
|
+
description: "A purpose tier, never a model id; default `inherit`.",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "effort",
|
|
78
|
+
type: "`low` \\| `medium` \\| `high` \\| `xhigh` \\| `max`",
|
|
79
|
+
required: false,
|
|
80
|
+
description: "Reasoning effort.",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "maxSteps",
|
|
84
|
+
type: "integer",
|
|
85
|
+
required: false,
|
|
86
|
+
description: "Step limit; the kernel caps it.",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "skills",
|
|
90
|
+
type: "array of skill names",
|
|
91
|
+
required: false,
|
|
92
|
+
description: "At most 3, preloaded (pinned).",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "selectedSkills",
|
|
96
|
+
type: "array of skill names",
|
|
97
|
+
required: false,
|
|
98
|
+
description: "At most 32 further skills the agent may use.",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "connectors",
|
|
102
|
+
type: "array of objects",
|
|
103
|
+
required: false,
|
|
104
|
+
description: "At most 16: `{ template, tools?, policies? }` for a connector template (`<id>` or `<plugin>:<id>`), or `{ needs: { protocol, endpoint, tools, label }, policies? }` for a described need.",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "subagents",
|
|
108
|
+
type: "object",
|
|
109
|
+
required: false,
|
|
110
|
+
description: "`{ general?, types?, inline? }`: the sub-agents in its roster (at most 16 types plus inline).",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "modelRef",
|
|
114
|
+
type: "string",
|
|
115
|
+
required: false,
|
|
116
|
+
description: "`provider/model`; set on org templates saved from an agent.",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "quickActions",
|
|
120
|
+
type: "array of objects",
|
|
121
|
+
required: false,
|
|
122
|
+
description: "At most 9 `{ title, body }` shortcuts.",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "instructionFiles",
|
|
126
|
+
type: "array of objects",
|
|
127
|
+
required: false,
|
|
128
|
+
description: "At most 20 `{ name, content }` files.",
|
|
129
|
+
},
|
|
130
|
+
];
|
|
21
131
|
/** The name rule of a template (the catalog item name rule). */
|
|
22
132
|
export const AGENT_TEMPLATE_NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
23
133
|
const TIERS = ["inherit", "fast", "standard", "capable"];
|
|
24
134
|
const EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
25
|
-
|
|
135
|
+
/** Room for sub-agents described inline (up to 16 x 100 000 characters of instructions, P4-1). */
|
|
136
|
+
const FRONTMATTER_MAX_CHARS = 2 * 1024 * 1024;
|
|
26
137
|
/** The kernel's tool key rule (model-visible keys): no dots, no whitespace, at most 64 chars. */
|
|
27
138
|
const TOOL_KEY_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
139
|
+
const distinct = (v) => new Set(v).size === v.length;
|
|
140
|
+
const NAME_REF_RE = /^[a-z0-9][a-z0-9-]{0,63}(:[a-z0-9][a-z0-9-]{0,63})?$/;
|
|
141
|
+
const MODEL_REF_RE = /^[^/\s]+\/\S+$/;
|
|
142
|
+
/** The name rule of an instruction file (the agent's rule, mirrored). */
|
|
143
|
+
export const AGENT_TEMPLATE_INSTRUCTION_FILE_NAME_RE = /^[^/\\]{1,64}\.md$/;
|
|
28
144
|
const chars = (s) => [...s].length;
|
|
29
145
|
const isToolKey = (v) => typeof v === "string" && TOOL_KEY_RE.test(v);
|
|
30
146
|
const toolList = (v) => Array.isArray(v) &&
|
|
@@ -112,12 +228,255 @@ export function parseAgentTemplate(text) {
|
|
|
112
228
|
}
|
|
113
229
|
if (fm.skills !== undefined) {
|
|
114
230
|
const s = fm.skills;
|
|
115
|
-
if (Array.isArray(s) && s.length <= L.skills && s.every(isSkillName))
|
|
231
|
+
if (Array.isArray(s) && s.length <= L.skills && s.every(isSkillName) && distinct(s))
|
|
116
232
|
out.skills = s;
|
|
117
233
|
else
|
|
118
234
|
errors.push(`skills must list at most ${L.skills} skill names`);
|
|
119
235
|
}
|
|
236
|
+
if (fm.selectedSkills !== undefined) {
|
|
237
|
+
const s = fm.selectedSkills;
|
|
238
|
+
if (Array.isArray(s) && s.length <= L.selectedSkills && s.every(isSkillName) && distinct(s)) {
|
|
239
|
+
out.selectedSkills = s;
|
|
240
|
+
}
|
|
241
|
+
else
|
|
242
|
+
errors.push(`selectedSkills must list at most ${L.selectedSkills} skill names`);
|
|
243
|
+
}
|
|
244
|
+
if (fm.connectors !== undefined) {
|
|
245
|
+
const c = readConnectors(fm.connectors, errors);
|
|
246
|
+
if (c)
|
|
247
|
+
out.connectors = c;
|
|
248
|
+
}
|
|
249
|
+
if (fm.subagents !== undefined) {
|
|
250
|
+
const s = readSubagents(fm.subagents, errors);
|
|
251
|
+
if (s)
|
|
252
|
+
out.subagents = s;
|
|
253
|
+
}
|
|
254
|
+
if (fm.modelRef !== undefined) {
|
|
255
|
+
if (typeof fm.modelRef === "string" &&
|
|
256
|
+
fm.modelRef.length <= 200 &&
|
|
257
|
+
MODEL_REF_RE.test(fm.modelRef)) {
|
|
258
|
+
out.modelRef = fm.modelRef;
|
|
259
|
+
}
|
|
260
|
+
else
|
|
261
|
+
errors.push("modelRef must be <provider>/<model>");
|
|
262
|
+
}
|
|
263
|
+
if (fm.quickActions !== undefined) {
|
|
264
|
+
const q = readQuickActions(fm.quickActions, errors);
|
|
265
|
+
if (q)
|
|
266
|
+
out.quickActions = q;
|
|
267
|
+
}
|
|
268
|
+
if (fm.instructionFiles !== undefined) {
|
|
269
|
+
const f = readInstructionFiles(fm.instructionFiles, errors);
|
|
270
|
+
if (f)
|
|
271
|
+
out.instructionFiles = f;
|
|
272
|
+
}
|
|
120
273
|
if (errors.length > 0)
|
|
121
274
|
return { errors };
|
|
122
275
|
return { template: out };
|
|
123
276
|
}
|
|
277
|
+
const isObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
278
|
+
const isStr = (v, max) => typeof v === "string" && v.trim() !== "" && chars(v) <= max;
|
|
279
|
+
const strList = (v, max, itemMax) => Array.isArray(v) && v.length <= max && v.every((x) => isStr(x, itemMax));
|
|
280
|
+
const connectorPolicies = (v) => isObj(v) &&
|
|
281
|
+
Object.keys(v).length <= AGENT_TEMPLATE_LIMITS.connectorTools &&
|
|
282
|
+
Object.entries(v).every(([k, p]) => isStr(k, 128) && (p === "ask" || p === "deny"));
|
|
283
|
+
const connectorPoliciesRule = `must map at most ${AGENT_TEMPLATE_LIMITS.connectorTools} tool names to ask or deny`;
|
|
284
|
+
function readConnectors(v, errors) {
|
|
285
|
+
const L = AGENT_TEMPLATE_LIMITS;
|
|
286
|
+
if (!Array.isArray(v) || v.length > L.connectors) {
|
|
287
|
+
errors.push(`connectors must be a list of at most ${L.connectors} entries`);
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
const out = [];
|
|
291
|
+
let ok = true;
|
|
292
|
+
v.forEach((e, i) => {
|
|
293
|
+
const at = `connectors[${i}]`;
|
|
294
|
+
if (!isObj(e)) {
|
|
295
|
+
errors.push(`${at} must be { template } or { needs }`);
|
|
296
|
+
ok = false;
|
|
297
|
+
}
|
|
298
|
+
else if ("template" in e) {
|
|
299
|
+
if (typeof e.template !== "string" || !NAME_REF_RE.test(e.template)) {
|
|
300
|
+
errors.push(`${at}.template must be a connector template id (<id> or <plugin>:<id>)`);
|
|
301
|
+
ok = false;
|
|
302
|
+
}
|
|
303
|
+
else if (e.tools !== undefined && !strList(e.tools, L.connectorTools, 128)) {
|
|
304
|
+
errors.push(`${at}.tools must list at most ${L.connectorTools} tool names`);
|
|
305
|
+
ok = false;
|
|
306
|
+
}
|
|
307
|
+
else if (e.policies !== undefined && !connectorPolicies(e.policies)) {
|
|
308
|
+
errors.push(`${at}.policies ${connectorPoliciesRule}`);
|
|
309
|
+
ok = false;
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
out.push({
|
|
313
|
+
template: e.template,
|
|
314
|
+
...(e.tools !== undefined && { tools: e.tools }),
|
|
315
|
+
...(e.policies !== undefined && {
|
|
316
|
+
policies: { ...e.policies },
|
|
317
|
+
}),
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else if ("needs" in e) {
|
|
322
|
+
const n = e.needs;
|
|
323
|
+
if (!isObj(n)) {
|
|
324
|
+
errors.push(`${at}.needs must be an object`);
|
|
325
|
+
ok = false;
|
|
326
|
+
}
|
|
327
|
+
else if (n.protocol !== "mcp" && n.protocol !== "openapi") {
|
|
328
|
+
errors.push(`${at}.needs.protocol must be mcp or openapi`);
|
|
329
|
+
ok = false;
|
|
330
|
+
}
|
|
331
|
+
else if (!isStr(n.endpoint, 2048)) {
|
|
332
|
+
errors.push(`${at}.needs.endpoint must be a non-empty string of at most 2048 characters`);
|
|
333
|
+
ok = false;
|
|
334
|
+
}
|
|
335
|
+
else if (!strList(n.tools, L.connectorTools, 128)) {
|
|
336
|
+
errors.push(`${at}.needs.tools must list at most ${L.connectorTools} tool names`);
|
|
337
|
+
ok = false;
|
|
338
|
+
}
|
|
339
|
+
else if (!isStr(n.label, 80)) {
|
|
340
|
+
errors.push(`${at}.needs.label must be a non-empty string of at most 80 characters`);
|
|
341
|
+
ok = false;
|
|
342
|
+
}
|
|
343
|
+
else if (e.policies !== undefined && !connectorPolicies(e.policies)) {
|
|
344
|
+
errors.push(`${at}.policies ${connectorPoliciesRule}`);
|
|
345
|
+
ok = false;
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
out.push({
|
|
349
|
+
needs: {
|
|
350
|
+
protocol: n.protocol,
|
|
351
|
+
endpoint: n.endpoint,
|
|
352
|
+
tools: n.tools,
|
|
353
|
+
label: n.label,
|
|
354
|
+
},
|
|
355
|
+
...(e.policies !== undefined && {
|
|
356
|
+
policies: { ...e.policies },
|
|
357
|
+
}),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
errors.push(`${at} must have template or needs`);
|
|
363
|
+
ok = false;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
return ok ? out : undefined;
|
|
367
|
+
}
|
|
368
|
+
function readSubagents(v, errors) {
|
|
369
|
+
const L = AGENT_TEMPLATE_LIMITS;
|
|
370
|
+
if (!isObj(v)) {
|
|
371
|
+
errors.push("subagents must be an object");
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
const out = {};
|
|
375
|
+
const n0 = errors.length;
|
|
376
|
+
if (v.general !== undefined) {
|
|
377
|
+
if (typeof v.general === "boolean")
|
|
378
|
+
out.general = v.general;
|
|
379
|
+
else
|
|
380
|
+
errors.push("subagents.general must be true or false");
|
|
381
|
+
}
|
|
382
|
+
if (v.types !== undefined) {
|
|
383
|
+
if (Array.isArray(v.types) &&
|
|
384
|
+
v.types.every((t) => typeof t === "string" && NAME_REF_RE.test(t)) &&
|
|
385
|
+
distinct(v.types)) {
|
|
386
|
+
out.types = v.types;
|
|
387
|
+
}
|
|
388
|
+
else
|
|
389
|
+
errors.push("subagents.types must list sub-agent type names");
|
|
390
|
+
}
|
|
391
|
+
if (v.inline !== undefined) {
|
|
392
|
+
if (!Array.isArray(v.inline))
|
|
393
|
+
errors.push("subagents.inline must be a list");
|
|
394
|
+
else {
|
|
395
|
+
const inline = [];
|
|
396
|
+
v.inline.forEach((e, i) => {
|
|
397
|
+
const at = `subagents.inline[${i}]`;
|
|
398
|
+
if (!isObj(e)) {
|
|
399
|
+
errors.push(`${at} must be an object`);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const before = errors.length;
|
|
403
|
+
if (typeof e.name !== "string" || !AGENT_TEMPLATE_NAME_RE.test(e.name)) {
|
|
404
|
+
errors.push(`${at}.name must match ^[a-z0-9][a-z0-9-]{0,63}$`);
|
|
405
|
+
}
|
|
406
|
+
if (!isStr(e.description, L.descriptionChars)) {
|
|
407
|
+
errors.push(`${at}.description must be a non-empty string of at most ${L.descriptionChars} characters`);
|
|
408
|
+
}
|
|
409
|
+
if (typeof e.instructions !== "string" ||
|
|
410
|
+
e.instructions.trim() === "" ||
|
|
411
|
+
chars(e.instructions) > L.inlineInstructionsChars) {
|
|
412
|
+
errors.push(`${at}.instructions must be a non-empty string of at most ${L.inlineInstructionsChars} characters`);
|
|
413
|
+
}
|
|
414
|
+
if (e.tools !== undefined && !toolList(e.tools)) {
|
|
415
|
+
errors.push(`${at}.tools must be ${toolListRule}`);
|
|
416
|
+
}
|
|
417
|
+
if (!(Array.isArray(e.skills) &&
|
|
418
|
+
e.skills.length <= L.skills &&
|
|
419
|
+
e.skills.every(isSkillName) &&
|
|
420
|
+
distinct(e.skills))) {
|
|
421
|
+
errors.push(`${at}.skills must list at most ${L.skills} skill names`);
|
|
422
|
+
}
|
|
423
|
+
if (!TIERS.includes(e.model)) {
|
|
424
|
+
errors.push(`${at}.model must be one of ${TIERS.join(", ")}`);
|
|
425
|
+
}
|
|
426
|
+
if (errors.length === before)
|
|
427
|
+
inline.push(e);
|
|
428
|
+
});
|
|
429
|
+
out.inline = inline;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if ((out.types?.length ?? 0) + (out.inline?.length ?? 0) > L.subagentTypes) {
|
|
433
|
+
errors.push(`subagents.types and subagents.inline together must be at most ${L.subagentTypes}`);
|
|
434
|
+
}
|
|
435
|
+
return errors.length === n0 ? out : undefined;
|
|
436
|
+
}
|
|
437
|
+
function readQuickActions(v, errors) {
|
|
438
|
+
const L = AGENT_TEMPLATE_LIMITS;
|
|
439
|
+
if (!Array.isArray(v) || v.length > L.quickActions) {
|
|
440
|
+
errors.push(`quickActions must be a list of at most ${L.quickActions} entries`);
|
|
441
|
+
return undefined;
|
|
442
|
+
}
|
|
443
|
+
const n0 = errors.length;
|
|
444
|
+
const out = [];
|
|
445
|
+
v.forEach((e, i) => {
|
|
446
|
+
if (isObj(e) &&
|
|
447
|
+
isStr(e.title, L.quickActionTitleChars) &&
|
|
448
|
+
isStr(e.body, L.quickActionBodyChars)) {
|
|
449
|
+
out.push({ title: e.title, body: e.body });
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
errors.push(`quickActions[${i}] must have a title (at most ${L.quickActionTitleChars} characters) and a body (at most ${L.quickActionBodyChars})`);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
return errors.length === n0 ? out : undefined;
|
|
456
|
+
}
|
|
457
|
+
function readInstructionFiles(v, errors) {
|
|
458
|
+
const L = AGENT_TEMPLATE_LIMITS;
|
|
459
|
+
if (!Array.isArray(v) || v.length > L.instructionFiles) {
|
|
460
|
+
errors.push(`instructionFiles must be a list of at most ${L.instructionFiles} entries`);
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
const n0 = errors.length;
|
|
464
|
+
const out = [];
|
|
465
|
+
let total = 0;
|
|
466
|
+
v.forEach((e, i) => {
|
|
467
|
+
if (isObj(e) &&
|
|
468
|
+
typeof e.name === "string" &&
|
|
469
|
+
AGENT_TEMPLATE_INSTRUCTION_FILE_NAME_RE.test(e.name) &&
|
|
470
|
+
typeof e.content === "string") {
|
|
471
|
+
total += chars(e.content);
|
|
472
|
+
out.push({ name: e.name, content: e.content });
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
errors.push(`instructionFiles[${i}] must have a name (<name>.md) and a content string`);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
if (errors.length === n0 && total > L.instructionFilesChars) {
|
|
479
|
+
errors.push(`instructionFiles must total at most ${L.instructionFilesChars} characters`);
|
|
480
|
+
}
|
|
481
|
+
return errors.length === n0 ? out : undefined;
|
|
482
|
+
}
|
package/dist/config-ui.d.ts
CHANGED
|
@@ -105,7 +105,8 @@ export declare function ui<T extends z.ZodTypeAny>(schema: T, hints: ConfigUiHin
|
|
|
105
105
|
/**
|
|
106
106
|
* An extension config schema as a JSON Schema for forms (the input side, defaults included, no
|
|
107
107
|
* `$schema`):
|
|
108
|
-
* - meta `secret: true` becomes `"x-secret": true`, `hot: true` becomes `"x-hot": true`, `
|
|
108
|
+
* - meta `secret: true` becomes `"x-secret": true`, `hot: true` becomes `"x-hot": true`, `org`
|
|
109
|
+
* (`orgAccess`) becomes `"x-org"`, `ui`
|
|
109
110
|
* becomes `"x-ui"` (a JSON copy of the hints); `title` / `description` are filled from
|
|
110
111
|
* `x-ui.label.en` / `x-ui.help.en` when the node has none;
|
|
111
112
|
* - unrepresentable types become `{ "x-unsupported": true }` instead of throwing;
|
package/dist/config-ui.js
CHANGED
|
@@ -48,7 +48,8 @@ const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
|
48
48
|
/**
|
|
49
49
|
* An extension config schema as a JSON Schema for forms (the input side, defaults included, no
|
|
50
50
|
* `$schema`):
|
|
51
|
-
* - meta `secret: true` becomes `"x-secret": true`, `hot: true` becomes `"x-hot": true`, `
|
|
51
|
+
* - meta `secret: true` becomes `"x-secret": true`, `hot: true` becomes `"x-hot": true`, `org`
|
|
52
|
+
* (`orgAccess`) becomes `"x-org"`, `ui`
|
|
52
53
|
* becomes `"x-ui"` (a JSON copy of the hints); `title` / `description` are filled from
|
|
53
54
|
* `x-ui.label.en` / `x-ui.help.en` when the node has none;
|
|
54
55
|
* - unrepresentable types become `{ "x-unsupported": true }` instead of throwing;
|
|
@@ -68,6 +69,9 @@ export function configJsonSchema(schema) {
|
|
|
68
69
|
if (js.hot === true)
|
|
69
70
|
js["x-hot"] = true;
|
|
70
71
|
delete js.hot;
|
|
72
|
+
if (js.org !== undefined)
|
|
73
|
+
js["x-org"] = js.org;
|
|
74
|
+
delete js.org;
|
|
71
75
|
if ("ui" in js) {
|
|
72
76
|
// A JSON copy: hints are data, and the scrub must never mutate the schema's meta.
|
|
73
77
|
const hints = JSON.parse(JSON.stringify(js.ui ?? null));
|
|
@@ -275,8 +279,21 @@ export function validateConfigUi(schema, level) {
|
|
|
275
279
|
}
|
|
276
280
|
return node;
|
|
277
281
|
};
|
|
278
|
-
const visit = (node, path, isRoot) => {
|
|
282
|
+
const visit = (node, path, isRoot, depth) => {
|
|
279
283
|
const report = (message) => problems.push({ path, message });
|
|
284
|
+
if (node["x-org"] !== undefined) {
|
|
285
|
+
const org = node["x-org"];
|
|
286
|
+
if (level !== "deployment")
|
|
287
|
+
report(`x-org is only allowed at the deployment level, not ${level}`);
|
|
288
|
+
else if (depth !== 1)
|
|
289
|
+
report("x-org is only allowed on a top-level field of the config");
|
|
290
|
+
else if (org !== "readonly" && org !== "override") {
|
|
291
|
+
report(`x-org must be "readonly" or "override", not ${JSON.stringify(org)}`);
|
|
292
|
+
}
|
|
293
|
+
else if (org === "readonly" && node["x-secret"] === true) {
|
|
294
|
+
report('x-org "readonly" on a secret field: an org never sees a secret, so there is nothing to show');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
280
297
|
const text = (value, what, max, plainOk = false) => {
|
|
281
298
|
if (value === undefined)
|
|
282
299
|
return;
|
|
@@ -316,13 +333,14 @@ export function validateConfigUi(schema, level) {
|
|
|
316
333
|
if (isObj(props)) {
|
|
317
334
|
for (const [k, child] of Object.entries(props)) {
|
|
318
335
|
if (isObj(child))
|
|
319
|
-
visit(child, path === "" ? k : `${path}.${k}`, false);
|
|
336
|
+
visit(child, path === "" ? k : `${path}.${k}`, false, depth + 1);
|
|
320
337
|
}
|
|
321
338
|
}
|
|
322
|
-
if (isObj(shape.items))
|
|
323
|
-
visit(shape.items, path === "" ? "items" : `${path}.items`, false);
|
|
339
|
+
if (isObj(shape.items)) {
|
|
340
|
+
visit(shape.items, path === "" ? "items" : `${path}.items`, false, depth + 1);
|
|
341
|
+
}
|
|
324
342
|
if (isObj(shape.additionalProperties)) {
|
|
325
|
-
visit(shape.additionalProperties, path === "" ? "additionalProperties" : `${path}.additionalProperties`, false);
|
|
343
|
+
visit(shape.additionalProperties, path === "" ? "additionalProperties" : `${path}.additionalProperties`, false, depth + 1);
|
|
326
344
|
}
|
|
327
345
|
};
|
|
328
346
|
const checkHints = (h, node, isRoot, report, text) => {
|
|
@@ -573,7 +591,7 @@ export function validateConfigUi(schema, level) {
|
|
|
573
591
|
});
|
|
574
592
|
}
|
|
575
593
|
};
|
|
576
|
-
visit(schema, "", true);
|
|
594
|
+
visit(schema, "", true, 0);
|
|
577
595
|
if (hintsBytes > L.hintsBytes) {
|
|
578
596
|
problems.push({
|
|
579
597
|
path: "",
|
package/dist/ctx.d.ts
CHANGED
|
@@ -109,7 +109,9 @@ export interface ExtensionContext {
|
|
|
109
109
|
* manifest's `requires` / `optional`. Without `orgId` it is the deployment-level view and returns
|
|
110
110
|
* synchronously; with `orgId` it returns a Promise, and orgScoped extension points (such as
|
|
111
111
|
* `channel`, `directory`) are filtered by that org's enablement (queried every time, not cached);
|
|
112
|
-
* `
|
|
112
|
+
* with `orgId`, a provider whose effective config is not valid in that org is excluded too;
|
|
113
|
+
* `get` rejects with `ExtensionPointUnavailableError` when the provider is not enabled in that
|
|
114
|
+
* org or its config is not valid there.
|
|
113
115
|
* When there is no provider, `get` throws `ExtensionPointUnavailableError` and `list` returns an
|
|
114
116
|
* empty list.
|
|
115
117
|
*/
|
package/dist/directory.d.ts
CHANGED
|
@@ -15,11 +15,20 @@ export interface Directory {
|
|
|
15
15
|
* treats the directory as "not configured": the hourly scan, `directory_changed` and the
|
|
16
16
|
* pre-removal recheck are skipped silently (no job, no audit, no warning), and a manual sync by an
|
|
17
17
|
* admin returns 409 `directory.not_configured`. When not implemented, any config that satisfies
|
|
18
|
-
* the schema counts as configured. Must be a pure function (no requests, never throws).
|
|
18
|
+
* the schema counts as configured. Must be a pure function (no requests, never throws). `config`
|
|
19
|
+
* is the effective deployment config for the org (see `listMembers`' `config`).
|
|
19
20
|
*/
|
|
20
|
-
configured?(orgConfig: unknown): boolean;
|
|
21
|
+
configured?(orgConfig: unknown, config?: unknown): boolean;
|
|
21
22
|
listMembers(args: {
|
|
22
23
|
orgConfig: unknown;
|
|
24
|
+
/**
|
|
25
|
+
* The extension's effective deployment config for this org: the platform's deployment config
|
|
26
|
+
* with the org's copy applied to the overridable fields (`orgAccess(…, "override")`), or the
|
|
27
|
+
* platform's config (equal to `ctx.config`) while the org follows it. Use it, not `ctx.config`,
|
|
28
|
+
* for work done for this org. Optional in the type so older extensions compile; the kernel
|
|
29
|
+
* always passes it.
|
|
30
|
+
*/
|
|
31
|
+
config?: unknown;
|
|
23
32
|
cursor?: string;
|
|
24
33
|
signal: AbortSignal;
|
|
25
34
|
}): Promise<{
|
|
@@ -4,11 +4,15 @@ export type ExtensionPointRef = `${string}@${number}`;
|
|
|
4
4
|
export type Cardinality = "single" | "multi";
|
|
5
5
|
/** Registry entry of an extension point. */
|
|
6
6
|
export interface ExtensionPointInfo {
|
|
7
|
+
/** What the extension point is for, in one sentence (served to agents by the Platform tool). */
|
|
8
|
+
description: string;
|
|
7
9
|
cardinality: Cardinality;
|
|
8
10
|
/** Brokered by the platform API (extensions may not `requires` it; they use it only through `ctx`). */
|
|
9
11
|
brokered: boolean;
|
|
10
12
|
/** Enabled per org (consumers may pass `orgId` to check that the org enabled the provider). */
|
|
11
13
|
orgScoped: boolean;
|
|
14
|
+
/** The majors of this point the kernel accepts; a plugin providing any other major is refused. */
|
|
15
|
+
majors: readonly number[];
|
|
12
16
|
/**
|
|
13
17
|
* How the kernel picks one provider among several: `org-default` means each org chooses its
|
|
14
18
|
* default provider (absent: no selection, every enabled provider is used).
|
package/dist/extension-point.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
const info = (cardinality, brokered = false, orgScoped = false, selection) => ({
|
|
1
|
+
const info = (majors, description, cardinality, brokered = false, orgScoped = false, selection) => ({
|
|
2
|
+
majors,
|
|
3
|
+
description,
|
|
2
4
|
cardinality,
|
|
3
5
|
brokered,
|
|
4
6
|
orgScoped,
|
|
@@ -6,19 +8,19 @@ const info = (cardinality, brokered = false, orgScoped = false, selection) => ({
|
|
|
6
8
|
});
|
|
7
9
|
/** The registered extension points. */
|
|
8
10
|
export const EXTENSION_POINTS = {
|
|
9
|
-
sandbox: info("single", true),
|
|
10
|
-
"config-source": info("single"),
|
|
11
|
-
"secret-store": info("single", true),
|
|
12
|
-
"object-store": info("single", true),
|
|
13
|
-
channel: info("multi", false, true),
|
|
14
|
-
"model-provider": info("multi", true),
|
|
15
|
-
"identity-source": info("multi"),
|
|
16
|
-
directory: info("multi", false, true),
|
|
17
|
-
counter: info("multi"),
|
|
18
|
-
connector: info("multi", true),
|
|
19
|
-
"file-preview": info("multi", false, true),
|
|
20
|
-
"web-search": info("multi", true, true, "org-default"),
|
|
21
|
-
"web-fetch": info("multi", true, true, "org-default"),
|
|
11
|
+
sandbox: info([2], "Runs an environment's instance and keeps its files for agents' commands (Docker, E2B).", "single", true),
|
|
12
|
+
"config-source": info([1], "Supplies deployment configuration from an external store such as Nacos.", "single"),
|
|
13
|
+
"secret-store": info([1], "Stores and resolves secrets (connector credentials, secret config) outside the database.", "single", true),
|
|
14
|
+
"object-store": info([1], "Stores files and artifacts in S3-compatible object storage, with presigned uploads.", "single", true),
|
|
15
|
+
channel: info([1], "Connects an instant-messaging platform (Slack, Lark): inbound events, replies and cards.", "multi", false, true),
|
|
16
|
+
"model-provider": info([1, 2], "Calls a model vendor's API and streams the result back to the runtime.", "multi", true),
|
|
17
|
+
"identity-source": info([1], "Signs people in through an external identity provider (SSO).", "multi"),
|
|
18
|
+
directory: info([1], "Syncs members and departments from an external company directory.", "multi", false, true),
|
|
19
|
+
counter: info([1], "Counts usage so quotas and reports can be built on it.", "multi"),
|
|
20
|
+
connector: info([1], "Speaks one protocol (MCP, OpenAPI) to external systems and exposes their tools to agents.", "multi", true),
|
|
21
|
+
"file-preview": info([1], "Renders previews of files of given types in the conversation UI.", "multi", false, true),
|
|
22
|
+
"web-search": info([1], "Searches the web for the WebSearch tool; each org picks a default provider.", "multi", true, true, "org-default"),
|
|
23
|
+
"web-fetch": info([1], "Fetches and extracts web pages for the WebFetch tool; each org picks a default provider.", "multi", true, true, "org-default"),
|
|
22
24
|
};
|
|
23
25
|
/** Cardinality of the registered extension points (derived from EXTENSION_POINTS). */
|
|
24
26
|
export const EXTENSION_POINT_CARDINALITY = Object.fromEntries(Object.entries(EXTENSION_POINTS).map(([k, v]) => [k, v.cardinality]));
|