@capybara-hq/kernel 0.1.0-rc.10 → 0.1.0-rc.11

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.
Files changed (79) hide show
  1. package/dist/admin/admin.schemas.d.ts +3 -0
  2. package/dist/catalog/catalog-content.d.ts +33 -1
  3. package/dist/catalog/catalog-content.js +65 -2
  4. package/dist/catalog/catalog-items.js +1 -0
  5. package/dist/catalog/catalog-scope.service.d.ts +29 -3
  6. package/dist/catalog/catalog-scope.service.js +82 -10
  7. package/dist/catalog/catalog.service.d.ts +7 -5
  8. package/dist/catalog/catalog.service.js +26 -5
  9. package/dist/catalog/claude/translate.js +3 -0
  10. package/dist/catalog/component-scope.schemas.d.ts +4 -0
  11. package/dist/catalog/component-scope.service.d.ts +48 -9
  12. package/dist/catalog/component-scope.service.js +152 -27
  13. package/dist/catalog/component-state.d.ts +12 -3
  14. package/dist/catalog/component-state.js +21 -3
  15. package/dist/catalog/marketplaces.service.d.ts +1 -1
  16. package/dist/catalog/org-catalog.service.js +3 -0
  17. package/dist/catalog/owned-items.js +7 -0
  18. package/dist/catalog/platform-components.d.ts +23 -3
  19. package/dist/catalog/platform-components.js +118 -9
  20. package/dist/catalog/platform-migration.js +2 -1
  21. package/dist/catalog/plugin-import.d.ts +24 -4
  22. package/dist/catalog/plugin-import.js +37 -6
  23. package/dist/catalog/plugin-install.d.ts +20 -1
  24. package/dist/catalog/plugin-install.js +71 -1
  25. package/dist/catalog/plugin-package.d.ts +41 -4
  26. package/dist/catalog/plugin-package.js +127 -7
  27. package/dist/catalog/plugin-upgrade.d.ts +13 -2
  28. package/dist/catalog/plugin-upgrade.js +124 -6
  29. package/dist/catalog/unshare.js +1 -0
  30. package/dist/db/kernel-schema.d.ts +41 -4
  31. package/dist/errors.d.ts +25 -0
  32. package/dist/errors.js +25 -0
  33. package/dist/http/api.module.js +7 -0
  34. package/dist/identity/accounts.d.ts +11 -4
  35. package/dist/identity/accounts.js +23 -10
  36. package/dist/identity/me.controller.d.ts +10 -10
  37. package/dist/identity/me.controller.js +8 -6
  38. package/dist/identity/me.schemas.d.ts +13 -3
  39. package/dist/identity/me.schemas.js +10 -2
  40. package/dist/identity/settings.d.ts +27 -0
  41. package/dist/identity/settings.js +64 -0
  42. package/dist/orgs/settings.service.d.ts +31 -0
  43. package/dist/orgs/settings.service.js +31 -1
  44. package/dist/platform/exclusions.js +16 -0
  45. package/dist/platform/operation.js +6 -2
  46. package/dist/platform/ops/admin.ops.d.ts +3 -1
  47. package/dist/platform/ops/admin.ops.js +25 -7
  48. package/dist/platform/ops/components.ops.js +1 -0
  49. package/dist/platform/ops/org-settings.ops.d.ts +3 -1
  50. package/dist/platform/ops/org-settings.ops.js +33 -8
  51. package/dist/platform/ops/orgs.ops.js +1 -0
  52. package/dist/platform/ops/platform.ops.d.ts +3 -1
  53. package/dist/platform/ops/platform.ops.js +43 -16
  54. package/dist/platform/ops/themes.ops.d.ts +14 -0
  55. package/dist/platform/ops/themes.ops.js +47 -0
  56. package/dist/platform/platform-ops.module.d.ts +2 -1
  57. package/dist/platform/platform-ops.module.js +5 -0
  58. package/dist/platform/route-schemas.js +3 -0
  59. package/dist/themes/render-css.d.ts +12 -0
  60. package/dist/themes/render-css.js +132 -0
  61. package/dist/themes/theme-delivery.service.d.ts +34 -0
  62. package/dist/themes/theme-delivery.service.js +111 -0
  63. package/dist/themes/theme-state.d.ts +95 -0
  64. package/dist/themes/theme-state.js +203 -0
  65. package/dist/themes/theme-usage.d.ts +61 -0
  66. package/dist/themes/theme-usage.js +98 -0
  67. package/dist/themes/themes-public.controller.d.ts +28 -0
  68. package/dist/themes/themes-public.controller.js +92 -0
  69. package/dist/themes/themes.controller.d.ts +13 -0
  70. package/dist/themes/themes.controller.js +38 -0
  71. package/dist/themes/themes.module.d.ts +3 -0
  72. package/dist/themes/themes.module.js +21 -0
  73. package/dist/themes/themes.schemas.d.ts +3 -0
  74. package/dist/themes/themes.schemas.js +2 -0
  75. package/dist/themes/themes.service.d.ts +50 -0
  76. package/dist/themes/themes.service.js +99 -0
  77. package/package.json +3 -3
  78. package/src/migrations/0102_themes.sql +64 -0
  79. package/src/migrations/0103_ui_theme_setting.sql +7 -0
@@ -17,6 +17,7 @@ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
17
17
  import { KernelDb } from "../infra/database/kernel-db.js";
18
18
  import { TransactionHost } from "../infra/database/transaction-host.js";
19
19
  import { Transactional } from "../infra/database/transactional.decorator.js";
