@capybara-hq/kernel 0.1.0-rc.15 → 0.1.0-rc.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/channels/channels.schemas.d.ts +4 -0
- package/dist/channels/channels.schemas.js +2 -0
- package/dist/channels/connections.controller.d.ts +5 -3
- package/dist/channels/connections.controller.js +6 -4
- package/dist/channels/connections.service.d.ts +26 -0
- package/dist/channels/connections.service.js +79 -6
- package/dist/extensions/guide-places.d.ts +3 -1
- package/dist/extensions/guide-places.js +1 -1
- package/dist/extensions/permission-view.d.ts +4 -4
- package/dist/extensions/permission-view.js +7 -10
- package/package.json +3 -3
|
@@ -188,7 +188,7 @@ export declare function headsToResolve(entries: readonly MarketplaceEntry[], imp
|
|
|
188
188
|
/** The host versions (an entry's `minKernel` is judged against them). */
|
|
189
189
|
export declare const HOST_VERSIONS: {
|
|
190
190
|
readonly kernel: string;
|
|
191
|
-
readonly sdk: "0.1.0-rc.
|
|
191
|
+
readonly sdk: "0.1.0-rc.12";
|
|
192
192
|
};
|
|
193
193
|
/** Whether this deployment satisfies the entry's `minKernel`. */
|
|
194
194
|
export declare const entryCompatible: (e: Pick<MarketplaceEntry, "minKernel">) => boolean;
|
|
@@ -9,6 +9,10 @@ export declare const ChannelCreateSchema: z.ZodObject<{
|
|
|
9
9
|
anonymous: "anonymous";
|
|
10
10
|
bind: "bind";
|
|
11
11
|
}>>;
|
|
12
|
+
inboundMode: z.ZodOptional<z.ZodEnum<{
|
|
13
|
+
long_connection: "long_connection";
|
|
14
|
+
webhook: "webhook";
|
|
15
|
+
}>>;
|
|
12
16
|
}, z.core.$strict>;
|
|
13
17
|
export declare const ChannelPatchSchema: z.ZodObject<{
|
|
14
18
|
name: z.ZodOptional<z.ZodString>;
|
|
@@ -8,6 +8,8 @@ export const ChannelCreateSchema = z
|
|
|
8
8
|
agentId: z.string().min(1).max(100),
|
|
9
9
|
config: Config,
|
|
10
10
|
unboundPolicy: Policy.optional(),
|
|
11
|
+
// default long_connection; webhook needs plugin support and channels.webhookBaseUrl (409)
|
|
12
|
+
inboundMode: z.enum(["long_connection", "webhook"]).optional(),
|
|
11
13
|
})
|
|
12
14
|
.strict();
|
|
13
15
|
export const ChannelPatchSchema = z
|
|
@@ -7,7 +7,7 @@ import { PluginIcons } from "../extensions/plugin-icons.js";
|
|
|
7
7
|
import { type Principal } from "../identity/principal.js";
|
|
8
8
|
import { type OrgContext } from "../orgs/org.guard.js";
|
|
9
9
|
import { ChannelCreateSchema as CreateBody, ChannelGuideAckSchema as GuideAckBody, ChannelPatchSchema as PatchBody } from "./channels.schemas.js";
|
|
10
|
-
import { ChannelConnections, type ChannelConnectionView } from "./connections.service.js";
|
|
10
|
+
import { ChannelConnections, type ChannelConnectionView, type ChannelInboundInfo } from "./connections.service.js";
|
|
11
11
|
/**
|
|
12
12
|
* A connection as the org's pages show it: its plugin's display name (null when it has none) and
|
|
13
13
|
* the URL of its plugin's icon on the org's catalog route (null without one, or when the org may
|
|
@@ -36,13 +36,15 @@ export declare class ChannelConnectionsController {
|
|
|
36
36
|
channels: ChannelConnectionPage[];
|
|
37
37
|
}>;
|
|
38
38
|
/**
|
|
39
|
-
* The connection form's JSON Schema,
|
|
40
|
-
*
|
|
39
|
+
* The connection form's JSON Schema, where the plugin's setup guide is (`guide`, null when the
|
|
40
|
+
* plugin has none the org may see): 新建渠道 shows it once the plugin is picked; and whether its
|
|
41
|
+
* connections can use webhook inbound (`inbound`, channel inbound mode fields spec §4).
|
|
41
42
|
*/
|
|
42
43
|
schema(org: OrgContext, plugin: string): Promise<{
|
|
43
44
|
plugin: string;
|
|
44
45
|
schema: JsonSchema;
|
|
45
46
|
guide: Omit<SetupGuideRef, "version"> | null;
|
|
47
|
+
inbound: ChannelInboundInfo;
|
|
46
48
|
}>;
|
|
47
49
|
create(principal: Principal, org: OrgContext, body: z.infer<typeof CreateBody>): Promise<{
|
|
48
50
|
channel: ChannelConnectionPage;
|
|
@@ -16,7 +16,7 @@ import { createZodDto } from "../infra/validation/create-zod-dto.js";
|
|
|
16
16
|
import { ZBody } from "../infra/validation/zod-params.js";
|
|
17
17
|
import { CurrentOrg, OrgAction } from "../orgs/org.guard.js";
|
|
18
18
|
import { ChannelCreateSchema as CreateBody, ChannelGuideAckSchema as GuideAckBody, ChannelPatchSchema as PatchBody, } from "./channels.schemas.js";
|
|
19
|
-
import { ChannelConnections } from "./connections.service.js";
|
|
19
|
+
import { ChannelConnections, } from "./connections.service.js";
|
|
20
20
|
class ChannelCreateBody extends createZodDto(CreateBody, "ChannelCreateBody") {
|
|
21
21
|
}
|
|
22
22
|
class ChannelPatchBody extends createZodDto(PatchBody, "ChannelPatchBody") {
|
|
@@ -62,13 +62,15 @@ let ChannelConnectionsController = class ChannelConnectionsController {
|
|
|
62
62
|
return { channels: await this.withPlugins(org, await this.channels.list(org.orgId)) };
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
|
-
* The connection form's JSON Schema,
|
|
66
|
-
*
|
|
65
|
+
* The connection form's JSON Schema, where the plugin's setup guide is (`guide`, null when the
|
|
66
|
+
* plugin has none the org may see): 新建渠道 shows it once the plugin is picked; and whether its
|
|
67
|
+
* connections can use webhook inbound (`inbound`, channel inbound mode fields spec §4).
|
|
67
68
|
*/
|
|
68
69
|
async schema(org, plugin) {
|
|
69
70
|
const schema = this.channels.jsonSchema(plugin);
|
|
71
|
+
const inbound = this.channels.inboundInfo(plugin);
|
|
70
72
|
const g = (await this.guides.channelGuides([plugin], org)).get(plugin);
|
|
71
|
-
return { plugin, schema, guide: g ? { itemId: g.itemId, guideId: g.guideId } : null };
|
|
73
|
+
return { plugin, schema, guide: g ? { itemId: g.itemId, guideId: g.guideId } : null, inbound };
|
|
72
74
|
}
|
|
73
75
|
async create(principal, org, body) {
|
|
74
76
|
const p = requireUser(principal);
|
|
@@ -15,6 +15,11 @@ import { OrgLock } from "../orgs/org-lock.js";
|
|
|
15
15
|
import type { OrgRole } from "../orgs/roles.js";
|
|
16
16
|
import { ChannelJobs } from "./jobs.js";
|
|
17
17
|
import type { ChannelLocale } from "./phrases.js";
|
|
18
|
+
/**
|
|
19
|
+
* A public https address: https, and the host is not loopback, a private / link-local / reserved
|
|
20
|
+
* IP, `localhost`, `*.localhost`, `*.local`, `*.internal` or a single label (no DNS lookup).
|
|
21
|
+
*/
|
|
22
|
+
export declare function isPublicHttpsUrl(url: string): boolean;
|
|
18
23
|
/** 连接配置里全部密钥字段存成一行:`{ 点路径: 值 }` 的 JSON。 */
|
|
19
24
|
export declare const connectionSecretKey: (connectionId: string) => string;
|
|
20
25
|
/**
|
|
@@ -82,6 +87,18 @@ export interface CreateChannelInput {
|
|
|
82
87
|
agentId: string;
|
|
83
88
|
config: Record<string, unknown>;
|
|
84
89
|
unboundPolicy?: UnboundPolicy | undefined;
|
|
90
|
+
/** Default `long_connection`; `webhook` needs plugin support and `channels.webhookBaseUrl`. */
|
|
91
|
+
inboundMode?: InboundMode | undefined;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Whether a channel plugin's connections can use webhook inbound (channel inbound mode fields spec
|
|
95
|
+
* §4): the plugin implements `Channel.webhook`; the kernel accepts webhook mode (a usable
|
|
96
|
+
* `channels.webhookBaseUrl`); and that base URL is a public https address.
|
|
97
|
+
*/
|
|
98
|
+
export interface ChannelInboundInfo {
|
|
99
|
+
webhookSupported: boolean;
|
|
100
|
+
webhookAvailable: boolean;
|
|
101
|
+
webhookPublic: boolean;
|
|
85
102
|
}
|
|
86
103
|
/** `config` 里的密钥字段:缺省 = 保留原值、`null` = 清除、其他值 = 新值(同组织插件配置)。 */
|
|
87
104
|
export interface UpdateChannelInput {
|
|
@@ -163,6 +180,15 @@ export declare class ChannelConnections implements OnApplicationShutdown {
|
|
|
163
180
|
* `channels.webhookBaseUrl` (otherwise there is no URL to give the platform): 409 otherwise.
|
|
164
181
|
*/
|
|
165
182
|
private assertWebhookAvailable;
|
|
183
|
+
/**
|
|
184
|
+
* The fields the inbound mode requires (`x-ui.inboundModes`, channel inbound mode fields spec
|
|
185
|
+
* §3) must have a value in `full` (the parsed config, stored secrets included): 400
|
|
186
|
+
* `extension.config_invalid` with one issue per missing field otherwise. A plugin without the
|
|
187
|
+
* hint passes.
|
|
188
|
+
*/
|
|
189
|
+
private assertModeFields;
|
|
190
|
+
/** Whether the plugin's connections can use webhook inbound (spec §4); `extension.not_found`. */
|
|
191
|
+
inboundInfo(plugin: string): ChannelInboundInfo;
|
|
166
192
|
private viewOf;
|
|
167
193
|
private live;
|
|
168
194
|
list(orgId: string): Promise<ChannelConnectionView[]>;
|
|
@@ -8,7 +8,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
8
8
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
9
|
};
|
|
10
10
|
import { randomUUID } from "node:crypto";
|
|
11
|
-
import {
|
|
11
|
+
import { isIP } from "node:net";
|
|
12
|
+
import { CHANNEL_EXTENSION_POINT, missingInboundModeFields, } from "@capybara-hq/plugin-sdk";
|
|
12
13
|
import { Inject, Injectable } from "@nestjs/common";
|
|
13
14
|
import { sql } from "kysely";
|
|
14
15
|
import { configFields, getPath, secretScalars, secretValues } from "../config/fields.js";
|
|
@@ -28,11 +29,35 @@ import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
|
28
29
|
import { KernelEvents } from "../infra/events/kernel-events.js";
|
|
29
30
|
import { KERNEL_BUS, KERNEL_LOGGER, KEYRING, REDIS } from "../infra/tokens.js";
|
|
30
31
|
import { createRedactor } from "../log.js";
|
|
32
|
+
import { isForbiddenAddress, isLoopbackHost, normalizeHost } from "../net/public-address.js";
|
|
31
33
|
import { OrgLock } from "../orgs/org-lock.js";
|
|
32
34
|
import { readOrgSettings } from "../orgs/settings.service.js";
|
|
33
35
|
import { adaptToFeatures } from "./features.js";
|
|
34
36
|
import { ChannelJobs } from "./jobs.js";
|
|
35
37
|
import { webhookBase, webhookUrlOf } from "./webhook-url.js";
|
|
38
|
+
/**
|
|
39
|
+
* A public https address: https, and the host is not loopback, a private / link-local / reserved
|
|
40
|
+
* IP, `localhost`, `*.localhost`, `*.local`, `*.internal` or a single label (no DNS lookup).
|
|
41
|
+
*/
|
|
42
|
+
export function isPublicHttpsUrl(url) {
|
|
43
|
+
let u;
|
|
44
|
+
try {
|
|
45
|
+
u = new URL(url);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (u.protocol !== "https:")
|
|
51
|
+
return false;
|
|
52
|
+
const host = normalizeHost(u.hostname);
|
|
53
|
+
if (isLoopbackHost(host))
|
|
54
|
+
return false;
|
|
55
|
+
if (isIP(host) !== 0)
|
|
56
|
+
return !isForbiddenAddress(host);
|
|
57
|
+
if (!host.includes("."))
|
|
58
|
+
return false;
|
|
59
|
+
return ![".localhost", ".local", ".internal"].some((s) => host.endsWith(s));
|
|
60
|
+
}
|
|
36
61
|
/** 内核 secrets 命名空间(插件名不能取 `kernel`)。 */
|
|
37
62
|
const SECRETS_NAMESPACE = "kernel";
|
|
38
63
|
/** 连接配置里全部密钥字段存成一行:`{ 点路径: 值 }` 的 JSON。 */
|
|
@@ -305,6 +330,38 @@ let ChannelConnections = class ChannelConnections {
|
|
|
305
330
|
throw new ApiError("channel.webhook_unavailable", "Webhook inbound needs channels.webhookBaseUrl to be configured");
|
|
306
331
|
}
|
|
307
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* The fields the inbound mode requires (`x-ui.inboundModes`, channel inbound mode fields spec
|
|
335
|
+
* §3) must have a value in `full` (the parsed config, stored secrets included): 400
|
|
336
|
+
* `extension.config_invalid` with one issue per missing field otherwise. A plugin without the
|
|
337
|
+
* hint passes.
|
|
338
|
+
*/
|
|
339
|
+
assertModeFields(plugin, schema, mode, full) {
|
|
340
|
+
const js = this.formSchema(plugin, this.versionOf(plugin), schema)?.schema;
|
|
341
|
+
if (js === undefined)
|
|
342
|
+
return;
|
|
343
|
+
const missing = missingInboundModeFields(js, mode, (field) => {
|
|
344
|
+
const v = getPath(full, field);
|
|
345
|
+
return v !== undefined && v !== null && v !== "";
|
|
346
|
+
});
|
|
347
|
+
if (missing.length === 0)
|
|
348
|
+
return;
|
|
349
|
+
throw new ApiError("extension.config_invalid", `Invalid channel connection config for extension ${plugin}`, missing.map((field) => ({
|
|
350
|
+
path: field,
|
|
351
|
+
segments: [field],
|
|
352
|
+
message: `required in ${mode} mode`,
|
|
353
|
+
})));
|
|
354
|
+
}
|
|
355
|
+
/** Whether the plugin's connections can use webhook inbound (spec §4); `extension.not_found`. */
|
|
356
|
+
inboundInfo(plugin) {
|
|
357
|
+
const webhookSupported = this.channel(plugin).webhook !== undefined;
|
|
358
|
+
const base = this.webhookBase();
|
|
359
|
+
return {
|
|
360
|
+
webhookSupported,
|
|
361
|
+
webhookAvailable: webhookSupported && base !== undefined,
|
|
362
|
+
webhookPublic: webhookSupported && base !== undefined && isPublicHttpsUrl(base),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
308
365
|
async viewOf(row) {
|
|
309
366
|
return this.view(row, new Set(Object.keys(await this.readSecrets(row.org_id, row.id))));
|
|
310
367
|
}
|
|
@@ -331,14 +388,16 @@ let ChannelConnections = class ChannelConnections {
|
|
|
331
388
|
await this.assertEnabled(orgId, input.plugin);
|
|
332
389
|
const schema = channel.connectionSchema;
|
|
333
390
|
const full = this.parse(input.plugin, schema, input.config);
|
|
391
|
+
const mode = input.inboundMode ?? "long_connection";
|
|
392
|
+
if (mode === "webhook")
|
|
393
|
+
this.assertWebhookAvailable(input.plugin);
|
|
394
|
+
this.assertModeFields(input.plugin, schema, mode, full);
|
|
334
395
|
await this.assertAgent(orgId, input.agentId);
|
|
335
396
|
const verified = await this.verify(channel, full);
|
|
336
397
|
const id = randomUUID();
|
|
337
398
|
const { plain, secrets } = split(schema, full);
|
|
338
|
-
// the guide the 新建渠道 page showed: the plugin's current one, for the
|
|
339
|
-
const guideAck = await this.guides.currentAck(input.plugin, "channel-connection", {
|
|
340
|
-
mode: "long_connection",
|
|
341
|
-
});
|
|
399
|
+
// the guide the 新建渠道 page showed: the plugin's current one, for the mode picked there
|
|
400
|
+
const guideAck = await this.guides.currentAck(input.plugin, "channel-connection", { mode });
|
|
342
401
|
const row = await this.insertIn({
|
|
343
402
|
orgId,
|
|
344
403
|
id,
|
|
@@ -372,6 +431,7 @@ let ChannelConnections = class ChannelConnections {
|
|
|
372
431
|
agent_id: input.agentId,
|
|
373
432
|
config: JSON.stringify(a.plain),
|
|
374
433
|
unbound_policy: input.unboundPolicy ?? "bind",
|
|
434
|
+
inbound_mode: input.inboundMode ?? "long_connection",
|
|
375
435
|
bot_user_id: a.verified.botUserId,
|
|
376
436
|
tenant_id: a.verified.tenantId,
|
|
377
437
|
guide_ack: a.guideAck === null ? null : JSON.stringify(a.guideAck),
|
|
@@ -385,7 +445,11 @@ let ChannelConnections = class ChannelConnections {
|
|
|
385
445
|
orgId,
|
|
386
446
|
action: "channel.created",
|
|
387
447
|
target: id,
|
|
388
|
-
detail: {
|
|
448
|
+
detail: {
|
|
449
|
+
plugin: input.plugin,
|
|
450
|
+
agentId: input.agentId,
|
|
451
|
+
inboundMode: input.inboundMode ?? "long_connection",
|
|
452
|
+
},
|
|
389
453
|
});
|
|
390
454
|
return inserted;
|
|
391
455
|
}
|
|
@@ -437,6 +501,15 @@ let ChannelConnections = class ChannelConnections {
|
|
|
437
501
|
const modeChanged = patch.inboundMode !== undefined && patch.inboundMode !== current.inbound_mode;
|
|
438
502
|
if (modeChanged && patch.inboundMode === "webhook")
|
|
439
503
|
this.assertWebhookAvailable(current.plugin);
|
|
504
|
+
if (rotated || modeChanged) {
|
|
505
|
+
// the fields the effective mode requires (spec §3): the new config, else the stored one
|
|
506
|
+
const schema = this.channel(current.plugin).connectionSchema;
|
|
507
|
+
const full = rotated?.full ?? {
|
|
508
|
+
...(current.config ?? {}),
|
|
509
|
+
...(await this.readSecrets(orgId, id)),
|
|
510
|
+
};
|
|
511
|
+
this.assertModeFields(current.plugin, schema, patch.inboundMode ?? current.inbound_mode, full);
|
|
512
|
+
}
|
|
440
513
|
if (patch.agentId !== undefined)
|
|
441
514
|
await this.assertAgent(orgId, patch.agentId);
|
|
442
515
|
// A mode switch sets the platform app up for the new mode: the guide of that mode is the one
|
|
@@ -4,8 +4,10 @@ import type { KernelDb } from "../infra/database/kernel-db.js";
|
|
|
4
4
|
import type { OrgAction } from "../orgs/roles.js";
|
|
5
5
|
/** Why a known variable has no value: what the web and the `Platform` tool tell the reader. */
|
|
6
6
|
export type UnresolvedReason =
|
|
7
|
-
/** The target is not saved yet (the webhook URL
|
|
7
|
+
/** The target is not saved yet (the webhook URL). */
|
|
8
8
|
"not_saved"
|
|
9
|
+
/** No name typed yet on the new target's form (a connection name on 新建渠道). */
|
|
10
|
+
| "no_name"
|
|
9
11
|
/** `http.publicUrl` is not set (the URLs built from it). */
|
|
10
12
|
| "public_url"
|
|
11
13
|
/** `channels.webhookBaseUrl` is not set or unusable. */
|
|
@@ -58,7 +58,7 @@ export async function resolvePlace(ctx) {
|
|
|
58
58
|
const typed = ctx.query.name?.trim();
|
|
59
59
|
values.connectionName =
|
|
60
60
|
connection?.name ?? (typed === undefined || typed === "" ? null : typed);
|
|
61
|
-
reasons.connectionName = "
|
|
61
|
+
reasons.connectionName = "no_name";
|
|
62
62
|
if (connection !== undefined) {
|
|
63
63
|
target = { kind: "channel-connection", id: connection.id };
|
|
64
64
|
storedMode = connection.inbound_mode;
|
|
@@ -12,11 +12,11 @@ import type { ExtensionSource } from "./sources.js";
|
|
|
12
12
|
export declare function reviewHash(set: PermissionSet, warnings?: readonly PackageWarning[]): string;
|
|
13
13
|
/** 权限清单展示用的一组:按类别分组,`sensitive` 标出需要管理员特别留意的类别。 */
|
|
14
14
|
export interface PermissionGroup {
|
|
15
|
-
category: "provides" | "requires" | "optional" | "config" | "tables" | "secrets" | "storage" | "http" | "publicRoutes" | "tools" | "hooks" | "models" | "network" | "ui" | "client";
|
|
15
|
+
category: "provides" | "requires" | "optional" | "config" | "tables" | "secrets" | "storage" | "http" | "publicRoutes" | "tools" | "hooks" | "models" | "network" | "ui" | "components" | "client";
|
|
16
16
|
/**
|
|
17
|
-
* Sensitive category: secrets, public routes, tools, hooks, client code; also
|
|
18
|
-
* when it holds one of {@link SENSITIVE_EXTENSION_POINTS}, and the "UI" group
|
|
19
|
-
* embedded app (embed: the extension's own page in an iframe)
|
|
17
|
+
* Sensitive category: secrets, public routes, tools, hooks, client components, client code; also
|
|
18
|
+
* the "provides" group when it holds one of {@link SENSITIVE_EXTENSION_POINTS}, and the "UI" group
|
|
19
|
+
* (views and embeds) when it holds an embedded app (embed: the extension's own page in an iframe).
|
|
20
20
|
*/
|
|
21
21
|
sensitive: boolean;
|
|
22
22
|
items: string[];
|
|
@@ -38,6 +38,7 @@ const ALWAYS_SENSITIVE = new Set([
|
|
|
38
38
|
"publicRoutes",
|
|
39
39
|
"tools",
|
|
40
40
|
"hooks",
|
|
41
|
+
"components",
|
|
41
42
|
"client",
|
|
42
43
|
]);
|
|
43
44
|
const CATEGORY_REASON = {
|
|
@@ -45,13 +46,14 @@ const CATEGORY_REASON = {
|
|
|
45
46
|
publicRoutes: "public_routes",
|
|
46
47
|
tools: "tools",
|
|
47
48
|
hooks: "hooks",
|
|
49
|
+
components: "ui_component",
|
|
48
50
|
client: "client_code",
|
|
49
51
|
};
|
|
50
52
|
/**
|
|
51
53
|
* Why an item in a category is sensitive (undefined when it is not; shared by the grouped list, the
|
|
52
54
|
* per-item `added` and the CLI markers): secrets, public routes, tools, hooks and client code are
|
|
53
|
-
* always sensitive
|
|
54
|
-
*
|
|
55
|
+
* always sensitive, and so are client components (their own group, items are the component ids);
|
|
56
|
+
* {@link SENSITIVE_EXTENSION_POINTS} in "provides"; embedded apps (`embed:<id>`) in "UI".
|
|
55
57
|
*/
|
|
56
58
|
function itemReason(category, item) {
|
|
57
59
|
// storage 不算敏感:ctx.storage 的键由内核加前缀 `plugins/<插件>/<orgId>/`,插件只能读写自己的前缀
|
|
@@ -63,8 +65,6 @@ function itemReason(category, item) {
|
|
|
63
65
|
if (category === "ui") {
|
|
64
66
|
if (item.startsWith("embed:"))
|
|
65
67
|
return "ui_embed";
|
|
66
|
-
if (item.startsWith("component:"))
|
|
67
|
-
return "ui_component";
|
|
68
68
|
return undefined;
|
|
69
69
|
}
|
|
70
70
|
return ALWAYS_SENSITIVE.has(category) ? CATEGORY_REASON[category] : undefined;
|
|
@@ -89,7 +89,7 @@ function keyItem(key) {
|
|
|
89
89
|
["permissions.hooks:", "hooks", ""],
|
|
90
90
|
["permissions.hooks.policy:", "hooks", "policy:"],
|
|
91
91
|
["ui.embed:", "ui", "embed:"],
|
|
92
|
-
[UI_COMPONENT_KEY_PREFIX, "
|
|
92
|
+
[UI_COMPONENT_KEY_PREFIX, "components", ""],
|
|
93
93
|
["ui.view:", "ui", "view:"],
|
|
94
94
|
];
|
|
95
95
|
for (const [prefix, category, itemPrefix] of table) {
|
|
@@ -162,12 +162,9 @@ export function permissionGroups(set) {
|
|
|
162
162
|
{ category: "network", items: p.network },
|
|
163
163
|
{
|
|
164
164
|
category: "ui",
|
|
165
|
-
items: [
|
|
166
|
-
...ui.views.map((v) => `view:${v}`),
|
|
167
|
-
...ui.embeds.map((e) => `embed:${e}`),
|
|
168
|
-
...components.map((c) => `component:${c}`),
|
|
169
|
-
],
|
|
165
|
+
items: [...ui.views.map((v) => `view:${v}`), ...ui.embeds.map((e) => `embed:${e}`)],
|
|
170
166
|
},
|
|
167
|
+
{ category: "components", items: components },
|
|
171
168
|
{ category: "client", items: p.client === true ? [CLIENT_ITEM] : [] },
|
|
172
169
|
];
|
|
173
170
|
return groups
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capybara-hq/kernel",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.16",
|
|
4
4
|
"description": "Capybara kernel: config, database, plugin host and HTTP API of the Capybara agent platform",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"yaml": "^2.9.1",
|
|
66
66
|
"yauzl": "3.4.0",
|
|
67
67
|
"zod": "^4.6.7",
|
|
68
|
-
"@capybara-hq/plugin-sdk": "^0.1.0-rc.
|
|
68
|
+
"@capybara-hq/plugin-sdk": "^0.1.0-rc.12"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@langchain/langgraph-checkpoint-validation": "^1.2.0",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"@types/ws": "^8.18.2",
|
|
75
75
|
"@types/yauzl": "3.4.0",
|
|
76
76
|
"ws": "^8.22.0",
|
|
77
|
-
"@capybara-hq/testing": "0.1.0-rc.
|
|
77
|
+
"@capybara-hq/testing": "0.1.0-rc.15"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
80
|
"build": "tsc -b"
|