@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
@@ -60,6 +60,9 @@ import { OrgsModule } from "../orgs/orgs.module.js";
60
60
  import { OrgSettingsController } from "../orgs/settings.controller.js";
61
61
  import { RuntimeModule } from "../runtime/runtime.module.js";
62
62
  import { ConversationStreamController } from "../runtime/stream.controller.js";
63
+ import { SetupModule } from "../setup/setup.module.js";
64
+ import { SetupChecksController } from "../setup/setup-checks.controller.js";
65
+ import { SetupGuidesController } from "../setup/setup-guides.controller.js";
63
66
  import { AgentSkillsController, InboxController, SkillChangesController, } from "../skills/changes.controller.js";
64
67
  import { InvocableSkillsController } from "../skills/invocable.controller.js";
65
68
  import { SkillsController } from "../skills/skills.controller.js";
@@ -138,6 +141,8 @@ const CONTROLLERS = [
138
141
  EvalRunsController,
139
142
  ModelPurposesController,
140
143
  OrgCatalogController,
144
+ SetupChecksController,
145
+ SetupGuidesController,
141
146
  OrgPluginUploadsController,
142
147
  OrgOwnedPluginsController,
143
148
  OrgComponentsController,
@@ -179,6 +184,7 @@ ApiModule = __decorate([
179
184
  UiModule,
180
185
  InboxModule,
181
186
  ThemesModule,
187
+ SetupModule,
182
188
  ],
183
189
  controllers: CONTROLLERS,
184
190
  providers: [{ provide: APP_GUARD, useClass: AuthGuard }],
@@ -74,6 +74,14 @@ export declare class LockService {
74
74
  holderOf(key: string): Promise<string | null>;
75
75
  /** Deletes `key` when `holder` still owns it. */
76
76
  releaseIf(key: string, holder: string): Promise<boolean>;
77
+ /**
78
+ * Takes one of `limit` slots of the counting semaphore `key` for `holder` (a unique token), for at
79
+ * most `ttlMs` (a crashed holder's slot frees itself then; no renewal). False when every slot is
80
+ * taken. Release with {@link releaseSlot}.
81
+ */
82
+ takeSlot(key: string, holder: string, limit: number, ttlMs: number): Promise<boolean>;
83
+ /** Gives back `holder`'s slot of the semaphore `key` (nothing when it already expired). */
84
+ releaseSlot(key: string, holder: string): Promise<void>;
77
85
  /**
78
86
  * Takes `key` (no `lock:` prefix) with the caller's `holder` value for `ttlMs`, unless it is
79
87
  * held, and renews it every TTL/3 like {@link acquire} (a run longer than the TTL keeps it, a
@@ -20,6 +20,20 @@ import { ACQUIRE_EXCLUSIVE, ACQUIRE_SHARED, COUNT_SHARED_LEASES, DOWNGRADE_SHARE
20
20
  const RELEASE_IF = `if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end`;
21
21
  /** Extends the key only while it holds our token. */
22
22
  const RENEW_IF = `if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('pexpire', KEYS[1], tonumber(ARGV[2])) else return 0 end`;
23
+ /**
24
+ * Takes a slot of a counting semaphore: a sorted set of holder tokens scored by expiry time. Expired
25
+ * holders (a crashed replica's) drop out first, so a slot is never lost for longer than its TTL;
26
+ * then the token is added when fewer than ARGV[2] slots are taken. The set itself expires with
27
+ * its last slot. KEYS[1] the set; ARGV: token, limit, ttl ms. Returns 1 when taken.
28
+ */
29
+ const TAKE_SLOT = `local now = redis.call('time')
30
+ local ms = tonumber(now[1]) * 1000 + math.floor(tonumber(now[2]) / 1000)
31
+ redis.call('zremrangebyscore', KEYS[1], '-inf', ms)
32
+ if redis.call('zcard', KEYS[1]) >= tonumber(ARGV[2]) then return 0 end
33
+ redis.call('zadd', KEYS[1], ms + tonumber(ARGV[3]), ARGV[1])
34
+ local last = redis.call('zrange', KEYS[1], -1, -1, 'withscores')
35
+ redis.call('pexpireat', KEYS[1], tonumber(last[2]))
36
+ return 1`;
23
37
  /** Resolves after `ms`; rejects with the abort reason when `signal` aborts first. */
24
38
  function sleep(ms, signal) {
25
39
  return new Promise((resolve, reject) => {
@@ -117,6 +131,19 @@ let LockService = class LockService {
117
131
  async releaseIf(key, holder) {
118
132
  return (await this.redis.eval(RELEASE_IF, 1, key, holder)) === 1;
119
133
  }
134
+ /**
135
+ * Takes one of `limit` slots of the counting semaphore `key` for `holder` (a unique token), for at
136
+ * most `ttlMs` (a crashed holder's slot frees itself then; no renewal). False when every slot is
137
+ * taken. Release with {@link releaseSlot}.
138
+ */
139
+ async takeSlot(key, holder, limit, ttlMs) {
140
+ assertNoTransaction("takeSlot");
141
+ return ((await this.redis.eval(TAKE_SLOT, 1, key, holder, String(limit), String(Math.ceil(ttlMs)))) === 1);
142
+ }
143
+ /** Gives back `holder`'s slot of the semaphore `key` (nothing when it already expired). */
144
+ async releaseSlot(key, holder) {
145
+ await this.redis.zrem(key, holder);
146
+ }
120
147
  /**
121
148
  * Takes `key` (no `lock:` prefix) with the caller's `holder` value for `ttlMs`, unless it is
122
149
  * held, and renews it every TTL/3 like {@link acquire} (a run longer than the TTL keeps it, a
@@ -8,6 +8,8 @@ export interface NamedThrottler {
8
8
  ttl: number;
9
9
  /** The 429 message (today's text of the route that used the limiter). */
10
10
  message: string;
11
+ /** The 429 error code; default `auth.rate_limited` (the credential routes'). */
12
+ code?: "auth.rate_limited" | "rate_limited";
11
13
  /** The credential group: throttlers of one group are reserved together (one Redis hash tag). */
12
14
  group: string;
13
15
  /** What a successful attempt does to this counter: delete it, or give one attempt back. */
@@ -29,7 +31,8 @@ export interface NamedThrottler {
29
31
  *
30
32
  * `POST /api/v1/device/authorize` and `POST /api/v1/device/token` opt in to `device-authorize-ip`
31
33
  * and `device-token-ip` with `@Throttled` (every request counts; neither checks a guessable
32
- * credential: the device code is 32 random bytes).
34
+ * credential: the device code is 32 random bytes). `POST …/guides/:guideId/checks/:checkId/run`
35
+ * opts in to `setup-check` (setup guides v2 §10.3: each run may call a third party).
33
36
  */
34
37
  export declare const NAMED_THROTTLERS: readonly NamedThrottler[];
35
38
  /** The storage key of a named throttler's counter for a tracker (shared by the guard and success paths). */
@@ -32,7 +32,8 @@ const LOGIN_MSG = "Too many failed login attempts; try again later";
32
32
  *
33
33
  * `POST /api/v1/device/authorize` and `POST /api/v1/device/token` opt in to `device-authorize-ip`
34
34
  * and `device-token-ip` with `@Throttled` (every request counts; neither checks a guessable
35
- * credential: the device code is 32 random bytes).
35
+ * credential: the device code is 32 random bytes). `POST …/guides/:guideId/checks/:checkId/run`
36
+ * opts in to `setup-check` (setup guides v2 §10.3: each run may call a third party).
36
37
  */
37
38
  export const NAMED_THROTTLERS = [
38
39
  // (login, IP): 10; an attacker filling one login from one IP leaves the owner's other IPs alone (R25)
@@ -152,6 +153,20 @@ export const NAMED_THROTTLERS = [
152
153
  message: "Too many device code attempts; try again later",
153
154
  getTracker: (req) => sha256(principalAccountId(req)),
154
155
  },
156
+ // setup check runs per signed-in person (setup guides v2 §10.5): 30 a minute, every request
157
+ // counts (@Throttled); a cached answer costs no third-party call but still counts, as the page
158
+ // asks at most once per row and stale result. The edge of the route (with `retry-after`): the
159
+ // budget shared with the Platform tool's setup.check is SetupChecksService's own counter
160
+ {
161
+ name: "setup-check",
162
+ group: "setup-check",
163
+ onSuccess: "clear",
164
+ limit: 30,
165
+ ttl: 60_000,
166
+ message: "Too many setup check runs; try again in a minute",
167
+ code: "rate_limited",
168
+ getTracker: (req) => sha256(principalAccountId(req)),
169
+ },
155
170
  ];
156
171
  /** The storage key of a named throttler's counter for a tracker (shared by the guard and success paths). */
157
172
  export const throttlerKey = (name, tracker) => sha256(`${name}:${tracker}`);
@@ -56,7 +56,7 @@ let KernelThrottlerGuard = class KernelThrottlerGuard extends ThrottlerGuard {
56
56
  const known = NAMED_THROTTLERS.find((t) => t.name === name);
57
57
  if (!known)
58
58
  throw new Error(`unknown throttler "${name}"`);
59
- throw new ApiError("auth.rate_limited", known.message);
59
+ throw new ApiError(known.code ?? "auth.rate_limited", known.message);
60
60
  }
61
61
  };
62
62
  KernelThrottlerGuard = __decorate([
@@ -12,6 +12,7 @@ import { RUN_HOOKS, type RunHooks } from "./runtime/run-worker.js";
12
12
  import { SUBAGENT_MESSAGES_HOOKS, type SubagentMessagesHooks } from "./runtime/subagents/messages.js";
13
13
  import { USAGE_REPORT_RETRY, type UsageReportRetry } from "./runtime/usage-report.js";
14
14
  import { SANDBOX_LEASE_TIMINGS, type SandboxLeaseTimings } from "./sandbox/leases.js";
15
+ import { SETUP_CHECK_TIMEOUT } from "./setup/setup-checks.service.js";
15
16
  import { TOOL_PIPELINE_HOOKS, type ToolPipelineHooks } from "./tools/pipeline.js";
16
17
  import { EMBED_TOKEN_CLOCK } from "./ui/embed-token.js";
17
18
  /**
@@ -24,7 +25,7 @@ import { EMBED_TOKEN_CLOCK } from "./ui/embed-token.js";
24
25
  * - `TOOL_PIPELINE_HOOKS`: the tool pipeline's hooks and policy decision;
25
26
  * - `SUBAGENT_MESSAGES_HOOKS`: the sub-agent message tools' hooks (a send raced by a run's end);
26
27
  * - `BOOTSTRAP_LOCK_TTL`, `FILE_PREVIEW_TIMEOUT`, `LIST_MODELS_TIMEOUT`, `USAGE_REPORT_RETRY`,
27
- * `SANDBOX_LEASE_TIMINGS`, `CHANNEL_WEBHOOK_TIMINGS`: shorter timings;
28
+ * `SANDBOX_LEASE_TIMINGS`, `CHANNEL_WEBHOOK_TIMINGS`, `SETUP_CHECK_TIMEOUT`: shorter timings;
28
29
  * - `EMBED_TOKEN_CLOCK`: the clock embed tokens are issued and checked with;
29
30
  * - `RUN_CONTEXT_CLOCK`: the clock run start reads the run context's date from, and the
30
31
  * `CurrentTime` builtin the time;
@@ -42,6 +43,7 @@ interface KernelSeams {
42
43
  [USAGE_REPORT_RETRY]: UsageReportRetry;
43
44
  [SANDBOX_LEASE_TIMINGS]: Partial<SandboxLeaseTimings>;
44
45
  [CHANNEL_WEBHOOK_TIMINGS]: Partial<ChannelWebhookTimings>;
46
+ [SETUP_CHECK_TIMEOUT]: number;
45
47
  [EMBED_TOKEN_CLOCK]: () => number;
46
48
  [RUN_CONTEXT_CLOCK]: () => number;
47
49
  [APP_INTERCEPTOR]: NestInterceptor;
package/dist/overrides.js CHANGED
@@ -10,6 +10,7 @@ import { RUN_HOOKS } from "./runtime/run-worker.js";
10
10
  import { SUBAGENT_MESSAGES_HOOKS, } from "./runtime/subagents/messages.js";
11
11
  import { USAGE_REPORT_RETRY } from "./runtime/usage-report.js";
12
12
  import { SANDBOX_LEASE_TIMINGS } from "./sandbox/leases.js";
13
+ import { SETUP_CHECK_TIMEOUT } from "./setup/setup-checks.service.js";
13
14
  import { TOOL_PIPELINE_HOOKS } from "./tools/pipeline.js";
14
15
  import { EMBED_TOKEN_CLOCK } from "./ui/embed-token.js";
15
16
  const SEAM_TOKENS = [
@@ -24,6 +25,7 @@ const SEAM_TOKENS = [
24
25
  USAGE_REPORT_RETRY,
25
26
  SANDBOX_LEASE_TIMINGS,
26
27
  CHANNEL_WEBHOOK_TIMINGS,
28
+ SETUP_CHECK_TIMEOUT,
27
29
  EMBED_TOKEN_CLOCK,
28
30
  RUN_CONTEXT_CLOCK,
29
31
  APP_INTERCEPTOR,
@@ -95,6 +95,11 @@ export const PLATFORM_EXCLUSIONS = [
95
95
  category: "transport",
96
96
  reason: "Serves icon image bytes",
97
97
  },
98
+ {
99
+ route: "GET /api/v1/admin/catalog/:itemId/guides/:guideId",
100
+ category: "internal",
101
+ reason: "The super admin's preview of a plugin's setup guide for the admin console, with no org and no target; agents assist with the org guides an org member sees",
102
+ },
98
103
  {
99
104
  route: "POST /api/v1/admin/catalog/items/:id/confirm",
100
105
  category: "human-only",
@@ -128,11 +133,6 @@ export const PLATFORM_EXCLUSIONS = [
128
133
  category: "transport",
129
134
  reason: "Serves icon image bytes",
130
135
  },
131
- {
132
- route: "GET /api/v1/orgs/:orgId/catalog/:itemId/guides/:guideId",
133
- category: "internal",
134
- reason: "A plugin's setup guide (an app manifest, URLs, steps) for the web's setup section; a person pastes it into the third-party platform's console, which an agent cannot reach",
135
- },
136
136
  {
137
137
  route: "POST /api/v1/orgs/:orgId/catalog/:itemId/install",
138
138
  category: "secret",
@@ -593,6 +593,11 @@ export const PLATFORM_EXCLUSIONS = [
593
593
  category: "secret",
594
594
  reason: "Edits connector endpoint config that can carry credentials",
595
595
  },
596
+ {
597
+ route: "POST /api/v1/orgs/:orgId/connectors/:connectorId/guide-ack",
598
+ category: "human-only",
599
+ reason: "A person confirms they updated the app on the third-party platform after a connector template's setup guide changed; only that person knows it was done",
600
+ },
596
601
  {
597
602
  route: "POST /api/v1/orgs/:orgId/connectors/:connectorId/tools/:name/review",
598
603
  category: "human-only",
@@ -87,6 +87,10 @@ export const PLATFORM_DOMAINS = [
87
87
  id: "devices",
88
88
  description: "Your own cloud devices (the workspaces your conversations run in): list, read, create, rename, delete, wipe the files.",
89
89
  },
90
+ {
91
+ id: "setup",
92
+ description: "Plugin setup guides: list the guides you may help with, read one as Markdown, the state of its checks for a channel connection or a connector, and running one check. Entering secrets, connecting an account and confirming the app was updated stay with the person on the web; read the docs topic `setup` first.",
93
+ },
90
94
  {
91
95
  id: "themes",
92
96
  description: "The themes on in this org, the one in effect for you and the org's theme setting: list. Themes are set through your preferences, the org settings and the deployment settings.",
@@ -120,6 +120,8 @@ let ConnectorsOperations = class ConnectorsOperations {
120
120
  on: t.on,
121
121
  switchable: t.switchable,
122
122
  owner: t.owner,
123
+ // where the template's setup guide is (catalog item and guide id), null without one
124
+ guide: t.guide ?? null,
123
125
  }));
124
126
  return pageOf(templates, startOf(input.cursor), PAGE);
125
127
  },
@@ -0,0 +1,24 @@
1
+ import { type OnModuleInit } from "@nestjs/common";
2
+ import { KernelDb } from "../../infra/database/kernel-db.js";
3
+ import { SetupChecksService } from "../../setup/setup-checks.service.js";
4
+ import { SetupGuidesService } from "../../setup/setup-guides.service.js";
5
+ import { PlatformOperations } from "../registry.js";
6
+ /**
7
+ * Setup guide operations of the `Platform` tool (spec 2026-11-09-setup-guides-v2-design.md §12),
8
+ * the `setup` domain: the guides a person may see, one guide rendered as Markdown, the cached check
9
+ * states of a guide and running one check. All are reads (a check never writes anywhere; its cost
10
+ * is bounded by the cache, the cooldown and the org's concurrency cap) and use the routes'
11
+ * services, so a guide carries no secret (§6.3) and check results only the fixed, redacted fields.
12
+ * Entering a secret, OAuth consent and the guide ack stay with the person on the web (`human-only`
13
+ * and `secret` exclusions); no setup operation takes a value.
14
+ */
15
+ export declare class SetupOperations implements OnModuleInit {
16
+ private readonly registry;
17
+ private readonly checks;
18
+ private readonly guides;
19
+ private readonly kdb;
20
+ constructor(registry: PlatformOperations, checks: SetupChecksService, guides: SetupGuidesService, kdb: KernelDb);
21
+ /** The actor as the routes see a caller: the org, their role there now and their account. */
22
+ private caller;
23
+ onModuleInit(): void;
24
+ }
@@ -0,0 +1,212 @@
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 { CatalogGuidePathParams, CatalogGuideQuery } from "../../catalog/org-catalog.schemas.js";
12
+ import { ApiError } from "../../errors.js";
13
+ import { KernelDb } from "../../infra/database/kernel-db.js";
14
+ import { SetupCheckPathParams, SetupCheckRunBody, SetupChecksQuery, SetupGuidePathParams, } from "../../setup/setup-checks.schemas.js";
15
+ import { SetupChecksService } from "../../setup/setup-checks.service.js";
16
+ import { SetupGuidesQuery } from "../../setup/setup-guides.schemas.js";
17
+ import { SetupGuidesService } from "../../setup/setup-guides.service.js";
18
+ import { defineOperation } from "../operation.js";
19
+ import { PlatformOperations } from "../registry.js";
20
+ import { roleOf } from "./role.js";
21
+ const ITEM = "00000000-0000-4000-8000-000000000000";
22
+ const CONNECTION = "00000000-0000-4000-8000-000000000001";
23
+ /**
24
+ * Setup guide operations of the `Platform` tool (spec 2026-11-09-setup-guides-v2-design.md §12),
25
+ * the `setup` domain: the guides a person may see, one guide rendered as Markdown, the cached check
26
+ * states of a guide and running one check. All are reads (a check never writes anywhere; its cost
27
+ * is bounded by the cache, the cooldown and the org's concurrency cap) and use the routes'
28
+ * services, so a guide carries no secret (§6.3) and check results only the fixed, redacted fields.
29
+ * Entering a secret, OAuth consent and the guide ack stay with the person on the web (`human-only`
30
+ * and `secret` exclusions); no setup operation takes a value.
31
+ */
32
+ let SetupOperations = class SetupOperations {
33
+ registry;
34
+ checks;
35
+ guides;
36
+ kdb;
37
+ constructor(registry, checks, guides, kdb) {
38
+ this.registry = registry;
39
+ this.checks = checks;
40
+ this.guides = guides;
41
+ this.kdb = kdb;
42
+ }
43
+ /** The actor as the routes see a caller: the org, their role there now and their account. */
44
+ async caller(ctx) {
45
+ const role = await roleOf(this.kdb.db, ctx.orgId, ctx.actorAccountId);
46
+ if (role === null)
47
+ throw new ApiError("forbidden", "Insufficient organization role");
48
+ return { orgId: ctx.orgId, role, accountId: ctx.actorAccountId };
49
+ }
50
+ onModuleInit() {
51
+ this.registry.register([
52
+ defineOperation({
53
+ id: "setup.guides",
54
+ description: "List the plugin setup guides this person may help with in the org: per guide its targets (channel connections, connectors made from a template, templates switched on without a connector yet), whether the guide changed since a target was set up (drift), a count of the cached check states and the web page where the person does the setup. Org admins get the admin guides; members get the guides for connecting their own account. Read a guide with setup.read.",
55
+ input: SetupGuidesQuery,
56
+ example: {},
57
+ authz: { orgAction: "catalog.read" },
58
+ mirrors: ["GET /api/v1/orgs/:orgId/setup-guides"],
59
+ confirm: "read",
60
+ run: async (input, ctx) => ({
61
+ guides: await this.guides.list(await this.caller(ctx), input),
62
+ }),
63
+ }),
64
+ defineOperation({
65
+ id: "setup.read",
66
+ description: "Read one plugin setup guide rendered for a target as a Markdown document: the steps, the app manifest and URLs to paste at the third-party platform, and the checks as '- [check <id>] <label>' lines (run them with setup.check), with the modes, the variables that have no value yet and why, and the web page of the target. Explain the step the person is on. Secrets are never in the guide: ask the person to paste each token or secret into its field on the returned webPath, never into the conversation.",
67
+ input: CatalogGuidePathParams.extend(CatalogGuideQuery.shape).strict(),
68
+ example: { itemId: ITEM, guideId: "slack-app", connectionId: CONNECTION },
69
+ authz: { orgAction: "catalog.read" },
70
+ mirrors: ["GET /api/v1/orgs/:orgId/catalog/:itemId/guides/:guideId"],
71
+ confirm: "read",
72
+ errors: [
73
+ {
74
+ code: "catalog.item_not_found",
75
+ next: "The plugin is not in this org's catalog; list the guides with setup.guides.",
76
+ },
77
+ {
78
+ code: "catalog.guide_not_found",
79
+ next: "The plugin has no setup guide with that id; list the guides with setup.guides.",
80
+ },
81
+ {
82
+ code: "channel.not_found",
83
+ next: "No channel connection with that id in this org; list the targets with setup.guides.",
84
+ },
85
+ {
86
+ code: "connector.not_found",
87
+ next: "No connector made from this guide's template with that id in this org; list the targets with setup.guides.",
88
+ },
89
+ {
90
+ code: "validation_failed",
91
+ next: "Pass a mode and audience the guide has (setup.guides lists the audience) and the target of its place: connectionId for a channel guide, connectorId for a connector template guide.",
92
+ },
93
+ {
94
+ code: "forbidden",
95
+ next: {
96
+ member: "Only org admins read the admin guides; members read the member section of a connector template guide (audience member).",
97
+ },
98
+ },
99
+ ],
100
+ run: async (input, ctx) => {
101
+ const { itemId, guideId, ...q } = input;
102
+ return { guide: await this.guides.read(await this.caller(ctx), itemId, guideId, q) };
103
+ },
104
+ }),
105
+ defineOperation({
106
+ id: "setup.status",
107
+ description: "Read the cached state of every check of a plugin's setup guide for one target (a channel connection or a connector made from a template), by check id: ok, failing, unknown, running or not_run, with the reason and fix to show the person. Runs nothing; run a check with setup.check.",
108
+ input: SetupGuidePathParams.extend(SetupChecksQuery.shape).strict(),
109
+ example: { itemId: ITEM, guideId: "slack-app", connectionId: CONNECTION },
110
+ authz: { orgAction: "catalog.read" },
111
+ mirrors: ["GET /api/v1/orgs/:orgId/catalog/:itemId/guides/:guideId/checks"],
112
+ confirm: "read",
113
+ errors: [
114
+ {
115
+ code: "catalog.item_not_found",
116
+ next: "The plugin is not in this org's catalog; check the item id on the plugin's page.",
117
+ },
118
+ {
119
+ code: "catalog.guide_not_found",
120
+ next: "The plugin has no setup guide with that id; read the guide id from the connection or connector page.",
121
+ },
122
+ {
123
+ code: "channel.not_found",
124
+ next: "No channel connection with that id in this org; list the targets with setup.guides.",
125
+ },
126
+ {
127
+ code: "connector.not_found",
128
+ next: "No connector made from this guide's template with that id in this org; list the targets with setup.guides.",
129
+ },
130
+ {
131
+ code: "validation_failed",
132
+ next: "Checks need a saved target: pass connectionId for a channel guide or connectorId for a connector template guide, and a mode and audience the guide has.",
133
+ },
134
+ {
135
+ code: "forbidden",
136
+ next: {
137
+ member: "Only org admins run the admin checks of a guide; members run the member checks (audience member).",
138
+ },
139
+ },
140
+ ],
141
+ run: async (input, ctx) => {
142
+ const { itemId, guideId, ...q } = input;
143
+ return { results: await this.checks.states(await this.caller(ctx), itemId, guideId, q) };
144
+ },
145
+ }),
146
+ defineOperation({
147
+ id: "setup.check",
148
+ description: "Run one check of a plugin's setup guide for one target (a channel connection or a connector made from a template) and return its state: ok, failing (with a reason and a fix to explain), unknown, or running (try again in a moment). A fresh cached result is returned as is; force reruns it, at most once per 10 seconds per check and target. A check only reads; run it after the person says they finished a step.",
149
+ input: SetupCheckPathParams.extend(SetupCheckRunBody.shape).strict(),
150
+ example: {
151
+ itemId: ITEM,
152
+ guideId: "slack-app",
153
+ checkId: "bot-token",
154
+ connectionId: CONNECTION,
155
+ },
156
+ authz: { orgAction: "catalog.read" },
157
+ mirrors: ["POST /api/v1/orgs/:orgId/catalog/:itemId/guides/:guideId/checks/:checkId/run"],
158
+ confirm: "read",
159
+ errors: [
160
+ {
161
+ code: "catalog.item_not_found",
162
+ next: "The plugin is not in this org's catalog; check the item id on the plugin's page.",
163
+ },
164
+ {
165
+ code: "catalog.guide_not_found",
166
+ next: "The plugin has no setup guide with that id; read the guide id from the connection or connector page.",
167
+ },
168
+ {
169
+ code: "channel.not_found",
170
+ next: "No channel connection with that id in this org; list the targets with setup.guides.",
171
+ },
172
+ {
173
+ code: "connector.not_found",
174
+ next: "No connector made from this guide's template with that id in this org; list the targets with setup.guides.",
175
+ },
176
+ {
177
+ code: "validation_failed",
178
+ next: "Checks need a saved target: pass connectionId for a channel guide or connectorId for a connector template guide, and a mode and audience the guide has.",
179
+ },
180
+ {
181
+ code: "forbidden",
182
+ next: {
183
+ member: "Only org admins run the admin checks of a guide; members run the member checks (audience member).",
184
+ },
185
+ },
186
+ {
187
+ code: "catalog.check_not_found",
188
+ next: "That check is not in the guide for this mode and audience; read the check ids with setup.status.",
189
+ },
190
+ {
191
+ code: "rate_limited",
192
+ next: "This person ran 30 checks in the last minute; wait a minute before running another, and read the cached states with setup.status meanwhile.",
193
+ },
194
+ ],
195
+ run: async (input, ctx) => {
196
+ const { itemId, guideId, checkId, ...body } = input;
197
+ return {
198
+ result: await this.checks.run(await this.caller(ctx), itemId, guideId, checkId, body),
199
+ };
200
+ },
201
+ }),
202
+ ]);
203
+ }
204
+ };
205
+ SetupOperations = __decorate([
206
+ Injectable(),
207
+ __param(0, Inject(PlatformOperations)),
208
+ __param(1, Inject(SetupChecksService)),
209
+ __param(2, Inject(SetupGuidesService)),
210
+ __param(3, Inject(KernelDb))
211
+ ], SetupOperations);
212
+ export { SetupOperations };
@@ -24,6 +24,7 @@ import { PlatformOperationsProvider } from "./ops/platform.ops.js";
24
24
  import { PluginsOperations } from "./ops/plugins.ops.js";
25
25
  import { PreviewersOperations } from "./ops/previewers.ops.js";
26
26
  import { RequestsOperations } from "./ops/requests.ops.js";
27
+ import { SetupOperations } from "./ops/setup.ops.js";
27
28
  import { SkillsOperations } from "./ops/skills.ops.js";
28
29
  import { ThemesOperations } from "./ops/themes.ops.js";
29
30
  import { UiOperations } from "./ops/ui.ops.js";
@@ -31,7 +32,7 @@ import { UiOperations } from "./ops/ui.ops.js";
31
32
  * One provider per domain, each taking `PlatformOperations` as its first constructor argument:
32
33
  * the parity test in `tools/checks` builds the operations from it without a Nest app.
33
34
  */
34
- export declare const PLATFORM_OPERATION_PROVIDERS: readonly [typeof PlatformOperationsProvider, typeof SkillsOperations, typeof AgentsOperations, typeof AgentTemplatesOperations, typeof PluginsOperations, typeof MarketplacesOperations, typeof RequestsOperations, typeof ConnectorsOperations, typeof ExtensionsOperations, typeof ConversationsOperations, typeof ComponentsOperations, typeof AccountsOperations, typeof OrgsOperations, typeof AdminOperations, typeof CatalogAdminOperations, typeof OrgPluginsOperations, typeof ExtensionsAdminOperations, typeof ModelsOperations, typeof InboxOperations, typeof ApprovalsOperations, typeof ChannelsOperations, typeof OrgSettingsOperations, typeof EvalsOperations, typeof BackgroundOperations, typeof PreviewersOperations, typeof DevicesOperations, typeof AgentSkillsOperations, typeof ThemesOperations, typeof UiOperations];
35
+ export declare const PLATFORM_OPERATION_PROVIDERS: readonly [typeof PlatformOperationsProvider, typeof SkillsOperations, typeof AgentsOperations, typeof AgentTemplatesOperations, typeof PluginsOperations, typeof MarketplacesOperations, typeof RequestsOperations, typeof ConnectorsOperations, typeof ExtensionsOperations, typeof ConversationsOperations, typeof ComponentsOperations, typeof AccountsOperations, typeof OrgsOperations, typeof AdminOperations, typeof CatalogAdminOperations, typeof OrgPluginsOperations, typeof ExtensionsAdminOperations, typeof ModelsOperations, typeof InboxOperations, typeof ApprovalsOperations, typeof ChannelsOperations, typeof OrgSettingsOperations, typeof EvalsOperations, typeof BackgroundOperations, typeof PreviewersOperations, typeof DevicesOperations, typeof AgentSkillsOperations, typeof ThemesOperations, typeof SetupOperations, typeof UiOperations];
35
36
  /**
36
37
  * The `Platform` tool's operations (ruling P2): one provider per domain, each registering its
37
38
  * operations into `PlatformOperations` from `onModuleInit`. It imports the domain modules, which
@@ -19,6 +19,7 @@ import { InboxModule } from "../inbox/inbox.module.js";
19
19
  import { ModelsModule } from "../models/models.module.js";
20
20
  import { OrgsModule } from "../orgs/orgs.module.js";
21
21
  import { ProvidersModule } from "../runtime/providers.js";
22
+ import { SetupModule } from "../setup/setup.module.js";
22
23
  import { SkillsModule } from "../skills/skills.module.js";
23
24
  import { ThemesModule } from "../themes/themes.module.js";
24
25
  import { ToolsModule } from "../tools/tools.module.js";
@@ -48,6 +49,7 @@ import { PlatformOperationsProvider } from "./ops/platform.ops.js";
48
49
  import { PluginsOperations } from "./ops/plugins.ops.js";
49
50
  import { PreviewersOperations } from "./ops/previewers.ops.js";
50
51
  import { RequestsOperations } from "./ops/requests.ops.js";
52
+ import { SetupOperations } from "./ops/setup.ops.js";
51
53
  import { SkillsOperations } from "./ops/skills.ops.js";
52
54
  import { ThemesOperations } from "./ops/themes.ops.js";
53
55
  import { UiOperations } from "./ops/ui.ops.js";
@@ -92,6 +94,8 @@ export const PLATFORM_OPERATION_PROVIDERS = [
92
94
  AgentSkillsOperations,
93
95
  // themes (themes spec §9)
94
96
  ThemesOperations,
97
+ // setup guide checks (setup guides v2 §12; plan task 13 adds the rest of the domain)
98
+ SetupOperations,
95
99
  // UI slot catalog (UI slots spec §3)
96
100
  UiOperations,
97
101
  ];
@@ -123,6 +127,7 @@ PlatformOpsModule = __decorate([
123
127
  BackgroundModule,
124
128
  FilesModule,
125
129
  ThemesModule,
130
+ SetupModule,
126
131
  ],
127
132
  providers: [...PLATFORM_OPERATION_PROVIDERS],
128
133
  })
@@ -19,6 +19,8 @@ import { ME_ROUTES } from "../identity/me.schemas.js";
19
19
  import { INBOX_ROUTES } from "../inbox/inbox.schemas.js";
20
20
  import { ORG_MODEL_ROUTES } from "../models/org-models.schemas.js";
21
21
  import { ORG_ROUTES, ORG_SETTINGS_ROUTES } from "../orgs/orgs.schemas.js";
22
+ import { SETUP_CHECK_ROUTES } from "../setup/setup-checks.schemas.js";
23
+ import { SETUP_GUIDE_ROUTES } from "../setup/setup-guides.schemas.js";
22
24
  import { SKILL_ROUTES } from "../skills/skills.schemas.js";
23
25
  import { THEME_ROUTES } from "../themes/themes.schemas.js";
24
26
  import { APPROVAL_ROUTES } from "../tools/approvals.schemas.js";
@@ -64,6 +66,9 @@ export const ROUTE_SCHEMAS = [
64
66
  ...ME_ROUTES,
65
67
  // themes (themes spec §9)
66
68
  ...THEME_ROUTES,
69
+ // setup guide checks (setup guides v2 §10.3, §12)
70
+ ...SETUP_CHECK_ROUTES,
71
+ ...SETUP_GUIDE_ROUTES,
67
72
  // UI slots (UI slots spec §3)
68
73
  ...UI_ROUTES,
69
74
  ];