20
+ import { assertThemeOnIn } from "../themes/theme-state.js";
20
21
  import { IanaTimeZone } from "../time-zone.js";
21
22
  import { OrgLock } from "./org-lock.js";
22
23
  /**
@@ -33,6 +34,8 @@ import { OrgLock } from "./org-lock.js";
33
34
  * `read-only` (writes listed as unavailable) or `off` (default `on`).
34
35
  * - `timeZone`: the org's default time zone (IANA; absent = UTC) for senders without one of their
35
36
  * own (run context spec §3.3); an invalid stored value reads as absent.
37
+ * - `theme`: the org's default theme and whether members may pick their own (themes spec §7);
38
+ * absent (or an invalid stored value) = follow the platform, personal picks allowed.
36
39
  */
37
40
  /** 组织沙箱出站白名单的主机数上限。 */
38
41
  export const ORG_SANDBOX_ALLOW_HOSTS_MAX = 200;
@@ -59,6 +62,19 @@ export const SandboxEgressSettingSchema = z
59
62
  .optional(),
60
63
  })
61
64
  .strict();
65
+ /**
66
+ * The org's theme setting (themes spec §7): `id` a theme on in the org (a `platform_themes` id),
67
+ * null = follow the platform's default; `personal: "locked"` hides the personal picker in the org
68
+ * and resolution ignores personal picks there.
69
+ */
70
+ export const OrgThemeSettingSchema = z
71
+ .object({
72
+ id: z.string().min(1).max(200).nullable(),
73
+ personal: z.enum(["allowed", "locked"]),
74
+ })
75
+ .strict();
76
+ /** The theme setting of an org that has none: follow the platform, personal picks allowed. */
77
+ export const DEFAULT_ORG_THEME = { id: null, personal: "allowed" };
62
78
  export const OrgSettingsSchema = z.object({
63
79
  toolSearch: z.enum(["on", "off"]).default("on").catch("on"),
64
80
  locale: z.enum(["zh", "en"]).default("en").catch("en"),
@@ -69,6 +85,8 @@ export const OrgSettingsSchema = z.object({
69
85
  wakeOnBackgroundTask: z.boolean().optional().catch(undefined),
70
86
  /** The org's default time zone (run context spec §3.3); absent = UTC. */
71
87
  timeZone: IanaTimeZone.optional().catch(undefined),
88
+ /** The org's default theme and personal lock (themes spec §7); absent = {@link DEFAULT_ORG_THEME}. */
89
+ theme: OrgThemeSettingSchema.optional().catch(undefined),
72
90
  });
73
91
  /** PATCH 体:只改给出的字段;未知字段 → 400。 */
74
92
  export const OrgSettingsPatchSchema = z
@@ -83,6 +101,11 @@ export const OrgSettingsPatchSchema = z
83
101
  sandboxEgress: SandboxEgressSettingSchema.nullable().optional(),
84
102
  /** The org's default time zone (IANA); null clears it (UTC). */
85
103
  timeZone: IanaTimeZone.nullable().optional(),
104
+ /**
105
+ * The org's default theme (`id`: a theme on in the org, else 400 `theme.not_available`; null
106
+ * follows the platform) and whether members may pick their own; null clears it.
107
+ */
108
+ theme: OrgThemeSettingSchema.nullable().optional(),
86
109
  })
87
110
  .strict();
