@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
@@ -0,0 +1,18 @@
1
+ import type { z } from "zod";
2
+ import { type Principal } from "../identity/principal.js";
3
+ import { type OrgContext } from "../orgs/org.guard.js";
4
+ import { SetupGuidesQuery } from "./setup-guides.schemas.js";
5
+ import { type SetupGuideListEntry, SetupGuidesService } from "./setup-guides.service.js";
6
+ /**
7
+ * The org's setup guides as the caller sees them (spec 2026-11-09-setup-guides-v2-design.md §12):
8
+ * per guide its targets, their drift, a summary of the cached check states and the web page of
9
+ * each. `catalog.read` here; which guides and targets a role sees is the service's (the guide
10
+ * route's place and audience actions).
11
+ */
12
+ export declare class SetupGuidesController {
13
+ private readonly guides;
14
+ constructor(guides: SetupGuidesService);
15
+ list(principal: Principal, org: OrgContext, q: z.infer<typeof SetupGuidesQuery>): Promise<{
16
+ guides: SetupGuideListEntry[];
17
+ }>;
18
+ }
@@ -0,0 +1,48 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { Controller, Get, Inject } from "@nestjs/common";
11
+ import { CurrentPrincipal, requireUser } from "../identity/principal.js";
12
+ import { createZodDto } from "../infra/validation/create-zod-dto.js";
13
+ import { ZQuery } from "../infra/validation/zod-params.js";
14
+ import { CurrentOrg, OrgAction } from "../orgs/org.guard.js";
15
+ import { SetupGuidesQuery } from "./setup-guides.schemas.js";
16
+ import { SetupGuidesService } from "./setup-guides.service.js";
17
+ class SetupGuidesQueryDto extends createZodDto(SetupGuidesQuery, "SetupGuidesQuery") {
18
+ }
19
+ /**
20
+ * The org's setup guides as the caller sees them (spec 2026-11-09-setup-guides-v2-design.md §12):
21
+ * per guide its targets, their drift, a summary of the cached check states and the web page of
22
+ * each. `catalog.read` here; which guides and targets a role sees is the service's (the guide
23
+ * route's place and audience actions).
24
+ */
25
+ let SetupGuidesController = class SetupGuidesController {
26
+ guides;
27
+ constructor(guides) {
28
+ this.guides = guides;
29
+ }
30
+ async list(principal, org, q) {
31
+ const p = requireUser(principal);
32
+ return {
33
+ guides: await this.guides.list({ orgId: org.orgId, role: org.role, accountId: p.account.id }, q),
34
+ };
35
+ }
36
+ };
37
+ __decorate([
38
+ Get(),
39
+ OrgAction("catalog.read"),
40
+ __param(0, CurrentPrincipal()),
41
+ __param(1, CurrentOrg()),
42
+ __param(2, ZQuery(SetupGuidesQueryDto))
43
+ ], SetupGuidesController.prototype, "list", null);
44
+ SetupGuidesController = __decorate([
45
+ Controller("api/v1/orgs/:orgId/setup-guides"),
46
+ __param(0, Inject(SetupGuidesService))
47
+ ], SetupGuidesController);
48
+ export { SetupGuidesController };
@@ -0,0 +1,10 @@
1
+ import { z } from "zod";
2
+ import type { RouteSchemas } from "../platform/route-schemas.js";
3
+ /** `GET /orgs/:orgId/setup-guides`: the language of the guide titles (zh falls back to en). */
4
+ export declare const SetupGuidesQuery: z.ZodObject<{
5
+ locale: z.ZodOptional<z.ZodEnum<{
6
+ en: "en";
7
+ zh: "zh";
8
+ }>>;
9
+ }, z.core.$strict>;
10
+ export declare const SETUP_GUIDE_ROUTES: readonly RouteSchemas[];
@@ -0,0 +1,6 @@
1
+ import { z } from "zod";
2
+ /** `GET /orgs/:orgId/setup-guides`: the language of the guide titles (zh falls back to en). */
3
+ export const SetupGuidesQuery = z.object({ locale: z.enum(["en", "zh"]).optional() }).strict();
4
+ export const SETUP_GUIDE_ROUTES = [
5
+ { route: "GET /api/v1/orgs/:orgId/setup-guides", query: SetupGuidesQuery },
6
+ ];
@@ -0,0 +1,126 @@
1
+ import type { SetupGuideAudience, SetupGuideBlock, SetupGuidePlace } from "@capybara-hq/plugin-sdk";
2
+ import { ComponentScopeService } from "../catalog/component-scope.service.js";
3
+ import { type GuideQuery, PluginGuides, type UnresolvedReason } from "../extensions/plugin-guides.js";
4
+ import { KernelDb } from "../infra/database/kernel-db.js";
5
+ import type { KernelLogger } from "../log.js";
6
+ import { type CheckCaller, SetupChecksService } from "./setup-checks.service.js";
7
+ /** The cached check states of one target, counted (`running` while a run holds the lock). */
8
+ export interface SetupCheckSummary {
9
+ ok: number;
10
+ failing: number;
11
+ unknown: number;
12
+ running: number;
13
+ notRun: number;
14
+ }
15
+ /** Something a guide is for: a channel connection, a connector, or a template without one yet. */
16
+ export interface SetupGuideTarget {
17
+ kind: "channel-connection" | "connector" | "connector-template";
18
+ /** The connection or connector id; the template id for `connector-template`. */
19
+ id: string;
20
+ name: string;
21
+ /** The guide changed since the target was set up (its ack): the versions; null otherwise. */
22
+ drift: {
23
+ from: string;
24
+ to: string;
25
+ } | null;
26
+ /** Null when there is nothing to check yet (a template without a connector) or it is unreadable. */
27
+ checks: SetupCheckSummary | null;
28
+ /** The web page where the person does this setup (enters values, acks the guide). */
29
+ webPath: string;
30
+ }
31
+ /** One guide the caller may see in the org, with its targets (`GET /orgs/:orgId/setup-guides`). */
32
+ export interface SetupGuideListEntry {
33
+ itemId: string;
34
+ plugin: string;
35
+ guideId: string;
36
+ place: SetupGuidePlace;
37
+ /** The connector template of a `connector-template` guide; null for other places. */
38
+ template: string | null;
39
+ /** The section rendered: org admins get the admin guides, members their member sections. */
40
+ audience: SetupGuideAudience;
41
+ title: string;
42
+ targets: SetupGuideTarget[];
43
+ }
44
+ /** A rendered guide as one Markdown document, for an agent assisting the person (`setup.read`). */
45
+ export interface SetupGuideDocument {
46
+ itemId: string;
47
+ plugin: string;
48
+ guideId: string;
49
+ place: SetupGuidePlace;
50
+ title: string;
51
+ /** Blocks joined; checks as `- [check <id>] <label>` lines. */
52
+ markdown: string;
53
+ modes: string[];
54
+ mode: string | null;
55
+ audiences: SetupGuideAudience[];
56
+ audience: SetupGuideAudience;
57
+ checks: {
58
+ id: string;
59
+ label: string;
60
+ }[];
61
+ unresolved: {
62
+ name: string;
63
+ reason: UnresolvedReason;
64
+ }[];
65
+ target: {
66
+ kind: "channel-connection" | "connector";
67
+ id: string;
68
+ } | null;
69
+ webPath: string;
70
+ /** What stays with the person, and where they do it. */
71
+ handover: string;
72
+ }
73
+ /** The web page of a target (`apps/web` routes; the guide sections live there). */
74
+ export declare const setupWebPath: {
75
+ connection: (id: string) => string;
76
+ newConnection: () => string;
77
+ connector: (id: string) => string;
78
+ /** A member's connector section: the connector's row on their connections page (§9.3). */
79
+ memberConnections: () => string;
80
+ template: (itemId: string) => string;
81
+ };
82
+ /** The blocks of a rendered guide as one Markdown document (checks as `- [check <id>]` lines). */
83
+ export declare function guideMarkdown(title: string, blocks: readonly SetupGuideBlock[]): string;
84
+ /**
85
+ * The setup guides of an org as a person sees them (spec 2026-11-09-setup-guides-v2-design.md §12:
86
+ * `GET /orgs/:orgId/setup-guides`, the `Platform` tool's `setup.guides` and `setup.read`). Nothing
87
+ * new is decided here: guides, visibility, drift and actions are `PluginGuides`' (the guide route's
88
+ * rules), check states are `SetupChecksService.states` (cached, nothing runs), the templates on in
89
+ * the org are `ComponentScopeService.orgList`'s. Rendered text carries no secret (§6.3) and check
90
+ * states only their fixed, redacted fields (§10.8). Stateless: everything comes from PG and Redis.
91
+ */
92
+ export declare class SetupGuidesService {
93
+ private readonly db;
94
+ private readonly guides;
95
+ private readonly checks;
96
+ private readonly components;
97
+ private readonly log;
98
+ constructor(db: KernelDb, guides: Pick<PluginGuides, "render" | "connectionStates" | "connectorStates">, checks: Pick<SetupChecksService, "states">, components: Pick<ComponentScopeService, "orgList">, log: Pick<KernelLogger, "warn">);
99
+ /**
100
+ * The guides the caller may see, each with its targets: org admins (`channels.manage`) the
101
+ * channel connections; org admins (`connectors.manage`) the connectors made from templates and
102
+ * the templates on in the org without a connector, admin audience; members (`connectors.use`
103
+ * without `connectors.manage`) the connectors whose template guide has a member section, member
104
+ * audience, without drift (only an admin acks). Each target carries its cached check summary.
105
+ * A guide that cannot be rendered (gone, a stored document unreadable) is left out and logged.
106
+ */
107
+ list(caller: CheckCaller, q?: {
108
+ locale?: "en" | "zh" | undefined;
109
+ }): Promise<SetupGuideListEntry[]>;
110
+ /** The cached check summary of a target, or null when the guide cannot be read for it. */
111
+ private summary;
112
+ private renderOrSkip;
113
+ /** The org's channel connections whose plugin has a guide the org sees. */
114
+ private connections;
115
+ /**
116
+ * The org's connectors made from a template with a guide; for org admins also the templates on
117
+ * in the org that have a guide and no connector yet.
118
+ */
119
+ private connectors;
120
+ /**
121
+ * One guide rendered for the caller as a Markdown document (`setup.read`): the guide route's
122
+ * rendering (`PluginGuides.render`, its visibility, action and target rules), joined, with the
123
+ * page where the person enters values and acks.
124
+ */
125
+ read(caller: CheckCaller, itemId: string, guideId: string, q: GuideQuery): Promise<SetupGuideDocument>;
126
+ }
@@ -0,0 +1,315 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { Inject, Injectable } from "@nestjs/common";
11
+ import { ComponentScopeService } from "../catalog/component-scope.service.js";
12
+ import { ApiError } from "../errors.js";
13
+ import { PluginGuides, } from "../extensions/plugin-guides.js";
14
+ import { KernelDb } from "../infra/database/kernel-db.js";
15
+ import { KERNEL_LOGGER } from "../infra/tokens.js";
16
+ import { can } from "../orgs/roles.js";
17
+ import { SetupChecksService } from "./setup-checks.service.js";
18
+ const enc = encodeURIComponent;
19
+ /** The web page of a target (`apps/web` routes; the guide sections live there). */
20
+ export const setupWebPath = {
21
+ connection: (id) => `/channels/${enc(id)}`,
22
+ newConnection: () => "/channels/new",
23
+ connector: (id) => `/connectors/${enc(id)}?tab=guide`,
24
+ /** A member's connector section: the connector's row on their connections page (§9.3). */
25
+ memberConnections: () => "/account/connections",
26
+ template: (itemId) => `/connectors/install?template=${enc(itemId)}`,
27
+ };
28
+ /**
29
+ * The page a rendered guide belongs on: a member's connections page for the member audience, else
30
+ * its target's, else where a target is created.
31
+ */
32
+ function webPathOf(g) {
33
+ if (g.audience === "member")
34
+ return setupWebPath.memberConnections();
35
+ if (g.target?.kind === "channel-connection")
36
+ return setupWebPath.connection(g.target.id);
37
+ if (g.target?.kind === "connector")
38
+ return setupWebPath.connector(g.target.id);
39
+ return g.place === "channel-connection"
40
+ ? setupWebPath.newConnection()
41
+ : setupWebPath.template(g.itemId);
42
+ }
43
+ /** A fence longer than any run of backticks in the text. */
44
+ function fenceFor(text) {
45
+ const longest = Math.max(0, ...[...text.matchAll(/`+/g)].map((m) => m[0].length));
46
+ return "`".repeat(Math.max(3, longest + 1));
47
+ }
48
+ /** The blocks of a rendered guide as one Markdown document (checks as `- [check <id>]` lines). */
49
+ export function guideMarkdown(title, blocks) {
50
+ const parts = [`# ${title}`];
51
+ for (const b of blocks) {
52
+ if (b.kind === "markdown")
53
+ parts.push(b.text.trimEnd());
54
+ else if (b.kind === "check")
55
+ parts.push(`- [check ${b.id}] ${b.label}`);
56
+ else {
57
+ const fence = fenceFor(b.text);
58
+ const code = `${fence}${b.language}\n${b.text.replace(/\n$/, "")}\n${fence}`;
59
+ parts.push(b.title === null ? code : `**${b.title}**\n\n${code}`);
60
+ }
61
+ }
62
+ return `${parts.join("\n\n")}\n`;
63
+ }
64
+ function summarize(states) {
65
+ const out = { ok: 0, failing: 0, unknown: 0, running: 0, notRun: 0 };
66
+ for (const s of Object.values(states)) {
67
+ if (s.status === "ok")
68
+ out.ok += 1;
69
+ else if (s.status === "failing")
70
+ out.failing += 1;
71
+ else if (s.status === "running")
72
+ out.running += 1;
73
+ else if (s.status === "not_run")
74
+ out.notRun += 1;
75
+ else
76
+ out.unknown += 1;
77
+ }
78
+ return out;
79
+ }
80
+ /**
81
+ * The setup guides of an org as a person sees them (spec 2026-11-09-setup-guides-v2-design.md §12:
82
+ * `GET /orgs/:orgId/setup-guides`, the `Platform` tool's `setup.guides` and `setup.read`). Nothing
83
+ * new is decided here: guides, visibility, drift and actions are `PluginGuides`' (the guide route's
84
+ * rules), check states are `SetupChecksService.states` (cached, nothing runs), the templates on in
85
+ * the org are `ComponentScopeService.orgList`'s. Rendered text carries no secret (§6.3) and check
86
+ * states only their fixed, redacted fields (§10.8). Stateless: everything comes from PG and Redis.
87
+ */
88
+ let SetupGuidesService = class SetupGuidesService {
89
+ db;
90
+ guides;
91
+ checks;
92
+ components;
93
+ log;
94
+ constructor(db, guides, checks, components, log) {
95
+ this.db = db;
96
+ this.guides = guides;
97
+ this.checks = checks;
98
+ this.components = components;
99
+ this.log = log;
100
+ }
101
+ /**
102
+ * The guides the caller may see, each with its targets: org admins (`channels.manage`) the
103
+ * channel connections; org admins (`connectors.manage`) the connectors made from templates and
104
+ * the templates on in the org without a connector, admin audience; members (`connectors.use`
105
+ * without `connectors.manage`) the connectors whose template guide has a member section, member
106
+ * audience, without drift (only an admin acks). Each target carries its cached check summary.
107
+ * A guide that cannot be rendered (gone, a stored document unreadable) is left out and logged.
108
+ */
109
+ async list(caller, q = {}) {
110
+ const org = { orgId: caller.orgId, role: caller.role };
111
+ const pending = [
112
+ ...(can(caller.role, "channels.manage") ? await this.connections(org) : []),
113
+ ...(await this.connectors(org)),
114
+ ];
115
+ const entries = new Map();
116
+ for (const p of pending) {
117
+ const key = `${p.itemId}/${p.guideId}/${p.audience}`;
118
+ let entry = entries.get(key);
119
+ if (entry === undefined) {
120
+ const g = await this.renderOrSkip(org, p.itemId, p.guideId, {
121
+ audience: p.audience,
122
+ locale: q.locale ?? "en",
123
+ });
124
+ // members get a guide only when it has a member section (the web's How to connect rule, §9.3);
125
+ // text outside every section applies to all audiences, so admins need no section
126
+ if (g === null || (p.audience === "member" && !g.audiences.includes("member")))
127
+ continue;
128
+ entry = {
129
+ itemId: g.itemId,
130
+ plugin: g.plugin,
131
+ guideId: g.id,
132
+ place: g.place,
133
+ template: g.template,
134
+ audience: p.audience,
135
+ title: g.title,
136
+ targets: [],
137
+ };
138
+ entries.set(key, entry);
139
+ }
140
+ const { checked, ...target } = p.target;
141
+ entry.targets.push({
142
+ ...target,
143
+ checks: checked ? await this.summary(caller, p) : null,
144
+ });
145
+ }
146
+ return [...entries.values()];
147
+ }
148
+ /** The cached check summary of a target, or null when the guide cannot be read for it. */
149
+ async summary(caller, p) {
150
+ try {
151
+ return summarize(await this.checks.states(caller, p.itemId, p.guideId, {
152
+ audience: p.audience,
153
+ connectionId: p.query.connectionId,
154
+ connectorId: p.query.connectorId,
155
+ }));
156
+ }
157
+ catch (err) {
158
+ if (err instanceof ApiError)
159
+ return null;
160
+ throw err;
161
+ }
162
+ }
163
+ async renderOrSkip(org, itemId, guideId, q) {
164
+ try {
165
+ return await this.guides.render(org, itemId, guideId, q);
166
+ }
167
+ catch (err) {
168
+ if (!(err instanceof ApiError))
169
+ throw err;
170
+ this.log.warn("setup guide left out of the list", { itemId, guideId, code: err.code });
171
+ return null;
172
+ }
173
+ }
174
+ /** The org's channel connections whose plugin has a guide the org sees. */
175
+ async connections(org) {
176
+ const rows = await this.db
177
+ .selectFrom("channel_connections")
178
+ .select(["id", "plugin", "name", "guide_ack"])
179
+ .where("org_id", "=", org.orgId)
180
+ .where("deleted_at", "is", null)
181
+ .orderBy("created_at")
182
+ .orderBy("id")
183
+ .execute();
184
+ const states = await this.guides.connectionStates(rows.map((r) => ({ id: r.id, plugin: r.plugin, guideAck: r.guide_ack ?? null })), org);
185
+ return rows.flatMap((r) => pendingOf(states.get(r.id), "admin", {
186
+ kind: "channel-connection",
187
+ id: r.id,
188
+ name: r.name,
189
+ query: { connectionId: r.id },
190
+ webPath: setupWebPath.connection(r.id),
191
+ }));
192
+ }
193
+ /**
194
+ * The org's connectors made from a template with a guide; for org admins also the templates on
195
+ * in the org that have a guide and no connector yet.
196
+ */
197
+ async connectors(org) {
198
+ const admin = can(org.role, "connectors.manage");
199
+ if (!admin && !can(org.role, "connectors.use"))
200
+ return [];
201
+ const audience = admin ? "admin" : "member";
202
+ const rows = await this.db
203
+ .selectFrom("connectors as c")
204
+ .leftJoin("platform_connector_templates as t", "t.id", "c.platform_template_id")
205
+ .select([
206
+ "c.id",
207
+ "c.name",
208
+ "c.guide_ack",
209
+ "c.platform_template_id",
210
+ "t.catalog_item_id",
211
+ "t.template_id",
212
+ ])
213
+ .where("c.org_id", "=", org.orgId)
214
+ .where("c.deleted_at", "is", null)
215
+ .orderBy("c.created_at")
216
+ .orderBy("c.id")
217
+ .execute();
218
+ const states = await this.guides.connectorStates(rows.map((r) => ({
219
+ id: r.id,
220
+ itemId: r.catalog_item_id ?? null,
221
+ templateId: r.template_id ?? null,
222
+ guideAck: r.guide_ack ?? null,
223
+ })), org);
224
+ const out = rows.flatMap((r) => {
225
+ const state = states.get(r.id);
226
+ return pendingOf(state && (admin ? state : { ...state, guideDrift: null }), audience, {
227
+ kind: "connector",
228
+ id: r.id,
229
+ name: r.name,
230
+ query: { connectorId: r.id },
231
+ webPath: admin ? setupWebPath.connector(r.id) : setupWebPath.memberConnections(),
232
+ });
233
+ });
234
+ if (!admin)
235
+ return out;
236
+ const made = new Set(rows.flatMap((r) => (r.platform_template_id ? [r.platform_template_id] : [])));
237
+ const templates = await this.components.orgList(org.orgId, { kind: "connector_template" });
238
+ for (const t of templates) {
239
+ if (!t.on || made.has(t.id) || !t.guide || t.itemId === null)
240
+ continue;
241
+ out.push({
242
+ itemId: t.guide.itemId,
243
+ guideId: t.guide.guideId,
244
+ audience,
245
+ query: {},
246
+ target: {
247
+ kind: "connector-template",
248
+ id: t.name,
249
+ name: t.displayName?.en ?? t.name,
250
+ drift: null,
251
+ webPath: setupWebPath.template(t.itemId),
252
+ checked: false,
253
+ },
254
+ });
255
+ }
256
+ return out;
257
+ }
258
+ /**
259
+ * One guide rendered for the caller as a Markdown document (`setup.read`): the guide route's
260
+ * rendering (`PluginGuides.render`, its visibility, action and target rules), joined, with the
261
+ * page where the person enters values and acks.
262
+ */
263
+ async read(caller, itemId, guideId, q) {
264
+ const g = await this.guides.render({ orgId: caller.orgId, role: caller.role }, itemId, guideId, q);
265
+ const webPath = webPathOf(g);
266
+ return {
267
+ itemId: g.itemId,
268
+ plugin: g.plugin,
269
+ guideId: g.id,
270
+ place: g.place,
271
+ title: g.title,
272
+ markdown: guideMarkdown(g.title, g.blocks),
273
+ modes: g.modes,
274
+ mode: g.mode,
275
+ audiences: g.audiences,
276
+ audience: g.audience,
277
+ checks: g.checks.map((c) => ({ id: c.id, label: c.label })),
278
+ unresolved: g.unresolved,
279
+ target: g.target,
280
+ webPath,
281
+ handover: `Secrets (tokens, signing secrets, client secrets) are typed by the person into their fields on ${webPath}; never ask for one in the conversation. Connecting a personal account and confirming the app was updated (the guide acknowledgement) also happen on that page.`,
282
+ };
283
+ }
284
+ };
285
+ SetupGuidesService = __decorate([
286
+ Injectable(),
287
+ __param(0, Inject(KernelDb)),
288
+ __param(1, Inject(PluginGuides)),
289
+ __param(2, Inject(SetupChecksService)),
290
+ __param(3, Inject(ComponentScopeService)),
291
+ __param(4, Inject(KERNEL_LOGGER))
292
+ ], SetupGuidesService);
293
+ export { SetupGuidesService };
294
+ /** A pending target from a guide state; none when the target has no guide the org sees. */
295
+ function pendingOf(state, audience, t) {
296
+ const ref = state?.setupGuide;
297
+ if (!ref)
298
+ return [];
299
+ return [
300
+ {
301
+ itemId: ref.itemId,
302
+ guideId: ref.guideId,
303
+ audience,
304
+ query: t.query,
305
+ target: {
306
+ kind: t.kind,
307
+ id: t.id,
308
+ name: t.name,
309
+ drift: state.guideDrift,
310
+ webPath: t.webPath,
311
+ checked: true,
312
+ },
313
+ },
314
+ ];
315
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Setup guide checks (spec 2026-11-09-setup-guides-v2-design.md §10): the registry of kernel and
3
+ * extension checks, the kernel's own `capybara:*` checks (registered on init) and the service that runs them (cache, single flight, cooldown, the org's
4
+ * concurrency cap, the time limit, redaction), and the org's guide list (`SetupGuidesService`). Its
5
+ * controllers are the api role's; the `Platform` tool's `setup` operations use the same services.
6
+ */
7
+ export declare class SetupModule {
8
+ }
@@ -0,0 +1,31 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Module } from "@nestjs/common";
8
+ import { CatalogModule } from "../catalog/catalog.module.js";
9
+ import { ChannelsModule } from "../channels/channels.module.js";
10
+ import { ExtensionsModule } from "../extensions/extensions.module.js";
11
+ import { ToolsModule } from "../tools/tools.module.js";
12
+ import { KernelChecks } from "./kernel-checks.js";
13
+ import { SetupCheckRegistry } from "./setup-check-registry.js";
14
+ import { SetupChecksService } from "./setup-checks.service.js";
15
+ import { SetupGuidesService } from "./setup-guides.service.js";
16
+ /**
17
+ * Setup guide checks (spec 2026-11-09-setup-guides-v2-design.md §10): the registry of kernel and
18
+ * extension checks, the kernel's own `capybara:*` checks (registered on init) and the service that runs them (cache, single flight, cooldown, the org's
19
+ * concurrency cap, the time limit, redaction), and the org's guide list (`SetupGuidesService`). Its
20
+ * controllers are the api role's; the `Platform` tool's `setup` operations use the same services.
21
+ */
22
+ let SetupModule = class SetupModule {
23
+ };
24
+ SetupModule = __decorate([
25
+ Module({
26
+ imports: [ExtensionsModule, ChannelsModule, ToolsModule, CatalogModule],
27
+ providers: [SetupCheckRegistry, SetupChecksService, SetupGuidesService, KernelChecks],
28
+ exports: [SetupCheckRegistry, SetupChecksService, SetupGuidesService],
29
+ })
30
+ ], SetupModule);
31
+ export { SetupModule };
@@ -1,9 +1,10 @@
1
1
  import { z } from "zod";
2
+ import type { ConnectionGuideState } from "../extensions/plugin-guides.js";
2
3
  import { PluginIcons } from "../extensions/plugin-icons.js";
3
4
  import { type Principal } from "../identity/principal.js";
4
5
  import { type OrgContext } from "../orgs/org.guard.js";
5
6
  import { ConnectorAccountPolicies, type MemberToolPolicyView } from "./connector-account-policies.js";
6
- import { CredentialValue, MemberToolPoliciesBody } from "./connectors.schemas.js";
7
+ import { ConnectorGuideAckSchema, CredentialValue, MemberToolPoliciesBody } from "./connectors.schemas.js";
7
8
  import { type ConnectorDetail, type ConnectorPreview, type ConnectorSummary, ConnectorsService, type ToolIssue } from "./connectors.service.js";
8
9
  declare const CreateBody: z.ZodObject<{
9
10
  name: z.ZodString;
@@ -169,6 +170,14 @@ declare const UpdateBody: z.ZodObject<{
169
170
  }, z.core.$strict>>>;
170
171
  allowUnannotated: z.ZodOptional<z.ZodBoolean>;
171
172
  }, z.core.$strict>;
173
+ /**
174
+ * A connector as the org's pages show it: with its plugin's icon (`icon`, see `withIcons`), its
175
+ * template's setup guide and the drift since it was set up (`setupGuide`, `guideDrift`; spec
176
+ * 2026-11-09-setup-guides-v2-design.md §9.3, §11).
177
+ */
178
+ export type ConnectorPage<T extends ConnectorSummary = ConnectorDetail> = T & ConnectionGuideState & {
179
+ icon: string | null;
180
+ };
172
181
  /**
173
182
  * 连接器(`/api/v1/orgs/:orgId/connectors`):读与个人凭据用 `connectors.use`(访客起),安装 / 修改 / 删除 / 刷新 /
174
183
  * 组织凭据用 `connectors.manage`(管理员起);两者都仅限用户主体。
@@ -185,10 +194,11 @@ export declare class ConnectorsController {
185
194
  */
186
195
  private withIcons;
187
196
  list(org: OrgContext): Promise<{
188
- connectors: (ConnectorSummary & {
189
- icon: string | null;
190
- })[];
197
+ connectors: ConnectorPage<ConnectorSummary>[];
191
198
  }>;
199
+ /** Adds the icon and the setup guide fields to connector views (one lookup each for all). */
200
+ private withGuides;
201
+ private withGuide;
192
202
  /**
193
203
  * The connector kinds the org can create connectors of, each with the plugin providing it (the
194
204
  * install wizard offers only these; an empty list means no connector extension is enabled).
@@ -200,7 +210,7 @@ export declare class ConnectorsController {
200
210
  }[];
201
211
  }>;
202
212
  create(principal: Principal, org: OrgContext, body: z.infer<typeof CreateBody>): Promise<{
203
- connector: ConnectorDetail;
213
+ connector: ConnectorPage;
204
214
  issues: ToolIssue[];
205
215
  }>;
206
216
  /**
@@ -209,16 +219,14 @@ export declare class ConnectorsController {
209
219
  */
210
220
  preview(principal: Principal, org: OrgContext, body: z.infer<typeof PreviewBody>): Promise<ConnectorPreview>;
211
221
  get(principal: Principal, org: OrgContext, connectorId: string): Promise<{
212
- connector: ConnectorDetail & {
213
- icon: string | null;
214
- };
222
+ connector: ConnectorPage;
215
223
  }>;
216
224
  update(principal: Principal, org: OrgContext, connectorId: string, body: z.infer<typeof UpdateBody>): Promise<{
217
- connector: ConnectorDetail;
225
+ connector: ConnectorPage;
218
226
  }>;
219
227
  remove(principal: Principal, org: OrgContext, connectorId: string): Promise<void>;
220
228
  refresh(principal: Principal, org: OrgContext, connectorId: string): Promise<{
221
- connector: ConnectorDetail;
229
+ connector: ConnectorPage;
222
230
  issues: ToolIssue[];
223
231
  changes: {
224
232
  added: string[];
@@ -228,7 +236,16 @@ export declare class ConnectorsController {
228
236
  }>;
229
237
  /** 复核待复核的工具(漂移检测,spec 7.3):approve 采用新定义并启用,reject 停用并保留旧定义。 */
230
238
  review(principal: Principal, org: OrgContext, connectorId: string, name: string, body: z.infer<typeof ReviewBody>): Promise<{
231
- connector: ConnectorDetail;
239
+ connector: ConnectorPage;
240
+ }>;
241
+ /**
242
+ * 「我已更新应用」: the org admin updated the third-party app to the connector template's current
243
+ * setup guide (`{ hash }` of the guide shown) -> 200 `{ connector }`; 409
244
+ * `connector.guide_changed` when the guide moved on since, 404 `catalog.guide_not_found` for a
245
+ * connector without a template guide.
246
+ */
247
+ ackGuide(principal: Principal, org: OrgContext, connectorId: string, body: z.infer<typeof ConnectorGuideAckSchema>): Promise<{
248
+ connector: ConnectorPage;
232
249
  }>;
233
250
  setOrgCredential(principal: Principal, org: OrgContext, connectorId: string, body: z.infer<typeof CredentialValue>): Promise<void>;
234
251
  setMyCredential(principal: Principal, org: OrgContext, connectorId: string, body: z.infer<typeof CredentialValue>): Promise<void>;