@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.
Files changed (106) hide show
  1. package/dist/admin/admin.schemas.d.ts +3 -0
  2. package/dist/agents/agents.schemas.d.ts +3 -0
  3. package/dist/agents/agents.service.js +11 -1
  4. package/dist/agents/definition.d.ts +1 -0
  5. package/dist/agents/definition.js +7 -3
  6. package/dist/agents/org-templates.js +4 -0
  7. package/dist/agents/pending-connectors.service.js +1 -1
  8. package/dist/catalog/catalog-content.d.ts +33 -1
  9. package/dist/catalog/catalog-content.js +65 -2
  10. package/dist/catalog/catalog-items.js +1 -0
  11. package/dist/catalog/catalog-scope.service.d.ts +29 -3
  12. package/dist/catalog/catalog-scope.service.js +82 -10
  13. package/dist/catalog/catalog.service.d.ts +7 -5
  14. package/dist/catalog/catalog.service.js +26 -5
  15. package/dist/catalog/claude/translate.js +3 -0
  16. package/dist/catalog/component-scope.controller.d.ts +18 -6
  17. package/dist/catalog/component-scope.controller.js +36 -13
  18. package/dist/catalog/component-scope.schemas.d.ts +4 -0
  19. package/dist/catalog/component-scope.service.d.ts +59 -9
  20. package/dist/catalog/component-scope.service.js +156 -27
  21. package/dist/catalog/component-state.d.ts +20 -3
  22. package/dist/catalog/component-state.js +40 -14
  23. package/dist/catalog/marketplaces.service.d.ts +1 -1
  24. package/dist/catalog/org-catalog.service.js +3 -0
  25. package/dist/catalog/owned-items.js +7 -0
  26. package/dist/catalog/platform-components.d.ts +23 -3
  27. package/dist/catalog/platform-components.js +118 -9
  28. package/dist/catalog/platform-migration.js +2 -1
  29. package/dist/catalog/plugin-import.d.ts +24 -4
  30. package/dist/catalog/plugin-import.js +41 -7
  31. package/dist/catalog/plugin-install.d.ts +20 -1
  32. package/dist/catalog/plugin-install.js +71 -1
  33. package/dist/catalog/plugin-package.d.ts +41 -4
  34. package/dist/catalog/plugin-package.js +127 -7
  35. package/dist/catalog/plugin-upgrade.d.ts +13 -2
  36. package/dist/catalog/plugin-upgrade.js +124 -6
  37. package/dist/catalog/unshare.js +1 -0
  38. package/dist/conversations/conversations.service.d.ts +9 -0
  39. package/dist/conversations/conversations.service.js +9 -2
  40. package/dist/conversations/events.d.ts +1 -0
  41. package/dist/db/kernel-schema.d.ts +54 -4
  42. package/dist/errors.d.ts +27 -0
  43. package/dist/errors.js +27 -0
  44. package/dist/http/api.module.js +7 -0
  45. package/dist/identity/accounts.d.ts +11 -4
  46. package/dist/identity/accounts.js +23 -10
  47. package/dist/identity/me.controller.d.ts +10 -10
  48. package/dist/identity/me.controller.js +8 -6
  49. package/dist/identity/me.schemas.d.ts +13 -3
  50. package/dist/identity/me.schemas.js +10 -2
  51. package/dist/identity/settings.d.ts +27 -0
  52. package/dist/identity/settings.js +64 -0
  53. package/dist/orgs/settings.service.d.ts +31 -0
  54. package/dist/orgs/settings.service.js +31 -1
  55. package/dist/platform/exclusions.js +26 -0
  56. package/dist/platform/operation.js +6 -2
  57. package/dist/platform/ops/admin.ops.d.ts +3 -1
  58. package/dist/platform/ops/admin.ops.js +25 -7
  59. package/dist/platform/ops/agents.ops.js +19 -6
  60. package/dist/platform/ops/components.ops.js +1 -0
  61. package/dist/platform/ops/org-settings.ops.d.ts +3 -1
  62. package/dist/platform/ops/org-settings.ops.js +33 -8
  63. package/dist/platform/ops/orgs.ops.js +1 -0
  64. package/dist/platform/ops/platform.ops.d.ts +3 -1
  65. package/dist/platform/ops/platform.ops.js +43 -16
  66. package/dist/platform/ops/themes.ops.d.ts +14 -0
  67. package/dist/platform/ops/themes.ops.js +47 -0
  68. package/dist/platform/platform-ops.module.d.ts +2 -1
  69. package/dist/platform/platform-ops.module.js +5 -0
  70. package/dist/platform/route-schemas.js +3 -0
  71. package/dist/runtime/subagents/definition.d.ts +3 -2
  72. package/dist/runtime/subagents/definition.js +2 -1
  73. package/dist/themes/render-css.d.ts +12 -0
  74. package/dist/themes/render-css.js +132 -0
  75. package/dist/themes/theme-delivery.service.d.ts +34 -0
  76. package/dist/themes/theme-delivery.service.js +111 -0
  77. package/dist/themes/theme-state.d.ts +95 -0
  78. package/dist/themes/theme-state.js +203 -0
  79. package/dist/themes/theme-usage.d.ts +61 -0
  80. package/dist/themes/theme-usage.js +98 -0
  81. package/dist/themes/themes-public.controller.d.ts +28 -0
  82. package/dist/themes/themes-public.controller.js +92 -0
  83. package/dist/themes/themes.controller.d.ts +13 -0
  84. package/dist/themes/themes.controller.js +38 -0
  85. package/dist/themes/themes.module.d.ts +3 -0
  86. package/dist/themes/themes.module.js +21 -0
  87. package/dist/themes/themes.schemas.d.ts +3 -0
  88. package/dist/themes/themes.schemas.js +2 -0
  89. package/dist/themes/themes.service.d.ts +50 -0
  90. package/dist/themes/themes.service.js +99 -0
  91. package/dist/tools/connector-account-policies.d.ts +51 -0
  92. package/dist/tools/connector-account-policies.js +165 -0
  93. package/dist/tools/connectors.controller.d.ts +35 -4
  94. package/dist/tools/connectors.controller.js +66 -7
  95. package/dist/tools/connectors.schemas.d.ts +11 -0
  96. package/dist/tools/connectors.schemas.js +11 -0
  97. package/dist/tools/connectors.service.d.ts +2 -0
  98. package/dist/tools/connectors.service.js +1 -0
  99. package/dist/tools/pipeline.d.ts +1 -1
  100. package/dist/tools/policy.d.ts +23 -1
  101. package/dist/tools/policy.js +99 -24
  102. package/dist/tools/tools.module.js +3 -0
  103. package/package.json +3 -3
  104. package/src/migrations/0102_themes.sql +64 -0
  105. package/src/migrations/0103_ui_theme_setting.sql +7 -0
  106. package/src/migrations/0104_connector_tool_account_policies.sql +16 -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 }) })
