@capybara-hq/kernel 0.1.0-rc.13 → 0.1.0-rc.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/dist/admin/admin.schemas.d.ts +5 -13
  2. package/dist/catalog/admin-catalog.controller.d.ts +39 -5
  3. package/dist/catalog/admin-catalog.controller.js +75 -6
  4. package/dist/catalog/admin-catalog.schemas.d.ts +10 -0
  5. package/dist/catalog/admin-catalog.schemas.js +7 -0
  6. package/dist/catalog/appearance.schemas.d.ts +10 -0
  7. package/dist/catalog/appearance.schemas.js +20 -0
  8. package/dist/catalog/appearance.service.d.ts +105 -0
  9. package/dist/catalog/appearance.service.js +306 -0
  10. package/dist/catalog/catalog-content.d.ts +9 -1
  11. package/dist/catalog/catalog-content.js +2 -1
  12. package/dist/catalog/catalog-files.d.ts +3 -2
  13. package/dist/catalog/catalog-files.js +8 -5
  14. package/dist/catalog/catalog-import.module.js +3 -0
  15. package/dist/catalog/catalog-items.d.ts +11 -0
  16. package/dist/catalog/catalog-items.js +21 -0
  17. package/dist/catalog/catalog-scope.service.d.ts +7 -0
  18. package/dist/catalog/catalog-scope.service.js +55 -15
  19. package/dist/catalog/catalog.service.js +10 -2
  20. package/dist/catalog/cli.js +3 -0
  21. package/dist/catalog/component-scope.service.d.ts +14 -1
  22. package/dist/catalog/component-scope.service.js +12 -2
  23. package/dist/catalog/marketplaces.service.d.ts +1 -1
  24. package/dist/catalog/org-catalog.controller.d.ts +5 -4
  25. package/dist/catalog/org-catalog.controller.js +5 -4
  26. package/dist/catalog/org-catalog.schemas.d.ts +18 -2
  27. package/dist/catalog/org-catalog.schemas.js +16 -2
  28. package/dist/catalog/org-catalog.service.js +8 -2
  29. package/dist/catalog/org-extension.controller.d.ts +27 -7
  30. package/dist/catalog/org-extension.controller.js +48 -6
  31. package/dist/catalog/plugin-import.d.ts +28 -4
  32. package/dist/catalog/plugin-import.js +96 -20
  33. package/dist/catalog/plugin-package.d.ts +4 -2
  34. package/dist/catalog/plugin-package.js +66 -12
  35. package/dist/catalog/plugin-upgrade.d.ts +15 -1
  36. package/dist/catalog/plugin-upgrade.js +18 -5
  37. package/dist/channels/connections.controller.d.ts +1 -1
  38. package/dist/channels/connections.controller.js +1 -1
  39. package/dist/channels/connections.service.d.ts +22 -5
  40. package/dist/channels/connections.service.js +56 -10
  41. package/dist/channels/webhook.d.ts +21 -1
  42. package/dist/channels/webhook.js +39 -4
  43. package/dist/client/client-access.service.d.ts +101 -0
  44. package/dist/client/client-access.service.js +294 -0
  45. package/dist/client/client-builds.service.d.ts +109 -0
  46. package/dist/client/client-builds.service.js +229 -0
  47. package/dist/client/client-delivery.controller.d.ts +25 -0
  48. package/dist/client/client-delivery.controller.js +66 -0
  49. package/dist/client/client-files.d.ts +48 -0
  50. package/dist/client/client-files.js +136 -0
  51. package/dist/client/client-reports.cron.d.ts +13 -0
  52. package/dist/client/client-reports.cron.js +42 -0
  53. package/dist/client/client-reports.service.d.ts +30 -0
  54. package/dist/client/client-reports.service.js +102 -0
  55. package/dist/client/client.controller.d.ts +45 -0
  56. package/dist/client/client.controller.js +121 -0
  57. package/dist/client/client.module.d.ts +7 -0
  58. package/dist/client/client.module.js +26 -0
  59. package/dist/client/client.schemas.d.ts +55 -0
  60. package/dist/client/client.schemas.js +27 -0
  61. package/dist/ctx/create-ctx.d.ts +19 -4
  62. package/dist/ctx/create-ctx.js +51 -3
  63. package/dist/db/kernel-schema.d.ts +58 -2
  64. package/dist/errors.d.ts +33 -0
  65. package/dist/errors.js +33 -0
  66. package/dist/extensions/admin-ops.d.ts +4 -1
  67. package/dist/extensions/admin-ops.js +22 -1
  68. package/dist/extensions/admin-view.d.ts +8 -0
  69. package/dist/extensions/admin-view.js +78 -0
  70. package/dist/extensions/blobs.d.ts +14 -1
  71. package/dist/extensions/blobs.js +48 -5
  72. package/dist/extensions/guide-places.d.ts +66 -0
  73. package/dist/extensions/guide-places.js +152 -0
  74. package/dist/extensions/host.d.ts +6 -1
  75. package/dist/extensions/host.js +23 -12
  76. package/dist/extensions/instance.d.ts +5 -1
  77. package/dist/extensions/instance.js +8 -0
  78. package/dist/extensions/ledger.d.ts +1 -1
  79. package/dist/extensions/package.d.ts +4 -3
  80. package/dist/extensions/package.js +5 -1
  81. package/dist/extensions/permission-set.d.ts +35 -5
  82. package/dist/extensions/permission-set.js +36 -5
  83. package/dist/extensions/permission-view.d.ts +38 -7
  84. package/dist/extensions/permission-view.js +27 -8
  85. package/dist/extensions/plugin-guides.d.ts +207 -54
  86. package/dist/extensions/plugin-guides.js +436 -165
  87. package/dist/extensions/sources.d.ts +6 -0
  88. package/dist/extensions/sources.js +2 -0
  89. package/dist/extensions/state.module.js +9 -1
  90. package/dist/http/api.module.js +16 -0
  91. package/dist/identity/settings.d.ts +41 -26
  92. package/dist/identity/settings.js +87 -4
  93. package/dist/infra/lock/lock.service.d.ts +8 -0
  94. package/dist/infra/lock/lock.service.js +27 -0
  95. package/dist/infra/throttler/named-throttlers.d.ts +8 -1
  96. package/dist/infra/throttler/named-throttlers.js +30 -1
  97. package/dist/infra/throttler/throttler.guard.js +1 -1
  98. package/dist/kernel.module.js +3 -0
  99. package/dist/orgs/settings.service.d.ts +2 -26
  100. package/dist/overrides.d.ts +3 -1
  101. package/dist/overrides.js +2 -0
  102. package/dist/platform/exclusions.js +37 -6
  103. package/dist/platform/operation.js +4 -0
  104. package/dist/platform/ops/admin.ops.js +17 -2
  105. package/dist/platform/ops/catalog-admin.ops.d.ts +19 -1
  106. package/dist/platform/ops/catalog-admin.ops.js +85 -2
  107. package/dist/platform/ops/connectors.ops.js +2 -0
  108. package/dist/platform/ops/org-plugins.ops.d.ts +7 -1
  109. package/dist/platform/ops/org-plugins.ops.js +63 -4
  110. package/dist/platform/ops/org-settings.ops.js +2 -2
  111. package/dist/platform/ops/orgs.ops.js +1 -0
  112. package/dist/platform/ops/setup.ops.d.ts +24 -0
  113. package/dist/platform/ops/setup.ops.js +212 -0
  114. package/dist/platform/ops/ui.ops.js +1 -1
  115. package/dist/platform/platform-ops.module.d.ts +2 -1
  116. package/dist/platform/platform-ops.module.js +7 -0
  117. package/dist/platform/route-schemas.js +8 -0
  118. package/dist/roles/worker.module.js +4 -0
  119. package/dist/setup/kernel-checks.d.ts +91 -0
  120. package/dist/setup/kernel-checks.js +295 -0
  121. package/dist/setup/oauth-token-probe.d.ts +47 -0
  122. package/dist/setup/oauth-token-probe.js +108 -0
  123. package/dist/setup/redact-check.d.ts +18 -0
  124. package/dist/setup/redact-check.js +46 -0
  125. package/dist/setup/setup-check-registry.d.ts +86 -0
  126. package/dist/setup/setup-check-registry.js +75 -0
  127. package/dist/setup/setup-checks.controller.d.ts +28 -0
  128. package/dist/setup/setup-checks.controller.js +78 -0
  129. package/dist/setup/setup-checks.schemas.d.ts +38 -0
  130. package/dist/setup/setup-checks.schemas.js +43 -0
  131. package/dist/setup/setup-checks.service.d.ts +144 -0
  132. package/dist/setup/setup-checks.service.js +683 -0
  133. package/dist/setup/setup-guides.controller.d.ts +18 -0
  134. package/dist/setup/setup-guides.controller.js +48 -0
  135. package/dist/setup/setup-guides.schemas.d.ts +10 -0
  136. package/dist/setup/setup-guides.schemas.js +6 -0
  137. package/dist/setup/setup-guides.service.d.ts +126 -0
  138. package/dist/setup/setup-guides.service.js +315 -0
  139. package/dist/setup/setup.module.d.ts +8 -0
  140. package/dist/setup/setup.module.js +31 -0
  141. package/dist/themes/render-css.d.ts +2 -1
  142. package/dist/themes/render-css.js +5 -2
  143. package/dist/themes/theme-delivery.service.d.ts +4 -2
  144. package/dist/themes/theme-delivery.service.js +8 -3
  145. package/dist/themes/theme-state.d.ts +28 -7
  146. package/dist/themes/theme-state.js +24 -6
  147. package/dist/themes/themes-public.controller.d.ts +7 -2
  148. package/dist/themes/themes-public.controller.js +11 -5
  149. package/dist/themes/themes.service.js +2 -2
  150. package/dist/tools/connectors.controller.d.ts +28 -11
  151. package/dist/tools/connectors.controller.js +49 -14
  152. package/dist/tools/connectors.schemas.d.ts +4 -0
  153. package/dist/tools/connectors.schemas.js +4 -0
  154. package/dist/tools/connectors.service.d.ts +30 -2
  155. package/dist/tools/connectors.service.js +98 -3
  156. package/dist/tools/credentials.d.ts +20 -0
  157. package/dist/tools/credentials.js +90 -1
  158. package/dist/tools/oauth/tokens.d.ts +20 -0
  159. package/dist/tools/oauth/tokens.js +49 -24
  160. package/dist/tools/pipeline.d.ts +3 -2
  161. package/dist/ui/arrangement.d.ts +41 -50
  162. package/dist/ui/arrangement.js +88 -15
  163. package/dist/ui/registry.d.ts +13 -3
  164. package/dist/ui/registry.js +15 -4
  165. package/dist/ui/ui.schemas.d.ts +4 -0
  166. package/dist/ui/views.d.ts +36 -9
  167. package/dist/ui/views.js +58 -28
  168. package/package.json +3 -3
  169. package/src/migrations/0106_setup_guides_v2.sql +10 -0
  170. package/src/migrations/0107_extension_client_builds.sql +50 -0
