@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.
- package/dist/catalog/admin-catalog.controller.d.ts +13 -2
- package/dist/catalog/admin-catalog.controller.js +24 -3
- package/dist/catalog/admin-catalog.schemas.d.ts +10 -0
- package/dist/catalog/admin-catalog.schemas.js +7 -0
- package/dist/catalog/catalog-content.d.ts +9 -1
- package/dist/catalog/catalog-content.js +2 -1
- package/dist/catalog/catalog-files.d.ts +3 -2
- package/dist/catalog/catalog-files.js +8 -5
- package/dist/catalog/component-scope.service.d.ts +14 -1
- package/dist/catalog/component-scope.service.js +12 -2
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/org-catalog.controller.d.ts +5 -4
- package/dist/catalog/org-catalog.controller.js +5 -4
- package/dist/catalog/org-catalog.schemas.d.ts +18 -2
- package/dist/catalog/org-catalog.schemas.js +16 -2
- package/dist/catalog/org-catalog.service.js +8 -2
- package/dist/catalog/plugin-import.js +1 -1
- package/dist/catalog/plugin-package.js +37 -9
- package/dist/catalog/plugin-upgrade.d.ts +15 -1
- package/dist/catalog/plugin-upgrade.js +18 -5
- package/dist/channels/connections.controller.d.ts +1 -1
- package/dist/channels/connections.controller.js +1 -1
- package/dist/channels/connections.service.d.ts +22 -5
- package/dist/channels/connections.service.js +56 -10
- package/dist/channels/webhook.d.ts +21 -1
- package/dist/channels/webhook.js +39 -4
- package/dist/ctx/create-ctx.d.ts +17 -3
- package/dist/ctx/create-ctx.js +49 -2
- package/dist/db/kernel-schema.d.ts +11 -2
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +4 -0
- package/dist/extensions/guide-places.d.ts +66 -0
- package/dist/extensions/guide-places.js +152 -0
- package/dist/extensions/host.js +3 -3
- package/dist/extensions/instance.d.ts +5 -1
- package/dist/extensions/instance.js +8 -0
- package/dist/extensions/ledger.d.ts +1 -1
- package/dist/extensions/permission-set.d.ts +16 -2
- package/dist/extensions/permission-set.js +17 -3
- package/dist/extensions/plugin-guides.d.ts +207 -54
- package/dist/extensions/plugin-guides.js +436 -165
- package/dist/http/api.module.js +6 -0
- package/dist/infra/lock/lock.service.d.ts +8 -0
- package/dist/infra/lock/lock.service.js +27 -0
- package/dist/infra/throttler/named-throttlers.d.ts +4 -1
- package/dist/infra/throttler/named-throttlers.js +16 -1
- package/dist/infra/throttler/throttler.guard.js +1 -1
- package/dist/overrides.d.ts +3 -1
- package/dist/overrides.js +2 -0
- package/dist/platform/exclusions.js +10 -5
- package/dist/platform/operation.js +4 -0
- package/dist/platform/ops/connectors.ops.js +2 -0
- package/dist/platform/ops/setup.ops.d.ts +24 -0
- package/dist/platform/ops/setup.ops.js +212 -0
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +5 -0
- package/dist/platform/route-schemas.js +5 -0
- package/dist/setup/kernel-checks.d.ts +91 -0
- package/dist/setup/kernel-checks.js +295 -0
- package/dist/setup/oauth-token-probe.d.ts +47 -0
- package/dist/setup/oauth-token-probe.js +108 -0
- package/dist/setup/redact-check.d.ts +18 -0
- package/dist/setup/redact-check.js +46 -0
- package/dist/setup/setup-check-registry.d.ts +86 -0
- package/dist/setup/setup-check-registry.js +75 -0
- package/dist/setup/setup-checks.controller.d.ts +28 -0
- package/dist/setup/setup-checks.controller.js +78 -0
- package/dist/setup/setup-checks.schemas.d.ts +38 -0
- package/dist/setup/setup-checks.schemas.js +43 -0
- package/dist/setup/setup-checks.service.d.ts +144 -0
- package/dist/setup/setup-checks.service.js +683 -0
- package/dist/setup/setup-guides.controller.d.ts +18 -0
- package/dist/setup/setup-guides.controller.js +48 -0
- package/dist/setup/setup-guides.schemas.d.ts +10 -0
- package/dist/setup/setup-guides.schemas.js +6 -0
- package/dist/setup/setup-guides.service.d.ts +126 -0
- package/dist/setup/setup-guides.service.js +315 -0
- package/dist/setup/setup.module.d.ts +8 -0
- package/dist/setup/setup.module.js +31 -0
- package/dist/tools/connectors.controller.d.ts +28 -11
- package/dist/tools/connectors.controller.js +49 -14
- package/dist/tools/connectors.schemas.d.ts +4 -0
- package/dist/tools/connectors.schemas.js +4 -0
- package/dist/tools/connectors.service.d.ts +30 -2
- package/dist/tools/connectors.service.js +98 -3
- package/dist/tools/credentials.d.ts +20 -0
- package/dist/tools/credentials.js +90 -1
- package/dist/tools/oauth/tokens.d.ts +20 -0
- package/dist/tools/oauth/tokens.js +49 -24
- package/package.json +3 -3
- package/src/migrations/0106_setup_guides_v2.sql +10 -0
|
@@ -20,6 +20,7 @@ import { ExtensionAdminOps } from "../extensions/admin-ops.js";
|
|
|
20
20
|
import { ExtensionBlobs } from "../extensions/blobs.js";
|
|
21
21
|
import { permissionSetOfPackage, readExtensionPackage } from "../extensions/package.js";
|
|
22
22
|
import { blockingKeys, diffPermissions } from "../extensions/permission-set.js";
|
|
23
|
+
import { PluginGuides } from "../extensions/plugin-guides.js";
|
|
23
24
|
import { ExtensionRegistry } from "../extensions/registry.js";
|
|
24
25
|
import { audit } from "../identity/audit.js";
|
|
25
26
|
import { SuperAdminLock } from "../identity/super-admin.js";
|
|
@@ -143,11 +144,14 @@ let PlatformPluginUpdater = class PlatformPluginUpdater {
|
|
|
143
144
|
connectors;
|
|
144
145
|
sync;
|
|
145
146
|
log;
|
|
147
|
+
guides;
|
|
146
148
|
constructor(kdb, txHost, adminLock, itemLock, orgLock, locks, storage, registry, ops,
|
|
147
149
|
/** Reads the target's extension package (its permissions, for `needsApproval`). */
|
|
148
150
|
blobs, config, platform,
|
|
149
151
|
/** Template dependencies (the deployed connector kinds). */
|
|
150
|
-
installer, connectors, sync, log
|
|
152
|
+
installer, connectors, sync, log,
|
|
153
|
+
/** The connector template guides an update changes (the preview's `guideChanges`). */
|
|
154
|
+
guides) {
|
|
151
155
|
this.kdb = kdb;
|
|
152
156
|
this.txHost = txHost;
|
|
153
157
|
this.adminLock = adminLock;
|
|
@@ -164,13 +168,21 @@ let PlatformPluginUpdater = class PlatformPluginUpdater {
|
|
|
164
168
|
this.connectors = connectors;
|
|
165
169
|
this.sync = sync;
|
|
166
170
|
this.log = log;
|
|
171
|
+
this.guides = guides;
|
|
167
172
|
}
|
|
168
173
|
get db() {
|
|
169
174
|
return this.kdb.db;
|
|
170
175
|
}
|
|
171
176
|
/** The update preview (see the class comment); writes nothing. */
|
|
172
177
|
async preview(itemId, version) {
|
|
173
|
-
return
|
|
178
|
+
return this.withGuideChanges(await this.diff(itemId, version));
|
|
179
|
+
}
|
|
180
|
+
/** The preview of a diff with the template guides it changes. */
|
|
181
|
+
async withGuideChanges(d) {
|
|
182
|
+
return {
|
|
183
|
+
...previewOf(d),
|
|
184
|
+
guideChanges: await this.guides.templateGuideChanges(d.itemId, d.from, d.to),
|
|
185
|
+
};
|
|
174
186
|
}
|
|
175
187
|
/**
|
|
176
188
|
* The update preview of a new version of an org's own plugin (ruling P3-12): the item must be
|
|
@@ -186,7 +198,7 @@ let PlatformPluginUpdater = class PlatformPluginUpdater {
|
|
|
186
198
|
if (staged !== undefined && staged.version !== version) {
|
|
187
199
|
throw new Error(`internal: previewForOwner got the staged target of version ${staged.version} for version ${version}`);
|
|
188
200
|
}
|
|
189
|
-
const preview =
|
|
201
|
+
const preview = await this.withGuideChanges(await this.diff(itemId, version, {
|
|
190
202
|
owner: { orgId },
|
|
191
203
|
...(staged !== undefined && { target: staged }),
|
|
192
204
|
}));
|
|
@@ -1502,10 +1514,11 @@ PlatformPluginUpdater = __decorate([
|
|
|
1502
1514
|
__param(12, Inject(PluginInstaller)),
|
|
1503
1515
|
__param(13, Inject(ConnectorsService)),
|
|
1504
1516
|
__param(14, Inject(PluginScopeSync)),
|
|
1505
|
-
__param(15, Inject(KERNEL_LOGGER))
|
|
1517
|
+
__param(15, Inject(KERNEL_LOGGER)),
|
|
1518
|
+
__param(16, Inject(PluginGuides))
|
|
1506
1519
|
], PlatformPluginUpdater);
|
|
1507
1520
|
export { PlatformPluginUpdater };
|
|
1508
|
-
/** The preview of a diff. */
|
|
1521
|
+
/** The preview of a diff (without `guideChanges`, which reads PG). */
|
|
1509
1522
|
function previewOf(d) {
|
|
1510
1523
|
return {
|
|
1511
1524
|
itemId: d.itemId,
|
|
@@ -13,7 +13,7 @@ import { ChannelConnections, type ChannelConnectionView } from "./connections.se
|
|
|
13
13
|
* the URL of its plugin's icon on the org's catalog route (null without one, or when the org may
|
|
14
14
|
* not see the plugin's catalog item; maintainer decision 2026-11-08), and its plugin's setup guide
|
|
15
15
|
* with the drift since the connection was set up (`setupGuide`, `guideDrift`; spec
|
|
16
|
-
* 2026-11-
|
|
16
|
+
* 2026-11-09-setup-guides-v2-design.md §11).
|
|
17
17
|
*/
|
|
18
18
|
export type ChannelConnectionPage = ChannelConnectionView & ConnectionGuideState & {
|
|
19
19
|
pluginDisplayName: LocalizedName | null;
|
|
@@ -93,7 +93,7 @@ let ChannelConnectionsController = class ChannelConnectionsController {
|
|
|
93
93
|
*/
|
|
94
94
|
async ackGuide(principal, org, id, body) {
|
|
95
95
|
const p = requireUser(principal);
|
|
96
|
-
const view = await this.channels.ackGuide(org.orgId, id, body.hash, p.account.id, patTokenId(p));
|
|
96
|
+
const view = await this.channels.ackGuide({ orgId: org.orgId, role: org.role }, id, body.hash, p.account.id, patTokenId(p));
|
|
97
97
|
return { channel: await this.withPlugin(org, view) };
|
|
98
98
|
}
|
|
99
99
|
async remove(principal, org, id) {
|
|
@@ -12,6 +12,7 @@ import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
|
12
12
|
import { KernelEvents } from "../infra/events/kernel-events.js";
|
|
13
13
|
import { type KernelLogger } from "../log.js";
|
|
14
14
|
import { OrgLock } from "../orgs/org-lock.js";
|
|
15
|
+
import type { OrgRole } from "../orgs/roles.js";
|
|
15
16
|
import { ChannelJobs } from "./jobs.js";
|
|
16
17
|
import type { ChannelLocale } from "./phrases.js";
|
|
17
18
|
/** 连接配置里全部密钥字段存成一行:`{ 点路径: 值 }` 的 JSON。 */
|
|
@@ -112,7 +113,7 @@ export declare class ChannelConnections implements OnApplicationShutdown {
|
|
|
112
113
|
/** Secret values handled here (written or decrypted) are redacted from the logs (`addOrgSecrets`). */
|
|
113
114
|
private readonly log;
|
|
114
115
|
readonly txHost: TransactionHost;
|
|
115
|
-
/** The setup guide acks (spec 2026-11-
|
|
116
|
+
/** The setup guide acks (spec 2026-11-09-setup-guides-v2-design.md §11). */
|
|
116
117
|
private readonly guides;
|
|
117
118
|
private readonly unsubscribe;
|
|
118
119
|
/** `<plugin>@<version>` whose invalid connection hints were warned about (a log dedupe only). */
|
|
@@ -126,7 +127,7 @@ export declare class ChannelConnections implements OnApplicationShutdown {
|
|
|
126
127
|
config: Pick<KernelConfigService, "get">, events: Pick<KernelEvents, "on">,
|
|
127
128
|
/** Secret values handled here (written or decrypted) are redacted from the logs (`addOrgSecrets`). */
|
|
128
129
|
log: Pick<KernelLogger, "warn" | "addOrgSecrets">, txHost: TransactionHost,
|
|
129
|
-
/** The setup guide acks (spec 2026-11-
|
|
130
|
+
/** The setup guide acks (spec 2026-11-09-setup-guides-v2-design.md §11). */
|
|
130
131
|
guides: Pick<PluginGuides, "currentAck" | "ackDrifted">);
|
|
131
132
|
/** Unsubscribes after the stop phases: events emitted while they drain are still handled. */
|
|
132
133
|
onApplicationShutdown(): void;
|
|
@@ -186,11 +187,15 @@ export declare class ChannelConnections implements OnApplicationShutdown {
|
|
|
186
187
|
private updateIn;
|
|
187
188
|
/**
|
|
188
189
|
* 「我已更新应用」: records the plugin's current channel-connection guide for the connection's
|
|
189
|
-
* mode as the one it is set up with. `hash` is the
|
|
190
|
+
* mode as the one it is set up with. `hash` is the drift hash of the guide the person was
|
|
190
191
|
* shown; when the plugin has moved on since, 409 `channel.guide_changed` (show the new guide
|
|
191
|
-
* first). A plugin without a guide -> 404
|
|
192
|
+
* first). A plugin without a guide, or whose item the org's catalog does not show -> 404
|
|
193
|
+
* `catalog.guide_not_found`.
|
|
192
194
|
*/
|
|
193
|
-
ackGuide(
|
|
195
|
+
ackGuide(org: {
|
|
196
|
+
orgId: string;
|
|
197
|
+
role: OrgRole;
|
|
198
|
+
}, id: string, hash: string, actorAccountId: string, actorTokenId?: string): Promise<ChannelConnectionView>;
|
|
194
199
|
/** The row of {@link ackGuide}, in its own transaction. */
|
|
195
200
|
private ackIn;
|
|
196
201
|
/** 删除(软删除,同一事务删掉密钥);持有长连接的副本收到广播后断开。 */
|
|
@@ -211,6 +216,18 @@ export declare class ChannelConnections implements OnApplicationShutdown {
|
|
|
211
216
|
* 持有长连接的副本立即断开(扫描复核只是兜底)。只广播,失败只记日志。
|
|
212
217
|
*/
|
|
213
218
|
notifyOrgChanged(orgId: string, plugin?: string): Promise<void>;
|
|
219
|
+
/**
|
|
220
|
+
* For setup checks (setup guides v2 §10.4): the input revision of a live connection of the org
|
|
221
|
+
* on `plugin`: the `updated_at` of its row and of its secrets row (ISO), never a value. Missing,
|
|
222
|
+
* deleted, of another org or plugin -> `channel.not_found`.
|
|
223
|
+
*/
|
|
224
|
+
checkRevision(orgId: string, plugin: string, id: string): Promise<string>;
|
|
225
|
+
/**
|
|
226
|
+
* For setup checks (setup guides v2 §10.8): the stored secret values of a live connection of the
|
|
227
|
+
* org on `plugin` (every secret field, decrypted), what a check's result is redacted by. Missing,
|
|
228
|
+
* deleted, of another org or plugin -> `channel.not_found`. Never echo them.
|
|
229
|
+
*/
|
|
230
|
+
checkSecrets(orgId: string, plugin: string, id: string): Promise<string[]>;
|
|
214
231
|
/**
|
|
215
232
|
* The extension implementation and full config a connection needs to run (decrypted secrets
|
|
216
233
|
* included, validated against `connectionSchema`). Connection missing / deleted ->
|
|
@@ -11,7 +11,7 @@ import { randomUUID } from "node:crypto";
|
|
|
11
11
|
import { CHANNEL_EXTENSION_POINT, } from "@capybara-hq/plugin-sdk";
|
|
12
12
|
import { Inject, Injectable } from "@nestjs/common";
|
|
13
13
|
import { sql } from "kysely";
|
|
14
|
-
import { configFields, getPath, secretValues } from "../config/fields.js";
|
|
14
|
+
import { configFields, getPath, secretScalars, secretValues } from "../config/fields.js";
|
|
15
15
|
import { queryEnabledPlugins } from "../ctx/create-ctx.js";
|
|
16
16
|
import { createSecrets } from "../ctx/secrets.js";
|
|
17
17
|
import { ApiError } from "../errors.js";
|
|
@@ -135,7 +135,7 @@ let ChannelConnections = class ChannelConnections {
|
|
|
135
135
|
config, events,
|
|
136
136
|
/** Secret values handled here (written or decrypted) are redacted from the logs (`addOrgSecrets`). */
|
|
137
137
|
log, txHost,
|
|
138
|
-
/** The setup guide acks (spec 2026-11-
|
|
138
|
+
/** The setup guide acks (spec 2026-11-09-setup-guides-v2-design.md §11). */
|
|
139
139
|
guides) {
|
|
140
140
|
this.db = db;
|
|
141
141
|
this.orgLock = orgLock;
|
|
@@ -336,7 +336,9 @@ let ChannelConnections = class ChannelConnections {
|
|
|
336
336
|
const id = randomUUID();
|
|
337
337
|
const { plain, secrets } = split(schema, full);
|
|
338
338
|
// the guide the 新建渠道 page showed: the plugin's current one, for the default mode
|
|
339
|
-
const guideAck = await this.guides.currentAck(input.plugin, "
|
|
339
|
+
const guideAck = await this.guides.currentAck(input.plugin, "channel-connection", {
|
|
340
|
+
mode: "long_connection",
|
|
341
|
+
});
|
|
340
342
|
const row = await this.insertIn({
|
|
341
343
|
orgId,
|
|
342
344
|
id,
|
|
@@ -440,11 +442,13 @@ let ChannelConnections = class ChannelConnections {
|
|
|
440
442
|
// A mode switch sets the platform app up for the new mode: the guide of that mode is the one
|
|
441
443
|
// on the page. Only an ack without drift moves on: a drifted one stays until the person
|
|
442
444
|
// confirms through guide-ack, so switching modes never clears the update notice (spec
|
|
443
|
-
// 2026-11-
|
|
445
|
+
// 2026-11-09-setup-guides-v2-design.md §11)
|
|
444
446
|
const guideAck = modeChanged &&
|
|
445
447
|
patch.inboundMode !== undefined &&
|
|
446
|
-
!(await this.guides.ackDrifted(current.plugin, current.guide_ack ?? null))
|
|
447
|
-
? await this.guides.currentAck(current.plugin,
|
|
448
|
+
!(await this.guides.ackDrifted(current.plugin, "channel-connection", current.guide_ack ?? null))
|
|
449
|
+
? await this.guides.currentAck(current.plugin, "channel-connection", {
|
|
450
|
+
mode: patch.inboundMode,
|
|
451
|
+
})
|
|
448
452
|
: undefined;
|
|
449
453
|
const row = await this.updateIn({
|
|
450
454
|
orgId,
|
|
@@ -533,15 +537,21 @@ let ChannelConnections = class ChannelConnections {
|
|
|
533
537
|
}
|
|
534
538
|
/**
|
|
535
539
|
* 「我已更新应用」: records the plugin's current channel-connection guide for the connection's
|
|
536
|
-
* mode as the one it is set up with. `hash` is the
|
|
540
|
+
* mode as the one it is set up with. `hash` is the drift hash of the guide the person was
|
|
537
541
|
* shown; when the plugin has moved on since, 409 `channel.guide_changed` (show the new guide
|
|
538
|
-
* first). A plugin without a guide -> 404
|
|
542
|
+
* first). A plugin without a guide, or whose item the org's catalog does not show -> 404
|
|
543
|
+
* `catalog.guide_not_found`.
|
|
539
544
|
*/
|
|
540
|
-
async ackGuide(
|
|
545
|
+
async ackGuide(org, id, hash, actorAccountId, actorTokenId) {
|
|
546
|
+
const { orgId } = org;
|
|
541
547
|
const current = await this.live(orgId).where("id", "=", id).executeTakeFirst();
|
|
542
548
|
if (!current)
|
|
543
549
|
throw notFound();
|
|
544
|
-
|
|
550
|
+
// the guide read's visibility: an item the org's catalog does not show has no guide to ack
|
|
551
|
+
const ack = await this.guides.currentAck(current.plugin, "channel-connection", {
|
|
552
|
+
mode: current.inbound_mode,
|
|
553
|
+
org,
|
|
554
|
+
});
|
|
545
555
|
if (ack === null) {
|
|
546
556
|
throw new ApiError("catalog.guide_not_found", "The plugin has no channel setup guide");
|
|
547
557
|
}
|
|
@@ -671,6 +681,42 @@ let ChannelConnections = class ChannelConnections {
|
|
|
671
681
|
for (const r of rows)
|
|
672
682
|
await this.changed(r.id, true);
|
|
673
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* For setup checks (setup guides v2 §10.4): the input revision of a live connection of the org
|
|
686
|
+
* on `plugin`: the `updated_at` of its row and of its secrets row (ISO), never a value. Missing,
|
|
687
|
+
* deleted, of another org or plugin -> `channel.not_found`.
|
|
688
|
+
*/
|
|
689
|
+
async checkRevision(orgId, plugin, id) {
|
|
690
|
+
const row = await this.live(orgId)
|
|
691
|
+
.where("id", "=", id)
|
|
692
|
+
.where("plugin", "=", plugin)
|
|
693
|
+
.executeTakeFirst();
|
|
694
|
+
if (!row)
|
|
695
|
+
throw notFound();
|
|
696
|
+
const secretRow = await this.db
|
|
697
|
+
.selectFrom("secrets")
|
|
698
|
+
.select("updated_at")
|
|
699
|
+
.where("namespace", "=", SECRETS_NAMESPACE)
|
|
700
|
+
.where("org_id", "=", orgId)
|
|
701
|
+
.where("key", "=", connectionSecretKey(id))
|
|
702
|
+
.executeTakeFirst();
|
|
703
|
+
const iso = (d) => new Date(d).toISOString();
|
|
704
|
+
return `${iso(row.updated_at)}|${secretRow ? iso(secretRow.updated_at) : "-"}`;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* For setup checks (setup guides v2 §10.8): the stored secret values of a live connection of the
|
|
708
|
+
* org on `plugin` (every secret field, decrypted), what a check's result is redacted by. Missing,
|
|
709
|
+
* deleted, of another org or plugin -> `channel.not_found`. Never echo them.
|
|
710
|
+
*/
|
|
711
|
+
async checkSecrets(orgId, plugin, id) {
|
|
712
|
+
const row = await this.live(orgId)
|
|
713
|
+
.where("id", "=", id)
|
|
714
|
+
.where("plugin", "=", plugin)
|
|
715
|
+
.executeTakeFirst();
|
|
716
|
+
if (!row)
|
|
717
|
+
throw notFound();
|
|
718
|
+
return secretScalars(Object.values(await this.readSecrets(orgId, id)));
|
|
719
|
+
}
|
|
674
720
|
/**
|
|
675
721
|
* The extension implementation and full config a connection needs to run (decrypted secrets
|
|
676
722
|
* included, validated against `connectionSchema`). Connection missing / deleted ->
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ChannelWebhookRequest, ChannelWebhookResponse, Logger } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import type { Redis } from "ioredis";
|
|
3
|
+
import { LockService } from "../infra/lock/lock.service.js";
|
|
2
4
|
import { ChannelConnections } from "./connections.service.js";
|
|
3
5
|
import { ChannelInbound } from "./inbound.js";
|
|
4
6
|
/**
|
|
@@ -19,6 +21,10 @@ export interface ChannelWebhookTimings {
|
|
|
19
21
|
export declare const CHANNEL_WEBHOOK_TIMINGS: unique symbol;
|
|
20
22
|
/** Most connections whose runtime is cached in one process. */
|
|
21
23
|
export declare const WEBHOOK_RUNTIME_CACHE_MAX = 1000;
|
|
24
|
+
/** How long a connection's last accepted webhook is remembered (`capybara:webhook-received`). */
|
|
25
|
+
export declare const WEBHOOK_SEEN_TTL_SECONDS: number;
|
|
26
|
+
/** When the gateway last accepted a verified webhook for the connection (ISO time, TTL 7 days). */
|
|
27
|
+
export declare const webhookSeenKey: (connectionId: string) => string;
|
|
22
28
|
/**
|
|
23
29
|
* Headers every webhook response carries; a plugin cannot override them (the body is never meant to
|
|
24
30
|
* be rendered or sniffed by a browser).
|
|
@@ -49,6 +55,10 @@ export declare const WEBHOOK_SECURITY_HEADERS: Readonly<Record<string, string>>;
|
|
|
49
55
|
* for it (`extensionHost.drainSeconds`) before `dispose`; past that the call is aborted → 503
|
|
50
56
|
* `channel.webhook_retry`.
|
|
51
57
|
* Every response carries {@link WEBHOOK_SECURITY_HEADERS}.
|
|
58
|
+
* - An accepted `events` result (every event handed to `onEvent`) sets the marker
|
|
59
|
+
* {@link webhookSeenKey} (the setup check `capybara:webhook-received`), at most once a minute per
|
|
60
|
+
* connection (`LockService.lease`, `SET NX` with a 60 s TTL, on a minute key), in Redis with a TTL, never in PG; a Redis error
|
|
61
|
+
* never fails the request.
|
|
52
62
|
*
|
|
53
63
|
* The decrypted runtime (plugin implementation + config) is cached per connection for a few seconds
|
|
54
64
|
* (bounded, process local), so unauthenticated requests stay cheap before the signature check. It is
|
|
@@ -60,6 +70,9 @@ export declare class ChannelWebhooks {
|
|
|
60
70
|
private readonly connections;
|
|
61
71
|
private readonly inbound;
|
|
62
72
|
private readonly log;
|
|
73
|
+
/** The minute gate of the seen marker (`SET NX` with a TTL). */
|
|
74
|
+
private readonly locks;
|
|
75
|
+
private readonly redis;
|
|
63
76
|
private readonly runtimes;
|
|
64
77
|
/** Bumped by {@link invalidateAll}: a fetch started before the bump is not stored. */
|
|
65
78
|
private generation;
|
|
@@ -67,12 +80,19 @@ export declare class ChannelWebhooks {
|
|
|
67
80
|
private readonly handleTimeoutMs;
|
|
68
81
|
/** How long a connection's runtime is reused; 0 disables the cache. */
|
|
69
82
|
private readonly runtimeCacheMs;
|
|
70
|
-
constructor(connections: Pick<ChannelConnections, "row" | "runtime">, inbound: Pick<ChannelInbound, "onEvent">, log: Pick<Logger, "warn">,
|
|
83
|
+
constructor(connections: Pick<ChannelConnections, "row" | "runtime">, inbound: Pick<ChannelInbound, "onEvent">, log: Pick<Logger, "warn">,
|
|
84
|
+
/** The minute gate of the seen marker (`SET NX` with a TTL). */
|
|
85
|
+
locks: Pick<LockService, "lease">, redis: Pick<Redis, "set">, timings?: Partial<ChannelWebhookTimings>);
|
|
71
86
|
/** Drops the cached runtime of one connection (on `channel.changed`). */
|
|
72
87
|
invalidate(connectionId: string): void;
|
|
73
88
|
/** Drops every cached runtime (a plugin was replaced, revoked or reloaded). */
|
|
74
89
|
invalidateAll(): void;
|
|
75
90
|
handle(connectionId: string, request: ChannelWebhookRequest, signal: AbortSignal): Promise<ChannelWebhookResponse>;
|
|
91
|
+
/**
|
|
92
|
+
* Records that a verified webhook arrived for the connection: the minute gate first (`NX`), then
|
|
93
|
+
* the marker, both with a TTL. Failures are logged and swallowed (the platform got its ack).
|
|
94
|
+
*/
|
|
95
|
+
private markSeen;
|
|
76
96
|
private runtime;
|
|
77
97
|
private respond;
|
|
78
98
|
private respondBefore;
|
package/dist/channels/webhook.js
CHANGED
|
@@ -10,7 +10,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
10
10
|
import { Inject, Injectable, Optional } from "@nestjs/common";
|
|
11
11
|
import { ApiError, errorBody } from "../errors.js";
|
|
12
12
|
import { ExtensionUnloadedError } from "../extensions/inflight.js";
|
|
13
|
-
import {
|
|
13
|
+
import { LockService } from "../infra/lock/lock.service.js";
|
|
14
|
+
import { KERNEL_LOGGER, REDIS } from "../infra/tokens.js";
|
|
14
15
|
import { ChannelConnections } from "./connections.service.js";
|
|
15
16
|
import { ChannelInbound } from "./inbound.js";
|
|
16
17
|
/**
|
|
@@ -24,6 +25,12 @@ export const WEBHOOK_RUNTIME_CACHE_MS = 5_000;
|
|
|
24
25
|
export const CHANNEL_WEBHOOK_TIMINGS = Symbol("CHANNEL_WEBHOOK_TIMINGS");
|
|
25
26
|
/** Most connections whose runtime is cached in one process. */
|
|
26
27
|
export const WEBHOOK_RUNTIME_CACHE_MAX = 1_000;
|
|
28
|
+
/** How long a connection's last accepted webhook is remembered (`capybara:webhook-received`). */
|
|
29
|
+
export const WEBHOOK_SEEN_TTL_SECONDS = 7 * 24 * 3600;
|
|
30
|
+
/** When the gateway last accepted a verified webhook for the connection (ISO time, TTL 7 days). */
|
|
31
|
+
export const webhookSeenKey = (connectionId) => `capk:channel-webhook-seen:${connectionId}`;
|
|
32
|
+
/** The minute gate of {@link webhookSeenKey}: the marker is written at most once a minute. */
|
|
33
|
+
const webhookSeenMinuteKey = (connectionId, minute) => `capk:channel-webhook-seen-min:${connectionId}:${minute}`;
|
|
27
34
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
28
35
|
/**
|
|
29
36
|
* Headers every webhook response carries; a plugin cannot override them (the body is never meant to
|
|
@@ -99,6 +106,10 @@ function raceAbort(p, signal) {
|
|
|
99
106
|
* for it (`extensionHost.drainSeconds`) before `dispose`; past that the call is aborted → 503
|
|
100
107
|
* `channel.webhook_retry`.
|
|
101
108
|
* Every response carries {@link WEBHOOK_SECURITY_HEADERS}.
|
|
109
|
+
* - An accepted `events` result (every event handed to `onEvent`) sets the marker
|
|
110
|
+
* {@link webhookSeenKey} (the setup check `capybara:webhook-received`), at most once a minute per
|
|
111
|
+
* connection (`LockService.lease`, `SET NX` with a 60 s TTL, on a minute key), in Redis with a TTL, never in PG; a Redis error
|
|
112
|
+
* never fails the request.
|
|
102
113
|
*
|
|
103
114
|
* The decrypted runtime (plugin implementation + config) is cached per connection for a few seconds
|
|
104
115
|
* (bounded, process local), so unauthenticated requests stay cheap before the signature check. It is
|
|
@@ -110,6 +121,8 @@ let ChannelWebhooks = class ChannelWebhooks {
|
|
|
110
121
|
connections;
|
|
111
122
|
inbound;
|
|
112
123
|
log;
|
|
124
|
+
locks;
|
|
125
|
+
redis;
|
|
113
126
|
runtimes = new Map();
|
|
114
127
|
/** Bumped by {@link invalidateAll}: a fetch started before the bump is not stored. */
|
|
115
128
|
generation = 0;
|
|
@@ -117,10 +130,14 @@ let ChannelWebhooks = class ChannelWebhooks {
|
|
|
117
130
|
handleTimeoutMs;
|
|
118
131
|
/** How long a connection's runtime is reused; 0 disables the cache. */
|
|
119
132
|
runtimeCacheMs;
|
|
120
|
-
constructor(connections, inbound, log,
|
|
133
|
+
constructor(connections, inbound, log,
|
|
134
|
+
/** The minute gate of the seen marker (`SET NX` with a TTL). */
|
|
135
|
+
locks, redis, timings = {}) {
|
|
121
136
|
this.connections = connections;
|
|
122
137
|
this.inbound = inbound;
|
|
123
138
|
this.log = log;
|
|
139
|
+
this.locks = locks;
|
|
140
|
+
this.redis = redis;
|
|
124
141
|
this.handleTimeoutMs = timings.handleTimeoutMs ?? WEBHOOK_HANDLE_TIMEOUT_MS;
|
|
125
142
|
this.runtimeCacheMs = timings.runtimeCacheMs ?? WEBHOOK_RUNTIME_CACHE_MS;
|
|
126
143
|
}
|
|
@@ -136,6 +153,21 @@ let ChannelWebhooks = class ChannelWebhooks {
|
|
|
136
153
|
async handle(connectionId, request, signal) {
|
|
137
154
|
return secure(await this.respond(connectionId, request, signal));
|
|
138
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Records that a verified webhook arrived for the connection: the minute gate first (`NX`), then
|
|
158
|
+
* the marker, both with a TTL. Failures are logged and swallowed (the platform got its ack).
|
|
159
|
+
*/
|
|
160
|
+
async markSeen(connectionId) {
|
|
161
|
+
try {
|
|
162
|
+
const minute = Math.floor(Date.now() / 60_000);
|
|
163
|
+
if (!(await this.locks.lease(webhookSeenMinuteKey(connectionId, minute), "1", 60_000)))
|
|
164
|
+
return;
|
|
165
|
+
await this.redis.set(webhookSeenKey(connectionId), new Date().toISOString(), "EX", WEBHOOK_SEEN_TTL_SECONDS);
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
this.log.warn("cannot record the channel webhook marker", { connectionId, err });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
139
171
|
async runtime(connectionId, deadline) {
|
|
140
172
|
const ttl = this.runtimeCacheMs;
|
|
141
173
|
const now = Date.now();
|
|
@@ -255,6 +287,7 @@ let ChannelWebhooks = class ChannelWebhooks {
|
|
|
255
287
|
return retryLater();
|
|
256
288
|
}
|
|
257
289
|
}
|
|
290
|
+
await this.markSeen(connectionId);
|
|
258
291
|
return sanitize(result.ack);
|
|
259
292
|
}
|
|
260
293
|
}
|
|
@@ -267,8 +300,10 @@ ChannelWebhooks = __decorate([
|
|
|
267
300
|
__param(0, Inject(ChannelConnections)),
|
|
268
301
|
__param(1, Inject(ChannelInbound)),
|
|
269
302
|
__param(2, Inject(KERNEL_LOGGER)),
|
|
270
|
-
__param(3,
|
|
271
|
-
__param(
|
|
303
|
+
__param(3, Inject(LockService)),
|
|
304
|
+
__param(4, Inject(REDIS)),
|
|
305
|
+
__param(5, Optional()),
|
|
306
|
+
__param(5, Inject(CHANNEL_WEBHOOK_TIMINGS))
|
|
272
307
|
], ChannelWebhooks);
|
|
273
308
|
export { ChannelWebhooks };
|
|
274
309
|
/** Generic codes for the statuses a plugin may reject with (same as the HTTP error filter's). */
|
package/dist/ctx/create-ctx.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ExtensionContext, type ExtensionManifest, type ExtensionPointRef, type Logger, resolveUiSlots, type ToolHandler, type ToolSpec } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import { type ExtensionContext, type ExtensionManifest, type ExtensionPointRef, type Logger, resolveUiSlots, type SetupCheckArgs, type SetupGuidePlace, type ToolHandler, type ToolSpec } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import type { Redis } from "ioredis";
|
|
3
3
|
import { Kysely } from "kysely";
|
|
4
4
|
import { type RegisteredHook } from "../extensions/hooks.js";
|
|
@@ -52,6 +52,15 @@ export type RegisteredTool = {
|
|
|
52
52
|
spec: ToolSpec;
|
|
53
53
|
handler: ToolHandler;
|
|
54
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* A setup check registered through `ctx.setup.check`: `place` and `run` read once from the
|
|
57
|
+
* definition at registration and frozen, so the extension cannot swap them afterwards (a getter
|
|
58
|
+
* on the definition runs once, at registration, never when a check runs).
|
|
59
|
+
*/
|
|
60
|
+
export type RegisteredSetupCheck = Readonly<{
|
|
61
|
+
place: SetupGuidePlace;
|
|
62
|
+
run: (args: SetupCheckArgs) => Promise<unknown>;
|
|
63
|
+
}>;
|
|
55
64
|
/** 插件经 `ctx.extensionPoints` 取用其他插件能力时,内核注入的访问接口。 */
|
|
56
65
|
export interface ExtensionPointAccess {
|
|
57
66
|
/** 已加载(setup 完成)的提供方,按加载顺序;按完整引用(含主版本)精确匹配。 */
|
|
@@ -105,7 +114,11 @@ export type KernelExtensionContext = ExtensionContext & {
|
|
|
105
114
|
uiViews: ReadonlyMap<string, UiRender>;
|
|
106
115
|
/** 经 `ctx.ui.action` 注册的动作处理函数(按贡献 id)。 */
|
|
107
116
|
uiActions: ReadonlyMap<string, UiActionHandler>;
|
|
108
|
-
/**
|
|
117
|
+
/** Setup checks registered through `ctx.setup.check`, by id. */
|
|
118
|
+
setupChecks: ReadonlyMap<string, RegisteredSetupCheck>;
|
|
119
|
+
/**
|
|
120
|
+
* setup 结束后调用:此后 `tools.register`、`ui.view`、`ui.action`、`hooks.on`、`setup.check` 抛错。
|
|
121
|
+
*/
|
|
109
122
|
sealTools(): void;
|
|
110
123
|
/** 中止 signal、封账并逐项撤销仍在账上的登记、关闭队列连接(幂等)。 */
|
|
111
124
|
close(): Promise<void>;
|
|
@@ -128,7 +141,8 @@ export declare class LiveConfig {
|
|
|
128
141
|
* only with `permissions.models` (and only its `embedding` purposes, through `deps.models`); `http.route`
|
|
129
142
|
* only the method + path declared in `permissions.http`; `tools.register` only names declared in
|
|
130
143
|
* `permissions.tools`; `ui.view` / `ui.action` only ids declared in `permissions.ui.views` (during
|
|
131
|
-
* setup only); `
|
|
144
|
+
* setup only); `setup.check` only ids declared in `permissions.setupChecks` (during setup only,
|
|
145
|
+
* `place` and `run` read once and frozen); `ui.invalidate` sends a refresh over the kernel bus (namespace `kernel`), across
|
|
132
146
|
* replicas; `ui.verifyEmbedToken` checks an embed token the kernel signed: `claims.plugin` must be
|
|
133
147
|
* this extension, the extension still enabled in that org and the caller still an active member of
|
|
134
148
|
* the org (org not deleted); returns the current role;
|
package/dist/ctx/create-ctx.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EXTENSION_POINTS, ExtensionPermissionError, ExtensionPointUnavailableError, FILE_PREVIEW_EXTENSION_POINT, OBJECT_STORE_EXTENSION_POINT, parseExtensionPoint, resolveUiSlots, validateFilePreviewProvider, validateToolSpec, } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import { EXTENSION_POINTS, ExtensionPermissionError, ExtensionPointUnavailableError, FILE_PREVIEW_EXTENSION_POINT, OBJECT_STORE_EXTENSION_POINT, parseExtensionPoint, resolveUiSlots, SETUP_CHECK_ID_RE, SETUP_GUIDE_PLACES, validateFilePreviewProvider, validateToolSpec, } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { Command, ConnectionBuilder, ControlledTransactionBuilder, Kysely, TransactionBuilder, } from "kysely";
|
|
3
3
|
import { conversationTopic } from "../conversations/event-log.js";
|
|
4
4
|
import { kernelDb } from "../db/kernel-schema.js";
|
|
@@ -182,7 +182,8 @@ export class LiveConfig {
|
|
|
182
182
|
* only with `permissions.models` (and only its `embedding` purposes, through `deps.models`); `http.route`
|
|
183
183
|
* only the method + path declared in `permissions.http`; `tools.register` only names declared in
|
|
184
184
|
* `permissions.tools`; `ui.view` / `ui.action` only ids declared in `permissions.ui.views` (during
|
|
185
|
-
* setup only); `
|
|
185
|
+
* setup only); `setup.check` only ids declared in `permissions.setupChecks` (during setup only,
|
|
186
|
+
* `place` and `run` read once and frozen); `ui.invalidate` sends a refresh over the kernel bus (namespace `kernel`), across
|
|
186
187
|
* replicas; `ui.verifyEmbedToken` checks an embed token the kernel signed: `claims.plugin` must be
|
|
187
188
|
* this extension, the extension still enabled in that org and the caller still an active member of
|
|
188
189
|
* the org (org not deleted); returns the current role;
|
|
@@ -261,6 +262,8 @@ export function createExtensionContext(deps, manifest, deploymentConfig, extensi
|
|
|
261
262
|
...(manifest.optional ?? []),
|
|
262
263
|
]);
|
|
263
264
|
const uiReg = new PluginUiRegistration(manifest, deny);
|
|
265
|
+
const declaredSetupChecks = new Set(perms.setupChecks ?? []);
|
|
266
|
+
const setupChecks = new Map();
|
|
264
267
|
const embedTokens = deps.embedTokens;
|
|
265
268
|
const uiResolution = resolveUiSlots(manifest.ui);
|
|
266
269
|
const uiSlots = new Map(uiResolution.resolved.map((r) => [r.contribution.id, r.slot]));
|
|
@@ -445,6 +448,49 @@ export function createExtensionContext(deps, manifest, deploymentConfig, extensi
|
|
|
445
448
|
});
|
|
446
449
|
},
|
|
447
450
|
},
|
|
451
|
+
// Setup checks (setup guides v2 §10.2): only during setup, only ids declared in
|
|
452
|
+
// permissions.setupChecks (an undeclared or malformed id is a denial, audited); the kernel's
|
|
453
|
+
// SetupChecksService runs them (bounded, cached, redacted).
|
|
454
|
+
setup: {
|
|
455
|
+
check(id, def) {
|
|
456
|
+
if (toolsSealed) {
|
|
457
|
+
throw new Error(`plugin ${name}: setup.check is only allowed during setup; setup has completed`);
|
|
458
|
+
}
|
|
459
|
+
if (typeof id !== "string" || !SETUP_CHECK_ID_RE.test(id) || !declaredSetupChecks.has(id)) {
|
|
460
|
+
// the item names the id only when it has the id shape (an audit row never carries
|
|
461
|
+
// arbitrary extension text)
|
|
462
|
+
deny(`permissions.setupChecks ${typeof id === "string" && SETUP_CHECK_ID_RE.test(id) ? id : "(invalid id)"}`);
|
|
463
|
+
}
|
|
464
|
+
if (setupChecks.has(id)) {
|
|
465
|
+
throw new Error(`plugin ${name}: duplicate setup check "${id}"`);
|
|
466
|
+
}
|
|
467
|
+
if (typeof def !== "object" || def === null) {
|
|
468
|
+
throw new Error(`plugin ${name}: setup check "${id}" needs a definition object`);
|
|
469
|
+
}
|
|
470
|
+
// read once: what runs later is exactly what was checked here
|
|
471
|
+
const place = def.place;
|
|
472
|
+
const run = def.run;
|
|
473
|
+
if (!SETUP_GUIDE_PLACES.includes(place)) {
|
|
474
|
+
throw new Error(`plugin ${name}: setup check "${id}" place must be one of ${SETUP_GUIDE_PLACES.join(", ")}`);
|
|
475
|
+
}
|
|
476
|
+
if (typeof run !== "function") {
|
|
477
|
+
throw new Error(`plugin ${name}: setup check "${id}" run must be a function`);
|
|
478
|
+
}
|
|
479
|
+
const check = Object.freeze({
|
|
480
|
+
place: place,
|
|
481
|
+
run: run,
|
|
482
|
+
});
|
|
483
|
+
setupChecks.set(id, check);
|
|
484
|
+
ledger.add({
|
|
485
|
+
kind: "setup.check",
|
|
486
|
+
key: id,
|
|
487
|
+
dispose: async () => {
|
|
488
|
+
if (setupChecks.get(id) === check)
|
|
489
|
+
setupChecks.delete(id);
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
},
|
|
493
|
+
},
|
|
448
494
|
hooks: {
|
|
449
495
|
on(mount, handler, opts) {
|
|
450
496
|
if (toolsSealed) {
|
|
@@ -585,6 +631,7 @@ export function createExtensionContext(deps, manifest, deploymentConfig, extensi
|
|
|
585
631
|
uiResolution,
|
|
586
632
|
uiViews: uiReg.views,
|
|
587
633
|
uiActions: uiReg.actions,
|
|
634
|
+
setupChecks,
|
|
588
635
|
ledger,
|
|
589
636
|
async beginDispose(deadline) {
|
|
590
637
|
drainDeadline = deadline;
|
|
@@ -419,6 +419,11 @@ export interface ConnectorsTable {
|
|
|
419
419
|
template_tool_policies: ColumnType<Record<string, "allow" | "ask" | "deny"> | null, string | null | undefined, string | null>;
|
|
420
420
|
/** 0081: the platform connector template this connector was made from (null = none). */
|
|
421
421
|
platform_template_id: ColumnType<string | null, string | null | undefined, string | null>;
|
|
422
|
+
/**
|
|
423
|
+
* 0106: the setup guide of its connector template it was last set up with (`{ guideId, version,
|
|
424
|
+
* hash, mode: null }`); null when unknown (custom connectors, connectors before 0106).
|
|
425
|
+
*/
|
|
426
|
+
guide_ack: ColumnType<GuideAckJson | null, string | null | undefined, string | null>;
|
|
422
427
|
}
|
|
423
428
|
/**
|
|
424
429
|
* 0104: a member's own policy for one tool of a personal-credential connector, for calls made with
|
|
@@ -567,12 +572,16 @@ export interface LgCheckpointWritesTable {
|
|
|
567
572
|
task_path: Generated<string>;
|
|
568
573
|
}
|
|
569
574
|
/** 0018_channels.sql:组织的渠道连接(非密钥配置;密钥在 secrets `channels:<id>:config`)。 */
|
|
570
|
-
/**
|
|
575
|
+
/**
|
|
576
|
+
* `channel_connections.guide_ack` (0099) and `connectors.guide_ack` (0106): the setup guide a
|
|
577
|
+
* target was set up with; `hash` is the v2 drift hash (`setupGuideHash`, admin audience) of
|
|
578
|
+
* `mode` (null for a place without modes).
|
|
579
|
+
*/
|
|
571
580
|
export interface GuideAckJson {
|
|
572
581
|
guideId: string;
|
|
573
582
|
version: string;
|
|
574
583
|
hash: string;
|
|
575
|
-
mode: string;
|
|
584
|
+
mode: string | null;
|
|
576
585
|
}
|
|
577
586
|
export interface ChannelConnectionsTable {
|
|
578
587
|
id: string;
|
package/dist/errors.d.ts
CHANGED
|
@@ -159,6 +159,8 @@ export declare const ERROR_CODES: {
|
|
|
159
159
|
readonly "catalog.not_deployed": 409;
|
|
160
160
|
readonly "catalog.icon_not_found": 404;
|
|
161
161
|
readonly "catalog.guide_not_found": 404;
|
|
162
|
+
/** The check is not in the setup guide rendered for that mode and audience (setup guides v2 §10.3). */
|
|
163
|
+
readonly "catalog.check_not_found": 404;
|
|
162
164
|
readonly "marketplace.not_found": 404;
|
|
163
165
|
readonly "marketplace.entry_not_found": 404;
|
|
164
166
|
readonly "marketplace.fetch_failed": 502;
|
|
@@ -246,6 +248,8 @@ export declare const ERROR_CODES: {
|
|
|
246
248
|
readonly "background_task.output_unavailable": 409;
|
|
247
249
|
readonly "connector.not_found": 404;
|
|
248
250
|
readonly "connector.slug_taken": 409;
|
|
251
|
+
/** The connector template's setup guide moved on since the person was shown it (guide-ack). */
|
|
252
|
+
readonly "connector.guide_changed": 409;
|
|
249
253
|
readonly "connector.kind_unavailable": 409;
|
|
250
254
|
readonly "connector.connect_failed": 502;
|
|
251
255
|
readonly "connector.tool_not_found": 404;
|
package/dist/errors.js
CHANGED
|
@@ -159,6 +159,8 @@ export const ERROR_CODES = {
|
|
|
159
159
|
"catalog.not_deployed": 409,
|
|
160
160
|
"catalog.icon_not_found": 404,
|
|
161
161
|
"catalog.guide_not_found": 404,
|
|
162
|
+
/** The check is not in the setup guide rendered for that mode and audience (setup guides v2 §10.3). */
|
|
163
|
+
"catalog.check_not_found": 404,
|
|
162
164
|
"marketplace.not_found": 404,
|
|
163
165
|
"marketplace.entry_not_found": 404,
|
|
164
166
|
"marketplace.fetch_failed": 502,
|
|
@@ -246,6 +248,8 @@ export const ERROR_CODES = {
|
|
|
246
248
|
"background_task.output_unavailable": 409,
|
|
247
249
|
"connector.not_found": 404,
|
|
248
250
|
"connector.slug_taken": 409,
|
|
251
|
+
/** The connector template's setup guide moved on since the person was shown it (guide-ack). */
|
|
252
|
+
"connector.guide_changed": 409,
|
|
249
253
|
"connector.kind_unavailable": 409,
|
|
250
254
|
"connector.connect_failed": 502,
|
|
251
255
|
"connector.tool_not_found": 404,
|