88
111
  /**
@@ -204,10 +227,17 @@ let OrgSettingsService = class OrgSettingsService {
204
227
  delete after.wakeOnBackgroundTask;
205
228
  if (after.timeZone === undefined)
206
229
  delete after.timeZone;
230
+ if (after.theme === undefined)
231
+ delete after.theme;
207
232
  if (sameSettings(before, after))
208
233
  return before;
234
+ // Lock order: the org row (orgLock above), then the theme's catalog item and its row FOR
235
+ // SHARE (assertThemeOnIn), then the check, so no scope writer changes what it read first
236
+ if (patch.theme?.id != null && patch.theme.id !== before.theme?.id) {
237
+ await assertThemeOnIn(this.db.db, orgId, patch.theme.id);
238
+ }
209
239
  // 与存量合并:保留本版本不认识的键(清除的 sandboxEgress 不留在存量里)
210
- const { sandboxEgress: _old, wakeOnBackgroundTask: _oldWake, timeZone: _oldZone, ...rest } = stored;
240
+ const { sandboxEgress: _old, wakeOnBackgroundTask: _oldWake, timeZone: _oldZone, theme: _oldTheme, ...rest } = stored;
211
241
  await this.db
212
242
  .updateTable("orgs")
213
243
  .set({ settings: JSON.stringify({ ...rest, ...after }) })
@@ -664,4 +664,20 @@ export const PLATFORM_EXCLUSIONS = [
664
664
  category: "transport",
665
665
  reason: "Server-sent event stream of UI invalidations",
666
666
  },
667
+ // themes (themes spec §8-§9)
668
+ {
669
+ route: "GET /api/v1/themes/:id/:version/theme.css",
670
+ category: "transport",
671
+ reason: "Serves a theme version's stylesheet to the browser; an agent reads themes.list",
672
+ },
673
+ {
674
+ route: "GET /api/v1/themes/:id/:version/assets/:file",
675
+ category: "transport",
676
+ reason: "Serves a theme version's font and logo bytes to the browser",
677
+ },
678
+ {
679
+ route: "GET /api/v1/platform/theme",
680
+ category: "transport",
681
+ reason: "The platform default theme for the web's pages before sign-in; no session to act for",
682
+ },
667
683
  ];
@@ -12,7 +12,7 @@ export const operationDomain = (id) => id.slice(0, id.indexOf("."));
12
12
  export const PLATFORM_DOMAINS = [
13
13
  {
14
14
  id: "platform",
15
- description: "What this platform is, its tool families, what you and this agent can do, and your own preferences (time zone).",
15
+ description: "What this platform is, its tool families, what you and this agent can do, and your own preferences (time zone, theme).",
16
16
  },
17
17
  { id: "skills", description: "The org's skill library: list, read, create, switch on or off." },
18
18
  {
@@ -81,10 +81,14 @@ export const PLATFORM_DOMAINS = [
81
81
  },
82
82
  {
83
83
  id: "org-settings",
84
- description: "The org's settings (tool search, language, sandbox policy, the Platform tool switch): read and change.",
84
+ description: "The org's settings (tool search, language, sandbox policy, the Platform tool switch, the default theme): read and change.",
85
85
  },
86
86
  {
87
87
  id: "environments",
88
88
  description: "Your own cloud environments (the workspaces your conversations run in): list, read, create, rename, delete, wipe the files.",
89
89
  },
90
+ {
91
+ id: "themes",
92
+ description: "The themes on in this org, the one in effect for you and the org's theme setting: list. Themes are set through your preferences, the org settings and the deployment settings.",
93
+ },
90
94
  ];
@@ -10,6 +10,7 @@ import { PricesService } from "../../models/prices.js";
10
10
  import { ModelPurposesService } from "../../models/purposes.js";
11
11
  import { OrgsService } from "../../orgs/orgs.service.js";
12
12
  import { ProviderRegistry } from "../../runtime/providers.js";
13
+ import { ThemesService } from "../../themes/themes.service.js";
13
14
  import { PlatformOperations } from "../registry.js";
14
15
  /**
15
16
  * Admin console operations of the `Platform` tool (spec §14.2): the dashboard totals, the
@@ -30,7 +31,8 @@ export declare class AdminOperations implements OnModuleInit {
30
31
  private readonly enablement;
31
32
  private readonly prices;
32
33
  private readonly purposes;
33
- constructor(registry: PlatformOperations, extensions: ExtensionAdmin, orgs: OrgsService, accounts: AccountsService, catalogRequests: CatalogRequestsService, settings: SettingsService, providers: ProviderRegistry, displayNames: ExtensionDisplayNames, extensionRegistry: ExtensionRegistry, enablement: ModelEnablement, prices: PricesService, purposes: ModelPurposesService);
34
+ private readonly themes;
35
+ constructor(registry: PlatformOperations, extensions: ExtensionAdmin, orgs: OrgsService, accounts: AccountsService, catalogRequests: CatalogRequestsService, settings: SettingsService, providers: ProviderRegistry, displayNames: ExtensionDisplayNames, extensionRegistry: ExtensionRegistry, enablement: ModelEnablement, prices: PricesService, purposes: ModelPurposesService, themes: ThemesService);
34
36
  private assertProvider;
35
37
  onModuleInit(): void;
36
38
  }
@@ -25,6 +25,7 @@ import { purposeTargetOf } from "../../models/purposes.controller.js";
25
25
  import { ModelPurposesService, PURPOSE_PUT, withDeclaredByNames } from "../../models/purposes.js";
26
26
  import { OrgsService } from "../../orgs/orgs.service.js";
27
27
  import { ProviderRegistry } from "../../runtime/providers.js";
28
+ import { ThemesService } from "../../themes/themes.service.js";
28
29
  import { defineOperation } from "../operation.js";
29
30
  import { PlatformOperations } from "../registry.js";
30
31
  import { precheckBy, viaAgent } from "./errors.js";
@@ -38,6 +39,7 @@ const SettingsInput = DEPLOYMENT_SETTINGS_PATCH.pick({
38
39
  customConnectors: true,
39
40
  customSkills: true,
40
41
  orgMarketplaceBrowse: true,
42
+ uiTheme: true,
41
43
  }).strict();
42
44
  const ProviderId = ModelProviderParams.shape.provider.describe("The model provider (models.platform-list), e.g. the plugin name of its extension.");
43
45
  /** `provider/model` of a purpose target, or null. */