@@ -119,6 +119,26 @@ export declare class Credentials {
119
119
  * 都会更新写入时间),最多 `limit` 个。漂移检查用它挑个人凭据型连接器的凭据。
120
120
  */
121
121
  personalHolders(orgId: string, connectorId: string, limit: number): Promise<string[]>;
122
+ /**
123
+ * For setup checks (setup guides v2 §10.4): the input revision of a connector's credentials,
124
+ * the `updated_at` of its OAuth client row, its org credential row and, given `accountId`, that
125
+ * member's row (ISO), never a value. Reads no secret.
126
+ */
127
+ checkRevision(orgId: string, connectorId: string, accountId: string | null): Promise<string>;
128
+ /**
129
+ * For setup checks (setup guides v2 §10.8), never refreshing or writing anything: every stored
130
+ * credential value of a connector (decrypted: the OAuth client secret and DCR registration
131
+ * token, the org credential, every member's access and refresh tokens or static values) that a
132
+ * check result is redacted by, with the forms they are sent in (what `injectionSecrets` covers
133
+ * for a tool call: the Basic header value, its base64 token and decoded `user:pass`, the
134
+ * `encodeURIComponent` forms; for the client also the `client_secret_basic` header the token
135
+ * request sends and the raw `id:secret`), and the stored OAuth client id (null without a
136
+ * client). Never echo the values; a plugin check gets only a manual client's id.
137
+ */
138
+ checkMaterial(orgId: string, connectorId: string): Promise<{
139
+ secrets: string[];
140
+ clientId: string | null;
141
+ }>;
122
142
  /** 只看与 auth.ownership 对应的那一条凭据是否存在(不解密);none 恒为 connected。 */
