@capybara-hq/kernel 0.1.0-rc.10 → 0.1.0-rc.12
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 +3 -0
- package/dist/agents/agents.schemas.d.ts +3 -0
- package/dist/agents/agents.service.js +11 -1
- package/dist/agents/definition.d.ts +1 -0
- package/dist/agents/definition.js +7 -3
- package/dist/agents/org-templates.js +4 -0
- package/dist/agents/pending-connectors.service.js +1 -1
- package/dist/catalog/catalog-content.d.ts +33 -1
- package/dist/catalog/catalog-content.js +65 -2
- package/dist/catalog/catalog-items.js +1 -0
- package/dist/catalog/catalog-scope.service.d.ts +29 -3
- package/dist/catalog/catalog-scope.service.js +82 -10
- package/dist/catalog/catalog.service.d.ts +7 -5
- package/dist/catalog/catalog.service.js +26 -5
- package/dist/catalog/claude/translate.js +3 -0
- package/dist/catalog/component-scope.controller.d.ts +18 -6
- package/dist/catalog/component-scope.controller.js +36 -13
- package/dist/catalog/component-scope.schemas.d.ts +4 -0
- package/dist/catalog/component-scope.service.d.ts +59 -9
- package/dist/catalog/component-scope.service.js +156 -27
- package/dist/catalog/component-state.d.ts +20 -3
- package/dist/catalog/component-state.js +40 -14
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/org-catalog.service.js +3 -0
- package/dist/catalog/owned-items.js +7 -0
- package/dist/catalog/platform-components.d.ts +23 -3
- package/dist/catalog/platform-components.js +118 -9
- package/dist/catalog/platform-migration.js +2 -1
- package/dist/catalog/plugin-import.d.ts +24 -4
- package/dist/catalog/plugin-import.js +41 -7
- package/dist/catalog/plugin-install.d.ts +20 -1
- package/dist/catalog/plugin-install.js +71 -1
- package/dist/catalog/plugin-package.d.ts +41 -4
- package/dist/catalog/plugin-package.js +127 -7
- package/dist/catalog/plugin-upgrade.d.ts +13 -2
- package/dist/catalog/plugin-upgrade.js +124 -6
- package/dist/catalog/unshare.js +1 -0
- package/dist/conversations/conversations.service.d.ts +9 -0
- package/dist/conversations/conversations.service.js +9 -2
- package/dist/conversations/events.d.ts +1 -0
- package/dist/db/kernel-schema.d.ts +54 -4
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +27 -0
- package/dist/http/api.module.js +7 -0
- package/dist/identity/accounts.d.ts +11 -4
- package/dist/identity/accounts.js +23 -10
- package/dist/identity/me.controller.d.ts +10 -10
- package/dist/identity/me.controller.js +8 -6
- package/dist/identity/me.schemas.d.ts +13 -3
- package/dist/identity/me.schemas.js +10 -2
- package/dist/identity/settings.d.ts +27 -0
- package/dist/identity/settings.js +64 -0
- package/dist/orgs/settings.service.d.ts +31 -0
- package/dist/orgs/settings.service.js +31 -1
- package/dist/platform/exclusions.js +26 -0
- package/dist/platform/operation.js +6 -2
- package/dist/platform/ops/admin.ops.d.ts +3 -1
- package/dist/platform/ops/admin.ops.js +25 -7
- package/dist/platform/ops/agents.ops.js +19 -6
- package/dist/platform/ops/components.ops.js +1 -0
- package/dist/platform/ops/org-settings.ops.d.ts +3 -1
- package/dist/platform/ops/org-settings.ops.js +33 -8
- package/dist/platform/ops/orgs.ops.js +1 -0
- package/dist/platform/ops/platform.ops.d.ts +3 -1
- package/dist/platform/ops/platform.ops.js +43 -16
- package/dist/platform/ops/themes.ops.d.ts +14 -0
- package/dist/platform/ops/themes.ops.js +47 -0
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +5 -0
- package/dist/platform/route-schemas.js +3 -0
- package/dist/runtime/subagents/definition.d.ts +3 -2
- package/dist/runtime/subagents/definition.js +2 -1
- package/dist/themes/render-css.d.ts +12 -0
- package/dist/themes/render-css.js +132 -0
- package/dist/themes/theme-delivery.service.d.ts +34 -0
- package/dist/themes/theme-delivery.service.js +111 -0
- package/dist/themes/theme-state.d.ts +95 -0
- package/dist/themes/theme-state.js +203 -0
- package/dist/themes/theme-usage.d.ts +61 -0
- package/dist/themes/theme-usage.js +98 -0
- package/dist/themes/themes-public.controller.d.ts +28 -0
- package/dist/themes/themes-public.controller.js +92 -0
- package/dist/themes/themes.controller.d.ts +13 -0
- package/dist/themes/themes.controller.js +38 -0
- package/dist/themes/themes.module.d.ts +3 -0
- package/dist/themes/themes.module.js +21 -0
- package/dist/themes/themes.schemas.d.ts +3 -0
- package/dist/themes/themes.schemas.js +2 -0
- package/dist/themes/themes.service.d.ts +50 -0
- package/dist/themes/themes.service.js +99 -0
- package/dist/tools/connector-account-policies.d.ts +51 -0
- package/dist/tools/connector-account-policies.js +165 -0
- package/dist/tools/connectors.controller.d.ts +35 -4
- package/dist/tools/connectors.controller.js +66 -7
- package/dist/tools/connectors.schemas.d.ts +11 -0
- package/dist/tools/connectors.schemas.js +11 -0
- package/dist/tools/connectors.service.d.ts +2 -0
- package/dist/tools/connectors.service.js +1 -0
- package/dist/tools/pipeline.d.ts +1 -1
- package/dist/tools/policy.d.ts +23 -1
- package/dist/tools/policy.js +99 -24
- package/dist/tools/tools.module.js +3 -0
- package/package.json +3 -3
- package/src/migrations/0102_themes.sql +64 -0
- package/src/migrations/0103_ui_theme_setting.sql +7 -0
- package/src/migrations/0104_connector_tool_account_policies.sql +16 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AgentTemplate, type ConnectorTemplateFile } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import { type AgentTemplate, type ConnectorTemplateFile, type ParsedTheme } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import type { SkillManifest, SourceFormat } from "../db/kernel-schema.js";
|
|
3
3
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
4
4
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
@@ -6,9 +6,9 @@ import type { KernelLogger } from "../log.js";
|
|
|
6
6
|
import { type SkillPackageFile } from "../skills/package.js";
|
|
7
7
|
import { type PrefixedStorage, StorageService } from "../storage/storage.service.js";
|
|
8
8
|
import { CatalogItemLock } from "./locks.js";
|
|
9
|
-
/** A skill
|
|
9
|
+
/** A skill, agent template or theme version a sync wrote (`from` null: the component was created). */
|
|
10
10
|
export interface ChangedRef {
|
|
11
|
-
kind: "skill" | "agent_template";
|
|
11
|
+
kind: "skill" | "agent_template" | "theme";
|
|
12
12
|
id: string;
|
|
13
13
|
name: string;
|
|
14
14
|
from: number | null;
|
|
@@ -31,11 +31,17 @@ export interface PlatformSyncResult {
|
|
|
31
31
|
skills: ChangedRef[];
|
|
32
32
|
agentTemplates: ChangedRef[];
|
|
33
33
|
connectorTemplates: ChangedConnectorTemplateRef[];
|
|
34
|
+
themes: ChangedRef[];
|
|
34
35
|
}
|
|
35
36
|
/** The prefix (in `deployment/`) of a platform skill version's files. */
|
|
36
37
|
export declare const platformSkillPrefix: (id: string, version: number) => string;
|
|
37
38
|
/** The prefix (in `deployment/`) of a platform agent template version's file. */
|
|
38
39
|
export declare const platformAgentTemplatePrefix: (id: string, version: number) => string;
|
|
40
|
+
/**
|
|
41
|
+
* The prefix (in `deployment/`) of a platform theme's files (themes spec §6); a version's fonts
|
|
42
|
+
* and logo are under `<prefix><version>-<catalog version>/` ({@link syncVersionPrefix}).
|
|
43
|
+
*/
|
|
44
|
+
export declare const platformThemePrefix: (id: string) => string;
|
|
39
45
|
/**
|
|
40
46
|
* A fresh prefix (in `deployment/`) for a version written before its transaction (ruling P3-45):
|
|
41
47
|
* `<id>/<version>-<uuid>/`, so two writers racing for one version number never overwrite each
|
|
@@ -73,6 +79,19 @@ interface TemplateStep {
|
|
|
73
79
|
file: SkillPackageFile;
|
|
74
80
|
template: AgentTemplate;
|
|
75
81
|
}
|
|
82
|
+
interface ThemeStep {
|
|
83
|
+
/** The theme id. */
|
|
84
|
+
name: string;
|
|
85
|
+
base: Base<number> | undefined;
|
|
86
|
+
/** The platform row id. */
|
|
87
|
+
id: string;
|
|
88
|
+
to: number;
|
|
89
|
+
/** null: the theme has no assets. */
|
|
90
|
+
prefix: string | null;
|
|
91
|
+
/** Fonts and logo, sanitized (paths relative to the plugin root). */
|
|
92
|
+
files: SkillPackageFile[];
|
|
93
|
+
theme: ParsedTheme;
|
|
94
|
+
}
|
|
76
95
|
interface ConnectorTemplateStep {
|
|
77
96
|
name: string;
|
|
78
97
|
base: Base<string> | undefined;
|
|
@@ -88,6 +107,7 @@ export interface SyncPlan {
|
|
|
88
107
|
skills: SkillStep[];
|
|
89
108
|
agentTemplates: TemplateStep[];
|
|
90
109
|
connectorTemplates: ConnectorTemplateStep[];
|
|
110
|
+
themes: ThemeStep[];
|
|
91
111
|
}
|
|
92
112
|
/** A component changed between the plan and its write (a concurrent sync): plan again. */
|
|
93
113
|
export declare class PlanStale extends Error {
|
|
@@ -7,10 +7,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
8
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
9
|
};
|
|
10
|
-
// Platform components (plugin scope phase 2, rulings A2 and C2): the skills, agent templates
|
|
11
|
-
// connector templates of a catalog item, stored once for the deployment. Files live
|
|
12
|
-
// `deployment/platform/{skills,agent-templates}/<id>/<version
|
|
13
|
-
// are purged by prefix).
|
|
10
|
+
// Platform components (plugin scope phase 2, rulings A2 and C2): the skills, agent templates,
|
|
11
|
+
// connector templates and themes of a catalog item, stored once for the deployment. Files live
|
|
12
|
+
// under `deployment/platform/{skills,agent-templates,themes}/<id>/<version>…/`, never under an org
|
|
13
|
+
// prefix (orgs are purged by prefix).
|
|
14
14
|
import { createHash, randomUUID } from "node:crypto";
|
|
15
15
|
import { parseAgentTemplate, } from "@capybara-hq/plugin-sdk";
|
|
16
16
|
import { Inject, Injectable } from "@nestjs/common";
|
|
@@ -24,7 +24,7 @@ import { packagePath } from "../packages/tgz.js";
|
|
|
24
24
|
import { readSkillPackage, skillManifest } from "../skills/package.js";
|
|
25
25
|
import { StorageService } from "../storage/storage.service.js";
|
|
26
26
|
import { canonicalJson } from "../tools/canonical-json.js";
|
|
27
|
-
import { readCatalogAgentTemplates, readCatalogFiles, readCatalogTemplates, skillFilesDigest, skillOf, } from "./catalog-content.js";
|
|
27
|
+
import { readCatalogAgentTemplates, readCatalogFiles, readCatalogTemplates, readCatalogThemes, skillFilesDigest, skillOf, themeAssetsDigest, } from "./catalog-content.js";
|
|
28
28
|
import { putCatalogFiles } from "./catalog-items.js";
|
|
29
29
|
import { CatalogItemLock } from "./locks.js";
|
|
30
30
|
import { assertNotAwaiting } from "./ownership.js";
|
|
@@ -32,6 +32,11 @@ import { assertNotAwaiting } from "./ownership.js";
|
|
|
32
32
|
export const platformSkillPrefix = (id, version) => `platform/skills/${id}/${version}/`;
|
|
33
33
|
/** The prefix (in `deployment/`) of a platform agent template version's file. */
|
|
34
34
|
export const platformAgentTemplatePrefix = (id, version) => `platform/agent-templates/${id}/${version}/`;
|
|
35
|
+
/**
|
|
36
|
+
* The prefix (in `deployment/`) of a platform theme's files (themes spec §6); a version's fonts
|
|
37
|
+
* and logo are under `<prefix><version>-<catalog version>/` ({@link syncVersionPrefix}).
|
|
38
|
+
*/
|
|
39
|
+
export const platformThemePrefix = (id) => `platform/themes/${id}/`;
|
|
35
40
|
/**
|
|
36
41
|
* A fresh prefix (in `deployment/`) for a version written before its transaction (ruling P3-45):
|
|
37
42
|
* `<id>/<version>-<uuid>/`, so two writers racing for one version number never overwrite each
|
|
@@ -94,9 +99,10 @@ let PlatformComponents = class PlatformComponents {
|
|
|
94
99
|
const plan = await this.plan(itemId, version);
|
|
95
100
|
if (plan.skills.length === 0 &&
|
|
96
101
|
plan.agentTemplates.length === 0 &&
|
|
97
|
-
plan.connectorTemplates.length === 0
|
|
102
|
+
plan.connectorTemplates.length === 0 &&
|
|
103
|
+
plan.themes.length === 0) {
|
|
98
104
|
await this.markSynced(itemId, version);
|
|
99
|
-
return { skills: [], agentTemplates: [], connectorTemplates: [] };
|
|
105
|
+
return { skills: [], agentTemplates: [], connectorTemplates: [], themes: [] };
|
|
100
106
|
}
|
|
101
107
|
await this.writeFiles(plan);
|
|
102
108
|
try {
|
|
@@ -109,6 +115,7 @@ let PlatformComponents = class PlatformComponents {
|
|
|
109
115
|
skills: r.skills.length,
|
|
110
116
|
agentTemplates: r.agentTemplates.length,
|
|
111
117
|
connectorTemplates: r.connectorTemplates.length,
|
|
118
|
+
themes: r.themes.length,
|
|
112
119
|
});
|
|
113
120
|
return r;
|
|
114
121
|
}
|
|
@@ -449,6 +456,7 @@ let PlatformComponents = class PlatformComponents {
|
|
|
449
456
|
skills: [],
|
|
450
457
|
agentTemplates: [],
|
|
451
458
|
connectorTemplates: [],
|
|
459
|
+
themes: [],
|
|
452
460
|
};
|
|
453
461
|
for (const s of manifest.skills ?? []) {
|
|
454
462
|
const files = await readCatalogFiles(this.files, row.object_prefix, row.files, packagePath(s.path));
|
|
@@ -524,17 +532,53 @@ let PlatformComponents = class PlatformComponents {
|
|
|
524
532
|
file: t.file,
|
|
525
533
|
});
|
|
526
534
|
}
|
|
535
|
+
// Themes (by plugin and id): changed when the definition or an asset changed
|
|
536
|
+
for (const t of await readCatalogThemes(this.files, v)) {
|
|
537
|
+
const live = await this.db
|
|
538
|
+
.selectFrom("platform_themes as p")
|
|
539
|
+
.innerJoin("platform_theme_versions as pv", (j) => j.onRef("pv.theme_id", "=", "p.id").onRef("pv.version", "=", "p.current_version"))
|
|
540
|
+
.leftJoin("catalog_item_versions as cv", (j) => j
|
|
541
|
+
.onRef("cv.item_id", "=", "p.catalog_item_id")
|
|
542
|
+
.onRef("cv.version", "=", "pv.catalog_version"))
|
|
543
|
+
.select(["p.id", "p.current_version", "pv.definition", "cv.files"])
|
|
544
|
+
.where("p.plugin_name", "=", plugin)
|
|
545
|
+
.where("p.name", "=", t.id)
|
|
546
|
+
.where("p.deleted_at", "is", null)
|
|
547
|
+
.executeTakeFirst();
|
|
548
|
+
if (live &&
|
|
549
|
+
canonicalJson(live.definition) === canonicalJson(t.theme) &&
|
|
550
|
+
live.files !== null &&
|
|
551
|
+
themeAssetsDigest(live.files, live.definition) === t.assetsDigest) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
const id = live?.id ?? randomUUID();
|
|
555
|
+
const to = (live?.current_version ?? 0) + 1;
|
|
556
|
+
plan.themes.push({
|
|
557
|
+
name: t.id,
|
|
558
|
+
base: live && { id: live.id, version: live.current_version },
|
|
559
|
+
id,
|
|
560
|
+
to,
|
|
561
|
+
prefix: t.assets.length > 0
|
|
562
|
+
? syncVersionPrefix(`${platformThemePrefix(id)}${to}/`, version)
|
|
563
|
+
: null,
|
|
564
|
+
files: t.assets,
|
|
565
|
+
theme: t.theme,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
527
568
|
return plan;
|
|
528
569
|
}
|
|
529
570
|
/** Copies the planned versions' files to their platform prefixes. */
|
|
530
571
|
async writeFiles(plan) {
|
|
531
|
-
|
|
572
|
+
const themes = plan.themes.filter((t) => t.prefix !== null);
|
|
573
|
+
if (plan.skills.length === 0 && plan.agentTemplates.length === 0 && themes.length === 0)
|
|
532
574
|
return;
|
|
533
575
|
this.files.assertAvailable();
|
|
534
576
|
for (const s of plan.skills)
|
|
535
577
|
await putCatalogFiles(this.files, s.prefix, s.files);
|
|
536
578
|
for (const a of plan.agentTemplates)
|
|
537
579
|
await putCatalogFiles(this.files, a.prefix, [a.file]);
|
|
580
|
+
for (const t of themes)
|
|
581
|
+
await putCatalogFiles(this.files, t.prefix, t.files);
|
|
538
582
|
}
|
|
539
583
|
/**
|
|
540
584
|
* Writes the planned rows under the item name's lock (the lock of the import confirm), after
|
|
@@ -543,7 +587,12 @@ let PlatformComponents = class PlatformComponents {
|
|
|
543
587
|
async apply(plan) {
|
|
544
588
|
await this.itemLock.lock(plan.plugin);
|
|
545
589
|
const t = this.db;
|
|
546
|
-
const out = {
|
|
590
|
+
const out = {
|
|
591
|
+
skills: [],
|
|
592
|
+
agentTemplates: [],
|
|
593
|
+
connectorTemplates: [],
|
|
594
|
+
themes: [],
|
|
595
|
+
};
|
|
547
596
|
// New component rows carry the plugin's owner and 可共享 flag (ruling P3-2); an org plugin's
|
|
548
597
|
// skills and agent templates were created in its owner org (`origin_org_id`, immutable, F27),
|
|
549
598
|
// and like the plugin they are not shown to other orgs until the super admin shows them
|
|
@@ -725,6 +774,66 @@ let PlatformComponents = class PlatformComponents {
|
|
|
725
774
|
to: plan.version,
|
|
726
775
|
});
|
|
727
776
|
}
|
|
777
|
+
for (const th of plan.themes) {
|
|
778
|
+
const live = await t
|
|
779
|
+
.selectFrom("platform_themes")
|
|
780
|
+
.select(["id", "current_version"])
|
|
781
|
+
.where("plugin_name", "=", plan.plugin)
|
|
782
|
+
.where("name", "=", th.name)
|
|
783
|
+
.where("deleted_at", "is", null)
|
|
784
|
+
.forUpdate()
|
|
785
|
+
.executeTakeFirst();
|
|
786
|
+
assertBase(live && { id: live.id, version: live.current_version }, th.base);
|
|
787
|
+
const description = th.theme.description?.en ?? null;
|
|
788
|
+
const descriptionZh = th.theme.description?.zh ?? null;
|
|
789
|
+
if (live) {
|
|
790
|
+
await t
|
|
791
|
+
.updateTable("platform_themes")
|
|
792
|
+
.set({
|
|
793
|
+
current_version: th.to,
|
|
794
|
+
description,
|
|
795
|
+
description_zh: descriptionZh,
|
|
796
|
+
catalog_item_id: plan.itemId,
|
|
797
|
+
updated_at: new Date(),
|
|
798
|
+
})
|
|
799
|
+
.where("id", "=", th.id)
|
|
800
|
+
.execute();
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
await t
|
|
804
|
+
.insertInto("platform_themes")
|
|
805
|
+
.values({
|
|
806
|
+
id: th.id,
|
|
807
|
+
catalog_item_id: plan.itemId,
|
|
808
|
+
plugin_name: plan.plugin,
|
|
809
|
+
name: th.name,
|
|
810
|
+
description,
|
|
811
|
+
description_zh: descriptionZh,
|
|
812
|
+
current_version: th.to,
|
|
813
|
+
scope_mode: "follow",
|
|
814
|
+
...owned,
|
|
815
|
+
...origin,
|
|
816
|
+
})
|
|
817
|
+
.execute();
|
|
818
|
+
}
|
|
819
|
+
await t
|
|
820
|
+
.insertInto("platform_theme_versions")
|
|
821
|
+
.values({
|
|
822
|
+
theme_id: th.id,
|
|
823
|
+
version: th.to,
|
|
824
|
+
definition: JSON.stringify(th.theme),
|
|
825
|
+
object_prefix: th.prefix,
|
|
826
|
+
catalog_version: plan.version,
|
|
827
|
+
})
|
|
828
|
+
.execute();
|
|
829
|
+
out.themes.push({
|
|
830
|
+
kind: "theme",
|
|
831
|
+
id: th.id,
|
|
832
|
+
name: th.name,
|
|
833
|
+
from: th.base?.version ?? null,
|
|
834
|
+
to: th.to,
|
|
835
|
+
});
|
|
836
|
+
}
|
|
728
837
|
return out;
|
|
729
838
|
}
|
|
730
839
|
};
|
|
@@ -203,7 +203,8 @@ let PlatformMigration = class PlatformMigration {
|
|
|
203
203
|
const version = await this.platformVersion(item.id, item.latest_version);
|
|
204
204
|
try {
|
|
205
205
|
const r = await this.platform.syncFromVersion(item.id, version, { initial: false });
|
|
206
|
-
|
|
206
|
+
const changed = r.skills.length + r.agentTemplates.length + r.connectorTemplates.length + r.themes.length;
|
|
207
|
+
if (changed > 0) {
|
|
207
208
|
stats.itemsSynced.push(`${item.name}@${version}`);
|
|
208
209
|
}
|
|
209
210
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AgentTemplate, type AgentTemplateTier, type LocalizedName, type PluginManifest } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import { type AgentTemplate, type AgentTemplateTier, type LocalizedName, type PluginManifest, type ThemeScheme } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import type { Queue } from "bullmq";
|
|
3
3
|
import { ApiError } from "../errors.js";
|
|
4
4
|
import { ExtensionAdminOps } from "../extensions/admin-ops.js";
|
|
@@ -134,6 +134,18 @@ export interface PluginReviewAgentTemplate {
|
|
|
134
134
|
maxSteps?: number;
|
|
135
135
|
skills?: string[];
|
|
136
136
|
}
|
|
137
|
+
/** One theme in a review (themes spec §6): what the super admin sees before confirming. */
|
|
138
|
+
export interface PluginReviewTheme {
|
|
139
|
+
/** The theme id (the file's base name). */
|
|
140
|
+
id: string;
|
|
141
|
+
name: LocalizedName;
|
|
142
|
+
/** The schemes it defines. */
|
|
143
|
+
schemes: ThemeScheme[];
|
|
144
|
+
/** Number of fonts. */
|
|
145
|
+
fonts: number;
|
|
146
|
+
/** It replaces the logo mark, the wordmark or the favicon. */
|
|
147
|
+
logo: boolean;
|
|
148
|
+
}
|
|
137
149
|
/** The source of a staged import. */
|
|
138
150
|
export interface PluginImportSource {
|
|
139
151
|
/**
|
|
@@ -215,6 +227,11 @@ export interface PluginReview {
|
|
|
215
227
|
* {@link pluginReviewHash} when empty, so their digests are unchanged.
|
|
216
228
|
*/
|
|
217
229
|
agentTemplates?: PluginReviewAgentTemplate[];
|
|
230
|
+
/**
|
|
231
|
+
* Themes (themes spec §6). Absent without any; left out of {@link pluginReviewHash} when empty,
|
|
232
|
+
* so the digests of plugins without themes are unchanged.
|
|
233
|
+
*/
|
|
234
|
+
themes?: PluginReviewTheme[];
|
|
218
235
|
/**
|
|
219
236
|
* Each dependency with the highest imported catalog version satisfying its range (null when
|
|
220
237
|
* nothing does yet: the import still completes, an org install is refused until it is imported).
|
|
@@ -333,7 +350,9 @@ export interface StagedTarget {
|
|
|
333
350
|
}
|
|
334
351
|
/**
|
|
335
352
|
* The API error of a plugin package that cannot be read in a catalog path: the pre-0.1.0 format ->
|
|
336
|
-
* `catalog.legacy_format
|
|
353
|
+
* `catalog.legacy_format`; a failing theme (themes spec §5) -> `theme.invalid` (details
|
|
354
|
+
* `[{ theme, errors }]`) or `theme.unreadable` (details `[{ theme, issues }]`, the failing pairs);
|
|
355
|
+
* anything else -> `catalog.invalid_manifest` (issues as details).
|
|
337
356
|
*/
|
|
338
357
|
export declare function catalogPackageError(e: PluginPackageError | ExtensionPackageError): ApiError;
|
|
339
358
|
export declare class PluginImport {
|
|
@@ -629,10 +648,11 @@ export declare class PluginImport {
|
|
|
629
648
|
* current version / package, its own review digest), every template field, each skill's name / path
|
|
630
649
|
* / file count, the dependency resolution, the sha256 of every stored file, the format, the
|
|
631
650
|
* translation report's items, the sha256 of the license text and the two review flags
|
|
632
|
-
* (`claudeManifestIgnored`, `licenseTruncated`),
|
|
651
|
+
* (`claudeManifestIgnored`, `licenseTruncated`), the agent templates when there are any and the
|
|
652
|
+
* themes when there are any.
|
|
633
653
|
* Exported for tests. `repository` is display only.
|
|
634
654
|
*/
|
|
635
|
-
export declare function pluginReviewHash(r: Pick<PluginReview, "name" | "version" | "contentDigest" | "extension" | "connectorTemplates" | "agentTemplates" | "skills" | "dependencies" | "format" | "translation" | "licenseText" | "claudeManifestIgnored" | "licenseTruncated">, files: {
|
|
655
|
+
export declare function pluginReviewHash(r: Pick<PluginReview, "name" | "version" | "contentDigest" | "extension" | "connectorTemplates" | "agentTemplates" | "themes" | "skills" | "dependencies" | "format" | "translation" | "licenseText" | "claudeManifestIgnored" | "licenseTruncated">, files: {
|
|
636
656
|
path: string;
|
|
637
657
|
sha256: string;
|
|
638
658
|
}[]): string;
|
|
@@ -52,7 +52,7 @@ import { CatalogItemLock } from "./locks.js";
|
|
|
52
52
|
import { assertNameFree, assertPackageFree, assertShareable, packageOwner, releaseExtensionClaim, shareGate, usableBy, } from "./ownership.js";
|
|
53
53
|
import { PlatformComponents } from "./platform-components.js";
|
|
54
54
|
import { PLATFORM_MIGRATION_JOB } from "./platform-migration.js";
|
|
55
|
-
import { PluginPackageError, packagePath, readPluginFiles, readPluginPackage, } from "./plugin-package.js";
|
|
55
|
+
import { PluginPackageError, packagePath, readPluginFiles, readPluginPackage, themeApiError, } from "./plugin-package.js";
|
|
56
56
|
import { hasImportRequests, importRequestOf, resolveImportRequests } from "./request-rules.js";
|
|
57
57
|
import { assertNameNotReserved } from "./reserved-names.js";
|
|
58
58
|
import { addScopeOrg } from "./scope-orgs.js";
|
|
@@ -112,12 +112,17 @@ const reviewStale = (review) => new ApiError("plugin.review_stale", `The review
|
|
|
112
112
|
const mismatch = (what) => new ApiError("marketplace.package_mismatch", `The downloaded package does not match the marketplace entry: ${what}`);
|
|
113
113
|
/**
|
|
114
114
|
* The API error of a plugin package that cannot be read in a catalog path: the pre-0.1.0 format ->
|
|
115
|
-
* `catalog.legacy_format
|
|
115
|
+
* `catalog.legacy_format`; a failing theme (themes spec §5) -> `theme.invalid` (details
|
|
116
|
+
* `[{ theme, errors }]`) or `theme.unreadable` (details `[{ theme, issues }]`, the failing pairs);
|
|
117
|
+
* anything else -> `catalog.invalid_manifest` (issues as details).
|
|
116
118
|
*/
|
|
117
119
|
export function catalogPackageError(e) {
|
|
118
120
|
const legacy = e.code === "catalog.legacy_format" || e.code === "extension.legacy_format";
|
|
119
121
|
if (legacy)
|
|
120
122
|
return new ApiError("catalog.legacy_format", e.message, e.issues);
|
|
123
|
+
if (e.code === "theme.invalid" || e.code === "theme.unreadable") {
|
|
124
|
+
return themeApiError(e);
|
|
125
|
+
}
|
|
121
126
|
return invalid(e.issues);
|
|
122
127
|
}
|
|
123
128
|
/** Runs a package read, mapping its errors with {@link catalogPackageError}. */
|
|
@@ -380,7 +385,7 @@ let PluginImport = class PluginImport {
|
|
|
380
385
|
const objectFiles = content.files.filter((f) => content.objectPaths.has(f.path));
|
|
381
386
|
if (objectFiles.length > 0 && retry === undefined) {
|
|
382
387
|
if (source.kind === "bootstrap" && !this.files.available) {
|
|
383
|
-
throw new ApiError("storage.unavailable", `Bootstrap plugin ${m.name} ships skills, connector templates
|
|
388
|
+
throw new ApiError("storage.unavailable", `Bootstrap plugin ${m.name} ships skills, connector templates, agent templates or themes, which are kept in object storage, and object storage is not running yet (it may be a bootstrap plugin itself); a required bootstrap plugin may ship only an icon and setup guides, an optional one is retried once the extensions have loaded`);
|
|
384
389
|
}
|
|
385
390
|
this.files.assertAvailable();
|
|
386
391
|
}
|
|
@@ -430,6 +435,7 @@ let PluginImport = class PluginImport {
|
|
|
430
435
|
skills: content.skills,
|
|
431
436
|
connectorTemplates: content.templates,
|
|
432
437
|
...(content.agentTemplates.length > 0 && { agentTemplates: content.agentTemplates }),
|
|
438
|
+
...(content.themes.length > 0 && { themes: content.themes }),
|
|
433
439
|
dependencies,
|
|
434
440
|
source: src,
|
|
435
441
|
provenance: (opts.provenance ?? NOT_NPM).provenance,
|
|
@@ -1456,6 +1462,23 @@ let PluginImport = class PluginImport {
|
|
|
1456
1462
|
const { name, description, instructions, model, ...rest } = a.template;
|
|
1457
1463
|
return { name, path: a.path, description, instructions, model: model ?? "inherit", ...rest };
|
|
1458
1464
|
});
|
|
1465
|
+
// Themes: the file as written, the assets as sanitized by the reader (never the uploaded
|
|
1466
|
+
// originals: a shared path, e.g. the plugin icon, is stored sanitized too)
|
|
1467
|
+
const themes = pkg.themes.map((t) => {
|
|
1468
|
+
keepObject(t.path);
|
|
1469
|
+
for (const [path, data] of t.assets) {
|
|
1470
|
+
stored.set(path, fileOf(path, data));
|
|
1471
|
+
objectPaths.add(path);
|
|
1472
|
+
}
|
|
1473
|
+
const logo = t.theme.logo;
|
|
1474
|
+
return {
|
|
1475
|
+
id: t.id,
|
|
1476
|
+
name: t.theme.name,
|
|
1477
|
+
schemes: ["dark", "light"].filter((k) => t.theme.schemes[k] !== undefined),
|
|
1478
|
+
fonts: t.theme.fonts?.length ?? 0,
|
|
1479
|
+
logo: logo !== undefined && Object.keys(logo).length > 0,
|
|
1480
|
+
};
|
|
1481
|
+
});
|
|
1459
1482
|
if (problems.length > 0)
|
|
1460
1483
|
throw invalid(problems);
|
|
1461
1484
|
return {
|
|
@@ -1465,6 +1488,7 @@ let PluginImport = class PluginImport {
|
|
|
1465
1488
|
templates,
|
|
1466
1489
|
skills,
|
|
1467
1490
|
agentTemplates,
|
|
1491
|
+
themes,
|
|
1468
1492
|
notLoaded,
|
|
1469
1493
|
templatePaths,
|
|
1470
1494
|
};
|
|
@@ -1639,7 +1663,7 @@ export { PluginImport };
|
|
|
1639
1663
|
*/
|
|
1640
1664
|
function normalizedManifest(pkg) {
|
|
1641
1665
|
const pinned = new Map((pkg.manifest.skills ?? []).map((s) => [packagePath(s.path), s.pinned]));
|
|
1642
|
-
const { agents: _, ...manifest } = pkg.manifest;
|
|
1666
|
+
const { agents: _, themes: __, ...manifest } = pkg.manifest;
|
|
1643
1667
|
return {
|
|
1644
1668
|
...manifest,
|
|
1645
1669
|
skills: pkg.skills.map((s) => {
|
|
@@ -1651,6 +1675,7 @@ function normalizedManifest(pkg) {
|
|
|
1651
1675
|
...(pkg.agentTemplates.length > 0 && {
|
|
1652
1676
|
agents: pkg.agentTemplates.map((a) => ({ path: `./${a.path}` })),
|
|
1653
1677
|
}),
|
|
1678
|
+
...(pkg.themes.length > 0 && { themes: pkg.themes.map((t) => ({ path: `./${t.path}` })) }),
|
|
1654
1679
|
};
|
|
1655
1680
|
}
|
|
1656
1681
|
/** package.json `name`, or null without one (a git plugin; never the string "undefined"). */
|
|
@@ -1682,13 +1707,17 @@ function actionOf(s, sha256, version) {
|
|
|
1682
1707
|
* current version / package, its own review digest), every template field, each skill's name / path
|
|
1683
1708
|
* / file count, the dependency resolution, the sha256 of every stored file, the format, the
|
|
1684
1709
|
* translation report's items, the sha256 of the license text and the two review flags
|
|
1685
|
-
* (`claudeManifestIgnored`, `licenseTruncated`),
|
|
1710
|
+
* (`claudeManifestIgnored`, `licenseTruncated`), the agent templates when there are any and the
|
|
1711
|
+
* themes when there are any.
|
|
1686
1712
|
* Exported for tests. `repository` is display only.
|
|
1687
1713
|
*/
|
|
1688
1714
|
export function pluginReviewHash(r, files) {
|
|
1689
1715
|
const e = r.extension;
|
|
1690
1716
|
const lic = r.licenseText;
|
|
1691
|
-
|
|
1717
|
+
// canonicalJson, not JSON.stringify: the review is stored as jsonb, which reorders object keys,
|
|
1718
|
+
// so a template's auth / endpoint object would hash differently at confirm (plugin.review_stale
|
|
1719
|
+
// on every import of a plugin whose connector template has a non-trivial auth block).
|
|
1720
|
+
const canonical = canonicalJson([
|
|
1692
1721
|
r.name,
|
|
1693
1722
|
r.version,
|
|
1694
1723
|
r.contentDigest,
|
|
@@ -1715,6 +1744,10 @@ export function pluginReviewHash(r, files) {
|
|
|
1715
1744
|
...((r.agentTemplates ?? []).length > 0
|
|
1716
1745
|
? [(r.agentTemplates ?? []).map((a) => [a.path, canonicalJson(a)])]
|
|
1717
1746
|
: []),
|
|
1747
|
+
// themes, only when there are any, tagged (never read as agent templates)
|
|
1748
|
+
...((r.themes ?? []).length > 0
|
|
1749
|
+
? [{ themes: (r.themes ?? []).map((t) => [t.id, canonicalJson(t)]) }]
|
|
1750
|
+
: []),
|
|
1718
1751
|
]);
|
|
1719
1752
|
return createHash("sha256").update(canonical).digest("hex").slice(0, 32);
|
|
1720
1753
|
}
|
|
@@ -1785,7 +1818,8 @@ function ownStaging(owner) {
|
|
|
1785
1818
|
/**
|
|
1786
1819
|
* An org upload's custom deny, by content (ruling P3-15'): skills or agent templates ->
|
|
1787
1820
|
* `customSkills`, connector templates -> `customConnectors`; each `deny` -> 403
|
|
1788
|
-
* `catalog.custom_disabled`. A package carrying neither passes
|
|
1821
|
+
* `catalog.custom_disabled`. A package carrying neither passes; themes are covered by neither
|
|
1822
|
+
* switch (themes spec §6), so a themes-only upload passes.
|
|
1789
1823
|
*/
|
|
1790
1824
|
async function assertCustomContent(db, content) {
|
|
1791
1825
|
if (content.skills.length > 0 || content.agentTemplates.length > 0) {
|
|
@@ -11,6 +11,7 @@ import { OrgLock } from "../orgs/org-lock.js";
|
|
|
11
11
|
import type { OrgAction } from "../orgs/roles.js";
|
|
12
12
|
import { type SkillConflict, SkillsService } from "../skills/skills.service.js";
|
|
13
13
|
import { StorageService } from "../storage/storage.service.js";
|
|
14
|
+
import { type ThemePreview } from "../themes/theme-usage.js";
|
|
14
15
|
import type { ConnectorDetail, CreateConnectorInput, ToolIssue } from "../tools/connectors.service.js";
|
|
15
16
|
import { ConnectorsService } from "../tools/connectors.service.js";
|
|
16
17
|
import { type CatalogVersionFiles, type TemplateDeps } from "./catalog-content.js";
|
|
@@ -42,7 +43,7 @@ export interface PluginInstallInput {
|
|
|
42
43
|
* agent template (ref = its agent_templates id; like a skill, `skill_version` records the version
|
|
43
44
|
* an install or upgrade added to an existing template).
|
|
44
45
|
*/
|
|
45
|
-
export type ComponentKind = "extension" | "connector" | "skill" | "plugin" | "agent_template";
|
|
46
|
+
export type ComponentKind = "extension" | "connector" | "skill" | "plugin" | "agent_template" | "theme";
|
|
46
47
|
export interface UninstallReport {
|
|
47
48
|
removed: {
|
|
48
49
|
kind: ComponentKind;
|
|
@@ -84,6 +85,11 @@ export interface UninstallPreview {
|
|
|
84
85
|
ref: string;
|
|
85
86
|
}[];
|
|
86
87
|
}[];
|
|
88
|
+
/**
|
|
89
|
+
* The plugin's themes on in the org through it (`follow`) that go off with it (themes spec §6):
|
|
90
|
+
* whether it is the org's default and how many people picked it. Absent when there are none.
|
|
91
|
+
*/
|
|
92
|
+
themes?: ThemePreview[];
|
|
87
93
|
}
|
|
88
94
|
/** The confirm hash of an uninstall preview: sha256 over its sorted JSON (the scheme of `diffHash`). */
|
|
89
95
|
export declare function uninstallPreviewHash(p: UninstallPreview): string;
|
|
@@ -303,6 +309,12 @@ export declare class PluginInstaller {
|
|
|
303
309
|
* and a name two plugins of the same plan both bring (the one installed first is the source).
|
|
304
310
|
*/
|
|
305
311
|
private assertNoSkillConflicts;
|
|
312
|
+
/**
|
|
313
|
+
* Refuses installing a plugin whose themes would come on in the org (they follow it) while
|
|
314
|
+
* another theme with the same id is on there (themes spec §6, rule 10): 409 `theme.id_taken`,
|
|
315
|
+
* details `[{ theme, orgId, themes, plugins }]`, before anything is written.
|
|
316
|
+
*/
|
|
317
|
+
private assertNoThemeClashes;
|
|
306
318
|
/** The connector checks of templates against what this replica loads right now. */
|
|
307
319
|
templateDeps(): TemplateDeps;
|
|
308
320
|
/**
|
|
@@ -453,6 +465,13 @@ export declare class PluginInstaller {
|
|
|
453
465
|
* nothing written), and keeps the connectors it created as the org's own (`org_owned`).
|
|
454
466
|
*/
|
|
455
467
|
private writeUninstall;
|
|
468
|
+
/**
|
|
469
|
+
* 409 `theme.platform_default` when uninstalling `installId` would switch the platform default
|
|
470
|
+
* theme off in the org (one of the plugin's `follow` themes on there through it, themes spec §7),
|
|
471
|
+
* in the caller's transaction before anything is written. A missing install passes (the
|
|
472
|
+
* uninstall reports it).
|
|
473
|
+
*/
|
|
474
|
+
assertKeepsPlatformDefault(orgId: string, installId: string): Promise<void>;
|
|
456
475
|
/**
|
|
457
476
|
* The oldest other live install (not in `removing`) that has component `kind` / `ref`, or
|
|
458
477
|
* undefined.
|
|
@@ -28,10 +28,11 @@ import { OrgLock } from "../orgs/org-lock.js";
|
|
|
28
28
|
import { packagePath } from "../packages/tgz.js";
|
|
29
29
|
import { SkillsService, skillConflictError, skillConflicts, } from "../skills/skills.service.js";
|
|
30
30
|
import { StorageService } from "../storage/storage.service.js";
|
|
31
|
+
import { assertNotPlatformDefault, themesPreview, } from "../themes/theme-usage.js";
|
|
31
32
|
import { ConnectorsService } from "../tools/connectors.service.js";
|
|
32
33
|
import { findConnectorIn, lockOrgForActions, readCatalogAgentTemplates, readCatalogFiles, readCatalogTemplates, requiredActions, templateConnectorInput, templatePluginProblem, } from "./catalog-content.js";
|
|
33
34
|
import { versionUsable } from "./catalog-items.js";
|
|
34
|
-
import { agentDefinitionRefs, componentOn, connectorTemplateState, connectorTemplatesOn, listedIn, loadComponents, loadScopeState, pluginFactsOf, referenceIds, switchOf, } from "./component-state.js";
|
|
35
|
+
import { agentDefinitionRefs, componentOn, connectorTemplateState, connectorTemplatesOn, listedIn, loadComponents, loadScopeState, pluginFactsOf, referenceIds, switchOf, themeIdClashes, } from "./component-state.js";
|
|
35
36
|
import { assertItemInstallable, disabledPluginItems, pluginItemDisabled, resolveRequestsByInstall, } from "./request-rules.js";
|
|
36
37
|
import { visibleTo } from "./scope.js";
|
|
37
38
|
import { ScopeNoLongerWanted, ScopeStillWanted, wantedByScope } from "./scope-state.js";
|
|
@@ -336,12 +337,14 @@ let PluginInstaller = class PluginInstaller {
|
|
|
336
337
|
async preflight(orgId, actor, itemId, version) {
|
|
337
338
|
const plan = await this.plan(orgId, actor, itemId, version, null, "", undefined);
|
|
338
339
|
await this.assertNoSkillConflicts(orgId, plan);
|
|
340
|
+
await this.assertNoThemeClashes(orgId, itemId);
|
|
339
341
|
return { actions: planActions(plan) };
|
|
340
342
|
}
|
|
341
343
|
async install(orgId, actor, itemId, version, input, opts = {}) {
|
|
342
344
|
const { switchOn, via, ...actorOpts } = opts;
|
|
343
345
|
const plan = await this.plan(orgId, actor, itemId, version, input, "", undefined);
|
|
344
346
|
await this.assertNoSkillConflicts(orgId, plan);
|
|
347
|
+
await this.assertNoThemeClashes(orgId, itemId);
|
|
345
348
|
let installId;
|
|
346
349
|
try {
|
|
347
350
|
installId = await this.execute(orgId, actor, plan, actorOpts, {
|
|
@@ -386,6 +389,39 @@ let PluginInstaller = class PluginInstaller {
|
|
|
386
389
|
if (conflicts.length > 0)
|
|
387
390
|
throw skillConflictError(conflicts);
|
|
388
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Refuses installing a plugin whose themes would come on in the org (they follow it) while
|
|
394
|
+
* another theme with the same id is on there (themes spec §6, rule 10): 409 `theme.id_taken`,
|
|
395
|
+
* details `[{ theme, orgId, themes, plugins }]`, before anything is written.
|
|
396
|
+
*/
|
|
397
|
+
async assertNoThemeClashes(orgId, itemId) {
|
|
398
|
+
const themes = await loadComponents(this.db, "theme", { itemId });
|
|
399
|
+
if (themes.length === 0)
|
|
400
|
+
return;
|
|
401
|
+
const st = await loadScopeState(this.db, "theme", themes);
|
|
402
|
+
const conflicts = [];
|
|
403
|
+
for (const c of themes) {
|
|
404
|
+
const p = pluginFactsOf(st, c.itemId, orgId);
|
|
405
|
+
const listed = listedIn(st, c.id, orgId);
|
|
406
|
+
const sw = switchOf(st, c.id, orgId);
|
|
407
|
+
if (componentOn(c, p, listed, sw) ||
|
|
408
|
+
!componentOn(c, p && { ...p, installed: true }, listed, sw)) {
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
const clash = await themeIdClashes(this.db, orgId, c, c.name);
|
|
412
|
+
if (clash.length > 0) {
|
|
413
|
+
conflicts.push({
|
|
414
|
+
theme: c.name,
|
|
415
|
+
orgId,
|
|
416
|
+
themes: clash.map((x) => x.id),
|
|
417
|
+
plugins: clash.map((x) => x.plugin ?? "platform"),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (conflicts.length > 0) {
|
|
422
|
+
throw new ApiError("theme.id_taken", `The plugin's themes would come on next to themes with the same id already on in this organization (${conflicts.map((c) => c.theme).join(", ")}); switch those themes off first`, conflicts);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
389
425
|
/** The connector checks of templates against what this replica loads right now. */
|
|
390
426
|
templateDeps() {
|
|
391
427
|
const versions = new Map(this.registry.manifests().map((m) => [m.name, m.version]));
|
|
@@ -1108,6 +1144,10 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1108
1144
|
}
|
|
1109
1145
|
}
|
|
1110
1146
|
}
|
|
1147
|
+
// An org's own uninstall never takes the platform default theme away from it (themes spec §7;
|
|
1148
|
+
// a scope sync uninstall follows a scope change or disable that was guarded already)
|
|
1149
|
+
if (via !== "scope")
|
|
1150
|
+
await this.assertKeepsPlatformDefault(orgId, installId);
|
|
1111
1151
|
const outcome = await this.uninstallIn(orgId, actor, installId, {
|
|
1112
1152
|
report,
|
|
1113
1153
|
removing: new Set(),
|
|
@@ -1131,6 +1171,24 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1131
1171
|
});
|
|
1132
1172
|
return report;
|
|
1133
1173
|
}
|
|
1174
|
+
/**
|
|
1175
|
+
* 409 `theme.platform_default` when uninstalling `installId` would switch the platform default
|
|
1176
|
+
* theme off in the org (one of the plugin's `follow` themes on there through it, themes spec §7),
|
|
1177
|
+
* in the caller's transaction before anything is written. A missing install passes (the
|
|
1178
|
+
* uninstall reports it).
|
|
1179
|
+
*/
|
|
1180
|
+
async assertKeepsPlatformDefault(orgId, installId) {
|
|
1181
|
+
const found = await this.db
|
|
1182
|
+
.selectFrom("plugin_installs")
|
|
1183
|
+
.select("id")
|
|
1184
|
+
.where("id", "=", installId)
|
|
1185
|
+
.where("org_id", "=", orgId)
|
|
1186
|
+
.executeTakeFirst();
|
|
1187
|
+
if (!found)
|
|
1188
|
+
return;
|
|
1189
|
+
const p = await this.previewUninstall(orgId, installId);
|
|
1190
|
+
await assertNotPlatformDefault(this.db, (p.themes ?? []).map((t) => t.id), () => "narrower");
|
|
1191
|
+
}
|
|
1134
1192
|
/**
|
|
1135
1193
|
* The oldest other live install (not in `removing`) that has component `kind` / `ref`, or
|
|
1136
1194
|
* undefined.
|
|
@@ -1883,8 +1941,19 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1883
1941
|
}
|
|
1884
1942
|
}
|
|
1885
1943
|
}
|
|
1944
|
+
const themes = await loadComponents(t, "theme", { itemId });
|
|
1945
|
+
const tst = await loadScopeState(t, "theme", themes);
|
|
1946
|
+
for (const c of themes) {
|
|
1947
|
+
if (c.scopeMode === "follow" &&
|
|
1948
|
+
componentOn(c, pluginFactsOf(tst, c.itemId, orgId), listedIn(tst, c.id, orgId), switchOf(tst, c.id, orgId))) {
|
|
1949
|
+
lostThemes.set(c.id, new Set([orgId]));
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1886
1952
|
};
|
|
1953
|
+
/** The themes that go off in the org with the install (and the dependencies it removes). */
|
|
1954
|
+
const lostThemes = new Map();
|
|
1887
1955
|
await visit(row.item_id, comps, true, new Set([installId]));
|
|
1956
|
+
const themes = await themesPreview(t, lostThemes);
|
|
1888
1957
|
return {
|
|
1889
1958
|
installId,
|
|
1890
1959
|
name: row.name,
|
|
@@ -1894,6 +1963,7 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1894
1963
|
agents: [...used.values()]
|
|
1895
1964
|
.map((a) => ({ ...a, uses: a.uses.sort((x, y) => x.ref.localeCompare(y.ref)) }))
|
|
1896
1965
|
.sort((x, y) => x.id.localeCompare(y.id)),
|
|
1966
|
+
...(themes !== undefined && { themes }),
|
|
1897
1967
|
};
|
|
1898
1968
|
}
|
|
1899
1969
|
/**
|