@@ -608,6 +608,16 @@ export const PLATFORM_EXCLUSIONS = [
608
608
  category: "secret",
609
609
  reason: "Writes the caller's connector credentials",
610
610
  },
611
+ {
612
+ route: "GET /api/v1/orgs/:orgId/connectors/:connectorId/tool-policies/me",
613
+ category: "internal",
614
+ reason: "Display list of the connector card's 权限 sheet; connectors.get gives an agent the tools and their org defaults",
615
+ },
616
+ {
617
+ route: "PUT /api/v1/orgs/:orgId/connectors/:connectorId/tool-policies/me",
618
+ category: "human-only",
619
+ reason: "A member's own tool policies decide what agents may call with their connection without asking; an agent must never loosen them for itself",
620
+ },
611
621
  {
612
622
  route: "DELETE /api/v1/orgs/:orgId/connectors/:connectorId/credentials/me",
613
623
  category: "secret",
@@ -664,4 +674,20 @@ export const PLATFORM_EXCLUSIONS = [
664
674
  category: "transport",
665
675
  reason: "Server-sent event stream of UI invalidations",
666
676
  },
677
+ // themes (themes spec §8-§9)
678
+ {
679
+ route: "GET /api/v1/themes/:id/:version/theme.css",
680
+ category: "transport",
681
+ reason: "Serves a theme version's stylesheet to the browser; an agent reads themes.list",
682
+ },
683
+ {
684
+ route: "GET /api/v1/themes/:id/:version/assets/:file",
685
+ category: "transport",
686
+ reason: "Serves a theme version's font and logo bytes to the browser",
687
+ },
688
+ {
689
+ route: "GET /api/v1/platform/theme",
690
+ category: "transport",
691
+ reason: "The platform default theme for the web's pages before sign-in; no session to act for",
692
+ },
667
693
  ];
@@ -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 };
@@ -40,6 +40,15 @@ const RouteSkills = Def.skills.unwrap();
40
40
  const Skills = RouteSkills.pick({ pinned: true })
41
41
  .extend({ orgSkills: RouteSkills.shape.orgSkills.unwrap() })
42
42
  .strict();
43
+ /**
44
+ * Whether `next` sets an `allow` that `current` does not have (decision 2026-11-09: `allow` on a
45
+ * connector tool lets an agent call it without asking). Such a change is confirmed every time and
46
+ * never remembered, so an agent can never loosen its own policies on a grant the person remembered
47
+ * for ordinary edits.
48
+ */
49
+ function addsAllow(next, current) {
50
+ return Object.entries(next ?? {}).some(([k, v]) => v === "allow" && current?.[k] !== "allow");
51
+ }
43
52
  /** The definition fields an agent can set through the tool; the rest keep the web's defaults. */
