@capybara-hq/kernel 0.1.0-rc.14 → 0.1.0-rc.15
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/admin/admin.schemas.d.ts +5 -13
- package/dist/catalog/admin-catalog.controller.d.ts +27 -4
- package/dist/catalog/admin-catalog.controller.js +53 -5
- package/dist/catalog/appearance.schemas.d.ts +10 -0
- package/dist/catalog/appearance.schemas.js +20 -0
- package/dist/catalog/appearance.service.d.ts +105 -0
- package/dist/catalog/appearance.service.js +306 -0
- package/dist/catalog/catalog-import.module.js +3 -0
- package/dist/catalog/catalog-items.d.ts +11 -0
- package/dist/catalog/catalog-items.js +21 -0
- package/dist/catalog/catalog-scope.service.d.ts +7 -0
- package/dist/catalog/catalog-scope.service.js +55 -15
- package/dist/catalog/catalog.service.js +10 -2
- package/dist/catalog/cli.js +3 -0
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/org-extension.controller.d.ts +27 -7
- package/dist/catalog/org-extension.controller.js +48 -6
- package/dist/catalog/plugin-import.d.ts +28 -4
- package/dist/catalog/plugin-import.js +95 -19
- package/dist/catalog/plugin-package.d.ts +4 -2
- package/dist/catalog/plugin-package.js +29 -3
- package/dist/client/client-access.service.d.ts +101 -0
- package/dist/client/client-access.service.js +294 -0
- package/dist/client/client-builds.service.d.ts +109 -0
- package/dist/client/client-builds.service.js +229 -0
- package/dist/client/client-delivery.controller.d.ts +25 -0
- package/dist/client/client-delivery.controller.js +66 -0
- package/dist/client/client-files.d.ts +48 -0
- package/dist/client/client-files.js +136 -0
- package/dist/client/client-reports.cron.d.ts +13 -0
- package/dist/client/client-reports.cron.js +42 -0
- package/dist/client/client-reports.service.d.ts +30 -0
- package/dist/client/client-reports.service.js +102 -0
- package/dist/client/client.controller.d.ts +45 -0
- package/dist/client/client.controller.js +121 -0
- package/dist/client/client.module.d.ts +7 -0
- package/dist/client/client.module.js +26 -0
- package/dist/client/client.schemas.d.ts +55 -0
- package/dist/client/client.schemas.js +27 -0
- package/dist/ctx/create-ctx.d.ts +4 -3
- package/dist/ctx/create-ctx.js +4 -3
- package/dist/db/kernel-schema.d.ts +47 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.js +29 -0
- package/dist/extensions/admin-ops.d.ts +4 -1
- package/dist/extensions/admin-ops.js +22 -1
- package/dist/extensions/admin-view.d.ts +8 -0
- package/dist/extensions/admin-view.js +78 -0
- package/dist/extensions/blobs.d.ts +14 -1
- package/dist/extensions/blobs.js +48 -5
- package/dist/extensions/host.d.ts +6 -1
- package/dist/extensions/host.js +20 -9
- package/dist/extensions/package.d.ts +4 -3
- package/dist/extensions/package.js +5 -1
- package/dist/extensions/permission-set.d.ts +19 -3
- package/dist/extensions/permission-set.js +19 -2
- package/dist/extensions/permission-view.d.ts +38 -7
- package/dist/extensions/permission-view.js +27 -8
- package/dist/extensions/sources.d.ts +6 -0
- package/dist/extensions/sources.js +2 -0
- package/dist/extensions/state.module.js +9 -1
- package/dist/http/api.module.js +10 -0
- package/dist/identity/settings.d.ts +41 -26
- package/dist/identity/settings.js +87 -4
- package/dist/infra/throttler/named-throttlers.d.ts +4 -0
- package/dist/infra/throttler/named-throttlers.js +14 -0
- package/dist/kernel.module.js +3 -0
- package/dist/orgs/settings.service.d.ts +2 -26
- package/dist/platform/exclusions.js +27 -1
- package/dist/platform/ops/admin.ops.js +17 -2
- package/dist/platform/ops/catalog-admin.ops.d.ts +19 -1
- package/dist/platform/ops/catalog-admin.ops.js +85 -2
- package/dist/platform/ops/org-plugins.ops.d.ts +7 -1
- package/dist/platform/ops/org-plugins.ops.js +63 -4
- package/dist/platform/ops/org-settings.ops.js +2 -2
- package/dist/platform/ops/orgs.ops.js +1 -0
- package/dist/platform/ops/ui.ops.js +1 -1
- package/dist/platform/platform-ops.module.js +2 -0
- package/dist/platform/route-schemas.js +3 -0
- package/dist/roles/worker.module.js +4 -0
- package/dist/themes/render-css.d.ts +2 -1
- package/dist/themes/render-css.js +5 -2
- package/dist/themes/theme-delivery.service.d.ts +4 -2
- package/dist/themes/theme-delivery.service.js +8 -3
- package/dist/themes/theme-state.d.ts +28 -7
- package/dist/themes/theme-state.js +24 -6
- package/dist/themes/themes-public.controller.d.ts +7 -2
- package/dist/themes/themes-public.controller.js +11 -5
- package/dist/themes/themes.service.js +2 -2
- package/dist/tools/pipeline.d.ts +3 -2
- package/dist/ui/arrangement.d.ts +41 -50
- package/dist/ui/arrangement.js +88 -15
- package/dist/ui/registry.d.ts +13 -3
- package/dist/ui/registry.js +15 -4
- package/dist/ui/ui.schemas.d.ts +4 -0
- package/dist/ui/views.d.ts +36 -9
- package/dist/ui/views.js +58 -28
- package/package.json +3 -3
- package/src/migrations/0107_extension_client_builds.sql +50 -0
package/dist/extensions/host.js
CHANGED
|
@@ -1176,15 +1176,24 @@ let ExtensionHost = class ExtensionHost {
|
|
|
1176
1176
|
/**
|
|
1177
1177
|
* 写本副本的加载状态;失败只记 warn(状态是给后台看的,不影响加载)。这一行的版本没加载成(failed / pending)
|
|
1178
1178
|
* 而本副本仍有别的版本在跑时,记下那个版本(`servingVersion`,后台同时显示「新版失败、旧版仍在服务」)。
|
|
1179
|
+
* `servingSha256` is the package this replica actually serves for the plugin (client code follows
|
|
1180
|
+
* it, client extensions spec §9.3): the row's own package when loaded; for a failed / pending row
|
|
1181
|
+
* the package of the instance still running here (an older one, or the row's own package when it
|
|
1182
|
+
* stays loaded, e.g. beside a provider conflict), else null.
|
|
1179
1183
|
*/
|
|
1180
1184
|
async writeStatus(s) {
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1185
|
+
const waiting = s.state === "failed" || s.state === "pending";
|
|
1186
|
+
const cur = waiting ? this.registry.current(s.plugin) : undefined;
|
|
1187
|
+
const other = cur && (cur.sha256 !== s.sha256 || cur.version !== s.version) ? cur : null;
|
|
1188
|
+
s = {
|
|
1189
|
+
...s,
|
|
1190
|
+
...(waiting && s.servingVersion === undefined && { servingVersion: other?.version ?? null }),
|
|
1191
|
+
servingSha256: s.servingSha256 !== undefined
|
|
1192
|
+
? s.servingSha256
|
|
1193
|
+
: s.state === "loaded"
|
|
1194
|
+
? s.sha256
|
|
1195
|
+
: (cur?.sha256 ?? null),
|
|
1196
|
+
};
|
|
1188
1197
|
if (s.source === "install") {
|
|
1189
1198
|
if (s.state === "unloaded")
|
|
1190
1199
|
this.installRows.delete(s.plugin);
|
|
@@ -2103,8 +2112,10 @@ let ExtensionHost = class ExtensionHost {
|
|
|
2103
2112
|
*/
|
|
2104
2113
|
uiContributions() {
|
|
2105
2114
|
return this.registry.all().flatMap((i) => i.ui.map(({ contribution: c, slot, after }) => {
|
|
2106
|
-
|
|
2107
|
-
const
|
|
2115
|
+
// a view's, or a component's server view (client extensions spec §5.2); embeds have none
|
|
2116
|
+
const served = c.kind === "view" || c.kind === "component";
|
|
2117
|
+
const render = served ? i.uiViews.get(c.id) : undefined;
|
|
2118
|
+
const action = served ? i.uiActions.get(c.id) : undefined;
|
|
2108
2119
|
return {
|
|
2109
2120
|
plugin: i.name,
|
|
2110
2121
|
contribution: c,
|
|
@@ -29,10 +29,11 @@ export { HOST_ZOD_VERSION };
|
|
|
29
29
|
/** Unpack limits: `maxBytes` is `packages.maxBytes`; the rest default to {@link EXTENSION_PACKAGE_LIMITS}. */
|
|
30
30
|
export type ExtensionPackageLimits = PackageTgzLimits;
|
|
31
31
|
/**
|
|
32
|
-
* Codes of {@link ExtensionPackageError}: an invalid package, a tgz in the pre-0.1.0 format,
|
|
33
|
-
* plugin without an extension (nothing to install on the deployment)
|
|
32
|
+
* Codes of {@link ExtensionPackageError}: an invalid package, a tgz in the pre-0.1.0 format, a
|
|
33
|
+
* plugin without an extension (nothing to install on the deployment), or client code the
|
|
34
|
+
* extension manifest does not declare.
|
|
34
35
|
*/
|
|
35
|
-
export type ExtensionPackageErrorCode = "extension.package_invalid" | "extension.legacy_format" | "extension.no_extension";
|
|
36
|
+
export type ExtensionPackageErrorCode = "extension.package_invalid" | "extension.legacy_format" | "extension.no_extension" | "client.undeclared";
|
|
36
37
|
/** The extension package is invalid; `issues` lists the problems (English, shown to the admin). */
|
|
37
38
|
export declare class ExtensionPackageError extends Error {
|
|
38
39
|
readonly code: ExtensionPackageErrorCode;
|
|
@@ -111,7 +111,11 @@ function checkModuleGraph(entry, byPath) {
|
|
|
111
111
|
return issues;
|
|
112
112
|
}
|
|
113
113
|
/** Maps an error of `readPluginPackage` to the extension package error. */
|
|
114
|
-
const fromPluginPackageError = (e) => new ExtensionPackageError(e.issues, e.code === "catalog.legacy_format"
|
|
114
|
+
const fromPluginPackageError = (e) => new ExtensionPackageError(e.issues, e.code === "catalog.legacy_format"
|
|
115
|
+
? "extension.legacy_format"
|
|
116
|
+
: e.code === "client.undeclared"
|
|
117
|
+
? "client.undeclared"
|
|
118
|
+
: "extension.package_invalid");
|
|
115
119
|
/**
|
|
116
120
|
* Reads and validates an extension package without running any code from it: a plugin tgz
|
|
117
121
|
* (`readPluginPackage`) with an `extension` section, whose entry's module graph passes the import
|
|
@@ -42,12 +42,20 @@ export interface PermissionSet {
|
|
|
42
42
|
}[];
|
|
43
43
|
network: string[];
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
45
|
+
* Client code (`permissions.client`, client extensions spec §5.4): the plugin ships a browser
|
|
46
|
+
* bundle that runs with full trust in members' browsers. Sensitive and blocking. Older snapshots
|
|
47
|
+
* lack it (read as false).
|
|
48
|
+
*/
|
|
49
|
+
client: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* UI contributions (`permissions.ui`): `views` are recorded in the snapshot without blocking the
|
|
52
|
+
* load (added to the snapshot automatically on upgrade); `embeds` and `components` block the
|
|
53
|
+
* load like any other permission. Older snapshots lack `ui` or `components` (read as empty).
|
|
47
54
|
*/
|
|
48
55
|
ui: {
|
|
49
56
|
views: string[];
|
|
50
57
|
embeds: string[];
|
|
58
|
+
components: string[];
|
|
51
59
|
};
|
|
52
60
|
/**
|
|
53
61
|
* Setup check ids (`permissions.setupChecks`, setup guides v2 §10.2): recorded and shown at
|
|
@@ -100,7 +108,8 @@ export declare function buildPermissionSet(input: {
|
|
|
100
108
|
}): PermissionSet;
|
|
101
109
|
/**
|
|
102
110
|
* 权限集合的稳定字符串键(排序),如 `provides:channel@1`、`config.org.secret:apiKey`、`table:todos`、
|
|
103
|
-
* `permissions.tools:write`、`permissions.http.public:POST /events`、`ui.view:tasks`、`ui.embed:board
|
|
111
|
+
* `permissions.tools:write`、`permissions.http.public:POST /events`、`ui.view:tasks`、`ui.embed:board`,
|
|
112
|
+
* `permissions.client`, `ui.component:hud`.
|
|
104
113
|
* 容忍旧快照缺字段(按空处理),快照格式演进时不会抛错。
|
|
105
114
|
*/
|
|
106
115
|
export declare function permissionKeys(set: PermissionSet): string[];
|
|
@@ -109,6 +118,13 @@ export declare function permissionKeys(set: PermissionSet): string[];
|
|
|
109
118
|
* Lower-case letters and dots only, as catalog permission keys require (`PERMISSION_KEY_RE`).
|
|
110
119
|
*/
|
|
111
120
|
export declare const POLICY_HOOK_KEY_PREFIX = "permissions.hooks.policy:";
|
|
121
|
+
/**
|
|
122
|
+
* Permission key of client code (`permissions.client`): sensitive and blocking. The key, not the
|
|
123
|
+
* bundle's bytes, is reviewed: an upgrade that only changes the bundle adds no key.
|
|
124
|
+
*/
|
|
125
|
+
export declare const CLIENT_KEY = "permissions.client";
|
|
126
|
+
/** Key prefix of a client component (`ui.component:<id>`): sensitive and blocking. */
|
|
127
|
+
export declare const UI_COMPONENT_KEY_PREFIX = "ui.component:";
|
|
112
128
|
/** 渲染 schema 视图的权限键前缀:记入快照但不卡加载(见 {@link blockingKeys})。 */
|
|
113
129
|
export declare const UI_VIEW_KEY_PREFIX = "ui.view:";
|
|
114
130
|
/**
|
|
@@ -107,7 +107,12 @@ export function buildPermissionSet(input) {
|
|
|
107
107
|
.map((m) => ({ purpose: m.purpose, kind: m.kind }))
|
|
108
108
|
.sort((a, b) => cmp(a.purpose, b.purpose)),
|
|
109
109
|
network: sortedUnique(p.network),
|
|
110
|
-
|
|
110
|
+
client: p.client === true,
|
|
111
|
+
ui: {
|
|
112
|
+
views: sortedUnique(p.ui?.views),
|
|
113
|
+
embeds: sortedUnique(p.ui?.embeds),
|
|
114
|
+
components: sortedUnique(p.ui?.components),
|
|
115
|
+
},
|
|
111
116
|
// only when declared: sets (and snapshots) of plugins without setup checks stay as they were
|
|
112
117
|
...(p.setupChecks && p.setupChecks.length > 0
|
|
113
118
|
? { setupChecks: sortedUnique(p.setupChecks) }
|
|
@@ -118,7 +123,8 @@ export function buildPermissionSet(input) {
|
|
|
118
123
|
}
|
|
119
124
|
/**
|
|
120
125
|
* 权限集合的稳定字符串键(排序),如 `provides:channel@1`、`config.org.secret:apiKey`、`table:todos`、
|
|
121
|
-
* `permissions.tools:write`、`permissions.http.public:POST /events`、`ui.view:tasks`、`ui.embed:board
|
|
126
|
+
* `permissions.tools:write`、`permissions.http.public:POST /events`、`ui.view:tasks`、`ui.embed:board`,
|
|
127
|
+
* `permissions.client`, `ui.component:hud`.
|
|
122
128
|
* 容忍旧快照缺字段(按空处理),快照格式演进时不会抛错。
|
|
123
129
|
*/
|
|
124
130
|
export function permissionKeys(set) {
|
|
@@ -155,10 +161,14 @@ export function permissionKeys(set) {
|
|
|
155
161
|
keys.push(`permissions.models:${m.purpose} (${m.kind})`);
|
|
156
162
|
for (const d of p.network ?? [])
|
|
157
163
|
keys.push(`permissions.network:${d}`);
|
|
164
|
+
if (p.client)
|
|
165
|
+
keys.push(CLIENT_KEY);
|
|
158
166
|
for (const v of p.ui?.views ?? [])
|
|
159
167
|
keys.push(`${UI_VIEW_KEY_PREFIX}${v}`);
|
|
160
168
|
for (const e of p.ui?.embeds ?? [])
|
|
161
169
|
keys.push(`ui.embed:${e}`);
|
|
170
|
+
for (const c of p.ui?.components ?? [])
|
|
171
|
+
keys.push(`${UI_COMPONENT_KEY_PREFIX}${c}`);
|
|
162
172
|
for (const c of p.setupChecks ?? [])
|
|
163
173
|
keys.push(`${SETUP_CHECK_KEY_PREFIX}${c}`);
|
|
164
174
|
if (s.warningsDigest)
|
|
@@ -170,6 +180,13 @@ export function permissionKeys(set) {
|
|
|
170
180
|
* Lower-case letters and dots only, as catalog permission keys require (`PERMISSION_KEY_RE`).
|
|
171
181
|
*/
|
|
172
182
|
export const POLICY_HOOK_KEY_PREFIX = "permissions.hooks.policy:";
|
|
183
|
+
/**
|
|
184
|
+
* Permission key of client code (`permissions.client`): sensitive and blocking. The key, not the
|
|
185
|
+
* bundle's bytes, is reviewed: an upgrade that only changes the bundle adds no key.
|
|
186
|
+
*/
|
|
187
|
+
export const CLIENT_KEY = "permissions.client";
|
|
188
|
+
/** Key prefix of a client component (`ui.component:<id>`): sensitive and blocking. */
|
|
189
|
+
export const UI_COMPONENT_KEY_PREFIX = "ui.component:";
|
|
173
190
|
/** 渲染 schema 视图的权限键前缀:记入快照但不卡加载(见 {@link blockingKeys})。 */
|
|
174
191
|
export const UI_VIEW_KEY_PREFIX = "ui.view:";
|
|
175
192
|
/**
|
|
@@ -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";
|
|
15
|
+
category: "provides" | "requires" | "optional" | "config" | "tables" | "secrets" | "storage" | "http" | "publicRoutes" | "tools" | "hooks" | "models" | "network" | "ui" | "client";
|
|
16
16
|
/**
|
|
17
|
-
* Sensitive category: secrets, public routes, tools, hooks; also the "provides" group
|
|
18
|
-
* one of {@link SENSITIVE_EXTENSION_POINTS}, and the "UI" group when it holds an
|
|
19
|
-
* (embed: the extension's own page in an iframe).
|
|
17
|
+
* Sensitive category: secrets, public routes, tools, hooks, client code; also the "provides" group
|
|
18
|
+
* when it holds one of {@link SENSITIVE_EXTENSION_POINTS}, and the "UI" group when it holds an
|
|
19
|
+
* embedded app (embed: the extension's own page in an iframe) or a client component.
|
|
20
20
|
*/
|
|
21
21
|
sensitive: boolean;
|
|
22
22
|
items: string[];
|
|
@@ -25,10 +25,12 @@ export interface PermissionGroup {
|
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Sensitive reason codes (the UI translates them by code): secrets, public routes, tools, hooks,
|
|
28
|
-
* embedded apps, and
|
|
29
|
-
*
|
|
28
|
+
* embedded apps, client code and client components (client extensions spec §5.4: code that runs with
|
|
29
|
+
* full trust in members' browsers), and sensitive extension points
|
|
30
|
+
* ({@link SENSITIVE_EXTENSION_POINTS}; the code is the extension point name without `@<major>`, with
|
|
31
|
+
* `-` replaced by `_`).
|
|
30
32
|
*/
|
|
31
|
-
export type SensitiveReason = "secrets" | "public_routes" | "tools" | "hooks" | "ui_embed" | "channel" | "directory" | "identity_source" | "sandbox" | "object_store" | "migration_warnings";
|
|
33
|
+
export type SensitiveReason = "secrets" | "public_routes" | "tools" | "hooks" | "ui_embed" | "client_code" | "ui_component" | "channel" | "directory" | "identity_source" | "sandbox" | "object_store" | "migration_warnings";
|
|
32
34
|
/** 权限清单里的一项(分组里的一项,或 `added` 里的一个权限键)。 */
|
|
33
35
|
export interface PermissionItem {
|
|
34
36
|
value: string;
|
|
@@ -151,6 +153,35 @@ export interface AdminExtensionView {
|
|
|
151
153
|
ref: string;
|
|
152
154
|
reason: UiDropReason;
|
|
153
155
|
}[];
|
|
156
|
+
/**
|
|
157
|
+
* The client build that runs (client extensions spec §9.1, §9.3, §10.4): the one a live replica
|
|
158
|
+
* serves (the install's own package when it runs, else the older one still served beside an
|
|
159
|
+
* upgrade awaiting approval or failed), or the install record package's build when no replica
|
|
160
|
+
* serves one; its id (the package sha256), every file with its size, and the client API
|
|
161
|
+
* version; null when that package has no client code (or the view is degraded).
|
|
162
|
+
*/
|
|
163
|
+
client: {
|
|
164
|
+
build: string;
|
|
165
|
+
files: {
|
|
166
|
+
path: string;
|
|
167
|
+
bytes: number;
|
|
168
|
+
}[];
|
|
169
|
+
apiVersion: number;
|
|
170
|
+
} | null;
|
|
171
|
+
/**
|
|
172
|
+
* Crash and load reports of that build, newest first, at most 50: `orgId` is null for admin
|
|
173
|
+
* pages and may name an org deleted since (rows stay until the 30-day prune). Empty without a
|
|
174
|
+
* client build.
|
|
175
|
+
*/
|
|
176
|
+
clientReports: {
|
|
177
|
+
orgId: string | null;
|
|
178
|
+
contribution: string | null;
|
|
179
|
+
kind: string;
|
|
180
|
+
count: number;
|
|
181
|
+
firstAt: string;
|
|
182
|
+
lastAt: string;
|
|
183
|
+
lastMessage: string;
|
|
184
|
+
}[];
|
|
154
185
|
/**
|
|
155
186
|
* Only with status `conflict`: the single-provider extension point and the other extensions that
|
|
156
187
|
* provide it (they keep this one from loading; disabling them loads it, no restart).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { pluginSchemaName } from "../ctx/create-ctx.js";
|
|
3
3
|
import { isDestructiveSql, splitSqlStatements } from "../db/sql-statements.js";
|
|
4
|
-
import { permissionKeys, WARNINGS_KEY_PREFIX } from "./permission-set.js";
|
|
4
|
+
import { CLIENT_KEY, permissionKeys, UI_COMPONENT_KEY_PREFIX, WARNINGS_KEY_PREFIX, } from "./permission-set.js";
|
|
5
5
|
/**
|
|
6
6
|
* Review digest: the first 16 hex digits of `sha256(permissionKeys(set).join("\n"))`; with migration
|
|
7
7
|
* warnings their digest (kind, file, line and statement) is folded in, without warnings it equals the
|
|
@@ -30,23 +30,28 @@ export const SENSITIVE_EXTENSION_POINTS = [
|
|
|
30
30
|
"sandbox@2",
|
|
31
31
|
"object-store@1",
|
|
32
32
|
];
|
|
33
|
+
/** The one item of the "client" group (client extensions spec §5.4). */
|
|
34
|
+
const CLIENT_ITEM = "client code";
|
|
33
35
|
/** 总是敏感的类别。 */
|
|
34
36
|
const ALWAYS_SENSITIVE = new Set([
|
|
35
37
|
"secrets",
|
|
36
38
|
"publicRoutes",
|
|
37
39
|
"tools",
|
|
38
40
|
"hooks",
|
|
41
|
+
"client",
|
|
39
42
|
]);
|
|
40
43
|
const CATEGORY_REASON = {
|
|
41
44
|
secrets: "secrets",
|
|
42
45
|
publicRoutes: "public_routes",
|
|
43
46
|
tools: "tools",
|
|
44
47
|
hooks: "hooks",
|
|
48
|
+
client: "client_code",
|
|
45
49
|
};
|
|
46
50
|
/**
|
|
47
51
|
* Why an item in a category is sensitive (undefined when it is not; shared by the grouped list, the
|
|
48
|
-
* per-item `added` and the CLI markers): secrets, public routes, tools and
|
|
49
|
-
* sensitive; {@link SENSITIVE_EXTENSION_POINTS} in "provides"; embedded apps (`embed:<id>`)
|
|
52
|
+
* per-item `added` and the CLI markers): secrets, public routes, tools, hooks and client code are
|
|
53
|
+
* always sensitive; {@link SENSITIVE_EXTENSION_POINTS} in "provides"; embedded apps (`embed:<id>`)
|
|
54
|
+
* and client components (`component:<id>`) in "UI".
|
|
50
55
|
*/
|
|
51
56
|
function itemReason(category, item) {
|
|
52
57
|
// storage 不算敏感:ctx.storage 的键由内核加前缀 `plugins/<插件>/<orgId>/`,插件只能读写自己的前缀
|
|
@@ -55,8 +60,13 @@ function itemReason(category, item) {
|
|
|
55
60
|
? item.split("@")[0].replaceAll("-", "_")
|
|
56
61
|
: undefined;
|
|
57
62
|
}
|
|
58
|
-
if (category === "ui")
|
|
59
|
-
|
|
63
|
+
if (category === "ui") {
|
|
64
|
+
if (item.startsWith("embed:"))
|
|
65
|
+
return "ui_embed";
|
|
66
|
+
if (item.startsWith("component:"))
|
|
67
|
+
return "ui_component";
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
60
70
|
return ALWAYS_SENSITIVE.has(category) ? CATEGORY_REASON[category] : undefined;
|
|
61
71
|
}
|
|
62
72
|
const itemOf = (category, value) => {
|
|
@@ -68,6 +78,8 @@ function keyItem(key) {
|
|
|
68
78
|
const after = (prefix) => (key.startsWith(prefix) ? key.slice(prefix.length) : null);
|
|
69
79
|
if (key === "permissions.secrets")
|
|
70
80
|
return { category: "secrets", item: "secret store" };
|
|
81
|
+
if (key === CLIENT_KEY)
|
|
82
|
+
return { category: "client", item: CLIENT_ITEM };
|
|
71
83
|
if (/^config\.(deployment|org)\.secret:/.test(key))
|
|
72
84
|
return { category: "secrets", item: key.slice(key.indexOf(":") + 1) };
|
|
73
85
|
const table = [
|
|
@@ -77,6 +89,7 @@ function keyItem(key) {
|
|
|
77
89
|
["permissions.hooks:", "hooks", ""],
|
|
78
90
|
["permissions.hooks.policy:", "hooks", "policy:"],
|
|
79
91
|
["ui.embed:", "ui", "embed:"],
|
|
92
|
+
[UI_COMPONENT_KEY_PREFIX, "ui", "component:"],
|
|
80
93
|
["ui.view:", "ui", "view:"],
|
|
81
94
|
];
|
|
82
95
|
for (const [prefix, category, itemPrefix] of table) {
|
|
@@ -113,8 +126,9 @@ export function permissionKeyItems(keys) {
|
|
|
113
126
|
/** 把权限集合整理成分组清单(固定顺序,空组省略)。纯函数。 */
|
|
114
127
|
export function permissionGroups(set) {
|
|
115
128
|
const p = set.permissions;
|
|
116
|
-
//
|
|
117
|
-
const ui = p.ui ?? { views: [], embeds: [] };
|
|
129
|
+
// older snapshots lack `ui`, `ui.components` and `client` (read as empty / false)
|
|
130
|
+
const ui = p.ui ?? { views: [], embeds: [], components: [] };
|
|
131
|
+
const components = ui.components ?? [];
|
|
118
132
|
const secretFields = [...set.config.deployment, ...set.config.org]
|
|
119
133
|
.filter((f) => f.secret)
|
|
120
134
|
.map((f) => f.path || "<root>");
|
|
@@ -148,8 +162,13 @@ export function permissionGroups(set) {
|
|
|
148
162
|
{ category: "network", items: p.network },
|
|
149
163
|
{
|
|
150
164
|
category: "ui",
|
|
151
|
-
items: [
|
|
165
|
+
items: [
|
|
166
|
+
...ui.views.map((v) => `view:${v}`),
|
|
167
|
+
...ui.embeds.map((e) => `embed:${e}`),
|
|
168
|
+
...components.map((c) => `component:${c}`),
|
|
169
|
+
],
|
|
152
170
|
},
|
|
171
|
+
{ category: "client", items: p.client === true ? [CLIENT_ITEM] : [] },
|
|
153
172
|
];
|
|
154
173
|
return groups
|
|
155
174
|
.filter((g) => g.items.length > 0)
|
|
@@ -36,6 +36,12 @@ export interface ExtensionLoadStatus {
|
|
|
36
36
|
* 省略视同 null。
|
|
37
37
|
*/
|
|
38
38
|
servingVersion?: string | null;
|
|
39
|
+
/**
|
|
40
|
+
* The package sha256 this replica serves for the plugin: the row's own `sha256` when `loaded`,
|
|
41
|
+
* the older package still running beside a failed / pending version, null when none runs.
|
|
42
|
+
* Omitted counts as null.
|
|
43
|
+
*/
|
|
44
|
+
servingSha256?: string | null;
|
|
39
45
|
}
|
|
40
46
|
/** This replica's extension load status (PG `extension_replica_status`; each replica writes only its own rows). */
|
|
41
47
|
export declare class ReplicaStatusStore {
|
|
@@ -55,6 +55,7 @@ let ReplicaStatusStore = class ReplicaStatusStore {
|
|
|
55
55
|
reason: s.reason,
|
|
56
56
|
detail: s.detail === null ? null : this.cleanDetail(s.detail),
|
|
57
57
|
serving_version: s.servingVersion ?? null,
|
|
58
|
+
serving_sha256: s.servingSha256 ?? null,
|
|
58
59
|
http_replica_id: this.httpReplicaId ?? null,
|
|
59
60
|
heartbeat_at: sql `now()`,
|
|
60
61
|
};
|
|
@@ -69,6 +70,7 @@ let ReplicaStatusStore = class ReplicaStatusStore {
|
|
|
69
70
|
reason: values.reason,
|
|
70
71
|
detail: values.detail,
|
|
71
72
|
serving_version: values.serving_version,
|
|
73
|
+
serving_sha256: values.serving_sha256,
|
|
72
74
|
http_replica_id: values.http_replica_id,
|
|
73
75
|
heartbeat_at: values.heartbeat_at,
|
|
74
76
|
}))
|
|
@@ -5,6 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { Module } from "@nestjs/common";
|
|
8
|
+
import { ClientModule } from "../client/client.module.js";
|
|
8
9
|
import { SuperAdminLockModule } from "../identity/super-admin.js";
|
|
9
10
|
import { OrgLockModule } from "../orgs/org-lock.js";
|
|
10
11
|
import { StorageModule } from "../storage/storage.module.js";
|
|
@@ -24,7 +25,14 @@ let ExtensionStateModule = class ExtensionStateModule {
|
|
|
24
25
|
};
|
|
25
26
|
ExtensionStateModule = __decorate([
|
|
26
27
|
Module({
|
|
27
|
-
imports: [
|
|
28
|
+
imports: [
|
|
29
|
+
ExtensionRegistryModule,
|
|
30
|
+
SuperAdminLockModule,
|
|
31
|
+
OrgLockModule,
|
|
32
|
+
StorageModule,
|
|
33
|
+
// a package with client code stores its client build (`ExtensionBlobs`)
|
|
34
|
+
ClientModule,
|
|
35
|
+
],
|
|
28
36
|
providers: [ExtensionApprovals, ExtensionBlobs, ExtensionInstalls, EffectiveConfigs],
|
|
29
37
|
exports: [
|
|
30
38
|
ExtensionRegistryModule,
|
package/dist/http/api.module.js
CHANGED
|
@@ -19,6 +19,7 @@ import { AgentSaveAsTemplateController, AgentTemplatesController, } from "../age
|
|
|
19
19
|
import { BackgroundController } from "../background/background.controller.js";
|
|
20
20
|
import { BackgroundModule } from "../background/background.module.js";
|
|
21
21
|
import { AdminCatalogController } from "../catalog/admin-catalog.controller.js";
|
|
22
|
+
import { AppearanceModule } from "../catalog/appearance.service.js";
|
|
22
23
|
import { CatalogModule } from "../catalog/catalog.module.js";
|
|
23
24
|
import { AdminComponentsController, OrgComponentsController, } from "../catalog/component-scope.controller.js";
|
|
24
25
|
import { MarketplacesController, OrgMarketplacesController, } from "../catalog/marketplaces.controller.js";
|
|
@@ -27,6 +28,9 @@ import { OrgExtensionConfigsController, OrgExtensionOverridesController, OrgExte
|
|
|
27
28
|
import { OrgOwnedPluginsController, OrgPluginUploadsController, } from "../catalog/org-plugin-uploads.controller.js";
|
|
28
29
|
import { ChannelsModule } from "../channels/channels.module.js";
|
|
29
30
|
import { ChannelConnectionsController } from "../channels/connections.controller.js";
|
|
31
|
+
import { AdminClientController, OrgClientController } from "../client/client.controller.js";
|
|
32
|
+
import { ClientModule } from "../client/client.module.js";
|
|
33
|
+
import { ClientDeliveryController } from "../client/client-delivery.controller.js";
|
|
30
34
|
import { AttachmentsController } from "../conversations/attachments.controller.js";
|
|
31
35
|
import { ConversationsController } from "../conversations/conversations.controller.js";
|
|
32
36
|
import { ConversationsModule } from "../conversations/conversations.module.js";
|
|
@@ -151,6 +155,9 @@ const CONTROLLERS = [
|
|
|
151
155
|
ThemeDeliveryController,
|
|
152
156
|
PlatformThemeController,
|
|
153
157
|
DeviceAuthController,
|
|
158
|
+
ClientDeliveryController,
|
|
159
|
+
OrgClientController,
|
|
160
|
+
AdminClientController,
|
|
154
161
|
];
|
|
155
162
|
/**
|
|
156
163
|
* The api role (nest kernel spec §2): the kernel API (`/api/v1/*`), imported by `KernelModule` only
|
|
@@ -171,6 +178,8 @@ ApiModule = __decorate([
|
|
|
171
178
|
AdminModule,
|
|
172
179
|
ExtensionsModule,
|
|
173
180
|
CatalogModule,
|
|
181
|
+
// a plugin's suggested appearance (its own module: it writes through the org settings)
|
|
182
|
+
AppearanceModule,
|
|
174
183
|
ToolsModule,
|
|
175
184
|
AgentsModule,
|
|
176
185
|
ConversationsModule,
|
|
@@ -184,6 +193,7 @@ ApiModule = __decorate([
|
|
|
184
193
|
UiModule,
|
|
185
194
|
InboxModule,
|
|
186
195
|
ThemesModule,
|
|
196
|
+
ClientModule,
|
|
187
197
|
SetupModule,
|
|
188
198
|
],
|
|
189
199
|
controllers: CONTROLLERS,
|
|
@@ -44,6 +44,31 @@ export declare const UiThemeSetting: z.ZodObject<{
|
|
|
44
44
|
id: z.ZodNullable<z.ZodString>;
|
|
45
45
|
}, z.core.$strip>;
|
|
46
46
|
export type UiTheme = z.infer<typeof UiThemeSetting>;
|
|
47
|
+
/** The deployment setting key of the client code kill switch (client extensions spec §10.2). */
|
|
48
|
+
export declare const CLIENT_EXTENSIONS_SETTING = "clientExtensions";
|
|
49
|
+
/** The plugins listed in `clientExtensions.disabled`: at most this many. */
|
|
50
|
+
export declare const CLIENT_DISABLED_MAX = 200;
|
|
51
|
+
/**
|
|
52
|
+
* The client code kill switch (client extensions spec §10.2): `enabled: false` stops every plugin's
|
|
53
|
+
* client code (the shell reads list nothing, delivery is 404); `disabled` names the plugins whose
|
|
54
|
+
* client code alone is stopped while their server side keeps running. Every reader goes through
|
|
55
|
+
* {@link readClientExtensions} or {@link SettingsService.get}.
|
|
56
|
+
*/
|
|
57
|
+
export declare const ClientExtensionsSetting: z.ZodObject<{
|
|
58
|
+
enabled: z.ZodBoolean;
|
|
59
|
+
disabled: z.ZodArray<z.ZodString>;
|
|
60
|
+
}, z.core.$strip>;
|
|
61
|
+
export type ClientExtensionsKillSwitch = z.infer<typeof ClientExtensionsSetting>;
|
|
62
|
+
/** The kill switch's default (and what a missing or invalid stored value reads as): everything on. */
|
|
63
|
+
export declare const clientExtensionsDefault: () => ClientExtensionsKillSwitch;
|
|
64
|
+
/**
|
|
65
|
+
* Parses a stored `clientExtensions` value (the `deployment_settings` row's `value`, undefined when
|
|
66
|
+
* the row is missing): the default when missing; when invalid the default too, with a warning naming
|
|
67
|
+
* the key (a kill switch that fails open is worth a log line, as {@link SettingsService.get} logs
|
|
68
|
+
* every other invalid key). The one parser of the setting: the settings service and every access
|
|
69
|
+
* check read through it.
|
|
70
|
+
*/
|
|
71
|
+
export declare function parseClientExtensions(value: unknown, log?: Pick<KernelLogger, "warn">): ClientExtensionsKillSwitch;
|
|
47
72
|
export declare const DEPLOYMENT_SETTINGS: z.ZodObject<{
|
|
48
73
|
orgCreation: z.ZodDefault<z.ZodEnum<{
|
|
49
74
|
anyone: "anyone";
|
|
@@ -73,19 +98,11 @@ export declare const DEPLOYMENT_SETTINGS: z.ZodObject<{
|
|
|
73
98
|
uiTheme: z.ZodDefault<z.ZodObject<{
|
|
74
99
|
id: z.ZodNullable<z.ZodString>;
|
|
75
100
|
}, z.core.$strip>>;
|
|
76
|
-
uiArrangement: z.ZodDefault<z.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"file.preview": "file.preview";
|
|
82
|
-
"org.page": "org.page";
|
|
83
|
-
"org.settings.section": "org.settings.section";
|
|
84
|
-
"platform.page": "platform.page";
|
|
85
|
-
}> & z.core.$partial, z.ZodObject<{
|
|
86
|
-
hidden: z.ZodArray<z.ZodString>;
|
|
87
|
-
order: z.ZodArray<z.ZodString>;
|
|
88
|
-
}, z.core.$strict>>>;
|
|
101
|
+
uiArrangement: z.ZodDefault<z.ZodType<Partial<Record<"agent.detail.section" | "conversation.composer.frame" | "conversation.empty" | "conversation.panel" | "conversation.properties.section" | "conversation.tool-result.card" | "file.preview" | "org.page" | "org.settings.section" | "platform.page" | "shell.brand" | "shell.overlay", import("../ui/arrangement.js").UiArrangementEntry>>, unknown, z.core.$ZodTypeInternals<Partial<Record<"agent.detail.section" | "conversation.composer.frame" | "conversation.empty" | "conversation.panel" | "conversation.properties.section" | "conversation.tool-result.card" | "file.preview" | "org.page" | "org.settings.section" | "platform.page" | "shell.brand" | "shell.overlay", import("../ui/arrangement.js").UiArrangementEntry>>, unknown>>>;
|
|
102
|
+
clientExtensions: z.ZodDefault<z.ZodObject<{
|
|
103
|
+
enabled: z.ZodBoolean;
|
|
104
|
+
disabled: z.ZodArray<z.ZodString>;
|
|
105
|
+
}, z.core.$strip>>;
|
|
89
106
|
}, z.core.$strip>;
|
|
90
107
|
export type DeploymentSettings = z.infer<typeof DEPLOYMENT_SETTINGS>;
|
|
91
108
|
/**
|
|
@@ -125,19 +142,11 @@ export declare const DEPLOYMENT_SETTINGS_PATCH: z.ZodObject<{
|
|
|
125
142
|
uiTheme: z.ZodOptional<z.ZodObject<{
|
|
126
143
|
id: z.ZodNullable<z.ZodString>;
|
|
127
144
|
}, z.core.$strict>>;
|
|
128
|
-
uiArrangement: z.ZodOptional<z.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
"file.preview": "file.preview";
|
|
134
|
-
"org.page": "org.page";
|
|
135
|
-
"org.settings.section": "org.settings.section";
|
|
136
|
-
"platform.page": "platform.page";
|
|
137
|
-
}> & z.core.$partial, z.ZodNullable<z.ZodObject<{
|
|
138
|
-
hidden: z.ZodArray<z.ZodString>;
|
|
139
|
-
order: z.ZodArray<z.ZodString>;
|
|
140
|
-
}, z.core.$strict>>>>;
|
|
145
|
+
uiArrangement: z.ZodOptional<z.ZodType<Partial<Record<"agent.detail.section" | "conversation.composer.frame" | "conversation.empty" | "conversation.panel" | "conversation.properties.section" | "conversation.tool-result.card" | "file.preview" | "org.page" | "org.settings.section" | "platform.page" | "shell.brand" | "shell.overlay", import("../ui/arrangement.js").UiArrangementEntry | null>>, unknown, z.core.$ZodTypeInternals<Partial<Record<"agent.detail.section" | "conversation.composer.frame" | "conversation.empty" | "conversation.panel" | "conversation.properties.section" | "conversation.tool-result.card" | "file.preview" | "org.page" | "org.settings.section" | "platform.page" | "shell.brand" | "shell.overlay", import("../ui/arrangement.js").UiArrangementEntry | null>>, unknown>>>;
|
|
146
|
+
clientExtensions: z.ZodOptional<z.ZodObject<{
|
|
147
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
148
|
+
disabled: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
149
|
+
}, z.core.$strict>>;
|
|
141
150
|
}, z.core.$strict>;
|
|
142
151
|
export type DeploymentSettingsPatch = z.infer<typeof DEPLOYMENT_SETTINGS_PATCH>;
|
|
143
152
|
/**
|
|
@@ -177,6 +186,12 @@ export declare function readUiTheme(db: KernelSchemaDb, opts?: {
|
|
|
177
186
|
* stored value reads as `{}` (the same fallback {@link SettingsService.get} uses).
|
|
178
187
|
*/
|
|
179
188
|
export declare function readUiArrangement(db: KernelSchemaDb): Promise<UiArrangement>;
|
|
189
|
+
/**
|
|
190
|
+
* The client code kill switch, read live from PG on every request (in the caller's transaction when
|
|
191
|
+
* there is one; never cached: every replica sees a change at once), through
|
|
192
|
+
* {@link parseClientExtensions} (missing or invalid -> everything on; invalid also warns on `log`).
|
|
193
|
+
*/
|
|
194
|
+
export declare function readClientExtensions(db: KernelSchemaDb, log?: Pick<KernelLogger, "warn">): Promise<ClientExtensionsKillSwitch>;
|
|
180
195
|
/** Deployment settings: one row per key, defaults for missing keys. */
|
|
181
196
|
export declare class SettingsService {
|
|
182
197
|
private readonly db;
|