@@ -61,7 +63,8 @@ let AdminOperations = class AdminOperations {
61
63
  enablement;
62
64
  prices;
63
65
  purposes;
64
- constructor(registry, extensions, orgs, accounts, catalogRequests, settings, providers, displayNames, extensionRegistry, enablement, prices, purposes) {
66
+ themes;
67
+ constructor(registry, extensions, orgs, accounts, catalogRequests, settings, providers, displayNames, extensionRegistry, enablement, prices, purposes, themes) {
65
68
  this.registry = registry;
66
69
  this.extensions = extensions;
67
70
  this.orgs = orgs;
@@ -74,6 +77,7 @@ let AdminOperations = class AdminOperations {
74
77
  this.enablement = enablement;
75
78
  this.prices = prices;
76
79
  this.purposes = purposes;
80
+ this.themes = themes;
77
81
  }
78
82
  assertProvider(provider) {
79
83
  if (!this.extensionRegistry.list("model-provider").some((e) => e.plugin === provider)) {
@@ -117,7 +121,7 @@ let AdminOperations = class AdminOperations {
117
121
  }),
118
122
  defineOperation({
119
123
  id: "admin.update-settings",
120
- description: "Change deployment settings (super admin); give only the keys to change: orgCreation (anyone or super_admin), priceCurrency (ISO 4217 code), customConnectors / customSkills (allow or deny: whether orgs may create their own instead of installing from the catalog), orgMarketplaceBrowse (true or false). Per-org limits are orgs.set-limits.",
124
+ description: "Change deployment settings (super admin); give only the keys to change: orgCreation (anyone or super_admin), priceCurrency (ISO 4217 code), customConnectors / customSkills (allow or deny: whether orgs may create their own instead of installing from the catalog), orgMarketplaceBrowse (true or false), uiTheme ({ id }: the platform default theme, a platform theme whose scope is all, or null for the built-in theme; it is what every org falls back to and what the sign-in pages show). Per-org limits are orgs.set-limits.",
121
125
  input: SettingsInput.refine((b) => Object.keys(b).length > 0, {
122
126
  message: "at least one setting is required",
123
127
  }),
@@ -132,6 +136,10 @@ let AdminOperations = class AdminOperations {
132
136
  code: "validation_failed",
133
137
  next: "Fix the values as the message says (admin.settings shows the current ones).",
134
138
  },
139
+ {
140
+ code: "theme.not_available",
141
+ next: "The platform default must be a platform theme whose scope is all, not disabled, and on in every organization. When the details list orgs, the theme is off in those organizations: tell the person their names, then choose another theme (plugins.components with kind theme lists them) or have it switched on in those organizations first; { id: null } sets the built-in theme.",
142
+ },
135
143
  ],
136
144
  precheck: async (i) => {
137
145
  const r = DeploymentSettingsBody.safeParse(i);
@@ -139,11 +147,20 @@ let AdminOperations = class AdminOperations {
139
147
  ? { ok: true }
140
148
  : { ok: false, code: "validation_failed", message: r.error.issues[0]?.message ?? "" };
141
149
  },
142
- summary: (i) => {
143
- const parts = Object.entries(i).map(([k, v]) => `${k}=${String(v)}`);
150
+ summary: async (i) => {
151
+ const { uiTheme, ...rest } = i;
152
+ const parts = Object.entries(rest).map(([k, v]) => `${k}=${String(v)}`);
153
+ const zh = [...parts];
154
+ const en = [...parts];
155
+ if (uiTheme !== undefined) {
156
+ // the theme by its name (the super admin sees every theme), the built-in one by name
157
+ const name = uiTheme.id === null ? null : await this.themes.nameOf(uiTheme.id);
158
+ zh.push(`uiTheme=${uiTheme.id === null ? "内置主题" : (name?.zh ?? uiTheme.id)}`);
159
+ en.push(`uiTheme=${uiTheme.id === null ? "built-in theme" : (name?.en ?? uiTheme.id)}`);
160
+ }
144
161
  return {
145
- zh: `修改平台部署设置:${parts.join(",")}`,
146
- en: `Change the deployment settings: ${parts.join(", ")}`,
162
+ zh: `修改平台部署设置:${zh.join(",")}`,
163
+ en: `Change the deployment settings: ${en.join(", ")}`,
147
164
  };
148
165
  },
149
166
  run: (input, ctx) => this.settings.update(input, ctx.actorAccountId, undefined, viaAgent("admin.update-settings", ctx)),
@@ -384,6 +401,7 @@ AdminOperations = __decorate([
384
401
  __param(8, Inject(ExtensionRegistry)),
385
402
  __param(9, Inject(ModelEnablement)),
386
403
  __param(10, Inject(PricesService)),
387
- __param(11, Inject(ModelPurposesService))
404
+ __param(11, Inject(ModelPurposesService)),
405
+ __param(12, Inject(ThemesService))
388
406
  ], AdminOperations);
389
407
  export { AdminOperations };
@@ -32,6 +32,7 @@ const LABEL = {
32
32
  skill: { en: "platform skill", zh: "平台技能" },
33
33
  agent_template: { en: "platform agent template", zh: "平台智能体模板" },
34
34
  connector_template: { en: "platform connector template", zh: "平台连接器模板" },
35
+ theme: { en: "platform theme", zh: "平台主题" },
35
36
  };
36
37
  const REFUSED = {
37
38
  "plugin.disabled": "This component is disabled by the super admin and cannot be switched on",
@@ -1,5 +1,6 @@
1
1
  import { type OnModuleInit } from "@nestjs/common";
2
2
  import { OrgSettingsService } from "../../orgs/settings.service.js";
3
+ import { ThemesService } from "../../themes/themes.service.js";
3
4
  import { PlatformOperations } from "../registry.js";
4
5
  /**
5
6
  * Org settings operations of the `Platform` tool (spec §14): read the settings every member sees,
@@ -11,6 +12,7 @@ import { PlatformOperations } from "../registry.js";
11
12
  export declare class OrgSettingsOperations implements OnModuleInit {
12
13
  private readonly registry;
13
14
  private readonly settings;
14
- constructor(registry: PlatformOperations, settings: OrgSettingsService);
15
+ private readonly themes;
16
+ constructor(registry: PlatformOperations, settings: OrgSettingsService, themes: ThemesService);
15
17
  onModuleInit(): void;
16
18
  }
@@ -10,6 +10,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
10
10
  import { Inject, Injectable } from "@nestjs/common";
11
11
  import { z } from "zod";
12
12
  import { OrgSettingsPatchSchema, OrgSettingsService } from "../../orgs/settings.service.js";
13
+ import { ThemesService } from "../../themes/themes.service.js";
13
14
  import { defineOperation } from "../operation.js";
14
15
  import { PlatformOperations } from "../registry.js";
15
16
  import { precheckBy, viaAgent } from "./errors.js";
@@ -28,15 +29,17 @@ const show = (v) => (v === null ? "default" : JSON.stringify(v));
28
29
  let OrgSettingsOperations = class OrgSettingsOperations {
29
30
  registry;
30
31
  settings;
31
- constructor(registry, settings) {
32
+ themes;
33
+ constructor(registry, settings, themes) {
32
34
  this.registry = registry;
33
35
  this.settings = settings;
36
+ this.themes = themes;
34
37
  }
35
38
  onModuleInit() {
36
39
  this.registry.register([
37
40
  defineOperation({
38
41
  id: "org-settings.get",
39
- description: "Read this org's settings: toolSearch, locale, sandboxToolPolicy, platformTool (on, read-only or off), sandboxEgress and wakeOnBackgroundTask (absent means it follows the deployment), the deployment's sandbox egress default and the sandbox cpu / memory limits in effect.",
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), the deployment's sandbox egress default and the sandbox cpu / memory limits in effect.",
40
43
  input: z.object({}).strict(),
41
44
  example: {},
42
45
  authz: { orgAction: "org.read" },
@@ -50,7 +53,7 @@ let OrgSettingsOperations = class OrgSettingsOperations {
50
53
  }),
51
54
  defineOperation({
52
55
  id: "org-settings.update",
53
- 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. 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.",
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. 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.",
54
57
  input: OrgSettingsPatchSchema.refine((i) => Object.values(i).some((v) => v !== undefined), {
55
58
  message: "give at least one setting",
56
59
  }),
@@ -63,22 +66,43 @@ let OrgSettingsOperations = class OrgSettingsOperations {
63
66
  code: "org.sandbox_egress_not_allowed",
64
67
  next: "This deployment disables sandbox network access, so the org can only set sandboxEgress to none; ask the super admin to change sandbox.limits.egress.",
65
68
  },
69
+ {
70
+ code: "theme.not_available",
71
+ next: "Pick a theme id that themes.list shows for this org, or null to follow the platform.",
72
+ },
66
73
  {
67
74
  code: "validation_failed",
68
75
  next: "Fix the setting as the message says (org-settings.get shows the current values).",
69
76
  },
70
77
  ],
71
78
  precheck: (i) => precheckBy(async () => this.settings.check(i)),
72
- summary: (i) => {
73
- const parts = Object.entries(i)
79
+ summary: async (i, ctx) => {
80
+ const { theme, ...rest } = i;
81
+ const parts = Object.entries(rest)
74
82
  .filter(([, v]) => v !== undefined)
75
83
  .map(([k, v]) => `${k} → ${show(v)}`);
84
+ const zhParts = [...parts];
85
+ const enParts = [...parts];
86
+ if (theme !== undefined) {
87
+ // the theme by its name when it is on in this org; any other id is shown as given
88
+ const name = theme?.id == null ? null : await this.themes.nameIn(ctx.orgId, theme.id);
89
+ if (theme === null) {
90
+ zhParts.push("theme → 清除(跟随平台,允许个人选择)");
91
+ enParts.push("theme → cleared (follow the platform, personal picks allowed)");
92
+ }
93
+ else {
94
+ const zh = theme.id === null ? "跟随平台" : (name?.zh ?? theme.id);
95
+ const en = theme.id === null ? "follow the platform" : (name?.en ?? theme.id);
96
+ zhParts.push(`theme → ${zh}(个人选择:${theme.personal === "locked" ? "锁定" : "允许"})`);
97
+ enParts.push(`theme → ${en} (personal picks ${theme.personal === "locked" ? "locked" : "allowed"})`);
98
+ }
99
+ }
76
100
  const warn = i.platformTool === "off" || i.platformTool === "read-only";
77
101
  return {
78
- zh: `修改组织设置:${parts.join(",")}${warn
102
+ zh: `修改组织设置:${zhParts.join(",")}${warn
79
103
  ? "(之后智能体不能再用平台工具" + (i.platformTool === "off" ? "" : "写入") + ")"
80
104
  : ""}`,
81
- en: `Update the org settings: ${parts.join(", ")}${warn
105
+ en: `Update the org settings: ${enParts.join(", ")}${warn
82
106
  ? `; from the next call the Platform tool is ${i.platformTool === "off" ? "off" : "read-only"} in this org`
83
107
  : ""}`,
84
108
  };
@@ -97,6 +121,7 @@ let OrgSettingsOperations = class OrgSettingsOperations {
97
121
  OrgSettingsOperations = __decorate([
98
122
  Injectable(),
99
123
  __param(0, Inject(PlatformOperations)),
100
- __param(1, Inject(OrgSettingsService))
124
+ __param(1, Inject(OrgSettingsService)),
125
+ __param(2, Inject(ThemesService))
101
126
  ], OrgSettingsOperations);
102
127
  export { OrgSettingsOperations };
@@ -158,6 +158,7 @@ let OrgsOperations = class OrgsOperations {
158
158
  "customConnectors",
159
159
  "customSkills",
160
160
  "orgMarketplaceBrowse",
161
+ "uiTheme",
161
162
  ],
162
163
  extras: ["orgId", "runConcurrency", "sandbox", "subagents"],
163
164
  confirm: "platform",
@@ -2,6 +2,7 @@ import { type OnModuleInit } from "@nestjs/common";
2
2
  import { AgentsService } from "../../agents/agents.service.js";
3
3
  import { AccountsService } from "../../identity/accounts.js";
4
4
  import { OrgSettingsService } from "../../orgs/settings.service.js";
5
+ import { ThemesService } from "../../themes/themes.service.js";
5
6
  import { ToolRegistry } from "../../tools/registry.js";
6
7
  import { PlatformActor } from "../actor.js";
7
8
  import { PlatformOperations } from "../registry.js";
@@ -16,7 +17,8 @@ export declare class PlatformOperationsProvider implements OnModuleInit {
16
17
  private readonly settings;
17
18
  private readonly tools;
18
19
  private readonly accounts;
19
- constructor(registry: PlatformOperations, actor: PlatformActor, agents: AgentsService, settings: OrgSettingsService, tools: ToolRegistry, accounts: AccountsService);
20
+ private readonly themes;
21
+ constructor(registry: PlatformOperations, actor: PlatformActor, agents: AgentsService, settings: OrgSettingsService, tools: ToolRegistry, accounts: AccountsService, themes: ThemesService);
20
22
  /**
21
23
  * The agent's tools by the keys the model sees, as the registry resolves them for a run (tools
22
24
  * default-on spec §8): every tool the org offers that the definition does not turn off, builtins
@@ -14,6 +14,7 @@ import { AccountsService } from "../../identity/accounts.js";
14
14
  import { PreferencesSchema } from "../../identity/me.schemas.js";
15
15
  import { can } from "../../orgs/roles.js";
16
16
  import { effectiveSandboxEgress, OrgSettingsService } from "../../orgs/settings.service.js";
17
+ import { ThemesService } from "../../themes/themes.service.js";
17
18
  import { SANDBOX_TOOL_NAMES } from "../../tools/builtin-names.js";
18
19
  import { ToolRegistry } from "../../tools/registry.js";
19
20
  import { PlatformActor } from "../actor.js";
@@ -47,13 +48,15 @@ let PlatformOperationsProvider = class PlatformOperationsProvider {
47
48
  settings;
48
49
  tools;
49
50
  accounts;
50
- constructor(registry, actor, agents, settings, tools, accounts) {
51
+ themes;
52
+ constructor(registry, actor, agents, settings, tools, accounts, themes) {
51
53
  this.registry = registry;
52
54
  this.actor = actor;
53
55
  this.agents = agents;
54
56
  this.settings = settings;
55
57
  this.tools = tools;
56
58
  this.accounts = accounts;
59
+ this.themes = themes;
57
60
  }
58
61
  /**
59
62
  * The agent's tools by the keys the model sees, as the registry resolves them for a run (tools
@@ -152,7 +155,7 @@ let PlatformOperationsProvider = class PlatformOperationsProvider {
152
155
  }),
153
156
  defineOperation({
154
157
  id: "platform.preferences",
155
- description: "Read your own preferences: your time zone (an IANA name such as Asia/Shanghai), or null when it follows the time zone of the browser you send messages from. The run context already tells you the zone in effect for this message.",
158
+ description: "Read your own preferences: your time zone (an IANA name such as Asia/Shanghai, or null when it follows the time zone of the browser you send messages from; the run context already tells you the zone in effect for this message) and your theme (a theme id, or null when you follow the org's theme; themes.list names them).",
156
159
  input: EMPTY,
157
160
  example: {},
158
161
  // any signed-in person may call the route; every run's actor is a member of this org
@@ -163,8 +166,10 @@ let PlatformOperationsProvider = class PlatformOperationsProvider {
163
166
  }),
164
167
  defineOperation({
165
168
  id: "platform.set-preferences",
166
- description: "Set your own time zone (an IANA name such as Europe/Berlin), or null to follow the time zone of the browser you send messages from. It applies to the messages you send afterwards, on the web and in IM; the current run keeps its zone.",
167
- input: PreferencesSchema,
169
+ description: "Change your own preferences; give only the ones to change, the others keep their values. timeZone: an IANA name such as Europe/Berlin, or null to follow the time zone of the browser you send messages from; it applies to the messages you send afterwards, on the web and in IM (the current run keeps its zone). theme: a theme id from themes.list (it applies in every org where that theme is on and personal picks are allowed), or null to follow the org's theme.",
170
+ input: PreferencesSchema.refine((i) => Object.values(i).some((v) => v !== undefined), {
171
+ message: "give timeZone, theme or both",
172
+ }),
168
173
  example: { timeZone: "Asia/Shanghai" },
169
174
  authz: { orgAction: "agents.use" },
170
175
  mirrors: ["PUT /api/v1/me/preferences"],
@@ -174,20 +179,41 @@ let PlatformOperationsProvider = class PlatformOperationsProvider {
174
179
  code: "validation_failed",
175
180
  next: "Use an IANA time zone name such as Asia/Shanghai or America/New_York, or null.",
176
181
  },
182
+ {
183
+ code: "theme.not_available",
184
+ next: "Pick a theme id that themes.list shows for this org, or null to follow the org's theme.",
185
+ },
177
186
  ],
178
187
  summary: async (input, ctx) => {
179
- const now = (await this.accounts.preferences(ctx.actorAccountId)).timeZone;
180
- const from = { zh: now ?? "跟随浏览器", en: now ?? "follow the browser" };
181
- const to = {
182
- zh: input.timeZone ?? "跟随浏览器",
183
- en: input.timeZone ?? "follow the browser",
184
- };
185
- return {
186
- zh: `把你的时区从「${from.zh}」改为「${to.zh}」`,
187
- en: `Change your time zone from "${from.en}" to "${to.en}"`,
188
- };
188
+ const now = await this.accounts.preferences(ctx.actorAccountId);
189
+ const zh = [];
190
+ const en = [];
191
+ if (input.timeZone !== undefined) {
192
+ const from = now.timeZone;
193
+ zh.push(`把你的时区从「${from ?? "跟随浏览器"}」改为「${input.timeZone ?? "跟随浏览器"}」`);
194
+ en.push(`Change your time zone from "${from ?? "follow the browser"}" to "${input.timeZone ?? "follow the browser"}"`);
195
+ }
196
+ if (input.theme !== undefined) {
197
+ // only themes on in one of your orgs are named; any other id is shown as given
198
+ const name = async (id) => id === null
199
+ ? null
200
+ : ((await this.themes.nameForAccount(ctx.actorAccountId, id)) ?? {
201
+ en: id,
202
+ zh: id,
203
+ });
204
+ const from = await name(now.theme);
205
+ const to = await name(input.theme);
206
+ const inZh = (n) => (n === null ? "跟随组织" : n.zh);
207
+ const inEn = (n) => (n === null ? "follow the org" : n.en);
208
+ zh.push(`把你的主题从「${inZh(from)}」改为「${inZh(to)}」`);
209
+ en.push(`Change your theme from "${inEn(from)}" to "${inEn(to)}"`);
210
+ }
211
+ return { zh: zh.join(";"), en: en.join("; ") };
189
212
  },
190
- run: async (input, ctx) => this.accounts.setPreferences(ctx.actorAccountId, { timeZone: input.timeZone }),
213
+ run: async (input, ctx) => this.accounts.setPreferences(ctx.actorAccountId, {
214
+ timeZone: input.timeZone,
215
+ theme: input.theme,
216
+ }),
191
217
  }),
192
218
  ]);
193
219
  }
@@ -199,6 +225,7 @@ PlatformOperationsProvider = __decorate([
199
225
  __param(2, Inject(AgentsService)),
200
226
  __param(3, Inject(OrgSettingsService)),
201
227
  __param(4, Inject(ToolRegistry)),
202
- __param(5, Inject(AccountsService))
228
+ __param(5, Inject(AccountsService)),
229
+ __param(6, Inject(ThemesService))
203
230
  ], PlatformOperationsProvider);
204
231
  export { PlatformOperationsProvider };
@@ -0,0 +1,14 @@
1
+ import { type OnModuleInit } from "@nestjs/common";
2
+ import { ThemesService } from "../../themes/themes.service.js";
3
+ import { PlatformOperations } from "../registry.js";
4
+ /**
5
+ * Theme operations of the `Platform` tool (themes spec §9): the org's theme listing. Setting a
6
+ * theme goes through the settings operations (`platform.set-preferences`, `org-settings.update`,
7
+ * `admin.update-settings`).
8
+ */
9
+ export declare class ThemesOperations implements OnModuleInit {
10
+ private readonly registry;
11
+ private readonly themes;
12
+ constructor(registry: PlatformOperations, themes: ThemesService);
13
+ onModuleInit(): void;
14
+ }
@@ -0,0 +1,47 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { Inject, Injectable } from "@nestjs/common";
11
+ import { z } from "zod";
12
+ import { ThemesService } from "../../themes/themes.service.js";
13
+ import { defineOperation } from "../operation.js";
14
+ import { PlatformOperations } from "../registry.js";
15
+ /**
16
+ * Theme operations of the `Platform` tool (themes spec §9): the org's theme listing. Setting a
17
+ * theme goes through the settings operations (`platform.set-preferences`, `org-settings.update`,
18
+ * `admin.update-settings`).
19
+ */
20
+ let ThemesOperations = class ThemesOperations {
21
+ registry;
22
+ themes;
23
+ constructor(registry, themes) {
24
+ this.registry = registry;
25
+ this.themes = themes;
26
+ }
27
+ onModuleInit() {
28
+ this.registry.register([
29
+ defineOperation({
30
+ id: "themes.list",
31
+ description: "List the themes on in this org (id, version, name, schemes, logo and the bg / panel / ink / accent swatches per scheme), the theme in effect for you here (resolved; null is the built-in theme) and the org's setting (its default theme id, null following the platform, and whether personal picks are allowed or locked). Use a theme id with platform.set-preferences to change your own theme, or with org-settings.update to change the org's default.",
32
+ input: z.object({}).strict(),
33
+ example: {},
34
+ authz: { orgAction: "org.read" },
35
+ mirrors: ["GET /api/v1/orgs/:orgId/themes"],
36
+ confirm: "read",
37
+ run: async (_input, ctx) => this.themes.forOrg(ctx.orgId, ctx.actorAccountId),
38
+ }),
39
+ ]);
40
+ }
41
+ };
42
+ ThemesOperations = __decorate([
43
+ Injectable(),
44
+ __param(0, Inject(PlatformOperations)),
45
+ __param(1, Inject(ThemesService))
46
+ ], ThemesOperations);
47
+ export { ThemesOperations };
@@ -25,11 +25,12 @@ import { PluginsOperations } from "./ops/plugins.ops.js";
25
25
  import { PreviewersOperations } from "./ops/previewers.ops.js";
26
26
  import { RequestsOperations } from "./ops/requests.ops.js";
27
27
  import { SkillsOperations } from "./ops/skills.ops.js";
28
+ import { ThemesOperations } from "./ops/themes.ops.js";
28
29
  /**
29
30
  * One provider per domain, each taking `PlatformOperations` as its first constructor argument:
30
31
  * the parity test in `tools/checks` builds the operations from it without a Nest app.
31
32
  */
32
- export declare const PLATFORM_OPERATION_PROVIDERS: readonly [typeof PlatformOperationsProvider, typeof SkillsOperations, typeof AgentsOperations, typeof AgentTemplatesOperations, typeof PluginsOperations, typeof MarketplacesOperations, typeof RequestsOperations, typeof ConnectorsOperations, typeof ExtensionsOperations, typeof ConversationsOperations, typeof ComponentsOperations, typeof AccountsOperations, typeof OrgsOperations, typeof AdminOperations, typeof CatalogAdminOperations, typeof OrgPluginsOperations, typeof ExtensionsAdminOperations, typeof ModelsOperations, typeof InboxOperations, typeof ApprovalsOperations, typeof ChannelsOperations, typeof OrgSettingsOperations, typeof EvalsOperations, typeof BackgroundOperations, typeof PreviewersOperations, typeof EnvironmentsOperations, typeof AgentSkillsOperations];
33
+ export declare const PLATFORM_OPERATION_PROVIDERS: readonly [typeof PlatformOperationsProvider, typeof SkillsOperations, typeof AgentsOperations, typeof AgentTemplatesOperations, typeof PluginsOperations, typeof MarketplacesOperations, typeof RequestsOperations, typeof ConnectorsOperations, typeof ExtensionsOperations, typeof ConversationsOperations, typeof ComponentsOperations, typeof AccountsOperations, typeof OrgsOperations, typeof AdminOperations, typeof CatalogAdminOperations, typeof OrgPluginsOperations, typeof ExtensionsAdminOperations, typeof ModelsOperations, typeof InboxOperations, typeof ApprovalsOperations, typeof ChannelsOperations, typeof OrgSettingsOperations, typeof EvalsOperations, typeof BackgroundOperations, typeof PreviewersOperations, typeof EnvironmentsOperations, typeof AgentSkillsOperations, typeof ThemesOperations];
33
34
  /**
34
35
  * The `Platform` tool's operations (ruling P2): one provider per domain, each registering its
35
36
  * operations into `PlatformOperations` from `onModuleInit`. It imports the domain modules, which
@@ -20,6 +20,7 @@ import { ModelsModule } from "../models/models.module.js";
20
20
  import { OrgsModule } from "../orgs/orgs.module.js";
21
21
  import { ProvidersModule } from "../runtime/providers.js";
22
22
  import { SkillsModule } from "../skills/skills.module.js";
23
+ import { ThemesModule } from "../themes/themes.module.js";
23
24
  import { ToolsModule } from "../tools/tools.module.js";
24
25
  import { AccountsOperations } from "./ops/accounts.ops.js";
25
26
  import { AdminOperations } from "./ops/admin.ops.js";
@@ -48,6 +49,7 @@ import { PluginsOperations } from "./ops/plugins.ops.js";
48
49
  import { PreviewersOperations } from "./ops/previewers.ops.js";
49
50
  import { RequestsOperations } from "./ops/requests.ops.js";
50
51
  import { SkillsOperations } from "./ops/skills.ops.js";
52
+ import { ThemesOperations } from "./ops/themes.ops.js";
51
53
  import { PlatformModule } from "./platform.module.js";
52
54
  /**
53
55
  * One provider per domain, each taking `PlatformOperations` as its first constructor argument:
@@ -87,6 +89,8 @@ export const PLATFORM_OPERATION_PROVIDERS = [
87
89
  // Task 11b: environments, agent skills
88
90
  EnvironmentsOperations,
89
91
  AgentSkillsOperations,
92
+ // themes (themes spec §9)
93
+ ThemesOperations,
90
94
  ];
91
95
  /**
92
96
  * The `Platform` tool's operations (ruling P2): one provider per domain, each registering its
@@ -115,6 +119,7 @@ PlatformOpsModule = __decorate([
115
119
  EnvironmentsModule,
116
120
  BackgroundModule,
117
121
  FilesModule,
122
+ ThemesModule,
118
123
  ],
119
124
  providers: [...PLATFORM_OPERATION_PROVIDERS],
120
125
  })
@@ -20,6 +20,7 @@ import { INBOX_ROUTES } from "../inbox/inbox.schemas.js";
20
20
  import { ORG_MODEL_ROUTES } from "../models/org-models.schemas.js";
21
21
  import { ORG_ROUTES, ORG_SETTINGS_ROUTES } from "../orgs/orgs.schemas.js";
22
22
  import { SKILL_ROUTES } from "../skills/skills.schemas.js";
23
+ import { THEME_ROUTES } from "../themes/themes.schemas.js";
23
24
  import { APPROVAL_ROUTES } from "../tools/approvals.schemas.js";
24
25
  import { CONNECTOR_ROUTES } from "../tools/connectors.schemas.js";
25
26
  /**
@@ -60,4 +61,6 @@ export const ROUTE_SCHEMAS = [
60
61
  ...ENVIRONMENT_ROUTES,
61
62
  // personal preferences (run context spec §3.4)
62
63
  ...ME_ROUTES,
64
+ // themes (themes spec §9)
65
+ ...THEME_ROUTES,
63
66
  ];
@@ -0,0 +1,12 @@
1
+ import { type ParsedTheme } from "@capybara-hq/plugin-sdk";
2
+ /**
3
+ * The stylesheet of a stored theme (themes spec §8): each scheme's overrides under
4
+ * `:root[data-theme="<scheme>"]` (a single-scheme theme: its whole scheme, the same block under
5
+ * both selectors, with `color-scheme: <scheme>`), an `@font-face` per font (`src` from
6
+ * `assetUrl(<the font's path as stored>)`, the one URL builder), and each font role's stack with
7
+ * the theme's families first, then the built-in stack. Every stored value is re-validated (token
8
+ * names against the overridable list, values against the SDK grammar, radius 0-16, families,
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.
11
+ */
12
+ export declare function renderThemeCss(theme: ParsedTheme, assetUrl: (pluginPath: string) => string, onSkipped?: (paths: string[]) => void): string;