@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
|
@@ -39,7 +39,7 @@ let OrgSettingsOperations = class OrgSettingsOperations {
|
|
|
39
39
|
this.registry.register([
|
|
40
40
|
defineOperation({
|
|
41
41
|
id: "org-settings.get",
|
|
42
|
-
description: "Read this org's settings: toolSearch, locale, sandboxToolPolicy, platformTool (on, read-only or off), sandboxEgress and wakeOnBackgroundTask (absent means it follows the deployment), theme (the org's default theme id, null or absent following the platform, and personal: allowed or locked), uiArrangement (per
|
|
42
|
+
description: "Read this org's settings: toolSearch, locale, sandboxToolPolicy, platformTool (on, read-only or off), sandboxEgress and wakeOnBackgroundTask (absent means it follows the deployment), theme (the org's default theme id, null or absent following the platform, and personal: allowed or locked), uiArrangement (per list slot, the extension contributions the org hides and their order; per single slot, the contribution that replaces the built-in as use, null for the built-in; a slot not listed follows the platform), the deployment's sandbox egress default and the sandbox cpu / memory limits in effect.",
|
|
43
43
|
input: z.object({}).strict(),
|
|
44
44
|
example: {},
|
|
45
45
|
authz: { orgAction: "org.read" },
|
|
@@ -53,7 +53,7 @@ let OrgSettingsOperations = class OrgSettingsOperations {
|
|
|
53
53
|
}),
|
|
54
54
|
defineOperation({
|
|
55
55
|
id: "org-settings.update",
|
|
56
|
-
description: "Change this org's settings (org admins). Give only the settings to change; null clears sandboxEgress or wakeOnBackgroundTask so they follow the deployment again. theme: { id, personal } sets the org's default theme (a theme id from themes.list, or null to follow the platform) and whether members may pick their own (allowed) or not (locked); null clears it. uiArrangement: { <slot>: { hidden, order } } replaces that slot's entry, where hidden and
|
|
56
|
+
description: "Change this org's settings (org admins). Give only the settings to change; null clears sandboxEgress or wakeOnBackgroundTask so they follow the deployment again. theme: { id, personal } sets the org's default theme (a theme id from themes.list, or null to follow the platform) and whether members may pick their own (allowed) or not (locked); null clears it. uiArrangement: { <slot>: { hidden, order } } (a list slot) or { <slot>: { use } } (a single slot: shell.brand, conversation.empty, conversation.composer.frame) replaces that slot's entry, where hidden, order and use name contribution keys <extension>/<contribution> (hidden ones are not listed in the web UI but their pages still open by URL; use null is the built-in); null for a slot makes it follow the platform again; platform.page is refused. Setting platformTool to off or read-only takes effect from the next call and cuts this Platform tool off in the org, so say so before asking; an org admin switches it back on in the web settings.",
|
|
57
57
|
input: OrgSettingsPatchSchema.refine((i) => Object.values(i).some((v) => v !== undefined), {
|
|
58
58
|
message: "give at least one setting",
|
|
59
59
|
}),
|
|
@@ -22,7 +22,7 @@ let UiOperations = class UiOperations {
|
|
|
22
22
|
this.registry.register([
|
|
23
23
|
defineOperation({
|
|
24
24
|
id: "ui.slots",
|
|
25
|
-
description: "List the UI slots an extension can contribute to (id, version, where it is, scope and args, accepted kinds view / embed, minimum role, anchors). Use a slot id as a ui contribution's `slot` in a plugin manifest.",
|
|
25
|
+
description: "List the UI slots an extension can contribute to (id, version, where it is, scope and args, accepted kinds view / embed / component, cardinality list or single, minimum role, anchors). Use a slot id as a ui contribution's `slot` in a plugin manifest.",
|
|
26
26
|
input: z.object({}).strict(),
|
|
27
27
|
example: {},
|
|
28
28
|
// any signed-in person may call the route; every run's actor is a member of this org
|
|
@@ -7,6 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { Module } from "@nestjs/common";
|
|
8
8
|
import { AgentsModule } from "../agents/agents.module.js";
|
|
9
9
|
import { BackgroundModule } from "../background/background.module.js";
|
|
10
|
+
import { AppearanceModule } from "../catalog/appearance.service.js";
|
|
10
11
|
import { CatalogModule } from "../catalog/catalog.module.js";
|
|
11
12
|
import { ChannelsModule } from "../channels/channels.module.js";
|
|
12
13
|
import { ConversationsModule } from "../conversations/conversations.module.js";
|
|
@@ -114,6 +115,7 @@ PlatformOpsModule = __decorate([
|
|
|
114
115
|
AgentsModule,
|
|
115
116
|
OrgsModule,
|
|
116
117
|
CatalogModule,
|
|
118
|
+
AppearanceModule,
|
|
117
119
|
ExtensionsModule,
|
|
118
120
|
ToolsModule,
|
|
119
121
|
ConversationsModule,
|
|
@@ -4,6 +4,7 @@ import { AGENT_ROUTES } from "../agents/agents.schemas.js";
|
|
|
4
4
|
import { AGENT_TEMPLATE_ROUTES } from "../agents/templates.schemas.js";
|
|
5
5
|
import { BACKGROUND_ROUTES } from "../background/background.schemas.js";
|
|
6
6
|
import { ADMIN_CATALOG_ROUTES } from "../catalog/admin-catalog.schemas.js";
|
|
7
|
+
import { APPEARANCE_ROUTES } from "../catalog/appearance.schemas.js";
|
|
7
8
|
import { COMPONENT_SCOPE_ROUTES } from "../catalog/component-scope.schemas.js";
|
|
8
9
|
import { MARKETPLACE_ROUTES } from "../catalog/marketplaces.schemas.js";
|
|
9
10
|
import { ORG_CATALOG_ROUTES } from "../catalog/org-catalog.schemas.js";
|
|
@@ -66,6 +67,8 @@ export const ROUTE_SCHEMAS = [
|
|
|
66
67
|
...ME_ROUTES,
|
|
67
68
|
// themes (themes spec §9)
|
|
68
69
|
...THEME_ROUTES,
|
|
70
|
+
// a plugin's suggested appearance (client extensions spec §6.5)
|
|
71
|
+
...APPEARANCE_ROUTES,
|
|
69
72
|
// setup guide checks (setup guides v2 §10.3, §12)
|
|
70
73
|
...SETUP_CHECK_ROUTES,
|
|
71
74
|
...SETUP_GUIDE_ROUTES,
|
|
@@ -14,6 +14,8 @@ import { PlatformMigration } from "../catalog/platform-migration.js";
|
|
|
14
14
|
import { PluginScopeWorkersModule } from "../catalog/scope-sync-workers.js";
|
|
15
15
|
import { ChannelsModule, ChannelWorkersModule } from "../channels/channels.module.js";
|
|
16
16
|
import { GatewayCheckJob } from "../channels/supervisor.js";
|
|
17
|
+
import { ClientModule } from "../client/client.module.js";
|
|
18
|
+
import { ClientReportsCron } from "../client/client-reports.cron.js";
|
|
17
19
|
import { AttachmentsModule } from "../conversations/attachments.module.js";
|
|
18
20
|
import { AttachmentCleanupJob } from "../conversations/attachments.service.js";
|
|
19
21
|
import { EventLogModule } from "../conversations/event-log.js";
|
|
@@ -55,6 +57,7 @@ let WorkerModule = WorkerModule_1 = class WorkerModule {
|
|
|
55
57
|
module: WorkerModule_1,
|
|
56
58
|
imports: [
|
|
57
59
|
KernelScheduleModule,
|
|
60
|
+
ClientModule,
|
|
58
61
|
// the queue consumers (their worker options come from the startup config)
|
|
59
62
|
RuntimeWorkersModule.forRoot(config),
|
|
60
63
|
EvalWorkersModule.forRoot(config),
|
|
@@ -87,6 +90,7 @@ let WorkerModule = WorkerModule_1 = class WorkerModule {
|
|
|
87
90
|
BackgroundTaskPollJob,
|
|
88
91
|
ToolJobs,
|
|
89
92
|
InitialPasswordCleanupJob,
|
|
93
|
+
ClientReportsCron,
|
|
90
94
|
CatalogJobs,
|
|
91
95
|
PlatformMigration,
|
|
92
96
|
DirectoryScanJob,
|
|
@@ -7,6 +7,7 @@ import { type ParsedTheme } from "@capybara-hq/plugin-sdk";
|
|
|
7
7
|
* the theme's families first, then the built-in stack. Every stored value is re-validated (token
|
|
8
8
|
* names against the overridable list, values against the SDK grammar, radius 0-16, families,
|
|
9
9
|
* paths, weights, styles, roles); one that fails is left out, and `onSkipped` (when given and
|
|
10
|
-
* something was skipped) is called once with the paths of every skipped value.
|
|
10
|
+
* something was skipped) is called once with the paths of every skipped value. The whole
|
|
11
|
+
* stylesheet is one `@layer theme { … }` block (client extensions spec §7.2).
|
|
11
12
|
*/
|
|
12
13
|
export declare function renderThemeCss(theme: ParsedTheme, assetUrl: (pluginPath: string) => string, onSkipped?: (paths: string[]) => void): string;
|
|
@@ -73,7 +73,8 @@ const declarations = (d, extra = []) => [...extra, ...[...d].map(([k, v]) => `${
|
|
|
73
73
|
* the theme's families first, then the built-in stack. Every stored value is re-validated (token
|
|
74
74
|
* names against the overridable list, values against the SDK grammar, radius 0-16, families,
|
|
75
75
|
* paths, weights, styles, roles); one that fails is left out, and `onSkipped` (when given and
|
|
76
|
-
* something was skipped) is called once with the paths of every skipped value.
|
|
76
|
+
* something was skipped) is called once with the paths of every skipped value. The whole
|
|
77
|
+
* stylesheet is one `@layer theme { … }` block (client extensions spec §7.2).
|
|
77
78
|
*/
|
|
78
79
|
export function renderThemeCss(theme, assetUrl, onSkipped) {
|
|
79
80
|
const skipped = [];
|
|
@@ -128,5 +129,7 @@ export function renderThemeCss(theme, assetUrl, onSkipped) {
|
|
|
128
129
|
rules.push(`:root { ${stacks.join(" ")} }`);
|
|
129
130
|
if (skipped.length > 0)
|
|
130
131
|
onSkipped?.(skipped);
|
|
131
|
-
|
|
132
|
+
// One block in the `theme` cascade layer (client extensions spec §7.2): the design stylesheet
|
|
133
|
+
// declares the order design, theme, extensions; an unlayered theme would beat plugin CSS.
|
|
134
|
+
return `@layer theme {\n${rules.join("\n")}\n}\n`;
|
|
132
135
|
}
|
|
@@ -22,9 +22,11 @@ export declare class ThemeDeliveryService {
|
|
|
22
22
|
/**
|
|
23
23
|
* The stylesheet of version `version` of theme `id` ({@link renderThemeCss}, asset URLs from
|
|
24
24
|
* {@link themeUrl}); a stored value that fails re-validation is left out with one warning per
|
|
25
|
-
* render naming every skipped value. Unknown theme or version
|
|
25
|
+
* render naming every skipped value. Unknown theme or version, or a `revision` other than the
|
|
26
|
+
* current `r<THEME_CSS_REVISION>` (an older one promised another format; a later one belongs to a
|
|
27
|
+
* newer replica): 404 `theme.not_found`, never cached.
|
|
26
28
|
*/
|
|
27
|
-
stylesheet(id: string, version: string): Promise<string>;
|
|
29
|
+
stylesheet(id: string, version: string, revision: string): Promise<string>;
|
|
28
30
|
/**
|
|
29
31
|
* Asset `file` (a path inside the plugin, as stored: `fonts/MiSans.woff2`) of version `version`
|
|
30
32
|
* of theme `id`: only a font or logo the version's definition names. Unknown theme, version or
|
|
@@ -15,7 +15,7 @@ import { KERNEL_LOGGER } from "../infra/tokens.js";
|
|
|
15
15
|
import { packagePath } from "../packages/tgz.js";
|
|
16
16
|
import { StorageService } from "../storage/storage.service.js";
|
|
17
17
|
import { renderThemeCss } from "./render-css.js";
|
|
18
|
-
import { themeUrl } from "./theme-state.js";
|
|
18
|
+
import { THEME_CSS_REVISION, themeUrl } from "./theme-state.js";
|
|
19
19
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20
20
|
/** A version number as it appears in a delivery URL: a positive int4. */
|
|
21
21
|
const VERSION_RE = /^[1-9]\d{0,9}$/;
|
|
@@ -61,9 +61,14 @@ let ThemeDeliveryService = class ThemeDeliveryService {
|
|
|
61
61
|
/**
|
|
62
62
|
* The stylesheet of version `version` of theme `id` ({@link renderThemeCss}, asset URLs from
|
|
63
63
|
* {@link themeUrl}); a stored value that fails re-validation is left out with one warning per
|
|
64
|
-
* render naming every skipped value. Unknown theme or version
|
|
64
|
+
* render naming every skipped value. Unknown theme or version, or a `revision` other than the
|
|
65
|
+
* current `r<THEME_CSS_REVISION>` (an older one promised another format; a later one belongs to a
|
|
66
|
+
* newer replica): 404 `theme.not_found`, never cached.
|
|
65
67
|
*/
|
|
66
|
-
async stylesheet(id, version) {
|
|
68
|
+
async stylesheet(id, version, revision) {
|
|
69
|
+
if (revision !== `r${THEME_CSS_REVISION}`) {
|
|
70
|
+
throw notFound(`Theme ${id} version ${version} stylesheet revision ${revision}`);
|
|
71
|
+
}
|
|
67
72
|
const { definition } = await this.version(id, version);
|
|
68
73
|
return renderThemeCss(definition, (path) => themeUrl(id, version, path), (skipped) => this.log.warn("theme stylesheet: stored values failed re-validation and were left out", {
|
|
69
74
|
themeId: id,
|
|
@@ -6,6 +6,11 @@ import { ApiError } from "../errors.js";
|
|
|
6
6
|
export interface ThemeRef {
|
|
7
7
|
/** The `platform_themes` id (what settings and picks store). */
|
|
8
8
|
id: string;
|
|
9
|
+
/**
|
|
10
|
+
* `"<plugin>/<theme file id>"`: the name the web's `theme=` state token and `useTheme()` carry
|
|
11
|
+
* (client extensions spec §7.3). Stable across versions, unlike `id`.
|
|
12
|
+
*/
|
|
13
|
+
key: string;
|
|
9
14
|
/** Its current version. */
|
|
10
15
|
version: string;
|
|
11
16
|
name: LocalizedName;
|
|
@@ -19,7 +24,7 @@ export interface ThemeRef {
|
|
|
19
24
|
wordmark: string | null;
|
|
20
25
|
faviconUrl: string | null;
|
|
21
26
|
} | null;
|
|
22
|
-
/** `/api/v1/themes/<id>/<version>/theme.css`. */
|
|
27
|
+
/** `/api/v1/themes/<id>/<version>/r<THEME_CSS_REVISION>/theme.css`. */
|
|
23
28
|
cssUrl: string;
|
|
24
29
|
}
|
|
25
30
|
/** The four colors a theme picker draws per scheme. */
|
|
@@ -34,24 +39,40 @@ export type ListedTheme = ThemeRef & {
|
|
|
34
39
|
swatches: Partial<Record<ThemeScheme, ThemeSwatch>>;
|
|
35
40
|
};
|
|
36
41
|
/**
|
|
37
|
-
* The
|
|
42
|
+
* The render revision of theme stylesheets, a segment of the stylesheet URL (`r<N>/theme.css`).
|
|
43
|
+
* The stylesheet is cached as immutable for a year, keyed by its URL: bump this whenever
|
|
44
|
+
* `renderThemeCss`'s output format changes, so no browser keeps an older format under a URL
|
|
45
|
+
* that now means the new one. The delivery route serves only the current revision (404
|
|
46
|
+
* `theme.not_found` otherwise). History: (none, `<version>/theme.css`) plain rules; 2: one
|
|
47
|
+
* `@layer theme { … }` block (client extensions spec §7.2).
|
|
48
|
+
*/
|
|
49
|
+
export declare const THEME_CSS_REVISION = 2;
|
|
50
|
+
/**
|
|
51
|
+
* The delivery URL of a theme version (spec §8): its stylesheet (with the render revision,
|
|
52
|
+
* {@link THEME_CSS_REVISION}), or with `asset` (a path inside the
|
|
38
53
|
* plugin as the theme file names it, e.g. `./fonts/MiSans.woff2`) that asset, as one path segment
|
|
39
54
|
* (`assets/fonts%2FMiSans.woff2`; the route decodes it back to the stored path `fonts/MiSans.woff2`).
|
|
40
55
|
* The one builder of these URLs: the listing, the stylesheet and the asset routes all use it.
|
|
41
56
|
*/
|
|
42
57
|
export declare function themeUrl(id: string, version: string, asset?: string): string;
|
|
43
|
-
/**
|
|
44
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The {@link ThemeRef} of version `version` of theme `id` of plugin `plugin` (null only for a row
|
|
60
|
+
* without provenance: the key is then the file id alone).
|
|
61
|
+
*/
|
|
62
|
+
export declare function themeRefOf(id: string, version: string, def: ParsedTheme, plugin: string | null): ThemeRef;
|
|
45
63
|
/**
|
|
46
64
|
* The swatches of each scheme the theme defines: its overrides merged onto the current built-in
|
|
47
65
|
* values (a stored definition holds only the theme file's own values, themes spec §6).
|
|
48
66
|
*/
|
|
49
67
|
export declare function themeSwatches(def: ParsedTheme): Partial<Record<ThemeScheme, ThemeSwatch>>;
|
|
50
|
-
/**
|
|
51
|
-
export
|
|
68
|
+
/** A live theme's current definition and its plugin (null: no provenance). */
|
|
69
|
+
export interface ThemeDefinition {
|
|
52
70
|
version: string;
|
|
53
71
|
definition: ParsedTheme;
|
|
54
|
-
|
|
72
|
+
plugin: string | null;
|
|
73
|
+
}
|
|
74
|
+
/** The current definitions of live themes `ids`, by id. */
|
|
75
|
+
export declare function themeDefinitions(db: KernelSchemaDb, ids: readonly string[]): Promise<Map<string, ThemeDefinition>>;
|
|
55
76
|
/** The live themes on in the org, sorted by id. */
|
|
56
77
|
export declare function themesOnIn(db: KernelSchemaDb, orgId: string): Promise<ComponentRow[]>;
|
|
57
78
|
/** The live theme `id` if it can be the platform default, else null. */
|
|
@@ -9,7 +9,17 @@ import { packagePath } from "../packages/tgz.js";
|
|
|
9
9
|
/** The schemes in the order they are listed. */
|
|
10
10
|
const SCHEMES = ["dark", "light"];
|
|
11
11
|
/**
|
|
12
|
-
* The
|
|
12
|
+
* The render revision of theme stylesheets, a segment of the stylesheet URL (`r<N>/theme.css`).
|
|
13
|
+
* The stylesheet is cached as immutable for a year, keyed by its URL: bump this whenever
|
|
14
|
+
* `renderThemeCss`'s output format changes, so no browser keeps an older format under a URL
|
|
15
|
+
* that now means the new one. The delivery route serves only the current revision (404
|
|
16
|
+
* `theme.not_found` otherwise). History: (none, `<version>/theme.css`) plain rules; 2: one
|
|
17
|
+
* `@layer theme { … }` block (client extensions spec §7.2).
|
|
18
|
+
*/
|
|
19
|
+
export const THEME_CSS_REVISION = 2;
|
|
20
|
+
/**
|
|
21
|
+
* The delivery URL of a theme version (spec §8): its stylesheet (with the render revision,
|
|
22
|
+
* {@link THEME_CSS_REVISION}), or with `asset` (a path inside the
|
|
13
23
|
* plugin as the theme file names it, e.g. `./fonts/MiSans.woff2`) that asset, as one path segment
|
|
14
24
|
* (`assets/fonts%2FMiSans.woff2`; the route decodes it back to the stored path `fonts/MiSans.woff2`).
|
|
15
25
|
* The one builder of these URLs: the listing, the stylesheet and the asset routes all use it.
|
|
@@ -17,14 +27,18 @@ const SCHEMES = ["dark", "light"];
|
|
|
17
27
|
export function themeUrl(id, version, asset) {
|
|
18
28
|
const base = `/api/v1/themes/${encodeURIComponent(id)}/${encodeURIComponent(version)}`;
|
|
19
29
|
return asset === undefined
|
|
20
|
-
? `${base}/theme.css`
|
|
30
|
+
? `${base}/r${THEME_CSS_REVISION}/theme.css`
|
|
21
31
|
: `${base}/assets/${encodeURIComponent(packagePath(asset))}`;
|
|
22
32
|
}
|
|
23
|
-
/**
|
|
24
|
-
|
|
33
|
+
/**
|
|
34
|
+
* The {@link ThemeRef} of version `version` of theme `id` of plugin `plugin` (null only for a row
|
|
35
|
+
* without provenance: the key is then the file id alone).
|
|
36
|
+
*/
|
|
37
|
+
export function themeRefOf(id, version, def, plugin) {
|
|
25
38
|
const url = (ref) => (ref === undefined ? null : themeUrl(id, version, ref));
|
|
26
39
|
return {
|
|
27
40
|
id,
|
|
41
|
+
key: plugin === null ? def.id : `${plugin}/${def.id}`,
|
|
28
42
|
version,
|
|
29
43
|
name: def.name,
|
|
30
44
|
schemes: SCHEMES.filter((s) => def.schemes[s] !== undefined),
|
|
@@ -62,12 +76,16 @@ export async function themeDefinitions(db, ids) {
|
|
|
62
76
|
const rows = await db
|
|
63
77
|
.selectFrom("platform_themes as p")
|
|
64
78
|
.innerJoin("platform_theme_versions as v", (j) => j.onRef("v.theme_id", "=", "p.id").onRef("v.version", "=", "p.current_version"))
|
|
65
|
-
.select(["p.id", "p.current_version", "v.definition"])
|
|
79
|
+
.select(["p.id", "p.current_version", "p.plugin_name", "v.definition"])
|
|
66
80
|
.where("p.id", "in", [...ids])
|
|
67
81
|
.where("p.deleted_at", "is", null)
|
|
68
82
|
.execute();
|
|
69
83
|
for (const r of rows) {
|
|
70
|
-
out.set(r.id, {
|
|
84
|
+
out.set(r.id, {
|
|
85
|
+
version: String(r.current_version),
|
|
86
|
+
definition: r.definition,
|
|
87
|
+
plugin: r.plugin_name,
|
|
88
|
+
});
|
|
71
89
|
}
|
|
72
90
|
return out;
|
|
73
91
|
}
|
|
@@ -10,8 +10,13 @@ import { ThemesService } from "./themes.service.js";
|
|
|
10
10
|
export declare class ThemeDeliveryController {
|
|
11
11
|
private readonly delivery;
|
|
12
12
|
constructor(delivery: ThemeDeliveryService);
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* The version's stylesheet: scheme overrides, `@font-face` rules and font stacks, in the render
|
|
15
|
+
* revision `revision` (`r<THEME_CSS_REVISION>`; only the current one is served). The URL without
|
|
16
|
+
* a revision (before revision 2) has no route: 404, so a browser's cached unlayered copy is never
|
|
17
|
+
* answered with the new format under an immutable cache.
|
|
18
|
+
*/
|
|
19
|
+
css(id: string, version: string, revision: string, reply: FastifyReply): Promise<void>;
|
|
15
20
|
/**
|
|
16
21
|
* A font (`font/woff2`) or logo (`image/svg+xml`, sanitized at import, served under a CSP
|
|
17
22
|
* sandbox; `image/png`) the version names; `file` is its plugin path as one segment
|
|
@@ -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[];
|