@capybara-hq/kernel 0.1.0-rc.26 → 0.1.0-rc.27

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.
@@ -77,6 +77,11 @@ export interface OrgCatalogTemplate {
77
77
  * install wizard does not ask for it (an org admin sets it up when switching it on).
78
78
  */
79
79
  defaultOff: boolean;
80
+ /**
81
+ * The template is on in this org right now (a default-off one an admin switched on): the install
82
+ * asks for its input too, since the install is what creates its connector.
83
+ */
84
+ on: boolean;
80
85
  /**
81
86
  * The template's setup guide in the item's latest version (`GET …/catalog/:itemId/guides/
82
87
  * :guideId`), null without one (spec 2026-11-09-setup-guides-v2-design.md §9.3).
@@ -118,7 +123,7 @@ export interface OrgCatalogDetail {
118
123
  * A connector template as an org sees it (the install wizard and the upgrade preview's inputs):
119
124
  * presets and editable paths, no credentials.
120
125
  */
121
- export declare function orgTemplateView(id: string, file: ConnectorTemplateFile, guide?: OrgCatalogTemplate["guide"]): OrgCatalogTemplate;
126
+ export declare function orgTemplateView(id: string, file: ConnectorTemplateFile, guide?: OrgCatalogTemplate["guide"], on?: boolean): OrgCatalogTemplate;
122
127
  /** An item's icon (the latest version's plugin icon). */
123
128
  export interface CatalogIcon {
124
129
  data: Uint8Array;
@@ -16,7 +16,7 @@ import { satisfiesRange } from "./versions.js";
16
16
  * A connector template as an org sees it (the install wizard and the upgrade preview's inputs):
17
17
  * presets and editable paths, no credentials.
18
18
  */
19
- export function orgTemplateView(id, file, guide = null) {
19
+ export function orgTemplateView(id, file, guide = null, on = true) {
20
20
  const t = file.template;
21
21
  return {
22
22
  id,
@@ -30,6 +30,7 @@ export function orgTemplateView(id, file, guide = null) {
30
30
  ownership: t.ownership ??
31
31
  (typeof t.auth.ownership === "string" ? t.auth.ownership : null),
32
32
  defaultOff: file.defaultEnabled === false,
33
+ on,
33
34
  guide,
34
35
  };
35
36
  }
@@ -273,6 +273,9 @@ let OrgCatalogService = class OrgCatalogService {
273
273
  throw itemNotFound();
274
274
  const m = v.manifest;
275
275
  const templates = await readCatalogTemplates(this.storage, v);
276
+ // Which templates are on in this org now: the install asks for the input of those, a
277
+ // default-off one an admin switched on included
278
+ const on = await connectorTemplatesOn(this.db, orgId, itemId, templates.map((t) => t.id));
276
279
  // where each template's page finds its setup guide (spec 2026-11-09-setup-guides-v2 §9.3)
277
280
  const guideOf = (templateId) => {
278
281
  const g = templateGuideOf(m, templateId);
@@ -290,7 +293,7 @@ let OrgCatalogService = class OrgCatalogService {
290
293
  item,
291
294
  hasIcon: m.icon !== undefined,
292
295
  extension: m.extension ? m.name : null,
293
- templates: templates.map((t) => orgTemplateView(t.id, t.file, guideOf(t.id))),
296
+ templates: templates.map((t) => orgTemplateView(t.id, t.file, guideOf(t.id), on.has(t.id))),
294
297
  dependencies: [
295
298
  ...(m.dependencies ?? []).map((d) => ({ name: d.name, range: d.version })),
296
299
  ...implied.map((d) => ({ name: d.name, range: d.range, implied: true })),
@@ -298,7 +301,7 @@ let OrgCatalogService = class OrgCatalogService {
298
301
  skills: (m.skills ?? []).map((s) => packagePath(s.path)),
299
302
  agents: (m.agents ?? []).map((a) => packagePath(a.path)),
300
303
  missingTemplates: item.installed !== null && item.state === "needs_config"
301
- ? ((await this.pluginInstaller.missingConnectors(orgId, itemId))?.templates ?? []).map((t) => orgTemplateView(t.id, t.file, guideOf(t.id)))
304
+ ? ((await this.pluginInstaller.missingConnectors(orgId, itemId))?.templates ?? []).map((t) => orgTemplateView(t.id, t.file, guideOf(t.id), true))
302
305
  : [],
303
306
  };
304
307
  }
@@ -540,6 +540,11 @@ export declare class PluginInstaller {
540
540
  file: ConnectorTemplateFile;
541
541
  }[];
542
542
  } | undefined>;
543
+ /**
544
+ * The org's live connector made from one of the plugin's connector templates, if any: an
545
+ * uninstall keeps a connector its agents use, and the plugin's next switch-on reuses it.
546
+ */
547
+ private liveTemplateConnector;
543
548
  /**
544
549
  * Creates the connectors an installed plugin is missing (ruling P2-10, `POST …/catalog/:itemId/
545
550
  * connectors`): `input.connectors` names some of {@link missingConnectors}' templates with the
@@ -484,6 +484,10 @@ let PluginInstaller = class PluginInstaller {
484
484
  // The kernel installs only what needs no org input (the scope sync's `needs_config` rule)
485
485
  if (actor === null)
486
486
  throw systemNeedsInput(manifest.name);
487
+ // An earlier switch-off kept this template's connector: the install takes it back, so its
488
+ // input is not asked for here (the connector keeps its endpoint and credentials)
489
+ if ((await this.liveTemplateConnector(orgId, itemId, ct.id)) !== undefined)
490
+ continue;
487
491
  connectors.set(ct.id, this.planConnector(actor, { itemId, version, name: manifest.name }, ct, input, at, approvedBy));
488
492
  }
489
493
  }
@@ -743,9 +747,24 @@ let PluginInstaller = class PluginInstaller {
743
747
  }
744
748
  step = "connectors";
745
749
  for (const ct of plan.templates) {
750
+ // An earlier switch-off kept this template's connector (its agents use it): the plugin
751
+ // takes it back instead of creating a second one, whose default slug the first holds
752
+ const existing = await this.liveTemplateConnector(orgId, itemId, ct.id);
753
+ if (existing !== undefined) {
754
+ await record({
755
+ kind: "connector",
756
+ ref: existing,
757
+ created: false,
758
+ skillVersion: null,
759
+ templateId: ct.id,
760
+ });
761
+ continue;
762
+ }
746
763
  const ci = plan.connectors.get(ct.id);
764
+ // No input was needed (a live connector was taken back above, or the template was skipped
765
+ // at plan time): nothing to create
747
766
  if (!ci)
748
- throw new ApiError("internal", `Connector template ${ct.id} was not planned`);
767
+ continue;
749
768
  if (actor === null)
750
769
  throw systemNeedsInput(manifest.name);
751
770
  const r = await this.connectors.create(orgId, actor, ci, opts.actorTokenId);
@@ -1657,6 +1676,23 @@ let PluginInstaller = class PluginInstaller {
1657
1676
  templates: all.filter((t) => on.has(t.id) && !made.has(t.id)),
1658
1677
  };
1659
1678
  }
1679
+ /**
1680
+ * The org's live connector made from one of the plugin's connector templates, if any: an
1681
+ * uninstall keeps a connector its agents use, and the plugin's next switch-on reuses it.
1682
+ */
1683
+ async liveTemplateConnector(orgId, itemId, templateId) {
1684
+ const row = await this.db
1685
+ .selectFrom("connectors as c")
1686
+ .innerJoin("platform_connector_templates as p", "p.id", "c.platform_template_id")
1687
+ .select("c.id")
1688
+ .where("c.org_id", "=", orgId)
1689
+ .where("c.deleted_at", "is", null)
1690
+ .where("p.catalog_item_id", "=", itemId)
1691
+ .where("p.template_id", "=", templateId)
1692
+ .orderBy("c.created_at")
1693
+ .executeTakeFirst();
1694
+ return row?.id;
1695
+ }
1660
1696
  /**
1661
1697
  * Creates the connectors an installed plugin is missing (ruling P2-10, `POST …/catalog/:itemId/
1662
1698
  * connectors`): `input.connectors` names some of {@link missingConnectors}' templates with the
@@ -287,10 +287,6 @@ let ConnectorsService = class ConnectorsService {
287
287
  checkCreateInput(actorAccountId, input) {
288
288
  const slug = this.resolveSlug(input);
289
289
  const endpoint = this.parseEndpoint(input.connector, input.endpoint);
290
- // a defaulted slug skips plugin names itself (`freeName`)
291
- if (!input.slugDefault && this.extensions.manifests().some((m) => m.name === slug)) {
292
- throw fail("slug", `Slug "${slug}" is the name of an installed plugin; choose another`);
293
- }
294
290
  const auth = input.auth;
295
291
  const credential = input.credential ?? undefined;
296
292
  if (!isOAuth(auth)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capybara-hq/kernel",
3
- "version": "0.1.0-rc.26",
3
+ "version": "0.1.0-rc.27",
4
4
  "description": "Capybara kernel: config, database, plugin host and HTTP API of the Capybara agent platform",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {