@capybara-hq/kernel 0.1.0-rc.26 → 0.1.0-rc.28
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/app.js +2 -0
- package/dist/catalog/catalog-content.d.ts +18 -1
- package/dist/catalog/catalog-content.js +5 -2
- package/dist/catalog/catalog-scope.service.js +3 -2
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/org-catalog.service.js +13 -3
- package/dist/catalog/plugin-install.d.ts +37 -16
- package/dist/catalog/plugin-install.js +108 -34
- package/dist/catalog/plugin-upgrade.js +1 -0
- package/dist/channels/actions.d.ts +4 -1
- package/dist/channels/actions.js +5 -2
- package/dist/channels/cards.d.ts +7 -1
- package/dist/channels/cards.js +28 -1
- package/dist/channels/phrases.d.ts +15 -0
- package/dist/channels/phrases.js +9 -0
- package/dist/config/schema.d.ts +8 -0
- package/dist/config/schema.js +69 -0
- package/dist/conversations/events.d.ts +14 -0
- package/dist/conversations/events.js +20 -0
- package/dist/db/kernel-schema.d.ts +19 -4
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +5 -0
- package/dist/git/gateway.d.ts +93 -0
- package/dist/git/gateway.js +750 -0
- package/dist/git/gateway.route.d.ts +16 -0
- package/dist/git/gateway.route.js +41 -0
- package/dist/git/git.module.d.ts +10 -0
- package/dist/git/git.module.js +31 -0
- package/dist/git/identity.d.ts +80 -0
- package/dist/git/identity.js +209 -0
- package/dist/git/pkt-line.d.ts +53 -0
- package/dist/git/pkt-line.js +146 -0
- package/dist/git/run-end.d.ts +29 -0
- package/dist/git/run-end.js +121 -0
- package/dist/git/sources.d.ts +42 -0
- package/dist/git/sources.js +134 -0
- package/dist/git/tickets.d.ts +69 -0
- package/dist/git/tickets.js +194 -0
- package/dist/http/api.module.js +3 -0
- package/dist/http/app.d.ts +6 -0
- package/dist/http/app.js +3 -0
- package/dist/infra/config/kernel-config.service.js +2 -0
- package/dist/orgs/settings.service.d.ts +21 -0
- package/dist/orgs/settings.service.js +18 -0
- package/dist/overrides.d.ts +6 -0
- package/dist/overrides.js +4 -0
- package/dist/platform/exclusions.js +3 -0
- package/dist/runtime/run-finish.d.ts +5 -0
- package/dist/runtime/run-finish.js +12 -3
- package/dist/runtime/runtime.module.js +2 -0
- package/dist/runtime/waits.js +7 -5
- package/dist/schedules/fire.d.ts +3 -3
- package/dist/schedules/fire.js +7 -4
- package/dist/schedules/grant-rules.d.ts +40 -0
- package/dist/schedules/grant-rules.js +83 -0
- package/dist/schedules/scheduled-tasks.service.d.ts +9 -7
- package/dist/schedules/scheduled-tasks.service.js +50 -15
- package/dist/schedules/types.d.ts +2 -0
- package/dist/tools/approvals.service.d.ts +100 -19
- package/dist/tools/approvals.service.js +288 -21
- package/dist/tools/builtin-names.d.ts +4 -2
- package/dist/tools/builtin-names.js +6 -1
- package/dist/tools/builtins/schedules.d.ts +11 -9
- package/dist/tools/builtins/schedules.js +202 -44
- package/dist/tools/connector-account-policies.d.ts +8 -0
- package/dist/tools/connector-account-policies.js +8 -0
- package/dist/tools/connectors.controller.d.ts +2 -0
- package/dist/tools/connectors.controller.js +5 -1
- package/dist/tools/connectors.service.d.ts +46 -3
- package/dist/tools/connectors.service.js +98 -9
- package/dist/tools/credentials.d.ts +18 -0
- package/dist/tools/credentials.js +60 -0
- package/dist/tools/credentials.module.d.ts +7 -0
- package/dist/tools/credentials.module.js +22 -0
- package/dist/tools/git-asks.d.ts +113 -0
- package/dist/tools/git-asks.js +174 -0
- package/dist/tools/git-push-policy.d.ts +89 -0
- package/dist/tools/git-push-policy.js +175 -0
- package/dist/tools/info.d.ts +3 -3
- package/dist/tools/info.js +3 -3
- package/dist/tools/network-asks.d.ts +5 -0
- package/dist/tools/network-asks.js +9 -2
- package/dist/tools/tools.module.js +4 -3
- package/package.json +3 -3
- package/src/migrations/0126_git_gateway.sql +10 -0
- package/src/migrations/0127_git_push_policy.sql +12 -0
- package/src/migrations/0128_scheduled_task_plugin_disabled.sql +10 -0
- package/src/migrations/0129_connector_plugin_disabled.sql +5 -0
package/dist/app.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
import { NestFactory } from "@nestjs/core";
|
|
3
|
+
import { GitGateway } from "./git/gateway.js";
|
|
3
4
|
import { createKernelAdapter } from "./http/adapter.js";
|
|
4
5
|
import { createHttpApp } from "./http/app.js";
|
|
5
6
|
import { NestLogAdapter } from "./http/nest-logger.js";
|
|
@@ -26,6 +27,7 @@ export async function buildKernelApp(config, options = {}) {
|
|
|
26
27
|
log: logger,
|
|
27
28
|
servePlugins: config.roles.includes("api") || config.roles.includes("gateway"),
|
|
28
29
|
...(gateway !== undefined && { gateway }),
|
|
30
|
+
...(config.roles.includes("api") && { git: app.get(GitGateway, { strict: false }) }),
|
|
29
31
|
replicaId: config.http.replicaId === undefined ? undefined : String(config.http.replicaId),
|
|
30
32
|
packageMaxBytes: config.packages.maxBytes,
|
|
31
33
|
}, app));
|
|
@@ -77,6 +77,19 @@ 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
|
+
* On for this org if the plugin is installed (or wanted on by its scope): the template's own
|
|
82
|
+
* switch, its scope and `defaultEnabled` evaluated with the plugin installed, so a default-on
|
|
83
|
+
* template of a plugin this org has not installed reads true too (the install creates its
|
|
84
|
+
* connector).
|
|
85
|
+
*/
|
|
86
|
+
on: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* The org already has a live connector made from this template (a switch-off kept it because an
|
|
89
|
+
* agent uses it): the install takes that connector back instead of creating one, so it does not
|
|
90
|
+
* ask for the template's input.
|
|
91
|
+
*/
|
|
92
|
+
connected: boolean;
|
|
80
93
|
/**
|
|
81
94
|
* The template's setup guide in the item's latest version (`GET …/catalog/:itemId/guides/
|
|
82
95
|
* :guideId`), null without one (spec 2026-11-09-setup-guides-v2-design.md §9.3).
|
|
@@ -118,7 +131,11 @@ export interface OrgCatalogDetail {
|
|
|
118
131
|
* A connector template as an org sees it (the install wizard and the upgrade preview's inputs):
|
|
119
132
|
* presets and editable paths, no credentials.
|
|
120
133
|
*/
|
|
121
|
-
export declare function orgTemplateView(id: string, file: ConnectorTemplateFile,
|
|
134
|
+
export declare function orgTemplateView(id: string, file: ConnectorTemplateFile, o?: {
|
|
135
|
+
guide?: OrgCatalogTemplate["guide"];
|
|
136
|
+
on?: boolean;
|
|
137
|
+
connected?: boolean;
|
|
138
|
+
}): OrgCatalogTemplate;
|
|
122
139
|
/** An item's icon (the latest version's plugin icon). */
|
|
123
140
|
export interface CatalogIcon {
|
|
124
141
|
data: Uint8Array;
|
|
@@ -16,7 +16,7 @@ import { satisfiesRange } from "./versions.js";
|
|
|
16
16
|
* A connector template as an org sees it (the install wizard and the upgrade preview's inputs):
|
|
17
17
|
* presets and editable paths, no credentials.
|
|
18
18
|
*/
|
|
19
|
-
export function orgTemplateView(id, file,
|
|
19
|
+
export function orgTemplateView(id, file, o = {}) {
|
|
20
20
|
const t = file.template;
|
|
21
21
|
return {
|
|
22
22
|
id,
|
|
@@ -30,7 +30,9 @@ 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
|
-
|
|
33
|
+
on: o.on ?? true,
|
|
34
|
+
connected: o.connected ?? false,
|
|
35
|
+
guide: o.guide ?? null,
|
|
34
36
|
};
|
|
35
37
|
}
|
|
36
38
|
const iconNotFound = () => new ApiError("catalog.icon_not_found", "This catalog item has no icon");
|
|
@@ -265,6 +267,7 @@ export function templateConnectorInput(deps, template, ref, input, defaultName,
|
|
|
265
267
|
endpoint,
|
|
266
268
|
auth,
|
|
267
269
|
...(credential !== undefined && { credential }),
|
|
270
|
+
...(template.git !== undefined && { git: template.git }),
|
|
268
271
|
template: {
|
|
269
272
|
...ref,
|
|
270
273
|
...(template.defaultToolPolicies !== undefined && {
|
|
@@ -1067,8 +1067,9 @@ let CatalogScopeService = class CatalogScopeService {
|
|
|
1067
1067
|
orgId: org.id,
|
|
1068
1068
|
orgName: org.name,
|
|
1069
1069
|
agents: p.agents.map((a) => ({ id: a.id, name: a.name })),
|
|
1070
|
-
//
|
|
1071
|
-
|
|
1070
|
+
// A switch-off keeps every connector recorded on the install but disables it with the
|
|
1071
|
+
// plugin (spec §5 rule 8); reused ones (created: false) are disabled too
|
|
1072
|
+
connectorsKept: p.components.filter((c) => c.kind === "connector").length,
|
|
1072
1073
|
});
|
|
1073
1074
|
continue;
|
|
1074
1075
|
}
|
|
@@ -188,7 +188,7 @@ export declare function headsToResolve(entries: readonly MarketplaceEntry[], imp
|
|
|
188
188
|
/** The host versions (an entry's `minKernel` is judged against them). */
|
|
189
189
|
export declare const HOST_VERSIONS: {
|
|
190
190
|
readonly kernel: string;
|
|
191
|
-
readonly sdk: "0.1.0-rc.
|
|
191
|
+
readonly sdk: "0.1.0-rc.21";
|
|
192
192
|
};
|
|
193
193
|
/** Whether this deployment satisfies the entry's `minKernel`. */
|
|
194
194
|
export declare const entryCompatible: (e: Pick<MarketplaceEntry, "minKernel">) => boolean;
|
|
@@ -273,11 +273,17 @@ let OrgCatalogService = class OrgCatalogService {
|
|
|
273
273
|
throw itemNotFound();
|
|
274
274
|
const m = v.manifest;
|
|
275
275
|
const templates = await readCatalogTemplates(this.storage, v);
|
|
276
|
-
//
|
|
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));
|
|
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);
|
|
279
282
|
return g ? { itemId, guideId: g.id } : null;
|
|
280
283
|
};
|
|
284
|
+
// A template whose connector an earlier switch-off kept: the install reuses it, so the wizard
|
|
285
|
+
// does not ask for its input
|
|
286
|
+
const connected = await this.pluginInstaller.connectedTemplates(orgId, itemId);
|
|
281
287
|
// Providers the install would switch on first (a refusal is the install's to report)
|
|
282
288
|
const implied = await this.pluginInstaller
|
|
283
289
|
.impliedProviders(orgId, m, templates, m.dependencies ?? [])
|
|
@@ -290,7 +296,11 @@ let OrgCatalogService = class OrgCatalogService {
|
|
|
290
296
|
item,
|
|
291
297
|
hasIcon: m.icon !== undefined,
|
|
292
298
|
extension: m.extension ? m.name : null,
|
|
293
|
-
templates: templates.map((t) => orgTemplateView(t.id, t.file,
|
|
299
|
+
templates: templates.map((t) => orgTemplateView(t.id, t.file, {
|
|
300
|
+
guide: guideOf(t.id),
|
|
301
|
+
on: on.has(t.id),
|
|
302
|
+
connected: connected.has(t.id),
|
|
303
|
+
})),
|
|
294
304
|
dependencies: [
|
|
295
305
|
...(m.dependencies ?? []).map((d) => ({ name: d.name, range: d.version })),
|
|
296
306
|
...implied.map((d) => ({ name: d.name, range: d.range, implied: true })),
|
|
@@ -298,7 +308,7 @@ let OrgCatalogService = class OrgCatalogService {
|
|
|
298
308
|
skills: (m.skills ?? []).map((s) => packagePath(s.path)),
|
|
299
309
|
agents: (m.agents ?? []).map((a) => packagePath(a.path)),
|
|
300
310
|
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)))
|
|
311
|
+
? ((await this.pluginInstaller.missingConnectors(orgId, itemId))?.templates ?? []).map((t) => orgTemplateView(t.id, t.file, { guide: guideOf(t.id), on: true }))
|
|
302
312
|
: [],
|
|
303
313
|
};
|
|
304
314
|
}
|
|
@@ -56,16 +56,18 @@ export interface UninstallReport {
|
|
|
56
56
|
/** A dependency install still wanted on in the org by its own scope: it stays, standalone. */
|
|
57
57
|
| "wanted_by_scope"
|
|
58
58
|
/**
|
|
59
|
-
* A connector the install created,
|
|
60
|
-
*
|
|
59
|
+
* A connector recorded on the install (created there, or taken back from an earlier
|
|
60
|
+
* switch-off): kept with its endpoint and credentials but switched off with the plugin
|
|
61
|
+
* (spec §5 rule 8); switching the plugin back on switches it back on.
|
|
61
62
|
*/
|
|
62
|
-
| "
|
|
63
|
+
| "disabled_with_plugin";
|
|
63
64
|
}[];
|
|
64
65
|
}
|
|
65
66
|
/**
|
|
66
67
|
* What switching a plugin off would do, read without changing anything: its components, the ones the
|
|
67
|
-
* uninstall rules would keep for a reason visible beforehand (a component that pre-existed,
|
|
68
|
-
* agent uses
|
|
68
|
+
* uninstall rules would keep for a reason visible beforehand (a component that pre-existed, one an
|
|
69
|
+
* agent uses, a connector kept but switched off with the plugin), and the agents that use the
|
|
70
|
+
* plugin's skills, connectors or agent templates.
|
|
69
71
|
*/
|
|
70
72
|
export interface UninstallPreview {
|
|
71
73
|
installId: string;
|
|
@@ -211,7 +213,7 @@ export interface UninstallCtx {
|
|
|
211
213
|
actorTokenId?: string;
|
|
212
214
|
auditDetail?: AuditDetail;
|
|
213
215
|
};
|
|
214
|
-
/** Uninstalled by the plugin scope sync
|
|
216
|
+
/** Uninstalled by the plugin scope sync (a deployment-level disable or a narrowed scope). */
|
|
215
217
|
via?: "scope";
|
|
216
218
|
}
|
|
217
219
|
/**
|
|
@@ -434,7 +436,9 @@ export declare class PluginInstaller {
|
|
|
434
436
|
* lock, one transaction). Removes only components with created=true:
|
|
435
437
|
* - a skill is kept when an agent names it (an inline sub-agent template's `skills`;
|
|
436
438
|
* used_by_agents) or another plugin install in this org uses it too (used_by_other_install);
|
|
437
|
-
* - a connector
|
|
439
|
+
* - a connector recorded on the install (created there, or taken back from an earlier switch-off)
|
|
440
|
+
* is kept with its endpoint and credentials but switched off with the plugin
|
|
441
|
+
* (disabled_with_plugin, spec §5 rule 8); switching the plugin back on switches it back on;
|
|
438
442
|
* - an extension is kept when another plugin install uses it too (used_by_other_install), a
|
|
439
443
|
* connector still uses a connector it provides (used_by_connectors), or another enabled
|
|
440
444
|
* extension depends on it (used_by_plugins);
|
|
@@ -462,7 +466,8 @@ export declare class PluginInstaller {
|
|
|
462
466
|
* The uninstall's one transaction; the extensions it disabled are notified once it commits. A
|
|
463
467
|
* scope sync uninstall (`via: "scope"`) re-checks under the org row lock that the item is still
|
|
464
468
|
* not wanted in the org and that no other live install depends on it ({@link ScopeStillWanted},
|
|
465
|
-
* nothing written)
|
|
469
|
+
* nothing written). Connectors recorded on the install are kept but switched off with the plugin
|
|
470
|
+
* (spec §5 rule 8), whichever uninstall path leads here.
|
|
466
471
|
*/
|
|
467
472
|
private writeUninstall;
|
|
468
473
|
/**
|
|
@@ -487,12 +492,12 @@ export declare class PluginInstaller {
|
|
|
487
492
|
*/
|
|
488
493
|
uninstallIn(orgId: string, actor: string | null, installId: string, ctx: UninstallCtx): Promise<"missing" | "kept" | "removed" | "wanted_by_scope">;
|
|
489
494
|
/**
|
|
490
|
-
* The uninstall rules for one component an install created
|
|
491
|
-
*
|
|
492
|
-
* when an agent uses the skill
|
|
493
|
-
* `removing` has it (`used_by_other_install`, ownership handed over to the oldest
|
|
494
|
-
* uninstall order doesn't change the result), when a connector still uses the
|
|
495
|
-
* connector kind (`used_by_connectors`) or another enabled extension depends on it
|
|
495
|
+
* The uninstall rules for one component an install created, in the caller's transaction (see
|
|
496
|
+
* {@link uninstall}); connectors go through the switch-off branch in {@link uninstallIn} instead,
|
|
497
|
+
* except in a rollback. Kept when an agent uses the skill (`used_by_agents`), when another live
|
|
498
|
+
* install outside `removing` has it (`used_by_other_install`, ownership handed over to the oldest
|
|
499
|
+
* one, so the uninstall order doesn't change the result), when a connector still uses the
|
|
500
|
+
* extension's connector kind (`used_by_connectors`) or another enabled extension depends on it
|
|
496
501
|
* (`used_by_plugins`); otherwise removed (`removed`, or `gone` when it no longer existed). Used
|
|
497
502
|
* by uninstall and by an upgrade's removals.
|
|
498
503
|
*/
|
|
@@ -540,6 +545,20 @@ export declare class PluginInstaller {
|
|
|
540
545
|
file: ConnectorTemplateFile;
|
|
541
546
|
}[];
|
|
542
547
|
} | undefined>;
|
|
548
|
+
/**
|
|
549
|
+
* The org's live connectors made from one of the item's connector templates, oldest first
|
|
550
|
+
* (`templateId` picks one): an uninstall keeps a connector its agents use, and the plugin's next
|
|
551
|
+
* switch-on reuses it.
|
|
552
|
+
*/
|
|
553
|
+
private liveTemplateConnectors;
|
|
554
|
+
/** {@link liveTemplateConnectors} for one template. */
|
|
555
|
+
private liveTemplateConnector;
|
|
556
|
+
/**
|
|
557
|
+
* The org's connector templates it already has a live connector for, by template id: the install
|
|
558
|
+
* takes that connector back instead of creating one, so the org item detail tells the wizard not
|
|
559
|
+
* to ask for their input (the same join as {@link liveTemplateConnector}).
|
|
560
|
+
*/
|
|
561
|
+
connectedTemplates(orgId: string, itemId: string): Promise<Set<string>>;
|
|
543
562
|
/**
|
|
544
563
|
* Creates the connectors an installed plugin is missing (ruling P2-10, `POST …/catalog/:itemId/
|
|
545
564
|
* connectors`): `input.connectors` names some of {@link missingConnectors}' templates with the
|
|
@@ -590,8 +609,10 @@ export declare class PluginInstaller {
|
|
|
590
609
|
* skills and agent templates are platform components (phase 2): the ones on in the org through
|
|
591
610
|
* the plugin (`follow`) go off with it, so the agents using them (by the platform id or an
|
|
592
611
|
* unpinned org copy linked to it) are listed, also for the dependency installs it created that
|
|
593
|
-
* go with it;
|
|
594
|
-
*
|
|
612
|
+
* go with it; the connectors recorded on it are listed as kept but disabled with it
|
|
613
|
+
* (`disabled_with_plugin`) and their users as affected. Ledger rows of earlier installs' org
|
|
614
|
+
* copies are not removed and not listed (an uninstall keeps legacy copies, like the platform
|
|
615
|
+
* update keeps copies of removed components).
|
|
595
616
|
*/
|
|
596
617
|
previewUninstall(orgId: string, installId: string): Promise<UninstallPreview>;
|
|
597
618
|
/**
|
|
@@ -356,8 +356,11 @@ let PluginInstaller = class PluginInstaller {
|
|
|
356
356
|
catch (e) {
|
|
357
357
|
if (!(e instanceof InstallFailure))
|
|
358
358
|
throw e;
|
|
359
|
-
//
|
|
360
|
-
|
|
359
|
+
// Retryable: a dependency another install is still installing, or the connector this install
|
|
360
|
+
// would reuse went away meanwhile: a 409 as it is, with the message saying what to do
|
|
361
|
+
if (e.cause instanceof ApiError &&
|
|
362
|
+
(e.cause.code === "plugin.install_in_progress" ||
|
|
363
|
+
e.cause.code === "catalog.connector_removed")) {
|
|
361
364
|
throw e.cause;
|
|
362
365
|
}
|
|
363
366
|
const dep = e.dependency.length > 0 ? ` in dependency ${e.dependency.join(" > ")}` : "";
|
|
@@ -484,6 +487,10 @@ let PluginInstaller = class PluginInstaller {
|
|
|
484
487
|
// The kernel installs only what needs no org input (the scope sync's `needs_config` rule)
|
|
485
488
|
if (actor === null)
|
|
486
489
|
throw systemNeedsInput(manifest.name);
|
|
490
|
+
// An earlier switch-off kept this template's connector: the install takes it back, so its
|
|
491
|
+
// input is not asked for here (the connector keeps its endpoint and credentials)
|
|
492
|
+
if ((await this.liveTemplateConnector(orgId, itemId, ct.id)) !== undefined)
|
|
493
|
+
continue;
|
|
487
494
|
connectors.set(ct.id, this.planConnector(actor, { itemId, version, name: manifest.name }, ct, input, at, approvedBy));
|
|
488
495
|
}
|
|
489
496
|
}
|
|
@@ -743,9 +750,29 @@ let PluginInstaller = class PluginInstaller {
|
|
|
743
750
|
}
|
|
744
751
|
step = "connectors";
|
|
745
752
|
for (const ct of plan.templates) {
|
|
753
|
+
// An earlier switch-off kept this template's connector (its agents use it): the plugin
|
|
754
|
+
// takes it back instead of creating a second one, whose default slug the first holds
|
|
755
|
+
const existing = await this.liveTemplateConnector(orgId, itemId, ct.id);
|
|
756
|
+
if (existing !== undefined) {
|
|
757
|
+
// Switching the plugin back on switches back on the connectors it switched off; one an
|
|
758
|
+
// admin disabled by hand stays off
|
|
759
|
+
await this.connectors.enableIfPluginDisabledIn(orgId, existing, itemId);
|
|
760
|
+
await record({
|
|
761
|
+
kind: "connector",
|
|
762
|
+
ref: existing,
|
|
763
|
+
created: false,
|
|
764
|
+
skillVersion: null,
|
|
765
|
+
templateId: ct.id,
|
|
766
|
+
});
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
746
769
|
const ci = plan.connectors.get(ct.id);
|
|
747
|
-
if (!ci)
|
|
748
|
-
|
|
770
|
+
if (!ci) {
|
|
771
|
+
// The plan skipped this template's input because a live connector existed, and it went
|
|
772
|
+
// away since (deleted between planning and this step): fail honestly, so the person
|
|
773
|
+
// retries and the new plan asks for the input
|
|
774
|
+
throw new ApiError("catalog.connector_removed", `The connector this install would reuse for connector template ${ct.id} was removed meanwhile; start the install again`);
|
|
775
|
+
}
|
|
749
776
|
if (actor === null)
|
|
750
777
|
throw systemNeedsInput(manifest.name);
|
|
751
778
|
const r = await this.connectors.create(orgId, actor, ci, opts.actorTokenId);
|
|
@@ -1069,7 +1096,9 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1069
1096
|
* lock, one transaction). Removes only components with created=true:
|
|
1070
1097
|
* - a skill is kept when an agent names it (an inline sub-agent template's `skills`;
|
|
1071
1098
|
* used_by_agents) or another plugin install in this org uses it too (used_by_other_install);
|
|
1072
|
-
* - a connector
|
|
1099
|
+
* - a connector recorded on the install (created there, or taken back from an earlier switch-off)
|
|
1100
|
+
* is kept with its endpoint and credentials but switched off with the plugin
|
|
1101
|
+
* (disabled_with_plugin, spec §5 rule 8); switching the plugin back on switches it back on;
|
|
1073
1102
|
* - an extension is kept when another plugin install uses it too (used_by_other_install), a
|
|
1074
1103
|
* connector still uses a connector it provides (used_by_connectors), or another enabled
|
|
1075
1104
|
* extension depends on it (used_by_plugins);
|
|
@@ -1095,7 +1124,8 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1095
1124
|
* The uninstall's one transaction; the extensions it disabled are notified once it commits. A
|
|
1096
1125
|
* scope sync uninstall (`via: "scope"`) re-checks under the org row lock that the item is still
|
|
1097
1126
|
* not wanted in the org and that no other live install depends on it ({@link ScopeStillWanted},
|
|
1098
|
-
* nothing written)
|
|
1127
|
+
* nothing written). Connectors recorded on the install are kept but switched off with the plugin
|
|
1128
|
+
* (spec §5 rule 8), whichever uninstall path leads here.
|
|
1099
1129
|
*/
|
|
1100
1130
|
async writeUninstall(orgId, actor, installId, opts, sw) {
|
|
1101
1131
|
const { via, ...actorOpts } = opts;
|
|
@@ -1296,6 +1326,11 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1296
1326
|
// failed (already rolled back) and a stale installing (a replica crashed midway) are cleaned up
|
|
1297
1327
|
// as a rollback
|
|
1298
1328
|
const rollbackMode = row.status !== "installed" && row.status !== "upgrading";
|
|
1329
|
+
// A dependency install removed by a failed install's (or a crashed upgrade's) rollback removes
|
|
1330
|
+
// what it created, like the rolled-back install itself: it is a rollback, not a switch-off
|
|
1331
|
+
const connectorRollback = rollbackMode ||
|
|
1332
|
+
ctx.reason === "plugin_install_rollback" ||
|
|
1333
|
+
ctx.reason === "plugin_upgrade_cleanup";
|
|
1299
1334
|
const comps = await t
|
|
1300
1335
|
.selectFrom("plugin_install_components")
|
|
1301
1336
|
.select(["kind", "ref", "created", "skill_version"])
|
|
@@ -1338,6 +1373,15 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1338
1373
|
created = now.created;
|
|
1339
1374
|
}
|
|
1340
1375
|
const comp = { kind: c.kind, ref: c.ref, created, skillVersion: c.skill_version };
|
|
1376
|
+
// A connector of a switch-off is kept with its endpoint and credentials but switched off
|
|
1377
|
+
// with the plugin (spec §5 rule 8), whether this install created it or took it back from an
|
|
1378
|
+
// earlier switch-off; switching the plugin back on re-enables it. A rollback keeps removing
|
|
1379
|
+
// what the install created (below).
|
|
1380
|
+
if (c.kind === "connector" && !connectorRollback) {
|
|
1381
|
+
await this.connectors.disableWithPluginIn(orgId, c.ref, row.item_id);
|
|
1382
|
+
keep("disabled_with_plugin");
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1341
1385
|
// Skills and agent templates of earlier installs (org copies, now linked to the platform
|
|
1342
1386
|
// components, phase 2) stay: the plugin's components go off in the org with the install
|
|
1343
1387
|
if ((c.kind === "skill" || c.kind === "agent_template") && !rollbackMode)
|
|
@@ -1365,12 +1409,8 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1365
1409
|
keep("wanted_by_scope");
|
|
1366
1410
|
continue;
|
|
1367
1411
|
}
|
|
1368
|
-
// A scope sync uninstall
|
|
1369
|
-
//
|
|
1370
|
-
if (c.kind === "connector" && ctx.via === "scope" && !rollbackMode) {
|
|
1371
|
-
keep("org_owned");
|
|
1372
|
-
continue;
|
|
1373
|
-
}
|
|
1412
|
+
// A scope sync uninstall is a switch-off too: its connectors are disabled above with the
|
|
1413
|
+
// plugin, like an org admin's switch-off.
|
|
1374
1414
|
const reason = rollbackMode ? "plugin_install_cleanup" : (ctx.reason ?? "plugin_uninstall");
|
|
1375
1415
|
const outcome = await this.decideRemovalIn(orgId, actor, comp, removing, agentRefs, reason);
|
|
1376
1416
|
if (outcome === "removed")
|
|
@@ -1400,12 +1440,12 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1400
1440
|
return "removed";
|
|
1401
1441
|
}
|
|
1402
1442
|
/**
|
|
1403
|
-
* The uninstall rules for one component an install created
|
|
1404
|
-
*
|
|
1405
|
-
* when an agent uses the skill
|
|
1406
|
-
* `removing` has it (`used_by_other_install`, ownership handed over to the oldest
|
|
1407
|
-
* uninstall order doesn't change the result), when a connector still uses the
|
|
1408
|
-
* connector kind (`used_by_connectors`) or another enabled extension depends on it
|
|
1443
|
+
* The uninstall rules for one component an install created, in the caller's transaction (see
|
|
1444
|
+
* {@link uninstall}); connectors go through the switch-off branch in {@link uninstallIn} instead,
|
|
1445
|
+
* except in a rollback. Kept when an agent uses the skill (`used_by_agents`), when another live
|
|
1446
|
+
* install outside `removing` has it (`used_by_other_install`, ownership handed over to the oldest
|
|
1447
|
+
* one, so the uninstall order doesn't change the result), when a connector still uses the
|
|
1448
|
+
* extension's connector kind (`used_by_connectors`) or another enabled extension depends on it
|
|
1409
1449
|
* (`used_by_plugins`); otherwise removed (`removed`, or `gone` when it no longer existed). Used
|
|
1410
1450
|
* by uninstall and by an upgrade's removals.
|
|
1411
1451
|
*/
|
|
@@ -1657,6 +1697,38 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1657
1697
|
templates: all.filter((t) => on.has(t.id) && !made.has(t.id)),
|
|
1658
1698
|
};
|
|
1659
1699
|
}
|
|
1700
|
+
/**
|
|
1701
|
+
* The org's live connectors made from one of the item's connector templates, oldest first
|
|
1702
|
+
* (`templateId` picks one): an uninstall keeps a connector its agents use, and the plugin's next
|
|
1703
|
+
* switch-on reuses it.
|
|
1704
|
+
*/
|
|
1705
|
+
async liveTemplateConnectors(orgId, itemId, templateId) {
|
|
1706
|
+
let q = this.db
|
|
1707
|
+
.selectFrom("connectors as c")
|
|
1708
|
+
.innerJoin("platform_connector_templates as p", "p.id", "c.platform_template_id")
|
|
1709
|
+
.select(["p.template_id", "c.id"])
|
|
1710
|
+
.where("c.org_id", "=", orgId)
|
|
1711
|
+
.where("c.deleted_at", "is", null)
|
|
1712
|
+
.where("p.catalog_item_id", "=", itemId);
|
|
1713
|
+
if (templateId !== undefined)
|
|
1714
|
+
q = q.where("p.template_id", "=", templateId);
|
|
1715
|
+
const rows = await q.orderBy("c.created_at").orderBy("c.id").execute();
|
|
1716
|
+
return rows.map((r) => ({ templateId: r.template_id, connectorId: r.id }));
|
|
1717
|
+
}
|
|
1718
|
+
/** {@link liveTemplateConnectors} for one template. */
|
|
1719
|
+
async liveTemplateConnector(orgId, itemId, templateId) {
|
|
1720
|
+
const [row] = await this.liveTemplateConnectors(orgId, itemId, templateId);
|
|
1721
|
+
return row?.connectorId;
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1724
|
+
* The org's connector templates it already has a live connector for, by template id: the install
|
|
1725
|
+
* takes that connector back instead of creating one, so the org item detail tells the wizard not
|
|
1726
|
+
* to ask for their input (the same join as {@link liveTemplateConnector}).
|
|
1727
|
+
*/
|
|
1728
|
+
async connectedTemplates(orgId, itemId) {
|
|
1729
|
+
const rows = await this.liveTemplateConnectors(orgId, itemId);
|
|
1730
|
+
return new Set(rows.map((r) => r.templateId));
|
|
1731
|
+
}
|
|
1660
1732
|
/**
|
|
1661
1733
|
* Creates the connectors an installed plugin is missing (ruling P2-10, `POST …/catalog/:itemId/
|
|
1662
1734
|
* connectors`): `input.connectors` names some of {@link missingConnectors}' templates with the
|
|
@@ -1851,8 +1923,10 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1851
1923
|
* skills and agent templates are platform components (phase 2): the ones on in the org through
|
|
1852
1924
|
* the plugin (`follow`) go off with it, so the agents using them (by the platform id or an
|
|
1853
1925
|
* unpinned org copy linked to it) are listed, also for the dependency installs it created that
|
|
1854
|
-
* go with it;
|
|
1855
|
-
*
|
|
1926
|
+
* go with it; the connectors recorded on it are listed as kept but disabled with it
|
|
1927
|
+
* (`disabled_with_plugin`) and their users as affected. Ledger rows of earlier installs' org
|
|
1928
|
+
* copies are not removed and not listed (an uninstall keeps legacy copies, like the platform
|
|
1929
|
+
* update keeps copies of removed components).
|
|
1856
1930
|
*/
|
|
1857
1931
|
async previewUninstall(orgId, installId) {
|
|
1858
1932
|
if (!UUID_RE.test(installId))
|
|
@@ -1885,13 +1959,23 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1885
1959
|
};
|
|
1886
1960
|
const kept = [];
|
|
1887
1961
|
/**
|
|
1888
|
-
* The agents using what goes with an install: connectors it
|
|
1889
|
-
*
|
|
1890
|
-
* (`follow`; by the platform id or an unpinned org
|
|
1891
|
-
* created go with it unless their own scope wants
|
|
1962
|
+
* The agents using what goes with an install: the connectors recorded on it (they are kept
|
|
1963
|
+
* but disabled with the plugin, so their tools stop) and the plugin's platform skills and
|
|
1964
|
+
* agent templates on in the org through it (`follow`; by the platform id or an unpinned org
|
|
1965
|
+
* copy linked to it). Dependency installs it created go with it unless their own scope wants
|
|
1966
|
+
* them (Task 7: their components count too).
|
|
1892
1967
|
*/
|
|
1893
1968
|
const visit = async (itemId, rows, top, seen) => {
|
|
1894
1969
|
for (const c of rows) {
|
|
1970
|
+
// Every connector of the install, created here or taken back, is kept and disabled with
|
|
1971
|
+
// the plugin (a rollback's preview is not shown): it is not removed, and its users lose it
|
|
1972
|
+
if (c.kind === "connector") {
|
|
1973
|
+
if (top)
|
|
1974
|
+
kept.push({ kind: c.kind, ref: c.ref, reason: "disabled_with_plugin" });
|
|
1975
|
+
for (const a of agents.filter((x) => x.connectors.has(c.ref)))
|
|
1976
|
+
use(a, c.kind, c.ref);
|
|
1977
|
+
continue;
|
|
1978
|
+
}
|
|
1895
1979
|
if (!c.created) {
|
|
1896
1980
|
if (top)
|
|
1897
1981
|
kept.push({ kind: c.kind, ref: c.ref, reason: "pre_existing" });
|
|
@@ -1916,17 +2000,7 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1916
2000
|
seen.add(c.ref);
|
|
1917
2001
|
const sub = (await ledger(c.ref)).filter((x) => x.kind !== "skill" && x.kind !== "agent_template");
|
|
1918
2002
|
await visit(dep.item_id, sub, false, seen);
|
|
1919
|
-
continue;
|
|
1920
2003
|
}
|
|
1921
|
-
if (c.kind !== "connector")
|
|
1922
|
-
continue;
|
|
1923
|
-
const users = agents.filter((a) => a.connectors.has(c.ref));
|
|
1924
|
-
if (users.length === 0)
|
|
1925
|
-
continue;
|
|
1926
|
-
if (top)
|
|
1927
|
-
kept.push({ kind: c.kind, ref: c.ref, reason: "used_by_agents" });
|
|
1928
|
-
for (const a of users)
|
|
1929
|
-
use(a, c.kind, c.ref);
|
|
1930
2004
|
}
|
|
1931
2005
|
for (const kind of ["skill", "agent_template"]) {
|
|
1932
2006
|
const platform = await loadComponents(t, kind, { itemId });
|
|
@@ -35,7 +35,10 @@ export declare class ChannelCardActions {
|
|
|
35
35
|
private readonly deps;
|
|
36
36
|
constructor(deps: ChannelCardActionsDeps);
|
|
37
37
|
handle(connectionId: string, e: CardActionEvent): Promise<void>;
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Whether the approval `id` of the org is a network or git push ask (each the asked person's
|
|
40
|
+
* own, decided by them in person).
|
|
41
|
+
*/
|
|
39
42
|
private isNetworkAsk;
|
|
40
43
|
}
|
|
41
44
|
export {};
|
package/dist/channels/actions.js
CHANGED
|
@@ -130,7 +130,10 @@ export class ChannelCardActions {
|
|
|
130
130
|
// row is the child's, which has no delivery of its own
|
|
131
131
|
await this.deps.outbound.enqueue(card.conversation_id);
|
|
132
132
|
}
|
|
133
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* Whether the approval `id` of the org is a network or git push ask (each the asked person's
|
|
135
|
+
* own, decided by them in person).
|
|
136
|
+
*/
|
|
134
137
|
async isNetworkAsk(orgId, id) {
|
|
135
138
|
const r = await this.deps.db
|
|
136
139
|
.selectFrom("approvals")
|
|
@@ -138,6 +141,6 @@ export class ChannelCardActions {
|
|
|
138
141
|
.where("id", "=", id)
|
|
139
142
|
.where("org_id", "=", orgId)
|
|
140
143
|
.executeTakeFirst();
|
|
141
|
-
return r?.kind === "network";
|
|
144
|
+
return r?.kind === "network" || r?.kind === "git";
|
|
142
145
|
}
|
|
143
146
|
}
|
package/dist/channels/cards.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ChannelCard, type FormAnswer, type InputForm, type OutboundMessage } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import type { KernelSchemaDb } from "../db/kernel-schema.js";
|
|
3
|
-
import type { ApprovalsService, ApprovalView, NetworkAskView } from "../tools/approvals.service.js";
|
|
3
|
+
import type { ApprovalsService, ApprovalView, GitAskView, NetworkAskView } from "../tools/approvals.service.js";
|
|
4
4
|
import { type ToolInfo } from "../tools/info.js";
|
|
5
5
|
import type { ToolRegistry } from "../tools/registry.js";
|
|
6
6
|
import { type ChannelLocale } from "./phrases.js";
|
|
@@ -62,6 +62,12 @@ export interface NetworkCardText {
|
|
|
62
62
|
* are redacted by `redact` (the connection's secret values); the description stays on the web.
|
|
63
63
|
*/
|
|
64
64
|
export declare function networkCardText(detail: Pick<NetworkAskView, "hosts" | "command">, locale: ChannelLocale, redact?: (s: string) => string): NetworkCardText;
|
|
65
|
+
/**
|
|
66
|
+
* A git push ask's card text (sandbox git credentials spec §7): the title names the repository;
|
|
67
|
+
* the preview is `<host>/<repo>`, the refs (`refs/heads/` dropped) and the command clipped to
|
|
68
|
+
* {@link CARD_SUMMARY_MAX}. Model-controlled values are redacted by `redact`.
|
|
69
|
+
*/
|
|
70
|
+
export declare function gitCardText(detail: Pick<GitAskView, "host" | "repo" | "refs" | "command">, locale: ChannelLocale, redact?: (s: string) => string): NetworkCardText;
|
|
65
71
|
/**
|
|
66
72
|
* The approval card in the approval's current state; undefined when the approval does not exist.
|
|
67
73
|
* A role escalation offers only "once". A network ask names its hosts and command and labels its
|
package/dist/channels/cards.js
CHANGED
|
@@ -194,6 +194,28 @@ export function networkCardText(detail, locale, redact = (s) => s) {
|
|
|
194
194
|
},
|
|
195
195
|
};
|
|
196
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* A git push ask's card text (sandbox git credentials spec §7): the title names the repository;
|
|
199
|
+
* the preview is `<host>/<repo>`, the refs (`refs/heads/` dropped) and the command clipped to
|
|
200
|
+
* {@link CARD_SUMMARY_MAX}. Model-controlled values are redacted by `redact`.
|
|
201
|
+
*/
|
|
202
|
+
export function gitCardText(detail, locale, redact = (s) => s) {
|
|
203
|
+
const repo = redact(detail.repo);
|
|
204
|
+
const refs = detail.refs.map((r) => redact(r.replace(/^refs\/heads\//, "")));
|
|
205
|
+
const command = cut(redact((detail.command ?? "").trim()), CARD_SUMMARY_MAX);
|
|
206
|
+
return {
|
|
207
|
+
title: phrase("gitTitle", locale).replace("{repo}", repo),
|
|
208
|
+
inputPreview: [
|
|
209
|
+
`${redact(detail.host)}/${repo}`,
|
|
210
|
+
...(refs.length > 0 ? [refs.join(", ")] : []),
|
|
211
|
+
...(command !== "" ? ["", command] : []),
|
|
212
|
+
].join("\n"),
|
|
213
|
+
scopeLabels: {
|
|
214
|
+
once: phrase("gitAllowOnce", locale),
|
|
215
|
+
always: phrase("gitAllowAlways", locale),
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
197
219
|
/**
|
|
198
220
|
* The approval card in the approval's current state; undefined when the approval does not exist.
|
|
199
221
|
* A role escalation offers only "once". A network ask names its hosts and command and labels its
|
|
@@ -204,7 +226,12 @@ export async function approvalCard(d, approvalId, base, locale, redact) {
|
|
|
204
226
|
if (!v)
|
|
205
227
|
return undefined;
|
|
206
228
|
const decidedBy = await displayName(d.db, v.decidedBy);
|
|
207
|
-
|
|
229
|
+
// a network or git ask names what it asks about instead of a tool call (same card shape)
|
|
230
|
+
const network = v.kind === "network" && v.detail !== null
|
|
231
|
+
? networkCardText(v.detail, locale, redact)
|
|
232
|
+
: v.kind === "git" && v.git != null
|
|
233
|
+
? gitCardText(v.git, locale, redact)
|
|
234
|
+
: null;
|
|
208
235
|
const display = v.summary === null && network === null
|
|
209
236
|
? (await toolInfo({ db: d.db, registry: d.registry }, v.orgId, [v.toolKey]))[0]
|
|
210
237
|
: undefined;
|
|
@@ -116,6 +116,21 @@ export declare const CHANNEL_PHRASES: {
|
|
|
116
116
|
readonly zh: "这台设备一直允许";
|
|
117
117
|
readonly en: "Always allow on this device";
|
|
118
118
|
};
|
|
119
|
+
/** A git push ask's card title (`{repo}`; sandbox git credentials spec §7). */
|
|
120
|
+
readonly gitTitle: {
|
|
121
|
+
readonly zh: "智能体想推送到 {repo},允许吗?";
|
|
122
|
+
readonly en: "The agent wants to push to {repo}. Allow?";
|
|
123
|
+
};
|
|
124
|
+
/** A git push ask's 「once」 button. */
|
|
125
|
+
readonly gitAllowOnce: {
|
|
126
|
+
readonly zh: "这次允许";
|
|
127
|
+
readonly en: "Allow this time";
|
|
128
|
+
};
|
|
129
|
+
/** A git push ask's 「always」 button: the person's standing allow for the repository. */
|
|
130
|
+
readonly gitAllowAlways: {
|
|
131
|
+
readonly zh: "这个仓库一直允许";
|
|
132
|
+
readonly en: "Always allow this repository";
|
|
133
|
+
};
|
|
119
134
|
/** 提问卡片的随附文本。 */
|
|
120
135
|
readonly questionTitle: {
|
|
121
136
|
readonly zh: "需要你的回答";
|