@capybara-hq/kernel 0.1.0-rc.14 → 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/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/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/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/guide-places.d.ts +3 -1
- package/dist/extensions/guide-places.js +1 -1
- 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 +23 -7
- 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
|
@@ -23,9 +23,14 @@ let ThemeDeliveryController = class ThemeDeliveryController {
|
|
|
23
23
|
constructor(delivery) {
|
|
24
24
|
this.delivery = delivery;
|
|
25
25
|
}
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The version's stylesheet: scheme overrides, `@font-face` rules and font stacks, in the render
|
|
28
|
+
* revision `revision` (`r<THEME_CSS_REVISION>`; only the current one is served). The URL without
|
|
29
|
+
* a revision (before revision 2) has no route: 404, so a browser's cached unlayered copy is never
|
|
30
|
+
* answered with the new format under an immutable cache.
|
|
31
|
+
*/
|
|
32
|
+
async css(id, version, revision, reply) {
|
|
33
|
+
const css = await this.delivery.stylesheet(id, version, revision);
|
|
29
34
|
await reply
|
|
30
35
|
.header("content-type", "text/css; charset=utf-8")
|
|
31
36
|
.header("x-content-type-options", "nosniff")
|
|
@@ -51,10 +56,11 @@ let ThemeDeliveryController = class ThemeDeliveryController {
|
|
|
51
56
|
};
|
|
52
57
|
__decorate([
|
|
53
58
|
Public(),
|
|
54
|
-
Get("theme.css"),
|
|
59
|
+
Get(":revision/theme.css"),
|
|
55
60
|
__param(0, Param("id")),
|
|
56
61
|
__param(1, Param("version")),
|
|
57
|
-
__param(2,
|
|
62
|
+
__param(2, Param("revision")),
|
|
63
|
+
__param(3, Res())
|
|
58
64
|
], ThemeDeliveryController.prototype, "css", null);
|
|
59
65
|
__decorate([
|
|
60
66
|
Public(),
|
|
@@ -32,7 +32,7 @@ let ThemesService = class ThemesService {
|
|
|
32
32
|
if (!d)
|
|
33
33
|
continue;
|
|
34
34
|
out.push({
|
|
35
|
-
...themeRefOf(c.id, d.version, d.definition),
|
|
35
|
+
...themeRefOf(c.id, d.version, d.definition, d.plugin),
|
|
36
36
|
swatches: themeSwatches(d.definition),
|
|
37
37
|
});
|
|
38
38
|
}
|
|
@@ -53,7 +53,7 @@ let ThemesService = class ThemesService {
|
|
|
53
53
|
if (id === null || (await eligiblePlatformDefault(this.db.db, id)) === null)
|
|
54
54
|
return null;
|
|
55
55
|
const d = (await themeDefinitions(this.db.db, [id])).get(id);
|
|
56
|
-
return d ? themeRefOf(id, d.version, d.definition) : null;
|
|
56
|
+
return d ? themeRefOf(id, d.version, d.definition, d.plugin) : null;
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
59
|
* The listing, the viewer's resolved theme and the org's setting. Separate reads, not one
|
package/dist/tools/pipeline.d.ts
CHANGED
|
@@ -151,8 +151,9 @@ interface PipelineDeps {
|
|
|
151
151
|
/** The org's switches of single extension tools: a turned-off one is not called (spec A §7). */
|
|
152
152
|
readonly switches: Pick<OrgToolSwitches, "isToolOff">;
|
|
153
153
|
/**
|
|
154
|
-
* Ids of an extension's `
|
|
155
|
-
* `ToolResult.card` must name one of them,
|
|
154
|
+
* Ids of an extension's `conversation.tool-result.card` contributions of any kind (view, embed
|
|
155
|
+
* or component; read live, no alias): a plugin tool's `ToolResult.card` must name one of them,
|
|
156
|
+
* otherwise it is dropped with a warning.
|
|
156
157
|
*/
|
|
157
158
|
inlineViews(plugin: string): ReadonlySet<string>;
|
|
158
159
|
/**
|
package/dist/ui/arrangement.d.ts
CHANGED
|
@@ -1,74 +1,65 @@
|
|
|
1
1
|
import { type UiSlotId } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import type { UiContributionItem } from "./views.js";
|
|
4
|
-
export
|
|
5
|
-
hidden:
|
|
6
|
-
order:
|
|
7
|
-
}
|
|
8
|
-
export type
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"platform.page": "platform.page";
|
|
18
|
-
}> & z.core.$partial, z.ZodObject<{
|
|
19
|
-
hidden: z.ZodArray<z.ZodString>;
|
|
20
|
-
order: z.ZodArray<z.ZodString>;
|
|
21
|
-
}, z.core.$strict>>;
|
|
22
|
-
export type UiArrangement = z.infer<typeof UiArrangementSchema>;
|
|
4
|
+
export type UiListEntry = {
|
|
5
|
+
hidden: string[];
|
|
6
|
+
order: string[];
|
|
7
|
+
};
|
|
8
|
+
export type UiUseEntry = {
|
|
9
|
+
use: string | null;
|
|
10
|
+
};
|
|
11
|
+
/** One slot's entry: `{ hidden, order }` on a list slot, `{ use }` on a single slot. */
|
|
12
|
+
export type UiArrangementEntry = UiListEntry | UiUseEntry;
|
|
13
|
+
export type UiArrangement = Partial<Record<UiSlotId, UiArrangementEntry>>;
|
|
14
|
+
/** Whether a slot is a single (replaceable) slot. */
|
|
15
|
+
export declare const isSingle: (slot: UiSlotId) => boolean;
|
|
16
|
+
export declare const UiArrangementSchema: z.ZodType<UiArrangement>;
|
|
23
17
|
/** The slots an org arranges: every arrangeable slot but the platform's own. */
|
|
24
18
|
export declare const ORG_ARRANGEABLE: readonly UiSlotId[];
|
|
25
19
|
/** An org's patch: a slot's entry replaces the stored one, null removes it. */
|
|
26
|
-
export declare const OrgUiArrangementPatchSchema: z.
|
|
27
|
-
"agent.detail.section": "agent.detail.section";
|
|
28
|
-
"conversation.panel": "conversation.panel";
|
|
29
|
-
"conversation.properties.section": "conversation.properties.section";
|
|
30
|
-
"conversation.tool-result.card": "conversation.tool-result.card";
|
|
31
|
-
"file.preview": "file.preview";
|
|
32
|
-
"org.page": "org.page";
|
|
33
|
-
"org.settings.section": "org.settings.section";
|
|
34
|
-
"platform.page": "platform.page";
|
|
35
|
-
}> & z.core.$partial, z.ZodNullable<z.ZodObject<{
|
|
36
|
-
hidden: z.ZodArray<z.ZodString>;
|
|
37
|
-
order: z.ZodArray<z.ZodString>;
|
|
38
|
-
}, z.core.$strict>>>;
|
|
20
|
+
export declare const OrgUiArrangementPatchSchema: 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", 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", UiArrangementEntry | null>>, unknown>>;
|
|
39
21
|
/** A deployment patch: like {@link OrgUiArrangementPatchSchema} over every arrangeable slot. */
|
|
40
|
-
export declare const UiArrangementPatchSchema: z.
|
|
41
|
-
"agent.detail.section": "agent.detail.section";
|
|
42
|
-
"conversation.panel": "conversation.panel";
|
|
43
|
-
"conversation.properties.section": "conversation.properties.section";
|
|
44
|
-
"conversation.tool-result.card": "conversation.tool-result.card";
|
|
45
|
-
"file.preview": "file.preview";
|
|
46
|
-
"org.page": "org.page";
|
|
47
|
-
"org.settings.section": "org.settings.section";
|
|
48
|
-
"platform.page": "platform.page";
|
|
49
|
-
}> & z.core.$partial, z.ZodNullable<z.ZodObject<{
|
|
50
|
-
hidden: z.ZodArray<z.ZodString>;
|
|
51
|
-
order: z.ZodArray<z.ZodString>;
|
|
52
|
-
}, z.core.$strict>>>;
|
|
22
|
+
export declare const UiArrangementPatchSchema: 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", 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", UiArrangementEntry | null>>, unknown>>;
|
|
53
23
|
/** Merges a per-slot patch into the stored arrangement (null removes the slot's entry). */
|
|
54
24
|
export declare function mergeArrangement(current: UiArrangement | undefined, patch: Partial<Record<UiSlotId, UiArrangementEntry | null>>): UiArrangement;
|
|
55
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* The list entry that applies to a slot: the org's if it arranged the slot, else the platform's
|
|
27
|
+
* (a `use` entry, which only a single slot stores, reads as none).
|
|
28
|
+
*/
|
|
56
29
|
export declare function effectiveEntry(org: UiArrangement | undefined, platform: UiArrangement, slot: UiSlotId): {
|
|
57
|
-
entry:
|
|
30
|
+
entry: UiListEntry;
|
|
58
31
|
source: "org" | "platform" | "none";
|
|
59
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* The winner of a single slot (client extensions spec §6.3): the org's entry if it has one, else
|
|
35
|
+
* the platform's, else none. No entry or `use: null` is the built-in (null); a `use` naming a key
|
|
36
|
+
* not in `available` (extension off in the org, contribution dropped, the viewer's role below the
|
|
37
|
+
* slot's) is the built-in too, never the other level's pick. A list slot, or an entry that is not
|
|
38
|
+
* a `use` entry, has no winner.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveSingleSlot(slot: UiSlotId, orgEntry: UiArrangementEntry | undefined, platformEntry: UiArrangementEntry | undefined, available: ReadonlySet<string>): string | null;
|
|
41
|
+
/** The `use` stored for a single slot: the org's entry if it has one, else the platform's. */
|
|
42
|
+
export declare function storedUse(org: UiArrangement | undefined, platform: UiArrangement, slot: UiSlotId): {
|
|
43
|
+
use: string | null;
|
|
44
|
+
followsPlatform: boolean;
|
|
45
|
+
};
|
|
46
|
+
/** The arrangement key of a contribution: `<plugin>/<id>`. */
|
|
47
|
+
export declare const keyOf: (i: {
|
|
48
|
+
plugin: string;
|
|
49
|
+
id: string;
|
|
50
|
+
}) => string;
|
|
60
51
|
/**
|
|
61
52
|
* Orders the items of one slot (hidden ones stay in place): grouped by anchor in the slot's anchor
|
|
62
53
|
* order, also without an entry (the lists match what the page renders per anchor); within a group
|
|
63
54
|
* the keys of `entry.order` come first in that order, the rest in base order. Unknown keys are
|
|
64
55
|
* ignored, so an order never moves an item across anchors.
|
|
65
56
|
*/
|
|
66
|
-
export declare function orderItems(items: UiContributionItem[], entry:
|
|
57
|
+
export declare function orderItems(items: UiContributionItem[], entry: UiListEntry): UiContributionItem[];
|
|
67
58
|
/** {@link orderItems} without the hidden keys. */
|
|
68
|
-
export declare function arrange(items: UiContributionItem[], entry:
|
|
59
|
+
export declare function arrange(items: UiContributionItem[], entry: UiListEntry): UiContributionItem[];
|
|
69
60
|
/**
|
|
70
61
|
* Applies the per-slot entries to a mixed list: each slot's items are arranged and written back
|
|
71
62
|
* into the positions that slot occupied (a slot without an entry: its base order, grouped by
|
|
72
63
|
* anchor).
|
|
73
64
|
*/
|
|
74
|
-
export declare function arrangeBySlot(items: UiContributionItem[], entryOf: (slot: UiSlotId) =>
|
|
65
|
+
export declare function arrangeBySlot(items: UiContributionItem[], entryOf: (slot: UiSlotId) => UiListEntry): UiContributionItem[];
|
package/dist/ui/arrangement.js
CHANGED
|
@@ -1,22 +1,66 @@
|
|
|
1
|
-
import { UI_ARRANGEABLE_SLOTS, UI_CONTRIBUTION_KEY_RE, uiSlot, } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import { UI_ARRANGEABLE_SLOTS, UI_CONTRIBUTION_KEY_RE, UI_SINGLE_SLOTS, uiSlot, } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
/**
|
|
4
|
-
* Arrangement of UI slots (UI slots spec §5): per slot, which
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Arrangement of UI slots (UI slots spec §5, client extensions spec §6.3): per list slot, which
|
|
5
|
+
* contributions are hidden and in which order; per single slot, which contribution replaces the
|
|
6
|
+
* built-in (`use`, null = the built-in). "Hidden" means not listed and `use` only picks what is
|
|
7
|
+
* listed: the access gate never reads an arrangement, so a contribution's view, action and embed
|
|
8
|
+
* routes keep working for an allowed role whatever the arrangement says.
|
|
7
9
|
*/
|
|
8
|
-
const Key = z.string().regex(UI_CONTRIBUTION_KEY_RE);
|
|
10
|
+
const Key = z.string().regex(UI_CONTRIBUTION_KEY_RE, "must be a contribution key <plugin>/<id>");
|
|
9
11
|
const LIST_MAX = 200;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* A list slot's entry: the keys hidden and the keys ordered first. The refusals name the slot and
|
|
14
|
+
* what it takes (actionable for an admin and the Platform tool's model).
|
|
15
|
+
*/
|
|
16
|
+
const listEntrySchema = (slot) => {
|
|
17
|
+
const keys = z
|
|
18
|
+
.array(Key, {
|
|
19
|
+
error: (iss) => iss.input === undefined
|
|
20
|
+
? `${slot} is a list slot: give an array of contribution keys <plugin>/<id>`
|
|
21
|
+
: undefined,
|
|
22
|
+
})
|
|
23
|
+
.max(LIST_MAX);
|
|
24
|
+
return z.strictObject({ hidden: keys, order: keys }, {
|
|
25
|
+
error: (iss) => iss.code === "unrecognized_keys"
|
|
26
|
+
? `${slot} is a list slot: give { hidden, order }; use is only for the single slots (${UI_SINGLE_SLOTS.join(", ")})`
|
|
27
|
+
: undefined,
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
/** A single slot's entry: the contribution that replaces the built-in, or null for the built-in. */
|
|
31
|
+
const useEntrySchema = (slot) => z.strictObject({
|
|
32
|
+
use: z.union([Key, z.null()], {
|
|
33
|
+
error: (iss) => iss.input === undefined
|
|
34
|
+
? `${slot} needs use: a contribution key <plugin>/<id>, or null for the built-in`
|
|
35
|
+
: undefined,
|
|
36
|
+
}),
|
|
37
|
+
}, {
|
|
38
|
+
error: (iss) => iss.code === "unrecognized_keys"
|
|
39
|
+
? `${slot} is a single slot: give { use } (a contribution key <plugin>/<id>, or null for the built-in); hidden and order are only for list slots`
|
|
40
|
+
: undefined,
|
|
41
|
+
});
|
|
42
|
+
/** Whether a slot is a single (replaceable) slot. */
|
|
43
|
+
export const isSingle = (slot) => UI_SINGLE_SLOTS.includes(slot);
|
|
44
|
+
/**
|
|
45
|
+
* An object over `slots`, each optional and of its kind's entry (nullable in a patch); unknown
|
|
46
|
+
* slots are refused, and so are `use` on a list slot and `hidden` / `order` on a single slot (400
|
|
47
|
+
* `validation_failed` on the settings routes).
|
|
48
|
+
*/
|
|
49
|
+
function arrangementShape(slots, nullable) {
|
|
50
|
+
const shape = {};
|
|
51
|
+
for (const slot of slots) {
|
|
52
|
+
const entry = isSingle(slot) ? useEntrySchema(slot) : listEntrySchema(slot);
|
|
53
|
+
shape[slot] = (nullable ? entry.nullable() : entry).optional();
|
|
54
|
+
}
|
|
55
|
+
return z.object(shape).strict();
|
|
56
|
+
}
|
|
57
|
+
export const UiArrangementSchema = arrangementShape(UI_ARRANGEABLE_SLOTS, false);
|
|
14
58
|
/** The slots an org arranges: every arrangeable slot but the platform's own. */
|
|
15
59
|
export const ORG_ARRANGEABLE = UI_ARRANGEABLE_SLOTS.filter((s) => s !== "platform.page");
|
|
16
60
|
/** An org's patch: a slot's entry replaces the stored one, null removes it. */
|
|
17
|
-
export const OrgUiArrangementPatchSchema =
|
|
61
|
+
export const OrgUiArrangementPatchSchema = arrangementShape(ORG_ARRANGEABLE, true);
|
|
18
62
|
/** A deployment patch: like {@link OrgUiArrangementPatchSchema} over every arrangeable slot. */
|
|
19
|
-
export const UiArrangementPatchSchema =
|
|
63
|
+
export const UiArrangementPatchSchema = arrangementShape(UI_ARRANGEABLE_SLOTS, true);
|
|
20
64
|
/** Merges a per-slot patch into the stored arrangement (null removes the slot's entry). */
|
|
21
65
|
export function mergeArrangement(current, patch) {
|
|
22
66
|
const merged = { ...current };
|
|
@@ -29,17 +73,46 @@ export function mergeArrangement(current, patch) {
|
|
|
29
73
|
return merged;
|
|
30
74
|
}
|
|
31
75
|
const NONE = { hidden: [], order: [] };
|
|
32
|
-
|
|
76
|
+
const asList = (e) => ("hidden" in e ? e : NONE);
|
|
77
|
+
/**
|
|
78
|
+
* The list entry that applies to a slot: the org's if it arranged the slot, else the platform's
|
|
79
|
+
* (a `use` entry, which only a single slot stores, reads as none).
|
|
80
|
+
*/
|
|
33
81
|
export function effectiveEntry(org, platform, slot) {
|
|
34
82
|
const o = org?.[slot];
|
|
35
83
|
if (o !== undefined)
|
|
36
|
-
return { entry: o, source: "org" };
|
|
84
|
+
return { entry: asList(o), source: "org" };
|
|
37
85
|
const p = platform[slot];
|
|
38
86
|
if (p !== undefined)
|
|
39
|
-
return { entry: p, source: "platform" };
|
|
87
|
+
return { entry: asList(p), source: "platform" };
|
|
40
88
|
return { entry: NONE, source: "none" };
|
|
41
89
|
}
|
|
42
|
-
|
|
90
|
+
/**
|
|
91
|
+
* The winner of a single slot (client extensions spec §6.3): the org's entry if it has one, else
|
|
92
|
+
* the platform's, else none. No entry or `use: null` is the built-in (null); a `use` naming a key
|
|
93
|
+
* not in `available` (extension off in the org, contribution dropped, the viewer's role below the
|
|
94
|
+
* slot's) is the built-in too, never the other level's pick. A list slot, or an entry that is not
|
|
95
|
+
* a `use` entry, has no winner.
|
|
96
|
+
*/
|
|
97
|
+
export function resolveSingleSlot(slot, orgEntry, platformEntry, available) {
|
|
98
|
+
if (!isSingle(slot))
|
|
99
|
+
return null;
|
|
100
|
+
const entry = orgEntry ?? platformEntry;
|
|
101
|
+
if (entry === undefined || !("use" in entry) || entry.use === null)
|
|
102
|
+
return null;
|
|
103
|
+
return available.has(entry.use) ? entry.use : null;
|
|
104
|
+
}
|
|
105
|
+
/** The `use` stored for a single slot: the org's entry if it has one, else the platform's. */
|
|
106
|
+
export function storedUse(org, platform, slot) {
|
|
107
|
+
const o = org?.[slot];
|
|
108
|
+
const e = o ?? platform[slot];
|
|
109
|
+
return {
|
|
110
|
+
use: e !== undefined && "use" in e ? e.use : null,
|
|
111
|
+
followsPlatform: o === undefined,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** The arrangement key of a contribution: `<plugin>/<id>`. */
|
|
115
|
+
export const keyOf = (i) => `${i.plugin}/${i.id}`;
|
|
43
116
|
/**
|
|
44
117
|
* Orders the items of one slot (hidden ones stay in place): grouped by anchor in the slot's anchor
|
|
45
118
|
* order, also without an entry (the lists match what the page renders per anchor); within a group
|
package/dist/ui/registry.d.ts
CHANGED
|
@@ -45,19 +45,29 @@ export declare const uiFrame: (m: UiBusMessage) => string;
|
|
|
45
45
|
/**
|
|
46
46
|
* 单个插件在 setup 期间注册的视图与动作:进程内的静态登记(同 `ctx.tools.register`),
|
|
47
47
|
* 不是跨请求的共享状态——各副本加载同一插件得到同样的登记。
|
|
48
|
-
* id
|
|
48
|
+
* The id must be in `permissions.ui.views` or `permissions.ui.components` (a component's server
|
|
49
|
+
* view, client extensions spec §5.2); `defineExtension` already cross-checked them with the
|
|
50
|
+
* manifest `ui`.
|
|
49
51
|
*/
|
|
50
52
|
export declare class PluginUiRegistration {
|
|
51
53
|
private readonly manifest;
|
|
52
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Out of scope (the id is in neither `permissions.ui.views` nor `permissions.ui.components`):
|
|
56
|
+
* throws ExtensionPermissionError and writes an audit entry.
|
|
57
|
+
*/
|
|
53
58
|
private readonly deny;
|
|
54
59
|
readonly views: Map<string, UiRender>;
|
|
55
60
|
readonly actions: Map<string, UiActionHandler>;
|
|
56
61
|
private readonly declaredViews;
|
|
57
62
|
private readonly contributions;
|
|
63
|
+
/** The ids of the manifest's `component` contributions: their permission is `ui.components`. */
|
|
64
|
+
private readonly components;
|
|
58
65
|
private sealed;
|
|
59
66
|
constructor(manifest: ExtensionManifest,
|
|
60
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* Out of scope (the id is in neither `permissions.ui.views` nor `permissions.ui.components`):
|
|
69
|
+
* throws ExtensionPermissionError and writes an audit entry.
|
|
70
|
+
*/
|
|
61
71
|
deny: (item: string) => never);
|
|
62
72
|
/** setup 结束后调用:此后 view / action 抛错。 */
|
|
63
73
|
seal(): void;
|
package/dist/ui/registry.js
CHANGED
|
@@ -31,7 +31,9 @@ export const uiFrame = (m) => `event: ui\ndata: ${JSON.stringify({ plugin: m.plu
|
|
|
31
31
|
/**
|
|
32
32
|
* 单个插件在 setup 期间注册的视图与动作:进程内的静态登记(同 `ctx.tools.register`),
|
|
33
33
|
* 不是跨请求的共享状态——各副本加载同一插件得到同样的登记。
|
|
34
|
-
* id
|
|
34
|
+
* The id must be in `permissions.ui.views` or `permissions.ui.components` (a component's server
|
|
35
|
+
* view, client extensions spec §5.2); `defineExtension` already cross-checked them with the
|
|
36
|
+
* manifest `ui`.
|
|
35
37
|
*/
|
|
36
38
|
export class PluginUiRegistration {
|
|
37
39
|
manifest;
|
|
@@ -40,14 +42,21 @@ export class PluginUiRegistration {
|
|
|
40
42
|
actions = new Map();
|
|
41
43
|
declaredViews;
|
|
42
44
|
contributions;
|
|
45
|
+
/** The ids of the manifest's `component` contributions: their permission is `ui.components`. */
|
|
46
|
+
components;
|
|
43
47
|
sealed = false;
|
|
44
48
|
constructor(manifest,
|
|
45
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Out of scope (the id is in neither `permissions.ui.views` nor `permissions.ui.components`):
|
|
51
|
+
* throws ExtensionPermissionError and writes an audit entry.
|
|
52
|
+
*/
|
|
46
53
|
deny) {
|
|
47
54
|
this.manifest = manifest;
|
|
48
55
|
this.deny = deny;
|
|
49
|
-
|
|
56
|
+
const ui = manifest.permissions?.ui;
|
|
57
|
+
this.declaredViews = new Set([...(ui?.views ?? []), ...(ui?.components ?? [])]);
|
|
50
58
|
this.contributions = new Set((manifest.ui ?? []).map((c) => c.id));
|
|
59
|
+
this.components = new Set((manifest.ui ?? []).filter((c) => c.kind === "component").map((c) => c.id));
|
|
51
60
|
}
|
|
52
61
|
/** setup 结束后调用:此后 view / action 抛错。 */
|
|
53
62
|
seal() {
|
|
@@ -99,7 +108,9 @@ export class PluginUiRegistration {
|
|
|
99
108
|
throw new Error(`plugin ${name}: ui.${what} is only allowed during setup; setup has completed`);
|
|
100
109
|
}
|
|
101
110
|
if (typeof id !== "string" || !this.declaredViews.has(id)) {
|
|
102
|
-
|
|
111
|
+
// name the list the id belongs in: a component's server view is declared under components
|
|
112
|
+
const list = typeof id === "string" && this.components.has(id) ? "components" : "views";
|
|
113
|
+
this.deny(`permissions.ui.${list} ${String(id)}`);
|
|
103
114
|
}
|
|
104
115
|
if (typeof fn !== "function") {
|
|
105
116
|
throw new Error(`plugin ${name}: ui.${what} "${id}" handler must be a function`);
|
package/dist/ui/ui.schemas.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export declare const ContributionParams: z.ZodObject<{
|
|
|
9
9
|
export declare const ContributionsQuery: z.ZodObject<{
|
|
10
10
|
slot: z.ZodOptional<z.ZodEnum<{
|
|
11
11
|
"agent.detail.section": "agent.detail.section";
|
|
12
|
+
"conversation.composer.frame": "conversation.composer.frame";
|
|
13
|
+
"conversation.empty": "conversation.empty";
|
|
12
14
|
"conversation.panel": "conversation.panel";
|
|
13
15
|
"conversation.properties.section": "conversation.properties.section";
|
|
14
16
|
"conversation.tool-result.card": "conversation.tool-result.card";
|
|
@@ -16,6 +18,8 @@ export declare const ContributionsQuery: z.ZodObject<{
|
|
|
16
18
|
"org.page": "org.page";
|
|
17
19
|
"org.settings.section": "org.settings.section";
|
|
18
20
|
"platform.page": "platform.page";
|
|
21
|
+
"shell.brand": "shell.brand";
|
|
22
|
+
"shell.overlay": "shell.overlay";
|
|
19
23
|
}>>;
|
|
20
24
|
}, z.core.$strip>;
|
|
21
25
|
export declare const ViewQuery: z.ZodObject<{
|
package/dist/ui/views.d.ts
CHANGED
|
@@ -24,14 +24,31 @@ export interface UiContributionItem {
|
|
|
24
24
|
/** From `UI_ICONS`; `puzzle` when the manifest names none. */
|
|
25
25
|
icon: UiIcon;
|
|
26
26
|
}
|
|
27
|
-
/**
|
|
28
|
-
export interface
|
|
27
|
+
/** A list slot of an arrangement listing: where its entry comes from and its items in order. */
|
|
28
|
+
export interface UiArrangementListSlot {
|
|
29
29
|
slot: UiSlotId;
|
|
30
30
|
source: "org" | "platform" | "none";
|
|
31
31
|
items: (UiContributionItem & {
|
|
32
32
|
hidden: boolean;
|
|
33
33
|
})[];
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* A single slot of an arrangement listing (client extensions spec §6.3): the contributions that may
|
|
37
|
+
* replace the built-in, the stored `use` (null = the built-in; it may name a key that is not a
|
|
38
|
+
* candidate any more, which renders the built-in) and whether it is the platform's (the org has no
|
|
39
|
+
* entry; always false on the platform's own listing).
|
|
40
|
+
*/
|
|
41
|
+
export interface UiArrangementSingleSlot {
|
|
42
|
+
slot: UiSlotId;
|
|
43
|
+
candidates: {
|
|
44
|
+
key: string;
|
|
45
|
+
title: UiContribution["title"];
|
|
46
|
+
}[];
|
|
47
|
+
use: string | null;
|
|
48
|
+
followsPlatform: boolean;
|
|
49
|
+
}
|
|
50
|
+
/** One slot of an arrangement listing. */
|
|
51
|
+
export type UiArrangementSlot = UiArrangementListSlot | UiArrangementSingleSlot;
|
|
35
52
|
/** 嵌入应用的签发目标:已核对组织启用与对话访问。 */
|
|
36
53
|
export interface UiEmbedTarget {
|
|
37
54
|
plugin: string;
|
|
@@ -101,6 +118,8 @@ export declare function dropForeignOpens(view: UiView, panels: ReadonlySet<strin
|
|
|
101
118
|
* org; org: the role alone); refused is 404 `ui.not_found`, the same as not existing;
|
|
102
119
|
* - a view takes only the context id of its scope (`conversationId` / `agentId`): a missing one or
|
|
103
120
|
* one of another scope is 400 `validation_failed`;
|
|
121
|
+
* - a component's server view (`ctx.ui.view(<component id>)`) is served like a view of its slot;
|
|
122
|
+
* only its views carry `data` (a plain view's `data` is 502 `ui.invalid_view`);
|
|
104
123
|
* - 调用带 5 秒时限与中止信号:render 抛错 502 `ui.render_failed`、action 抛错 502 `ui.action_failed`、
|
|
105
124
|
* 超时 504 `ui.timeout`、返回非法视图 502
|
|
106
125
|
* `ui.invalid_view`,都记告警日志、不写对话日志(这些不是模型可见的内容)。
|
|
@@ -111,27 +130,35 @@ export declare class UiViews {
|
|
|
111
130
|
constructor(host: ExtensionHost, conversations: ConversationsService, previewers: PreviewerRegistry, db: KernelDb, log: Pick<Logger, "warn">, effective: Pick<EffectiveConfigs, "get" | "platform">);
|
|
112
131
|
/**
|
|
113
132
|
* The contributions the role may use in the org, of one slot or of every org-reachable slot:
|
|
114
|
-
* the extension enabled and loaded, a view's render registered
|
|
115
|
-
* ({@link canUseSlot}, so never `platform.page`);
|
|
116
|
-
* file previewers list those). Base order (spec
|
|
133
|
+
* the extension enabled and loaded, a view's render registered (a component is listed with or
|
|
134
|
+
* without a server view), the slot's `minRole` ({@link canUseSlot}, so never `platform.page`);
|
|
135
|
+
* no alias, no `file.preview` contribution (the file previewers list those). Base order (spec
|
|
136
|
+
* §5): extension name, then manifest order. A single slot lists at most its winner
|
|
137
|
+
* ({@link resolveSingleSlot} over what this role may use: none until one is chosen, and the web
|
|
138
|
+
* renders the built-in); display only, the routes that serve a contribution never read it. The
|
|
139
|
+
* winner is listed whether or not its client code may load (kill switch, per-plugin disable,
|
|
140
|
+
* client API support): the web renders the built-in when the org's client set has no such
|
|
141
|
+
* component, and any other consumer must do the same.
|
|
117
142
|
*/
|
|
118
143
|
contributions(orgId: string, role: OrgRole, slot?: UiSlotId): Promise<UiContributionItem[]>;
|
|
119
144
|
/** {@link contributions} before the arrangement: what the role may use, in base order. */
|
|
120
145
|
private orgBase;
|
|
121
146
|
/**
|
|
122
147
|
* The arrangement page of an org: every org-arrangeable slot with the contributions the role may
|
|
123
|
-
* use
|
|
148
|
+
* use: a list slot in effective order with the hidden ones in place, and where the entry comes
|
|
149
|
+
* from; a single slot with its candidates, the stored `use` and whether it follows the platform.
|
|
124
150
|
*/
|
|
125
151
|
arrangementView(orgId: string, role: OrgRole): Promise<UiArrangementSlot[]>;
|
|
126
152
|
/**
|
|
127
153
|
* The platform's arrangement page: every arrangeable slot with the contributions of every running
|
|
128
|
-
* extension (whatever its org enablement)
|
|
154
|
+
* extension (whatever its org enablement): a list slot ordered by the platform entry, hidden ones
|
|
155
|
+
* in place; a single slot with its candidates and the platform's `use`.
|
|
129
156
|
*/
|
|
130
157
|
platformArrangementView(): Promise<UiArrangementSlot[]>;
|
|
131
158
|
/**
|
|
132
159
|
* The `platform.page` contributions of the running extensions, for the admin console (super
|
|
133
|
-
* admins; no org enablement applies): no alias, a view's render registered
|
|
134
|
-
* {@link contributions}.
|
|
160
|
+
* admins; no org enablement applies): no alias, a view's render registered (a component is
|
|
161
|
+
* listed with or without a server view). Base order like {@link contributions}.
|
|
135
162
|
*/
|
|
136
163
|
platformContributions(): Promise<UiContributionItem[]>;
|
|
137
164
|
/**
|