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

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 (91) hide show
  1. package/dist/catalog/admin-catalog.controller.d.ts +13 -2
  2. package/dist/catalog/admin-catalog.controller.js +24 -3
  3. package/dist/catalog/admin-catalog.schemas.d.ts +10 -0
  4. package/dist/catalog/admin-catalog.schemas.js +7 -0
  5. package/dist/catalog/catalog-content.d.ts +9 -1
  6. package/dist/catalog/catalog-content.js +2 -1
  7. package/dist/catalog/catalog-files.d.ts +3 -2
  8. package/dist/catalog/catalog-files.js +8 -5
  9. package/dist/catalog/component-scope.service.d.ts +14 -1
  10. package/dist/catalog/component-scope.service.js +12 -2
  11. package/dist/catalog/marketplaces.service.d.ts +1 -1
  12. package/dist/catalog/org-catalog.controller.d.ts +5 -4
  13. package/dist/catalog/org-catalog.controller.js +5 -4
  14. package/dist/catalog/org-catalog.schemas.d.ts +18 -2
  15. package/dist/catalog/org-catalog.schemas.js +16 -2
  16. package/dist/catalog/org-catalog.service.js +8 -2
  17. package/dist/catalog/plugin-import.js +1 -1
  18. package/dist/catalog/plugin-package.js +37 -9
  19. package/dist/catalog/plugin-upgrade.d.ts +15 -1
  20. package/dist/catalog/plugin-upgrade.js +18 -5
  21. package/dist/channels/connections.controller.d.ts +1 -1
  22. package/dist/channels/connections.controller.js +1 -1
  23. package/dist/channels/connections.service.d.ts +22 -5
  24. package/dist/channels/connections.service.js +56 -10
  25. package/dist/channels/webhook.d.ts +21 -1
  26. package/dist/channels/webhook.js +39 -4
  27. package/dist/ctx/create-ctx.d.ts +17 -3
  28. package/dist/ctx/create-ctx.js +49 -2
  29. package/dist/db/kernel-schema.d.ts +11 -2
  30. package/dist/errors.d.ts +4 -0
  31. package/dist/errors.js +4 -0
  32. package/dist/extensions/guide-places.d.ts +66 -0
  33. package/dist/extensions/guide-places.js +152 -0
  34. package/dist/extensions/host.js +3 -3
  35. package/dist/extensions/instance.d.ts +5 -1
  36. package/dist/extensions/instance.js +8 -0
  37. package/dist/extensions/ledger.d.ts +1 -1
  38. package/dist/extensions/permission-set.d.ts +16 -2
  39. package/dist/extensions/permission-set.js +17 -3
  40. package/dist/extensions/plugin-guides.d.ts +207 -54
  41. package/dist/extensions/plugin-guides.js +436 -165
  42. package/dist/http/api.module.js +6 -0
  43. package/dist/infra/lock/lock.service.d.ts +8 -0
  44. package/dist/infra/lock/lock.service.js +27 -0
  45. package/dist/infra/throttler/named-throttlers.d.ts +4 -1
  46. package/dist/infra/throttler/named-throttlers.js +16 -1
  47. package/dist/infra/throttler/throttler.guard.js +1 -1
  48. package/dist/overrides.d.ts +3 -1
  49. package/dist/overrides.js +2 -0
  50. package/dist/platform/exclusions.js +10 -5
  51. package/dist/platform/operation.js +4 -0
  52. package/dist/platform/ops/connectors.ops.js +2 -0
  53. package/dist/platform/ops/setup.ops.d.ts +24 -0
  54. package/dist/platform/ops/setup.ops.js +212 -0
  55. package/dist/platform/platform-ops.module.d.ts +2 -1
  56. package/dist/platform/platform-ops.module.js +5 -0
  57. package/dist/platform/route-schemas.js +5 -0
  58. package/dist/setup/kernel-checks.d.ts +91 -0
  59. package/dist/setup/kernel-checks.js +295 -0
  60. package/dist/setup/oauth-token-probe.d.ts +47 -0
  61. package/dist/setup/oauth-token-probe.js +108 -0
  62. package/dist/setup/redact-check.d.ts +18 -0
  63. package/dist/setup/redact-check.js +46 -0
  64. package/dist/setup/setup-check-registry.d.ts +86 -0
  65. package/dist/setup/setup-check-registry.js +75 -0
  66. package/dist/setup/setup-checks.controller.d.ts +28 -0
  67. package/dist/setup/setup-checks.controller.js +78 -0
  68. package/dist/setup/setup-checks.schemas.d.ts +38 -0
  69. package/dist/setup/setup-checks.schemas.js +43 -0
  70. package/dist/setup/setup-checks.service.d.ts +144 -0
  71. package/dist/setup/setup-checks.service.js +683 -0
  72. package/dist/setup/setup-guides.controller.d.ts +18 -0
  73. package/dist/setup/setup-guides.controller.js +48 -0
  74. package/dist/setup/setup-guides.schemas.d.ts +10 -0
  75. package/dist/setup/setup-guides.schemas.js +6 -0
  76. package/dist/setup/setup-guides.service.d.ts +126 -0
  77. package/dist/setup/setup-guides.service.js +315 -0
  78. package/dist/setup/setup.module.d.ts +8 -0
  79. package/dist/setup/setup.module.js +31 -0
  80. package/dist/tools/connectors.controller.d.ts +28 -11
  81. package/dist/tools/connectors.controller.js +49 -14
  82. package/dist/tools/connectors.schemas.d.ts +4 -0
  83. package/dist/tools/connectors.schemas.js +4 -0
  84. package/dist/tools/connectors.service.d.ts +30 -2
  85. package/dist/tools/connectors.service.js +98 -3
  86. package/dist/tools/credentials.d.ts +20 -0
  87. package/dist/tools/credentials.js +90 -1
  88. package/dist/tools/oauth/tokens.d.ts +20 -0
  89. package/dist/tools/oauth/tokens.js +49 -24
  90. package/package.json +3 -3
  91. package/src/migrations/0106_setup_guides_v2.sql +10 -0