123
143
  status(orgId: string, connectorId: string, auth: ConnectorAuth, accountId: string): Promise<"connected" | "missing">;
124
144
  /** 返回本次请求要注入的头 / 查询参数;缺凭据抛 CredentialMissing。 */
@@ -17,7 +17,7 @@ import { KernelDb } from "../infra/database/kernel-db.js";
17
17
  import { LockService } from "../infra/lock/lock.service.js";
18
18
  import { KERNEL_LOGGER, KEYRING, REDIS } from "../infra/tokens.js";
19
19
  import { oauthHttpOptions } from "./oauth/discovery.js";
20
- import { OAuthReconnectRequired, OAuthTokens, } from "./oauth/tokens.js";
20
+ import { basicClientAuth, OAuthReconnectRequired, OAuthTokens, } from "./oauth/tokens.js";
21
21
  import { injectionSecrets } from "./redact.js";
22
22
  export const isOAuth = (auth) => auth.type === "oauth_code" || auth.type === "oauth_client";
23
23
  /** 缺凭据:只带连接器 id 与归属,绝不带值。 */
@@ -239,6 +239,95 @@ let Credentials = class Credentials {
239
239
  LIMIT ${limit}`.execute(this.db.db);
240
240
  return rows.rows.map((r) => r.account_id);
241
241
  }
242
+ /**
243
+ * For setup checks (setup guides v2 §10.4): the input revision of a connector's credentials,
244
+ * the `updated_at` of its OAuth client row, its org credential row and, given `accountId`, that
245
+ * member's row (ISO), never a value. Reads no secret.
246
+ */
247
+ async checkRevision(orgId, connectorId, accountId) {
248
+ const keys = [clientKey(connectorId), orgKey(connectorId)];
249
+ if (accountId !== null)
250
+ keys.push(userKey(connectorId, accountId));
251
+ const rows = await this.db
252
+ .selectFrom("secrets")
253
+ .select(["key", "updated_at"])
254
+ .where("namespace", "=", NAMESPACE)
255
+ .where("org_id", "=", orgId)
256
+ .where("key", "in", keys)
257
+ .execute();
258
+ const at = new Map(rows.map((r) => [r.key, new Date(r.updated_at).toISOString()]));
259
+ return keys.map((k) => at.get(k) ?? "-").join("|");
260
+ }
261
+ /**
262
+ * For setup checks (setup guides v2 §10.8), never refreshing or writing anything: every stored
263
+ * credential value of a connector (decrypted: the OAuth client secret and DCR registration
264
+ * token, the org credential, every member's access and refresh tokens or static values) that a
265
+ * check result is redacted by, with the forms they are sent in (what `injectionSecrets` covers
266
+ * for a tool call: the Basic header value, its base64 token and decoded `user:pass`, the
267
+ * `encodeURIComponent` forms; for the client also the `client_secret_basic` header the token
268
+ * request sends and the raw `id:secret`), and the stored OAuth client id (null without a
269
+ * client). Never echo the values; a plugin check gets only a manual client's id.
270
+ */
271
+ async checkMaterial(orgId, connectorId) {
272
+ const prefix = orgKey(connectorId).slice(0, -"org".length);
273
+ const rows = await this.db
274
+ .selectFrom("secrets")
275
+ .select("key")
276
+ .where("namespace", "=", NAMESPACE)
277
+ .where("org_id", "=", orgId)
278
+ .where(sql `left(key, ${prefix.length})`, "=", prefix)
279
+ .execute();
280
+ const secrets = new Set();
281
+ const push = (...vs) => {
282
+ for (const v of vs) {
283
+ if (typeof v !== "string" || v === "")
284
+ continue;
285
+ // the value and the forms an injection carries it in (`Bearer`, query, percent-encoded)
286
+ for (const s of injectionSecrets({ headers: { a: `Bearer ${v}` }, query: { q: v } })) {
287
+ secrets.add(s);
288
+ }
289
+ }
290
+ };
291
+ const pushBasic = (user, password) => {
292
+ const header = `Basic ${Buffer.from(`${user}:${password}`).toString("base64")}`;
293
+ for (const s of injectionSecrets({ headers: { a: header } }))
294
+ secrets.add(s);
295
+ };
296
+ let clientId = null;
297
+ for (const r of rows) {
298
+ const v = (await this.readJson(orgId, r.key, connectorId));
299
+ if (v === null || typeof v !== "object")
300
+ continue;
301
+ if (r.key === clientKey(connectorId)) {
302
+ // the client id is not a secret (a plugin check gets it); the secret and DCR token are
303
+ if (typeof v.clientId === "string")
304
+ clientId = v.clientId;
305
+ push(v.clientSecret, v.registration?.accessToken);
306
+ if (typeof v.clientId === "string" && typeof v.clientSecret === "string") {
307
+ // as the token request sends it (both parts form-encoded), and as typed
308
+ const header = basicClientAuth(v.clientId, v.clientSecret);
309
+ for (const s of injectionSecrets({ headers: { a: header } }))
310
+ secrets.add(s);
311
+ pushBasic(v.clientId, v.clientSecret);
312
+ }
313
+ }
314
+ else if (typeof v.access_token === "string") {
315
+ push(v.access_token, v.refresh_token);
316
+ }
317
+ else {
318
+ push(v.token, v.password);
319
+ if (typeof v.username === "string" && typeof v.password === "string") {
320
+ pushBasic(v.username, v.password);
321
+ }
322
+ if (typeof v.headers === "object" && v.headers !== null) {
323
+ const headers = Object.fromEntries(Object.entries(v.headers).filter((e) => typeof e[1] === "string"));
324
+ for (const s of injectionSecrets({ headers }))
325
+ secrets.add(s);
326
+ }
327
+ }
328
+ }
329
+ return { secrets: [...secrets], clientId };
330
+ }
242
331
  /** 只看与 auth.ownership 对应的那一条凭据是否存在(不解密);none 恒为 connected。 */
243
332
  async status(orgId, connectorId, auth, accountId) {
244
333
  if (auth.type === "none")
@@ -75,6 +75,26 @@ export interface TokenRequestOptions {
75
75
  /** More `token_type` values to accept as bearer tokens (`auth.tokenTypes`, case-insensitive). */
76
76
  tokenTypes?: readonly string[] | undefined;
77
77
  }
78
+ /**
79
+ * The headers and form of a token request (RFC 6749 §3.2): client_id / secret per the client's
80
+ * auth method (Basic form-encodes them first, §2.3.1; otherwise `client_id`, plus
81
+ * `client_secret` for `client_secret_post`). Empty parameters are left out.
82
+ */
83
+ export declare function tokenRequestInit(client: OAuthClientRecord, params: Record<string, string | undefined>): {
84
+ headers: Record<string, string>;
85
+ body: URLSearchParams;
86
+ };
87
+ /** The form encoding of a Basic client credential part (RFC 6749 §2.3.1). */
88
+ export declare const formEncode: (s: string) => string;
89
+ /** The `Authorization` value of `client_secret_basic` (both parts form-encoded first). */
90
+ export declare const basicClientAuth: (clientId: string, clientSecret: string) => string;
91
+ /**
92
+ * The refusal code of a token endpoint answer, or null when it is not a refusal (the one parser of
93
+ * token refusals): a 400 / 401 whose JSON body has an RFC 6749-shaped `error`, or a 200 whose body
94
+ * carries `ok: false` (Slack) or an `error` and no `access_token`, with a code that is not
95
+ * transient. Only codes matching the RFC 6749 shape are returned, never other text of the body.
96
+ */
97
+ export declare function parseTokenRefusal(status: number, body: unknown): string | null;
78
98
  /**
79
99
  * Calls the token endpoint (RFC 6749 §3.2), sending client_id / secret per the client's auth method
80
100
  * (Basic form-encodes them first, §2.3.1).
@@ -68,20 +68,11 @@ const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
68
68
  /** RFC 6749 §5.2 形式的错误码(只有这种串才写进日志 / 错误,不回显对方任意文本)。 */
69
69
  const OAUTH_ERROR_RE = /^[a-z][a-z0-9_]{0,63}$/;
70
70
  /**
71
- * Calls the token endpoint (RFC 6749 §3.2), sending client_id / secret per the client's auth method
72
- * (Basic form-encodes them first, §2.3.1).
73
- * - 400 / 401 with a parsable RFC 6749 `error` code -> OAuthGrantError.
74
- * - 200 with `ok: false` (Slack reports every error this way), or with an `error` code and no
75
- * `access_token` -> OAuthGrantError with that code (`ok: false` wins over an `access_token`); a
76
- * transient code (`temporarily_unavailable`, `server_error`, Slack's `ratelimited`, ...) or no
77
- * parsable code -> OAuthUnavailable.
78
- * - Everything else (network error, timeout, 429, 408, 403 and other 4xx, a 4xx without a code,
79
- * 5xx, 3xx, a malformed body) -> OAuthUnavailable: a gateway's rate limit or a WAF block is not a
80
- * dead credential.
81
- * - A `token_type` other than Bearer / `user` / `opts.tokenTypes` -> OAuthGrantError
82
- * `unsupported_token_type`; an accepted one is stored as `Bearer`.
71
+ * The headers and form of a token request (RFC 6749 §3.2): client_id / secret per the client's
72
+ * auth method (Basic form-encodes them first, §2.3.1; otherwise `client_id`, plus
73
+ * `client_secret` for `client_secret_post`). Empty parameters are left out.
83
74
  */
84
- export async function tokenRequest(client, params, http, opts = {}) {
75
+ export function tokenRequestInit(client, params) {
85
76
  const form = new URLSearchParams();
86
77
  for (const [k, v] of Object.entries(params))
87
78
  if (v !== undefined && v !== "")
@@ -91,8 +82,7 @@ export async function tokenRequest(client, params, http, opts = {}) {
91
82
  accept: "application/json",
92
83
  };
93
84
  if (client.authMethod === "client_secret_basic" && client.clientSecret !== undefined) {
94
- const enc = (s) => encodeURIComponent(s).replace(/%20/g, "+");
95
- headers.authorization = `Basic ${Buffer.from(`${enc(client.clientId)}:${enc(client.clientSecret)}`).toString("base64")}`;
85
+ headers.authorization = basicClientAuth(client.clientId, client.clientSecret);
96
86
  }
97
87
  else {
98
88
  form.set("client_id", client.clientId);
@@ -100,6 +90,46 @@ export async function tokenRequest(client, params, http, opts = {}) {
100
90
  form.set("client_secret", client.clientSecret);
101
91
  }
102
92
  }
93
+ return { headers, body: form };
94
+ }
95
+ /** The form encoding of a Basic client credential part (RFC 6749 §2.3.1). */
96
+ export const formEncode = (s) => encodeURIComponent(s).replace(/%20/g, "+");
97
+ /** The `Authorization` value of `client_secret_basic` (both parts form-encoded first). */
98
+ export const basicClientAuth = (clientId, clientSecret) => `Basic ${Buffer.from(`${formEncode(clientId)}:${formEncode(clientSecret)}`).toString("base64")}`;
99
+ /**
100
+ * The refusal code of a token endpoint answer, or null when it is not a refusal (the one parser of
101
+ * token refusals): a 400 / 401 whose JSON body has an RFC 6749-shaped `error`, or a 200 whose body
102
+ * carries `ok: false` (Slack) or an `error` and no `access_token`, with a code that is not
103
+ * transient. Only codes matching the RFC 6749 shape are returned, never other text of the body.
104
+ */
105
+ export function parseTokenRefusal(status, body) {
106
+ const code = isObj(body) ? body.error : undefined;
107
+ const shaped = typeof code === "string" && OAUTH_ERROR_RE.test(code) ? code : null;
108
+ if (status === 400 || status === 401)
109
+ return shaped;
110
+ if (status !== 200 || !isObj(body))
111
+ return null;
112
+ if (body.ok === false || (body.access_token === undefined && body.error !== undefined)) {
113
+ return shaped !== null && !TRANSIENT_ERRORS.has(shaped) ? shaped : null;
114
+ }
115
+ return null;
116
+ }
117
+ /**
118
+ * Calls the token endpoint (RFC 6749 §3.2), sending client_id / secret per the client's auth method
119
+ * (Basic form-encodes them first, §2.3.1).
120
+ * - 400 / 401 with a parsable RFC 6749 `error` code -> OAuthGrantError.
121
+ * - 200 with `ok: false` (Slack reports every error this way), or with an `error` code and no
122
+ * `access_token` -> OAuthGrantError with that code (`ok: false` wins over an `access_token`); a
123
+ * transient code (`temporarily_unavailable`, `server_error`, Slack's `ratelimited`, ...) or no
124
+ * parsable code -> OAuthUnavailable.
125
+ * - Everything else (network error, timeout, 429, 408, 403 and other 4xx, a 4xx without a code,
126
+ * 5xx, 3xx, a malformed body) -> OAuthUnavailable: a gateway's rate limit or a WAF block is not a
127
+ * dead credential.
128
+ * - A `token_type` other than Bearer / `user` / `opts.tokenTypes` -> OAuthGrantError
129
+ * `unsupported_token_type`; an accepted one is stored as `Bearer`.
130
+ */
131
+ export async function tokenRequest(client, params, http, opts = {}) {
132
+ const { headers, body: form } = tokenRequestInit(client, params);
103
133
  let res;
104
134
  try {
105
135
  res = await oauthFetch(client.tokenEndpoint, { method: "POST", headers, body: form }, http);
@@ -116,18 +146,13 @@ export async function tokenRequest(client, params, http, opts = {}) {
116
146
  catch {
117
147
  body = undefined;
118
148
  }
119
- if (res.status === 400 || res.status === 401) {
120
- const code = isObj(body) ? body.error : undefined;
121
- if (typeof code === "string" && OAUTH_ERROR_RE.test(code))
122
- throw new OAuthGrantError(code);
123
- }
149
+ const refusal = parseTokenRefusal(res.status, body);
150
+ if (refusal !== null)
151
+ throw new OAuthGrantError(refusal);
124
152
  if (res.status !== 200 || !isObj(body))
125
153
  throw new OAuthUnavailable();
154
+ // an error body without a usable code (or a transient one): unavailable, not a refusal
126
155
  if (body.ok === false || (body.access_token === undefined && body.error !== undefined)) {
127
- const code = body.error;
128
- if (typeof code === "string" && OAUTH_ERROR_RE.test(code) && !TRANSIENT_ERRORS.has(code)) {
129
- throw new OAuthGrantError(code);
130
- }
131
156
  throw new OAuthUnavailable();
132
157
  }
133
158
  const { access_token, refresh_token, expires_in, token_type, scope } = body;
@@ -151,8 +151,9 @@ interface PipelineDeps {
151
151
  /** The org's switches of single extension tools: a turned-off one is not called (spec A §7). */
152
152
  readonly switches: Pick<OrgToolSwitches, "isToolOff">;
153
153
  /**
154
- * Ids of an extension's `inline` ui contributions (read live, no alias): a plugin tool's
155
- * `ToolResult.card` must name one of them, otherwise it is dropped with a warning.
154
+ * Ids of an extension's `conversation.tool-result.card` contributions of any kind (view, embed
155
+ * or component; read live, no alias): a plugin tool's `ToolResult.card` must name one of them,
156
+ * otherwise it is dropped with a warning.
156
157
  */
157
158
  inlineViews(plugin: string): ReadonlySet<string>;
158
159
  /**
@@ -1,74 +1,65 @@
1
1
  import { type UiSlotId } from "@capybara-hq/plugin-sdk";
2
2
  import { z } from "zod";
3
3
  import type { UiContributionItem } from "./views.js";
4
- export declare const UiArrangementEntrySchema: z.ZodObject<{
5
- hidden: z.ZodArray<z.ZodString>;
6
- order: z.ZodArray<z.ZodString>;
7
- }, z.core.$strict>;
8
- export type UiArrangementEntry = z.infer<typeof UiArrangementEntrySchema>;
9
- export declare const UiArrangementSchema: z.ZodRecord<z.ZodEnum<{
10
- "agent.detail.section": "agent.detail.section";
11
- "conversation.panel": "conversation.panel";
12
- "conversation.properties.section": "conversation.properties.section";
13
- "conversation.tool-result.card": "conversation.tool-result.card";
14
- "file.preview": "file.preview";
15
- "org.page": "org.page";
16
- "org.settings.section": "org.settings.section";
17
- "platform.page": "platform.page";
18
- }> & z.core.$partial, z.ZodObject<{
19
- hidden: z.ZodArray<z.ZodString>;
20
- order: z.ZodArray<z.ZodString>;
21
- }, z.core.$strict>>;
22
- export type UiArrangement = z.infer<typeof UiArrangementSchema>;
4
+ export type UiListEntry = {
5
+ hidden: string[];
6
+ order: string[];
7
+ };
8
+ export type UiUseEntry = {
9
+ use: string | null;
10
+ };
11
+ /** One slot's entry: `{ hidden, order }` on a list slot, `{ use }` on a single slot. */
12
+ export type UiArrangementEntry = UiListEntry | UiUseEntry;
13
+ export type UiArrangement = Partial<Record<UiSlotId, UiArrangementEntry>>;
14
+ /** Whether a slot is a single (replaceable) slot. */
15
+ export declare const isSingle: (slot: UiSlotId) => boolean;
16
+ export declare const UiArrangementSchema: z.ZodType<UiArrangement>;
23
17
  /** The slots an org arranges: every arrangeable slot but the platform's own. */
24
18
  export declare const ORG_ARRANGEABLE: readonly UiSlotId[];
25
19
  /** An org's patch: a slot's entry replaces the stored one, null removes it. */
26
- export declare const OrgUiArrangementPatchSchema: z.ZodRecord<z.ZodEnum<{
27
- "agent.detail.section": "agent.detail.section";
28
- "conversation.panel": "conversation.panel";
29
- "conversation.properties.section": "conversation.properties.section";
30
- "conversation.tool-result.card": "conversation.tool-result.card";
31
- "file.preview": "file.preview";
32
- "org.page": "org.page";
33
- "org.settings.section": "org.settings.section";
34
- "platform.page": "platform.page";
35
- }> & z.core.$partial, z.ZodNullable<z.ZodObject<{
36
- hidden: z.ZodArray<z.ZodString>;
37
- order: z.ZodArray<z.ZodString>;
38
- }, z.core.$strict>>>;
20
+ export declare const OrgUiArrangementPatchSchema: z.ZodType<Partial<Record<"agent.detail.section" | "conversation.composer.frame" | "conversation.empty" | "conversation.panel" | "conversation.properties.section" | "conversation.tool-result.card" | "file.preview" | "org.page" | "org.settings.section" | "platform.page" | "shell.brand" | "shell.overlay", UiArrangementEntry | null>>, unknown, z.core.$ZodTypeInternals<Partial<Record<"agent.detail.section" | "conversation.composer.frame" | "conversation.empty" | "conversation.panel" | "conversation.properties.section" | "conversation.tool-result.card" | "file.preview" | "org.page" | "org.settings.section" | "platform.page" | "shell.brand" | "shell.overlay", UiArrangementEntry | null>>, unknown>>;
39
21
  /** A deployment patch: like {@link OrgUiArrangementPatchSchema} over every arrangeable slot. */
40
- export declare const UiArrangementPatchSchema: z.ZodRecord<z.ZodEnum<{
41
- "agent.detail.section": "agent.detail.section";
42
- "conversation.panel": "conversation.panel";
43
- "conversation.properties.section": "conversation.properties.section";
44
- "conversation.tool-result.card": "conversation.tool-result.card";
45
- "file.preview": "file.preview";
46
- "org.page": "org.page";
47
- "org.settings.section": "org.settings.section";
48
- "platform.page": "platform.page";
49
- }> & z.core.$partial, z.ZodNullable<z.ZodObject<{
50
- hidden: z.ZodArray<z.ZodString>;
51
- order: z.ZodArray<z.ZodString>;
52
- }, z.core.$strict>>>;
22
+ export declare const UiArrangementPatchSchema: z.ZodType<Partial<Record<"agent.detail.section" | "conversation.composer.frame" | "conversation.empty" | "conversation.panel" | "conversation.properties.section" | "conversation.tool-result.card" | "file.preview" | "org.page" | "org.settings.section" | "platform.page" | "shell.brand" | "shell.overlay", UiArrangementEntry | null>>, unknown, z.core.$ZodTypeInternals<Partial<Record<"agent.detail.section" | "conversation.composer.frame" | "conversation.empty" | "conversation.panel" | "conversation.properties.section" | "conversation.tool-result.card" | "file.preview" | "org.page" | "org.settings.section" | "platform.page" | "shell.brand" | "shell.overlay", UiArrangementEntry | null>>, unknown>>;
53
23
  /** Merges a per-slot patch into the stored arrangement (null removes the slot's entry). */
54
24
  export declare function mergeArrangement(current: UiArrangement | undefined, patch: Partial<Record<UiSlotId, UiArrangementEntry | null>>): UiArrangement;
55
- /** The entry that applies to a slot: the org's if it arranged the slot, else the platform's. */
25
+ /**
26
+ * The list entry that applies to a slot: the org's if it arranged the slot, else the platform's
27
+ * (a `use` entry, which only a single slot stores, reads as none).
28
+ */
56
29
  export declare function effectiveEntry(org: UiArrangement | undefined, platform: UiArrangement, slot: UiSlotId): {
57
- entry: UiArrangementEntry;
30
+ entry: UiListEntry;
58
31
  source: "org" | "platform" | "none";
59
32
  };
33
+ /**
34
+ * The winner of a single slot (client extensions spec §6.3): the org's entry if it has one, else
35
+ * the platform's, else none. No entry or `use: null` is the built-in (null); a `use` naming a key
36
+ * not in `available` (extension off in the org, contribution dropped, the viewer's role below the
37
+ * slot's) is the built-in too, never the other level's pick. A list slot, or an entry that is not
38
+ * a `use` entry, has no winner.
39
+ */
40
+ export declare function resolveSingleSlot(slot: UiSlotId, orgEntry: UiArrangementEntry | undefined, platformEntry: UiArrangementEntry | undefined, available: ReadonlySet<string>): string | null;
41
+ /** The `use` stored for a single slot: the org's entry if it has one, else the platform's. */
42
+ export declare function storedUse(org: UiArrangement | undefined, platform: UiArrangement, slot: UiSlotId): {
43
+ use: string | null;
44
+ followsPlatform: boolean;
45
+ };
46
+ /** The arrangement key of a contribution: `<plugin>/<id>`. */
47
+ export declare const keyOf: (i: {
48
+ plugin: string;
49
+ id: string;
50
+ }) => string;
60
51
  /**
61
52
  * Orders the items of one slot (hidden ones stay in place): grouped by anchor in the slot's anchor
62
53
  * order, also without an entry (the lists match what the page renders per anchor); within a group
63
54
  * the keys of `entry.order` come first in that order, the rest in base order. Unknown keys are
64
55
  * ignored, so an order never moves an item across anchors.
65
56
  */
66
- export declare function orderItems(items: UiContributionItem[], entry: UiArrangementEntry): UiContributionItem[];
57
+ export declare function orderItems(items: UiContributionItem[], entry: UiListEntry): UiContributionItem[];
67
58
  /** {@link orderItems} without the hidden keys. */
68
- export declare function arrange(items: UiContributionItem[], entry: UiArrangementEntry): UiContributionItem[];
59
+ export declare function arrange(items: UiContributionItem[], entry: UiListEntry): UiContributionItem[];
69
60
  /**
70
61
  * Applies the per-slot entries to a mixed list: each slot's items are arranged and written back
71
62
  * into the positions that slot occupied (a slot without an entry: its base order, grouped by
72
63
  * anchor).
73
64
  */
74
- export declare function arrangeBySlot(items: UiContributionItem[], entryOf: (slot: UiSlotId) => UiArrangementEntry): UiContributionItem[];
65
+ export declare function arrangeBySlot(items: UiContributionItem[], entryOf: (slot: UiSlotId) => UiListEntry): UiContributionItem[];
@@ -1,22 +1,66 @@
1
- import { UI_ARRANGEABLE_SLOTS, UI_CONTRIBUTION_KEY_RE, uiSlot, } from "@capybara-hq/plugin-sdk";
1
+ import { UI_ARRANGEABLE_SLOTS, UI_CONTRIBUTION_KEY_RE, UI_SINGLE_SLOTS, uiSlot, } from "@capybara-hq/plugin-sdk";
2
2
  import { z } from "zod";
3
3
  /**
4
- * Arrangement of UI slots (UI slots spec §5): per slot, which contributions are hidden and in
5
- * which order. "Hidden" means not listed: the access gate never reads an arrangement, so a hidden
6
- * contribution's view, action and embed routes keep working for an allowed role.
4
+ * Arrangement of UI slots (UI slots spec §5, client extensions spec §6.3): per list slot, which
5
+ * contributions are hidden and in which order; per single slot, which contribution replaces the
6
+ * built-in (`use`, null = the built-in). "Hidden" means not listed and `use` only picks what is
7
+ * listed: the access gate never reads an arrangement, so a contribution's view, action and embed
8
+ * routes keep working for an allowed role whatever the arrangement says.
7
9
  */
8
- const Key = z.string().regex(UI_CONTRIBUTION_KEY_RE);
10
+ const Key = z.string().regex(UI_CONTRIBUTION_KEY_RE, "must be a contribution key <plugin>/<id>");
9
11
  const LIST_MAX = 200;
10
- export const UiArrangementEntrySchema = z
11
- .object({ hidden: z.array(Key).max(LIST_MAX), order: z.array(Key).max(LIST_MAX) })
12
- .strict();
13
- export const UiArrangementSchema = z.partialRecord(z.enum(UI_ARRANGEABLE_SLOTS), UiArrangementEntrySchema);
12
+ /**
13
+ * A list slot's entry: the keys hidden and the keys ordered first. The refusals name the slot and
14
+ * what it takes (actionable for an admin and the Platform tool's model).
15
+ */
16
+ const listEntrySchema = (slot) => {
17
+ const keys = z
18
+ .array(Key, {
19
+ error: (iss) => iss.input === undefined
20
+ ? `${slot} is a list slot: give an array of contribution keys <plugin>/<id>`
21
+ : undefined,
22
+ })
23
+ .max(LIST_MAX);
24
+ return z.strictObject({ hidden: keys, order: keys }, {
25
+ error: (iss) => iss.code === "unrecognized_keys"
26
+ ? `${slot} is a list slot: give { hidden, order }; use is only for the single slots (${UI_SINGLE_SLOTS.join(", ")})`
27
+ : undefined,
28
+ });
29
+ };
30
+ /** A single slot's entry: the contribution that replaces the built-in, or null for the built-in. */
31
+ const useEntrySchema = (slot) => z.strictObject({
32
+ use: z.union([Key, z.null()], {
33
+ error: (iss) => iss.input === undefined
34
+ ? `${slot} needs use: a contribution key <plugin>/<id>, or null for the built-in`
35
+ : undefined,
36
+ }),
37
+ }, {
38
+ error: (iss) => iss.code === "unrecognized_keys"
39
+ ? `${slot} is a single slot: give { use } (a contribution key <plugin>/<id>, or null for the built-in); hidden and order are only for list slots`
40
+ : undefined,
41
+ });
42
+ /** Whether a slot is a single (replaceable) slot. */
43
+ export const isSingle = (slot) => UI_SINGLE_SLOTS.includes(slot);
44
+ /**
45
+ * An object over `slots`, each optional and of its kind's entry (nullable in a patch); unknown
46
+ * slots are refused, and so are `use` on a list slot and `hidden` / `order` on a single slot (400
47
+ * `validation_failed` on the settings routes).
48
+ */
49
+ function arrangementShape(slots, nullable) {
50
+ const shape = {};
51
+ for (const slot of slots) {
52
+ const entry = isSingle(slot) ? useEntrySchema(slot) : listEntrySchema(slot);
53
+ shape[slot] = (nullable ? entry.nullable() : entry).optional();
54
+ }
55
+ return z.object(shape).strict();
56
+ }
57
+ export const UiArrangementSchema = arrangementShape(UI_ARRANGEABLE_SLOTS, false);
14
58
  /** The slots an org arranges: every arrangeable slot but the platform's own. */
15
59
  export const ORG_ARRANGEABLE = UI_ARRANGEABLE_SLOTS.filter((s) => s !== "platform.page");
16
60
  /** An org's patch: a slot's entry replaces the stored one, null removes it. */
17
- export const OrgUiArrangementPatchSchema = z.partialRecord(z.enum(ORG_ARRANGEABLE), UiArrangementEntrySchema.nullable());
61
+ export const OrgUiArrangementPatchSchema = arrangementShape(ORG_ARRANGEABLE, true);
18
62
  /** A deployment patch: like {@link OrgUiArrangementPatchSchema} over every arrangeable slot. */
19
- export const UiArrangementPatchSchema = z.partialRecord(z.enum(UI_ARRANGEABLE_SLOTS), UiArrangementEntrySchema.nullable());
63
+ export const UiArrangementPatchSchema = arrangementShape(UI_ARRANGEABLE_SLOTS, true);
20
64
  /** Merges a per-slot patch into the stored arrangement (null removes the slot's entry). */
21
65
  export function mergeArrangement(current, patch) {
22
66
  const merged = { ...current };
@@ -29,17 +73,46 @@ export function mergeArrangement(current, patch) {
29
73
  return merged;
30
74
  }
31
75
  const NONE = { hidden: [], order: [] };
32
- /** The entry that applies to a slot: the org's if it arranged the slot, else the platform's. */
76
+ const asList = (e) => ("hidden" in e ? e : NONE);
77
+ /**
78
+ * The list entry that applies to a slot: the org's if it arranged the slot, else the platform's
79
+ * (a `use` entry, which only a single slot stores, reads as none).
80
+ */
33
81
  export function effectiveEntry(org, platform, slot) {
34
82
  const o = org?.[slot];
35
83
  if (o !== undefined)
36
- return { entry: o, source: "org" };
84
+ return { entry: asList(o), source: "org" };
37
85
  const p = platform[slot];
38
86
  if (p !== undefined)
39
- return { entry: p, source: "platform" };
87
+ return { entry: asList(p), source: "platform" };
40
88
  return { entry: NONE, source: "none" };
41
89
  }
42
- const keyOf = (i) => `${i.plugin}/${i.id}`;
90
+ /**
91
+ * The winner of a single slot (client extensions spec §6.3): the org's entry if it has one, else
92
+ * the platform's, else none. No entry or `use: null` is the built-in (null); a `use` naming a key
93
+ * not in `available` (extension off in the org, contribution dropped, the viewer's role below the
94
+ * slot's) is the built-in too, never the other level's pick. A list slot, or an entry that is not
95
+ * a `use` entry, has no winner.
96
+ */
97
+ export function resolveSingleSlot(slot, orgEntry, platformEntry, available) {
98
+ if (!isSingle(slot))
99
+ return null;
100
+ const entry = orgEntry ?? platformEntry;
101
+ if (entry === undefined || !("use" in entry) || entry.use === null)
102
+ return null;
103
+ return available.has(entry.use) ? entry.use : null;
104
+ }
105
+ /** The `use` stored for a single slot: the org's entry if it has one, else the platform's. */
106
+ export function storedUse(org, platform, slot) {
107
+ const o = org?.[slot];
108
+ const e = o ?? platform[slot];
109
+ return {
110
+ use: e !== undefined && "use" in e ? e.use : null,
111
+ followsPlatform: o === undefined,
112
+ };
113
+ }
114
+ /** The arrangement key of a contribution: `<plugin>/<id>`. */
115
+ export const keyOf = (i) => `${i.plugin}/${i.id}`;
43
116
  /**
44
117
  * Orders the items of one slot (hidden ones stay in place): grouped by anchor in the slot's anchor
45
118
  * order, also without an entry (the lists match what the page renders per anchor); within a group
@@ -45,19 +45,29 @@ export declare const uiFrame: (m: UiBusMessage) => string;
45
45
  /**
46
46
  * 单个插件在 setup 期间注册的视图与动作:进程内的静态登记(同 `ctx.tools.register`),
47
47
  * 不是跨请求的共享状态——各副本加载同一插件得到同样的登记。
48
- * id 必须是 `permissions.ui.views` 里的(与清单 `ui` 里 kind view 的贡献一一对应,`defineExtension` 已交叉核对)。
48
+ * The id must be in `permissions.ui.views` or `permissions.ui.components` (a component's server
49
+ * view, client extensions spec §5.2); `defineExtension` already cross-checked them with the
50
+ * manifest `ui`.
49
51
  */
50
52
  export declare class PluginUiRegistration {
51
53
  private readonly manifest;
52
- /** 越权(id 未在 `permissions.ui.views` 声明):抛 ExtensionPermissionError 并写审计。 */
54
+ /**
55
+ * Out of scope (the id is in neither `permissions.ui.views` nor `permissions.ui.components`):
56
+ * throws ExtensionPermissionError and writes an audit entry.
57
+ */
53
58
  private readonly deny;
54
59
  readonly views: Map<string, UiRender>;
55
60
  readonly actions: Map<string, UiActionHandler>;
56
61
  private readonly declaredViews;
57
62
  private readonly contributions;
63
+ /** The ids of the manifest's `component` contributions: their permission is `ui.components`. */
64
+ private readonly components;
58
65
  private sealed;
59
66
  constructor(manifest: ExtensionManifest,
60
- /** 越权(id 未在 `permissions.ui.views` 声明):抛 ExtensionPermissionError 并写审计。 */
67
+ /**
68
+ * Out of scope (the id is in neither `permissions.ui.views` nor `permissions.ui.components`):
69
+ * throws ExtensionPermissionError and writes an audit entry.
70
+ */
61
71
  deny: (item: string) => never);
62
72
  /** setup 结束后调用:此后 view / action 抛错。 */
63
73
  seal(): void;
@@ -31,7 +31,9 @@ export const uiFrame = (m) => `event: ui\ndata: ${JSON.stringify({ plugin: m.plu
31
31
  /**
32
32
  * 单个插件在 setup 期间注册的视图与动作:进程内的静态登记(同 `ctx.tools.register`),
33
33
  * 不是跨请求的共享状态——各副本加载同一插件得到同样的登记。
34
- * id 必须是 `permissions.ui.views` 里的(与清单 `ui` 里 kind view 的贡献一一对应,`defineExtension` 已交叉核对)。
34
+ * The id must be in `permissions.ui.views` or `permissions.ui.components` (a component's server
35
+ * view, client extensions spec §5.2); `defineExtension` already cross-checked them with the
36
+ * manifest `ui`.
35
37
  */
36
38
  export class PluginUiRegistration {
37
39
  manifest;
@@ -40,14 +42,21 @@ export class PluginUiRegistration {
40
42
  actions = new Map();
41
43
  declaredViews;
42
44
  contributions;
45
+ /** The ids of the manifest's `component` contributions: their permission is `ui.components`. */
46
+ components;
43
47
  sealed = false;
44
48
  constructor(manifest,
45
- /** 越权(id 未在 `permissions.ui.views` 声明):抛 ExtensionPermissionError 并写审计。 */
49
+ /**
50
+ * Out of scope (the id is in neither `permissions.ui.views` nor `permissions.ui.components`):
51
+ * throws ExtensionPermissionError and writes an audit entry.
52
+ */
46
53
  deny) {
47
54
  this.manifest = manifest;
48
55
  this.deny = deny;
49
- this.declaredViews = new Set(manifest.permissions?.ui?.views ?? []);
56
+ const ui = manifest.permissions?.ui;
57
+ this.declaredViews = new Set([...(ui?.views ?? []), ...(ui?.components ?? [])]);
50
58
  this.contributions = new Set((manifest.ui ?? []).map((c) => c.id));
59
+ this.components = new Set((manifest.ui ?? []).filter((c) => c.kind === "component").map((c) => c.id));
51
60
  }
52
61
  /** setup 结束后调用:此后 view / action 抛错。 */
53
62
  seal() {
@@ -99,7 +108,9 @@ export class PluginUiRegistration {
99
108
  throw new Error(`plugin ${name}: ui.${what} is only allowed during setup; setup has completed`);
100
109
  }
101
110
  if (typeof id !== "string" || !this.declaredViews.has(id)) {
102
- this.deny(`permissions.ui.views ${String(id)}`);
111
+ // name the list the id belongs in: a component's server view is declared under components
112
+ const list = typeof id === "string" && this.components.has(id) ? "components" : "views";
113
+ this.deny(`permissions.ui.${list} ${String(id)}`);
103
114
  }
104
115
  if (typeof fn !== "function") {
105
116
  throw new Error(`plugin ${name}: ui.${what} "${id}" handler must be a function`);
@@ -9,6 +9,8 @@ export declare const ContributionParams: z.ZodObject<{
9
9
  export declare const ContributionsQuery: z.ZodObject<{
10
10
  slot: z.ZodOptional<z.ZodEnum<{
11
11
  "agent.detail.section": "agent.detail.section";
12
+ "conversation.composer.frame": "conversation.composer.frame";
13
+ "conversation.empty": "conversation.empty";
12
14
  "conversation.panel": "conversation.panel";
13
15
  "conversation.properties.section": "conversation.properties.section";
14
16
  "conversation.tool-result.card": "conversation.tool-result.card";
@@ -16,6 +18,8 @@ export declare const ContributionsQuery: z.ZodObject<{
16
18
  "org.page": "org.page";
17
19
  "org.settings.section": "org.settings.section";
18
20
  "platform.page": "platform.page";
21
+ "shell.brand": "shell.brand";
22
+ "shell.overlay": "shell.overlay";
19
23
  }>>;
20
24
  }, z.core.$strip>;
21
25
  export declare const ViewQuery: z.ZodObject<{