44
53
  const Fields = {
45
54
  name: Def.name,
@@ -156,8 +165,10 @@ let AgentsOperations = class AgentsOperations {
156
165
  * `agents.update`'s class (spec §13): `personal` (may be remembered) only on a private agent the
157
166
  * actor owns; any other agent the actor may edit is shared, so each change is confirmed.
158
167
  */
159
- async updateClass(agentId, ctx) {
168
+ async updateClass(agentId, toolPolicies, ctx) {
160
169
  const a = await this.agents.get(ctx.orgId, agentId, ctx.actorAccountId);
170
+ if (addsAllow(toolPolicies, a.definition.toolPolicies))
171
+ return "org";
161
172
  return a.visibility === "private" && a.ownerAccountId === ctx.actorAccountId
162
173
  ? "personal"
163
174
  : "org";
@@ -221,6 +232,8 @@ let AgentsOperations = class AgentsOperations {
221
232
  omits: ["memberIds", "profile"],
222
233
  loosens: { visibility: "optional: the operation creates private agents only" },
223
234
  confirm: "personal",
235
+ // an `allow` loosens: never on a remembered grant
236
+ confirmFor: async (i) => addsAllow(i.definition.toolPolicies, undefined) ? "org" : "personal",
224
237
  summary: (i) => ({
225
238
  zh: `新建私有智能体「${i.definition.name}」`,
226
239
  en: `Create private agent "${i.definition.name}"`,
@@ -246,7 +259,7 @@ let AgentsOperations = class AgentsOperations {
246
259
  }),
247
260
  defineOperation({
248
261
  id: "agents.update",
249
- description: 'Change an agent you may edit: your own, private or shared, and as an org admin any agent of the org. Send the version you read from agents.get and only the fields to change; the others keep their value. The description (one short paragraph, "" removes it) is saved with the definition; the avatar and quick actions are changed with agents.update-profile. disabledTools replaces the whole list of turned-off tools (send [] to turn every tool on). A version that is no longer current is refused, so read the agent again first. The visibility cannot be changed here (that is done on the agents page of the web app). A change to your own private agent may be remembered by the person; a change to a shared agent is confirmed every time.',
262
+ description: 'Change an agent you may edit: your own, private or shared, and as an org admin any agent of the org. Send the version you read from agents.get and only the fields to change; the others keep their value. The description (one short paragraph, "" removes it) is saved with the definition; the avatar and quick actions are changed with agents.update-profile. disabledTools replaces the whole list of turned-off tools (send [] to turn every tool on). A version that is no longer current is refused, so read the agent again first. The visibility cannot be changed here (that is done on the agents page of the web app). A change to your own private agent may be remembered by the person; a change to a shared agent, or one that sets a tool policy to allow, is confirmed every time.',
250
263
  input: z
251
264
  .object({
252
265
  agentId: AgentId,
@@ -289,7 +302,7 @@ let AgentsOperations = class AgentsOperations {
289
302
  next: "The agent changed since you read it: read it again with agents.get, then send its current version.",
290
303
  },
291
304
  ],
292
- confirmFor: (i, ctx) => this.updateClass(i.agentId, ctx),
305
+ confirmFor: (i, ctx) => this.updateClass(i.agentId, i.toolPolicies, ctx),
293
306
  summary: async (i, ctx) => {
294
307
  const a = await this.agents.get(ctx.orgId, i.agentId, ctx.actorAccountId);
295
308
  const name = named(a.definition.name);
@@ -353,7 +366,7 @@ let AgentsOperations = class AgentsOperations {
353
366
  // needs no declaration: an input that accepts less than the route passes the parity test.
354
367
  omits: ["expectedUpdatedAt"],
355
368
  confirm: "personal",
356
- confirmFor: (i, ctx) => this.updateClass(i.agentId, ctx),
369
+ confirmFor: (i, ctx) => this.updateClass(i.agentId, undefined, ctx),
357
370
  errors: [
358
371
  {
359
372
  code: "agent.not_found",
@@ -493,7 +506,7 @@ let AgentsOperations = class AgentsOperations {
493
506
  confirm: "personal",
494
507
  // destructive: asked every time and never remembered, a shared agent as an org write
495
508
  rememberable: false,
496
- confirmFor: (i, ctx) => this.updateClass(i.agentId, ctx),
509
+ confirmFor: (i, ctx) => this.updateClass(i.agentId, undefined, ctx),
497
510
  errors: [
498
511
  {
499
512
  code: "agent.not_found",
@@ -578,7 +591,7 @@ let AgentsOperations = class AgentsOperations {
578
591
  authz: { orgAction: "agents.create" },
579
592
  mirrors: ["POST /api/v1/orgs/:orgId/agents/:agentId/pending-connectors/:key/bind"],
580
593
  confirm: "personal",
581
- confirmFor: (i, ctx) => this.updateClass(i.agentId, ctx),
594
+ confirmFor: (i, ctx) => this.updateClass(i.agentId, undefined, ctx),
582
595
  errors: [
583
596
  {
584
597
  code: "agent.pending_connector_not_found",
@@ -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
+ }