@@ -1,8 +1,9 @@
1
1
  import type { FastifyReply } from "fastify";
2
2
  import { z } from "zod";
3
+ import { PluginGuides, type RenderedSetupGuide } from "../extensions/plugin-guides.js";
3
4
  import { PluginIcons } from "../extensions/plugin-icons.js";
4
5
  import { type Principal } from "../identity/principal.js";
5
- import { CatalogItemsQuery, CatalogUpdateBody, CatalogUpdatePreviewBody, CatalogDisabledBody as DisabledBody, CatalogGitImportBody as GitBody, CatalogNpmImportBody as NpmBody, CatalogScopeBody as ScopeBody, CatalogScopePreviewBody as ScopePreviewBody, CatalogShownBody as ShownBody } from "./admin-catalog.schemas.js";
6
+ import { AdminCatalogGuideQuery, CatalogItemsQuery, CatalogUpdateBody, CatalogUpdatePreviewBody, CatalogDisabledBody as DisabledBody, CatalogGitImportBody as GitBody, CatalogNpmImportBody as NpmBody, CatalogScopeBody as ScopeBody, CatalogScopePreviewBody as ScopePreviewBody, CatalogShownBody as ShownBody } from "./admin-catalog.schemas.js";
6
7
  import { type CatalogItemDetail, type CatalogItemView, CatalogService, type PermissionsMismatch } from "./catalog.service.js";
7
8
  import { CatalogScopeService, type ScopePreview } from "./catalog-scope.service.js";
8
9
  import { OrgCatalogService } from "./org-catalog.service.js";
@@ -38,7 +39,8 @@ export declare class AdminCatalogController {
38
39
  private readonly scope;
39
40
  private readonly updater;
40
41
  private readonly icons;
41
- constructor(catalog: CatalogService, pluginImport: PluginImport, orgCatalog: OrgCatalogService, scope: CatalogScopeService, updater: PlatformPluginUpdater, icons: PluginIcons);
42
+ private readonly guides;
43
+ constructor(catalog: CatalogService, pluginImport: PluginImport, orgCatalog: OrgCatalogService, scope: CatalogScopeService, updater: PlatformPluginUpdater, icons: PluginIcons, guides: PluginGuides);
42
44
  /**
43
45
  * Prepares the import of an uploaded plugin (an npm-pack tgz as the raw body,
44
46
  * `application/octet-stream`, at most `packages.maxBytes`): reads and checks the whole plugin
@@ -124,6 +126,15 @@ export declare class AdminCatalogController {
124
126
  * `sendCatalogIcon`). No icon -> 404 `catalog.icon_not_found`.
125
127
  */
126
128
  icon(id: string, reply: FastifyReply): Promise<void>;
129
+ /**
130
+ * A setup guide of the item as the platform sees it (spec 2026-11-09-setup-guides-v2-design.md
131
+ * §9.3; the admin console's connector template page): any live item, the admin audience, no org
132
+ * (`orgName` unresolved, reason `no_org`), no target, no checks run. Unknown item -> 404
133
+ * `catalog.item_not_found`, unknown guide -> 404 `catalog.guide_not_found`.
134
+ */
135
+ guide(itemId: string, guideId: string, q: z.infer<typeof AdminCatalogGuideQuery>): Promise<{
136
+ guide: RenderedSetupGuide;
137
+ }>;
127
138
  /**
128
139
  * What a scope change (`{ scope, orgs? }`, `orgs` only with `named`) or a disable
129
140
  * (`{ disabled: true }`) would take away: `{ affected: [{ orgId, orgName, agents,
@@ -10,11 +10,12 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
10
10
  import { Controller, Delete, Get, HttpCode, Inject, Param, Post, Put, Req, Res, } from "@nestjs/common";
11
11
  import { z } from "zod";
12
12
  import { ApiError } from "../errors.js";
13
+ import { PluginGuides } from "../extensions/plugin-guides.js";
13
14
  import { PluginIcons } from "../extensions/plugin-icons.js";
14
15
  import { CurrentPrincipal, patTokenId, requireUser, SuperAdmin, } from "../identity/principal.js";
15
16
  import { createZodDto } from "../infra/validation/create-zod-dto.js";
16
17
  import { ZBody, ZQuery } from "../infra/validation/zod-params.js";
17
- import { CatalogItemsQuery, CatalogUpdateBody, CatalogUpdatePreviewBody, CatalogDisabledBody as DisabledBody, CatalogGitImportBody as GitBody, CatalogHash as Hash, CatalogNpmImportBody as NpmBody, CatalogScopeOrgs as Orgs, CatalogScope as Scope, CatalogScopeBody as ScopeBody, CatalogScopePreviewBody as ScopePreviewBody, CatalogShownBody as ShownBody, } from "./admin-catalog.schemas.js";
18
+ import { AdminCatalogGuideQuery, CatalogItemsQuery, CatalogUpdateBody, CatalogUpdatePreviewBody, CatalogDisabledBody as DisabledBody, CatalogGitImportBody as GitBody, CatalogHash as Hash, CatalogNpmImportBody as NpmBody, CatalogScopeOrgs as Orgs, CatalogScope as Scope, CatalogScopeBody as ScopeBody, CatalogScopePreviewBody as ScopePreviewBody, CatalogShownBody as ShownBody, } from "./admin-catalog.schemas.js";
18
19
  import { CatalogService, } from "./catalog.service.js";
19
20
  import { CatalogScopeService } from "./catalog-scope.service.js";
20
21
  import { sendCatalogIcon } from "./org-catalog.controller.js";
@@ -42,6 +43,8 @@ class CatalogImportConfirmBody extends createZodDto(ConfirmBody, "CatalogImportC
42
43
  }
43
44
  class CatalogGitImportBody extends createZodDto(GitBody, "CatalogGitImportBody") {
44
45
  }
46
+ class AdminCatalogGuideQueryDto extends createZodDto(AdminCatalogGuideQuery, "AdminCatalogGuideQuery") {
47
+ }
45
48
  class CatalogItemsQueryDto extends createZodDto(CatalogItemsQuery, "CatalogItemsQuery") {
46
49
  }
47
50
  class CatalogNpmImportBody extends createZodDto(NpmBody, "CatalogNpmImportBody") {
@@ -75,13 +78,15 @@ let AdminCatalogController = class AdminCatalogController {
75
78
  scope;
76
79
  updater;
77
80
  icons;
78
- constructor(catalog, pluginImport, orgCatalog, scope, updater, icons) {
81
+ guides;
82
+ constructor(catalog, pluginImport, orgCatalog, scope, updater, icons, guides) {
79
83
  this.catalog = catalog;
80
84
  this.pluginImport = pluginImport;
81
85
  this.orgCatalog = orgCatalog;
82
86
  this.scope = scope;
83
87
  this.updater = updater;
84
88
  this.icons = icons;
89
+ this.guides = guides;
85
90
  }
86
91
  /**
87
92
  * Prepares the import of an uploaded plugin (an npm-pack tgz as the raw body,
@@ -189,6 +194,15 @@ let AdminCatalogController = class AdminCatalogController {
189
194
  async icon(id, reply) {
190
195
  await sendCatalogIcon(reply, await this.orgCatalog.icon(id, true));
191
196
  }
197
+ /**
198
+ * A setup guide of the item as the platform sees it (spec 2026-11-09-setup-guides-v2-design.md
199
+ * §9.3; the admin console's connector template page): any live item, the admin audience, no org
200
+ * (`orgName` unresolved, reason `no_org`), no target, no checks run. Unknown item -> 404
201
+ * `catalog.item_not_found`, unknown guide -> 404 `catalog.guide_not_found`.
202
+ */
203
+ async guide(itemId, guideId, q) {
204
+ return { guide: await this.guides.renderForPlatform(itemId, guideId, q) };
205
+ }
192
206
  /**
193
207
  * What a scope change (`{ scope, orgs? }`, `orgs` only with `named`) or a disable
194
208
  * (`{ disabled: true }`) would take away: `{ affected: [{ orgId, orgName, agents,
@@ -331,6 +345,12 @@ __decorate([
331
345
  __param(0, Param("id")),
332
346
  __param(1, Res())
333
347
  ], AdminCatalogController.prototype, "icon", null);
348
+ __decorate([
349
+ Get(":itemId/guides/:guideId"),
350
+ __param(0, Param("itemId")),
351
+ __param(1, Param("guideId")),
352
+ __param(2, ZQuery(AdminCatalogGuideQueryDto))
353
+ ], AdminCatalogController.prototype, "guide", null);
334
354
  __decorate([
335
355
  Post("items/:id/scope/preview"),
336
356
  HttpCode(200),
@@ -395,6 +415,7 @@ AdminCatalogController = __decorate([
395
415
  __param(2, Inject(OrgCatalogService)),
396
416
  __param(3, Inject(CatalogScopeService)),
397
417
  __param(4, Inject(PlatformPluginUpdater)),
398
- __param(5, Inject(PluginIcons))
418
+ __param(5, Inject(PluginIcons)),
419
+ __param(6, Inject(PluginGuides))
399
420
  ], AdminCatalogController);
400
421
  export { AdminCatalogController };
@@ -69,4 +69,14 @@ export declare const CatalogDisabledBody: z.ZodObject<{
69
69
  export declare const CatalogShownBody: z.ZodObject<{
70
70
  shownToOthers: z.ZodBoolean;
71
71
  }, z.core.$strict>;
72
+ /**
73
+ * `GET :itemId/guides/:guideId`, the super admin's preview of a setup guide (spec
74
+ * 2026-11-09-setup-guides-v2-design.md §9.3): only the document language; no org, no target.
75
+ */
76
+ export declare const AdminCatalogGuideQuery: z.ZodObject<{
77
+ locale: z.ZodOptional<z.ZodEnum<{
78
+ en: "en";
79
+ zh: "zh";
80
+ }>>;
81
+ }, z.core.$strict>;
72
82
  export declare const ADMIN_CATALOG_ROUTES: readonly RouteSchemas[];
@@ -52,6 +52,13 @@ export const CatalogDisabledBody = z
52
52
  .object({ disabled: z.boolean(), scopeHash: CatalogHash.optional() })
53
53
  .strict();
54
54
  export const CatalogShownBody = z.object({ shownToOthers: z.boolean() }).strict();
55
+ /**
56
+ * `GET :itemId/guides/:guideId`, the super admin's preview of a setup guide (spec
57
+ * 2026-11-09-setup-guides-v2-design.md §9.3): only the document language; no org, no target.
58
+ */
59
+ export const AdminCatalogGuideQuery = z
60
+ .object({ locale: z.enum(["en", "zh"]).optional() })
61
+ .strict();
55
62
  export const ADMIN_CATALOG_ROUTES = [
56
63
  { route: "GET /api/v1/admin/catalog/items", query: CatalogItemsQuery },
57
64
  { route: "GET /api/v1/admin/catalog/items/:id", params: CatalogIdPathParams },
@@ -72,6 +72,14 @@ export interface OrgCatalogTemplate {
72
72
  editableEndpoint: string[];
73
73
  auth: Record<string, unknown>;
74
74
  ownership: "org" | "personal" | null;
75
+ /**
76
+ * The template's setup guide in the item's latest version (`GET …/catalog/:itemId/guides/
77
+ * :guideId`), null without one (spec 2026-11-09-setup-guides-v2-design.md §9.3).
78
+ */
79
+ guide: {
80
+ itemId: string;
81
+ guideId: string;
82
+ } | null;
75
83
  }
76
84
  /** `GET /orgs/:orgId/catalog/:itemId`: the item and what the install wizard needs (latest version). */
77
85
  export interface OrgCatalogDetail {
@@ -105,7 +113,7 @@ export interface OrgCatalogDetail {
105
113
  * A connector template as an org sees it (the install wizard and the upgrade preview's inputs):
106
114
  * presets and editable paths, no credentials.
107
115
  */
108
- export declare function orgTemplateView(id: string, file: ConnectorTemplateFile): OrgCatalogTemplate;
116
+ export declare function orgTemplateView(id: string, file: ConnectorTemplateFile, guide?: OrgCatalogTemplate["guide"]): OrgCatalogTemplate;
109
117
  /** An item's icon (the latest version's plugin icon). */
110
118
  export interface CatalogIcon {
111
119
  data: Uint8Array;
@@ -16,7 +16,7 @@ import { satisfiesRange } from "./versions.js";
16
16
  * A connector template as an org sees it (the install wizard and the upgrade preview's inputs):
17
17
  * presets and editable paths, no credentials.
18
18
  */
19
- export function orgTemplateView(id, file) {
19
+ export function orgTemplateView(id, file, guide = null) {
20
20
  const t = file.template;
21
21
  return {
22
22
  id,
@@ -29,6 +29,7 @@ export function orgTemplateView(id, file) {
29
29
  auth: t.auth,
30
30
  ownership: t.ownership ??
31
31
  (typeof t.auth.ownership === "string" ? t.auth.ownership : null),
32
+ guide,
32
33
  };
33
34
  }
34
35
  const iconNotFound = () => new ApiError("catalog.icon_not_found", "This catalog item has no icon");
@@ -1,4 +1,4 @@
1
- import type { PluginManifest } from "@capybara-hq/plugin-sdk";
1
+ import { type PluginManifest } from "@capybara-hq/plugin-sdk";
2
2
  import type { KernelSchemaDb } from "../db/kernel-schema.js";
3
3
  type Db = Pick<KernelSchemaDb, "selectFrom" | "insertInto" | "deleteFrom">;
4
4
  /** A stored file as a version row lists it. */
@@ -9,7 +9,8 @@ interface FileMeta {
9
9
  }
10
10
  /**
11
11
  * The display file paths of a plugin (package paths): the plugin icon, the connector templates'
12
- * icons (given by the caller, which parsed the templates) and the setup guide files.
12
+ * icons (given by the caller, which parsed the templates) and the setup guide documents (both
13
+ * languages; the caller keeps the ones the package has).
13
14
  */
14
15
  export declare function displayFilePaths(m: PluginManifest, templateIcons?: string[]): Set<string>;
15
16
  /**
@@ -1,16 +1,18 @@
1
1
  // Catalog display files in PG (`catalog_files`, migration 0100): a plugin's icon, its connector
2
- // templates' icons and its setup guide files. They are small (64 KiB each at most, by the plugin
2
+ // templates' icons and its setup guide documents. They are small (64 KiB each at most, by the plugin
3
3
  // format) and must be readable without object storage: a bootstrap plugin is imported before any
4
4
  // object-store extension runs, and the object-store plugin's own icon can never wait for itself.
5
5
  // Skills, connector templates and agent templates stay in object storage under the version's
6
6
  // `object_prefix` (they are copied into platform components there).
7
7
  import { createHash } from "node:crypto";
8
+ import { setupGuideDocPath } from "@capybara-hq/plugin-sdk";
8
9
  import { sql } from "kysely";
9
10
  import { ApiError } from "../errors.js";
10
11
  import { packagePath } from "../packages/tgz.js";
11
12
  /**
12
13
  * The display file paths of a plugin (package paths): the plugin icon, the connector templates'
13
- * icons (given by the caller, which parsed the templates) and the setup guide files.
14
+ * icons (given by the caller, which parsed the templates) and the setup guide documents (both
15
+ * languages; the caller keeps the ones the package has).
14
16
  */
15
17
  export function displayFilePaths(m, templateIcons = []) {
16
18
  const out = new Set();
@@ -18,9 +20,10 @@ export function displayFilePaths(m, templateIcons = []) {
18
20
  out.add(packagePath(m.icon));
19
21
  for (const icon of templateIcons)
20
22
  out.add(packagePath(icon));
21
- for (const g of m.setupGuides ?? [])
22
- for (const f of g.files ?? [])
23
- out.add(packagePath(f.path));
23
+ for (const g of m.setupGuides ?? []) {
24
+ out.add(setupGuideDocPath(g.id, "en"));
25
+ out.add(setupGuideDocPath(g.id, "zh"));
26
+ }
24
27
  return out;
25
28
  }
26
29
  /**
@@ -1,5 +1,6 @@
1
1
  import type { LocalizedName, ThemeScheme } from "@capybara-hq/plugin-sdk";
2
2
  import { ApiError } from "../errors.js";
3
+ import { PluginGuides } from "../extensions/plugin-guides.js";
3
4
  import { SuperAdminLock } from "../identity/super-admin.js";
4
5
  import { KernelDb } from "../infra/database/kernel-db.js";
5
6
  import { TransactionHost } from "../infra/database/transaction-host.js";
@@ -172,6 +173,14 @@ export interface OrgOfferedComponentView extends OrgComponentView {
172
173
  * name as its owner (本组织 for the viewing org's own, the owner's name for another org's).
173
174
  */
174
175
  owner: Owner;
176
+ /**
177
+ * Connector templates only: the template's setup guide in its plugin's latest version (`GET
178
+ * …/catalog/:itemId/guides/:guideId`), null without one (spec 2026-11-09-setup-guides-v2 §9.3).
179
+ */
180
+ guide?: {
181
+ itemId: string;
182
+ guideId: string;
183
+ } | null;
175
184
  }
176
185
  /** The result of an org switch: the component, or (switching off without `confirm`) the preview. */
177
186
  export type ComponentSwitchResult = {
@@ -231,11 +240,15 @@ export declare class ComponentScopeService {
231
240
  /** Promote on share (ruling P3-3): an org row shown or associated becomes a platform row. */
232
241
  private readonly promotion;
233
242
  readonly txHost: TransactionHost;
243
+ /** The connector templates' setup guides (the org list's `guide`). */
244
+ private readonly guides;
234
245
  constructor(kdb: KernelDb, adminLock: SuperAdminLock,
235
246
  /** The org row lock (orgs/org-lock.ts): joins the caller's transaction. */
236
247
  orgLock: OrgLock,
237
248
  /** Promote on share (ruling P3-3): an org row shown or associated becomes a platform row. */
238
- promotion: ComponentPromotion, txHost: TransactionHost);
249
+ promotion: ComponentPromotion, txHost: TransactionHost,
250
+ /** The connector templates' setup guides (the org list's `guide`). */
251
+ guides: Pick<PluginGuides, "templateGuides">);
239
252
  private get db();
240
253
  /**
241
254
  * What the admin routes act on (ruling P3-23): the platform component `id`, an org row the org
@@ -12,6 +12,7 @@ import { Inject, Injectable } from "@nestjs/common";
12
12
  import { sql } from "kysely";
13
13
  import { templateTypeClashes, templateTypeOf } from "../agents/templates.js";
14
14
  import { ApiError } from "../errors.js";
15
+ import { PluginGuides } from "../extensions/plugin-guides.js";
15
16
  import { audit } from "../identity/audit.js";
16
17
  import { SuperAdminLock } from "../identity/super-admin.js";
17
18
  import { notifyItemShared } from "../inbox/org-notices.js";
@@ -124,16 +125,20 @@ let ComponentScopeService = class ComponentScopeService {
124
125
  orgLock;
125
126
  promotion;
126
127
  txHost;
128
+ guides;
127
129
  constructor(kdb, adminLock,
128
130
  /** The org row lock (orgs/org-lock.ts): joins the caller's transaction. */
129
131
  orgLock,
130
132
  /** Promote on share (ruling P3-3): an org row shown or associated becomes a platform row. */
131
- promotion, txHost) {
133
+ promotion, txHost,
134
+ /** The connector templates' setup guides (the org list's `guide`). */
135
+ guides) {
132
136
  this.kdb = kdb;
133
137
  this.adminLock = adminLock;
134
138
  this.orgLock = orgLock;
135
139
  this.promotion = promotion;
136
140
  this.txHost = txHost;
141
+ this.guides = guides;
137
142
  }
138
143
  get db() {
139
144
  return this.kdb.db;
@@ -245,6 +250,9 @@ let ComponentScopeService = class ComponentScopeService {
245
250
  const st = await loadScopeState(this.db, kind, comps);
246
251
  const linked = await this.linkedCopies(orgId, kind);
247
252
  const owners = await ownersOf(this.db, comps.flatMap((c) => (c.ownerOrgId === null ? [] : [c.ownerOrgId])));
253
+ const guides = kind === "connector_template"
254
+ ? await this.guides.templateGuides(comps.flatMap((c) => c.itemId === null ? [] : [{ itemId: c.itemId, templateId: c.name }]))
255
+ : undefined;
248
256
  const rows = [];
249
257
  for (const c of comps) {
250
258
  const p = pluginFactsOf(st, c.itemId, orgId);
@@ -273,6 +281,7 @@ let ComponentScopeService = class ComponentScopeService {
273
281
  platformDefault: !locked && (c.scopeMode === "follow" ? p?.scope === "all" : c.scope === "all"),
274
282
  linkedIds: linked.get(c.id) ?? [],
275
283
  owner: ownerOf(c.ownerOrgId, owners),
284
+ ...(guides !== undefined && { guide: guides.get(`${c.itemId}/${c.name}`) ?? null }),
276
285
  });
277
286
  }
278
287
  rows.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
@@ -1067,7 +1076,8 @@ ComponentScopeService = __decorate([
1067
1076
  __param(1, Inject(SuperAdminLock)),
1068
1077
  __param(2, Inject(OrgLock)),
1069
1078
  __param(3, Inject(ComponentPromotion)),
1070
- __param(4, Inject(TransactionHost))
1079
+ __param(4, Inject(TransactionHost)),
1080
+ __param(5, Inject(PluginGuides))
1071
1081
  ], ComponentScopeService);
1072
1082
  export { ComponentScopeService };
1073
1083
  /** Whether `c` is the platform default theme `uiTheme` (locked on in every org, themes spec §7). */
@@ -188,7 +188,7 @@ export declare function headsToResolve(entries: readonly MarketplaceEntry[], imp
188
188
  /** The host versions (an entry's `minKernel` is judged against them). */
189
189
  export declare const HOST_VERSIONS: {
190
190
  readonly kernel: string;
191
- readonly sdk: "0.1.0-rc.9";
191
+ readonly sdk: "0.1.0-rc.10";
192
192
  };
193
193
  /** Whether this deployment satisfies the entry's `minKernel`. */
194
194
  export declare const entryCompatible: (e: Pick<MarketplaceEntry, "minKernel">) => boolean;
@@ -98,10 +98,11 @@ export declare class OrgCatalogController {
98
98
  */
99
99
  icon(org: OrgContext, itemId: string, reply: FastifyReply): Promise<void>;
100
100
  /**
101
- * A setup guide of the item, rendered for this org (spec 2026-11-08-plugin-setup-guides-design.md
102
- * §4): steps and template files with the place's variables filled in, the modes and the template
103
- * hash. The item must be one the org's catalog shows; a `channel-connection` guide also needs
104
- * `channels.manage` (checked by the service).
101
+ * A setup guide of the item, rendered for this org (spec 2026-11-09-setup-guides-v2-design.md
102
+ * §7.2): the document's blocks with the place's variables filled in, its modes, audiences and
103
+ * checks, the unresolved variables with their reasons, the drift hash and the target. The item
104
+ * must be one the org's catalog shows; the place and audience's action (`channels.manage`,
105
+ * `connectors.manage`, `connectors.use`) is checked by the service.
105
106
  */
106
107
  guide(org: OrgContext, itemId: string, guideId: string, q: z.infer<typeof CatalogGuideQuery>): Promise<{
107
108
  guide: RenderedSetupGuide;
@@ -129,10 +129,11 @@ let OrgCatalogController = class OrgCatalogController {
129
129
  await sendCatalogIcon(reply, icon);
130
130
  }
131
131
  /**
132
- * A setup guide of the item, rendered for this org (spec 2026-11-08-plugin-setup-guides-design.md
133
- * §4): steps and template files with the place's variables filled in, the modes and the template
134
- * hash. The item must be one the org's catalog shows; a `channel-connection` guide also needs
135
- * `channels.manage` (checked by the service).
132
+ * A setup guide of the item, rendered for this org (spec 2026-11-09-setup-guides-v2-design.md
133
+ * §7.2): the document's blocks with the place's variables filled in, its modes, audiences and
134
+ * checks, the unresolved variables with their reasons, the drift hash and the target. The item
135
+ * must be one the org's catalog shows; the place and audience's action (`channels.manage`,
136
+ * `connectors.manage`, `connectors.use`) is checked by the service.
136
137
  */
137
138
  async guide(org, itemId, guideId, q) {
138
139
  return { guide: await this.guides.render(org, itemId, guideId, q) };
@@ -63,6 +63,11 @@ export declare const OrgPluginSwitchBody: z.ZodObject<{
63
63
  export declare const CatalogItemPathParams: z.ZodObject<{
64
64
  itemId: z.ZodString;
65
65
  }, z.core.$strip>;
66
+ /** `:itemId` / `:guideId` of the guide route (`@Param`). */
67
+ export declare const CatalogGuidePathParams: z.ZodObject<{
68
+ itemId: z.ZodString;
69
+ guideId: z.ZodString;
70
+ }, z.core.$strip>;
66
71
  /** `:installId` as the uninstall route takes it (`@Param`). */
67
72
  export declare const PluginInstallPathParams: z.ZodObject<{
68
73
  installId: z.ZodString;
@@ -77,12 +82,23 @@ export declare const CatalogRequestsListQuery: z.ZodObject<{
77
82
  }>>;
78
83
  }, z.core.$strip>;
79
84
  /**
80
- * `GET catalog/:itemId/guides/:guideId`: the mode to render (the place's modes, checked by the
81
- * service), a channel connection to render for, or the name typed so far on 新建渠道.
85
+ * `GET catalog/:itemId/guides/:guideId` (spec 2026-11-09-setup-guides-v2-design.md §7.2): the mode
86
+ * and audience to render (the place's, checked by the service), the document language, and the
87
+ * target: a channel connection or the name typed so far on 新建渠道 (`channel-connection`), a
88
+ * connector made from the template (`connector-template`).
82
89
  */
83
90
  export declare const CatalogGuideQuery: z.ZodObject<{
84
91
  mode: z.ZodOptional<z.ZodString>;
92
+ audience: z.ZodOptional<z.ZodEnum<{
93
+ admin: "admin";
94
+ member: "member";
95
+ }>>;
96
+ locale: z.ZodOptional<z.ZodEnum<{
97
+ en: "en";
98
+ zh: "zh";
99
+ }>>;
85
100
  connectionId: z.ZodOptional<z.ZodString>;
86
101
  name: z.ZodOptional<z.ZodString>;
102
+ connectorId: z.ZodOptional<z.ZodString>;
87
103
  }, z.core.$strict>;
88
104
  export declare const ORG_CATALOG_ROUTES: readonly RouteSchemas[];
@@ -53,6 +53,10 @@ export const OrgPluginSwitchBody = z
53
53
  .strict();
54
54
  /** `:itemId` as the routes take it (`@Param`). */
55
55
  export const CatalogItemPathParams = z.object({ itemId: z.string().min(1) });
56
+ /** `:itemId` / `:guideId` of the guide route (`@Param`). */
57
+ export const CatalogGuidePathParams = CatalogItemPathParams.extend({
58
+ guideId: z.string().min(1).max(64),
59
+ });
56
60
  /** `:installId` as the uninstall route takes it (`@Param`). */
57
61
  export const PluginInstallPathParams = z.object({ installId: z.string().min(1) });
58
62
  /** The super admin's request list filter. */
@@ -60,19 +64,29 @@ export const CatalogRequestsListQuery = z.object({
60
64
  status: z.enum(["pending", "approved", "rejected", "cancelled"]).optional(),
61
65
  });
62
66
  /**
63
- * `GET catalog/:itemId/guides/:guideId`: the mode to render (the place's modes, checked by the
64
- * service), a channel connection to render for, or the name typed so far on 新建渠道.
67
+ * `GET catalog/:itemId/guides/:guideId` (spec 2026-11-09-setup-guides-v2-design.md §7.2): the mode
68
+ * and audience to render (the place's, checked by the service), the document language, and the
69
+ * target: a channel connection or the name typed so far on 新建渠道 (`channel-connection`), a
70
+ * connector made from the template (`connector-template`).
65
71
  */
66
72
  export const CatalogGuideQuery = z
67
73
  .object({
68
74
  mode: z.string().min(1).max(64).optional(),
75
+ audience: z.enum(["admin", "member"]).optional(),
76
+ locale: z.enum(["en", "zh"]).optional(),
69
77
  connectionId: z.string().min(1).max(100).optional(),
70
78
  name: z.string().max(100).optional(),
79
+ connectorId: z.string().min(1).max(100).optional(),
71
80
  })
72
81
  .strict();
73
82
  export const ORG_CATALOG_ROUTES = [
74
83
  { route: "GET /api/v1/orgs/:orgId/catalog" },
75
84
  { route: "GET /api/v1/orgs/:orgId/catalog/:itemId", params: CatalogItemPathParams },
85
+ {
86
+ route: "GET /api/v1/orgs/:orgId/catalog/:itemId/guides/:guideId",
87
+ params: CatalogGuidePathParams,
88
+ query: CatalogGuideQuery,
89
+ },
76
90
  {
77
91
  route: "POST /api/v1/orgs/:orgId/catalog/:itemId/requests",
78
92
  params: CatalogItemPathParams,
@@ -9,6 +9,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
9
9
  };
10
10
  import { Inject, Injectable } from "@nestjs/common";
11
11
  import { ApiError } from "../errors.js";
12
+ import { templateGuideOf } from "../extensions/plugin-guides.js";
12
13
  import { ExtensionRegistry } from "../extensions/registry.js";
13
14
  import { ExtensionAvailability } from "../extensions/unavailable.js";
14
15
  import { KernelDb } from "../infra/database/kernel-db.js";
@@ -272,6 +273,11 @@ let OrgCatalogService = class OrgCatalogService {
272
273
  throw itemNotFound();
273
274
  const m = v.manifest;
274
275
  const templates = await readCatalogTemplates(this.storage, v);
276
+ // where each template's page finds its setup guide (spec 2026-11-09-setup-guides-v2 §9.3)
277
+ const guideOf = (templateId) => {
278
+ const g = templateGuideOf(m, templateId);
279
+ return g ? { itemId, guideId: g.id } : null;
280
+ };
275
281
  // Providers the install would switch on first (a refusal is the install's to report)
276
282
  const implied = await this.pluginInstaller
277
283
  .impliedProviders(orgId, m, templates, m.dependencies ?? [])
@@ -284,7 +290,7 @@ let OrgCatalogService = class OrgCatalogService {
284
290
  item,
285
291
  hasIcon: m.icon !== undefined,
286
292
  extension: m.extension ? m.name : null,
287
- templates: templates.map((t) => orgTemplateView(t.id, t.file)),
293
+ templates: templates.map((t) => orgTemplateView(t.id, t.file, guideOf(t.id))),
288
294
  dependencies: [
289
295
  ...(m.dependencies ?? []).map((d) => ({ name: d.name, range: d.version })),
290
296
  ...implied.map((d) => ({ name: d.name, range: d.range, implied: true })),
@@ -292,7 +298,7 @@ let OrgCatalogService = class OrgCatalogService {
292
298
  skills: (m.skills ?? []).map((s) => packagePath(s.path)),
293
299
  agents: (m.agents ?? []).map((a) => packagePath(a.path)),
294
300
  missingTemplates: item.installed !== null && item.state === "needs_config"
295
- ? ((await this.pluginInstaller.missingConnectors(orgId, itemId))?.templates ?? []).map((t) => orgTemplateView(t.id, t.file))
301
+ ? ((await this.pluginInstaller.missingConnectors(orgId, itemId))?.templates ?? []).map((t) => orgTemplateView(t.id, t.file, guideOf(t.id)))
296
302
  : [],
297
303
  };
298
304
  }
@@ -1377,7 +1377,7 @@ let PluginImport = class PluginImport {
1377
1377
  if (stored.has(path))
1378
1378
  objectPaths.add(path);
1379
1379
  };
1380
- // The plugin icon and the setup guide templates (rendered by PluginGuides): display files
1380
+ // The plugin icon and the setup guide documents (rendered by PluginGuides): display files
1381
1381
  const displayPaths = displayFilePaths(m, pkg.connectorTemplates.flatMap((c) => (c.file.icon === undefined ? [] : [c.file.icon])));
1382
1382
  for (const path of displayPaths)
1383
1383
  keep(path);
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { AGENT_TEMPLATE_LIMITS, CATALOG_ITEM_NAME_RE, CONNECTOR_TEMPLATE_LIMITS, hostSdkProblem, PLUGIN_MANIFEST_FILE, PLUGIN_MANIFEST_LIMITS, parseAgentTemplate, parseConnectorTemplateFile, parsePluginManifest, parseSkillMd, parseThemeFile, SKILL_ENTRY_FILE, setupGuideFileProblem, THEME_LIMITS, themeIdOf, validatePluginPackageJsonFor, validateThemeAssets, } from "@capybara-hq/plugin-sdk";
2
+ import { AGENT_TEMPLATE_LIMITS, CATALOG_ITEM_NAME_RE, CONNECTOR_TEMPLATE_LIMITS, hostSdkProblem, PLUGIN_MANIFEST_FILE, PLUGIN_MANIFEST_LIMITS, parseAgentTemplate, parseConnectorTemplateFile, parsePluginManifest, parseSkillMd, parseThemeFile, SKILL_ENTRY_FILE, setupGuideDocPath, setupGuideDocProblem, THEME_LIMITS, themeIdOf, validatePluginPackageJsonFor, validateSetupGuide, validateThemeAssets, } from "@capybara-hq/plugin-sdk";
3
3
  import { ApiError } from "../errors.js";
4
4
  import { HOST_ZOD_VERSION, LEGACY_FORMAT_MESSAGE, PackageTgzError, packagePath, parsePackageJsonFile, unpackPackageTgz, } from "../packages/tgz.js";
5
5
  export { LEGACY_FORMAT_MESSAGE, packagePath };
@@ -362,18 +362,46 @@ export function readPluginFiles(files, opts) {
362
362
  issues.push(`icon: ${problem}`);
363
363
  }
364
364
  const ext = manifest.extension;
365
- // Setup guides: their template files are text within the size limit (spec
366
- // 2026-11-08-plugin-setup-guides-design.md §2); a channel guide needs a channel extension, which
367
- // the manifest parser checks when the extension section is there
365
+ // Setup guides v2 (spec 2026-11-09-setup-guides-v2-design.md §4.5): the documents are text
366
+ // within the limits and pass the SDK validator (an unknown variable is accepted: it renders as
367
+ // written); a channel guide needs a channel extension, which the manifest parser checks when the
368
+ // extension section is there; a template guide names one of the plugin's connector templates
369
+ const templateIds = new Set(connectorTemplates.map((t) => t.id));
368
370
  manifest.setupGuides?.forEach((g, i) => {
371
+ const at = `setupGuides.${i} (${g.id})`;
369
372
  if (g.place === "channel-connection" && ext === undefined) {
370
- issues.push(`setupGuides.${i}: a channel-connection guide needs the plugin's extension to provide channel@1`);
373
+ issues.push(`${at}: a channel-connection guide needs the plugin's extension to provide channel@1`);
371
374
  }
372
- g.files?.forEach((f, j) => {
373
- const problem = setupGuideFileProblem(f.path, files.get(packagePath(f.path)));
375
+ // a listed template is checked by the manifest parser; scanned ones only here
376
+ if (g.template !== undefined &&
377
+ manifest.connectorTemplates === undefined &&
378
+ !templateIds.has(g.template)) {
379
+ issues.push(`${at}: template ${g.template} is not one of the plugin's connector templates`);
380
+ }
381
+ const docs = {};
382
+ for (const locale of ["en", "zh"]) {
383
+ const path = setupGuideDocPath(g.id, locale);
384
+ const data = files.get(path);
385
+ if (locale === "zh" && data === undefined)
386
+ continue;
387
+ const problem = setupGuideDocProblem(path, data);
374
388
  if (problem !== undefined)
375
- issues.push(`setupGuides.${i}.files.${j}: ${problem}`);
376
- });
389
+ issues.push(`${at}: ${problem}`);
390
+ else
391
+ docs[locale] = new TextDecoder().decode(data);
392
+ }
393
+ if (docs.en === undefined)
394
+ return;
395
+ for (const issue of validateSetupGuide({
396
+ place: g.place,
397
+ en: docs.en,
398
+ ...(docs.zh !== undefined && { zh: docs.zh }),
399
+ id: g.id,
400
+ allowUnknownVariables: true,
401
+ })) {
402
+ if (issue.level === "error")
403
+ issues.push(`${at}: ${issue.message}`);
404
+ }
377
405
  });
378
406
  if (ext !== undefined) {
379
407
  if (!files.has(packagePath(ext.entry))) {
@@ -1,6 +1,7 @@
1
1
  import type { PluginManifest } from "@capybara-hq/plugin-sdk";
2
2
  import { ExtensionAdminOps } from "../extensions/admin-ops.js";
3
3
  import { ExtensionBlobs } from "../extensions/blobs.js";
4
+ import { PluginGuides, type TemplateGuideChange } from "../extensions/plugin-guides.js";
4
5
  import { ExtensionRegistry } from "../extensions/registry.js";
5
6
  import type { SingleProviderNote } from "../extensions/single-provider.js";
6
7
  import { SuperAdminLock } from "../identity/super-admin.js";
@@ -110,6 +111,13 @@ export interface PlatformUpdatePreview {
110
111
  needsApproval: boolean;
111
112
  } | null;
112
113
  connectorOrgs: PlatformUpdateConnectorOrg[];
114
+ /**
115
+ * Connector templates whose setup guide the update changes (code blocks or checks of the admin
116
+ * text): "the setup guide of template X changed; org admins of N orgs will be asked to update
117
+ * their app" (spec 2026-11-09-setup-guides-v2-design.md §11). Counts only. Not covered by
118
+ * `diffHash` (nothing the confirm does depends on it).
119
+ */
120
+ guideChanges: TemplateGuideChange[];
113
121
  /**
114
122
  * The themes the update removes that are on in some org (themes spec §6): the orgs whose default
115
123
  * each is and how many people picked it. Absent when no such theme is removed.
@@ -211,14 +219,20 @@ export declare class PlatformPluginUpdater {
211
219
  private readonly connectors;
212
220
  private readonly sync;
213
221
  private readonly log;
222
+ /** The connector template guides an update changes (the preview's `guideChanges`). */
223
+ private readonly guides;
214
224
  constructor(kdb: KernelDb, txHost: TransactionHost, adminLock: SuperAdminLock, itemLock: CatalogItemLock, orgLock: OrgLock, locks: LockService, storage: StorageService, registry: ExtensionRegistry, ops: ExtensionAdminOps,
215
225
  /** Reads the target's extension package (its permissions, for `needsApproval`). */
216
226
  blobs: ExtensionBlobs, config: KernelConfigService, platform: PlatformComponents,
217
227
  /** Template dependencies (the deployed connector kinds). */
218
- installer: PluginInstaller, connectors: ConnectorsService, sync: PluginScopeSync, log: KernelLogger);
228
+ installer: PluginInstaller, connectors: ConnectorsService, sync: PluginScopeSync, log: KernelLogger,
229
+ /** The connector template guides an update changes (the preview's `guideChanges`). */
230
+ guides: Pick<PluginGuides, "templateGuideChanges">);
219
231
  private get db();
220
232
  /** The update preview (see the class comment); writes nothing. */
221
233
  preview(itemId: string, version: string): Promise<PlatformUpdatePreview>;
234
+ /** The preview of a diff with the template guides it changes. */
235
+ private withGuideChanges;
222
236
  /**
223
237
  * The update preview of a new version of an org's own plugin (ruling P3-12): the item must be
224
238
  * owned by `orgId` (else 404, as for any item the org cannot see), the target carries no