@capybara-hq/kernel 0.1.0-rc.27 → 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 +15 -3
- package/dist/catalog/catalog-content.js +5 -3
- 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 +10 -3
- package/dist/catalog/plugin-install.d.ts +34 -18
- package/dist/catalog/plugin-install.js +84 -46
- 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 -5
- 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));
|
|
@@ -78,10 +78,18 @@ export interface OrgCatalogTemplate {
|
|
|
78
78
|
*/
|
|
79
79
|
defaultOff: boolean;
|
|
80
80
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
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).
|
|
83
85
|
*/
|
|
84
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;
|
|
85
93
|
/**
|
|
86
94
|
* The template's setup guide in the item's latest version (`GET …/catalog/:itemId/guides/
|
|
87
95
|
* :guideId`), null without one (spec 2026-11-09-setup-guides-v2-design.md §9.3).
|
|
@@ -123,7 +131,11 @@ export interface OrgCatalogDetail {
|
|
|
123
131
|
* A connector template as an org sees it (the install wizard and the upgrade preview's inputs):
|
|
124
132
|
* presets and editable paths, no credentials.
|
|
125
133
|
*/
|
|
126
|
-
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;
|
|
127
139
|
/** An item's icon (the latest version's plugin icon). */
|
|
128
140
|
export interface CatalogIcon {
|
|
129
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,8 +30,9 @@ export function orgTemplateView(id, file, guide = null, on = true) {
|
|
|
30
30
|
ownership: t.ownership ??
|
|
31
31
|
(typeof t.auth.ownership === "string" ? t.auth.ownership : null),
|
|
32
32
|
defaultOff: file.defaultEnabled === false,
|
|
33
|
-
on,
|
|
34
|
-
|
|
33
|
+
on: o.on ?? true,
|
|
34
|
+
connected: o.connected ?? false,
|
|
35
|
+
guide: o.guide ?? null,
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
const iconNotFound = () => new ApiError("catalog.icon_not_found", "This catalog item has no icon");
|
|
@@ -266,6 +267,7 @@ export function templateConnectorInput(deps, template, ref, input, defaultName,
|
|
|
266
267
|
endpoint,
|
|
267
268
|
auth,
|
|
268
269
|
...(credential !== undefined && { credential }),
|
|
270
|
+
...(template.git !== undefined && { git: template.git }),
|
|
269
271
|
template: {
|
|
270
272
|
...ref,
|
|
271
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;
|
|
@@ -276,11 +276,14 @@ let OrgCatalogService = class OrgCatalogService {
|
|
|
276
276
|
// Which templates are on in this org now: the install asks for the input of those, a
|
|
277
277
|
// default-off one an admin switched on included
|
|
278
278
|
const on = await connectorTemplatesOn(this.db, orgId, itemId, templates.map((t) => t.id));
|
|
279
|
-
//
|
|
279
|
+
// Where each template's page finds its setup guide (spec 2026-11-09-setup-guides-v2 §9.3)
|
|
280
280
|
const guideOf = (templateId) => {
|
|
281
281
|
const g = templateGuideOf(m, templateId);
|
|
282
282
|
return g ? { itemId, guideId: g.id } : null;
|
|
283
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);
|
|
284
287
|
// Providers the install would switch on first (a refusal is the install's to report)
|
|
285
288
|
const implied = await this.pluginInstaller
|
|
286
289
|
.impliedProviders(orgId, m, templates, m.dependencies ?? [])
|
|
@@ -293,7 +296,11 @@ let OrgCatalogService = class OrgCatalogService {
|
|
|
293
296
|
item,
|
|
294
297
|
hasIcon: m.icon !== undefined,
|
|
295
298
|
extension: m.extension ? m.name : null,
|
|
296
|
-
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
|
+
})),
|
|
297
304
|
dependencies: [
|
|
298
305
|
...(m.dependencies ?? []).map((d) => ({ name: d.name, range: d.version })),
|
|
299
306
|
...implied.map((d) => ({ name: d.name, range: d.range, implied: true })),
|
|
@@ -301,7 +308,7 @@ let OrgCatalogService = class OrgCatalogService {
|
|
|
301
308
|
skills: (m.skills ?? []).map((s) => packagePath(s.path)),
|
|
302
309
|
agents: (m.agents ?? []).map((a) => packagePath(a.path)),
|
|
303
310
|
missingTemplates: item.installed !== null && item.state === "needs_config"
|
|
304
|
-
? ((await this.pluginInstaller.missingConnectors(orgId, itemId))?.templates ?? []).map((t) => orgTemplateView(t.id, t.file, guideOf(t.id), true))
|
|
311
|
+
? ((await this.pluginInstaller.missingConnectors(orgId, itemId))?.templates ?? []).map((t) => orgTemplateView(t.id, t.file, { guide: guideOf(t.id), on: true }))
|
|
305
312
|
: [],
|
|
306
313
|
};
|
|
307
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
|
*/
|
|
@@ -541,10 +546,19 @@ export declare class PluginInstaller {
|
|
|
541
546
|
}[];
|
|
542
547
|
} | undefined>;
|
|
543
548
|
/**
|
|
544
|
-
* The org's live
|
|
545
|
-
* uninstall keeps a connector its agents use, and the plugin's next
|
|
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.
|
|
546
552
|
*/
|
|
553
|
+
private liveTemplateConnectors;
|
|
554
|
+
/** {@link liveTemplateConnectors} for one template. */
|
|
547
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>>;
|
|
548
562
|
/**
|
|
549
563
|
* Creates the connectors an installed plugin is missing (ruling P2-10, `POST …/catalog/:itemId/
|
|
550
564
|
* connectors`): `input.connectors` names some of {@link missingConnectors}' templates with the
|
|
@@ -595,8 +609,10 @@ export declare class PluginInstaller {
|
|
|
595
609
|
* skills and agent templates are platform components (phase 2): the ones on in the org through
|
|
596
610
|
* the plugin (`follow`) go off with it, so the agents using them (by the platform id or an
|
|
597
611
|
* unpinned org copy linked to it) are listed, also for the dependency installs it created that
|
|
598
|
-
* go with it;
|
|
599
|
-
*
|
|
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).
|
|
600
616
|
*/
|
|
601
617
|
previewUninstall(orgId: string, installId: string): Promise<UninstallPreview>;
|
|
602
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(" > ")}` : "";
|
|
@@ -751,6 +754,9 @@ let PluginInstaller = class PluginInstaller {
|
|
|
751
754
|
// takes it back instead of creating a second one, whose default slug the first holds
|
|
752
755
|
const existing = await this.liveTemplateConnector(orgId, itemId, ct.id);
|
|
753
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);
|
|
754
760
|
await record({
|
|
755
761
|
kind: "connector",
|
|
756
762
|
ref: existing,
|
|
@@ -761,10 +767,12 @@ let PluginInstaller = class PluginInstaller {
|
|
|
761
767
|
continue;
|
|
762
768
|
}
|
|
763
769
|
const ci = plan.connectors.get(ct.id);
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
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
|
+
}
|
|
768
776
|
if (actor === null)
|
|
769
777
|
throw systemNeedsInput(manifest.name);
|
|
770
778
|
const r = await this.connectors.create(orgId, actor, ci, opts.actorTokenId);
|
|
@@ -1088,7 +1096,9 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1088
1096
|
* lock, one transaction). Removes only components with created=true:
|
|
1089
1097
|
* - a skill is kept when an agent names it (an inline sub-agent template's `skills`;
|
|
1090
1098
|
* used_by_agents) or another plugin install in this org uses it too (used_by_other_install);
|
|
1091
|
-
* - 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;
|
|
1092
1102
|
* - an extension is kept when another plugin install uses it too (used_by_other_install), a
|
|
1093
1103
|
* connector still uses a connector it provides (used_by_connectors), or another enabled
|
|
1094
1104
|
* extension depends on it (used_by_plugins);
|
|
@@ -1114,7 +1124,8 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1114
1124
|
* The uninstall's one transaction; the extensions it disabled are notified once it commits. A
|
|
1115
1125
|
* scope sync uninstall (`via: "scope"`) re-checks under the org row lock that the item is still
|
|
1116
1126
|
* not wanted in the org and that no other live install depends on it ({@link ScopeStillWanted},
|
|
1117
|
-
* 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.
|
|
1118
1129
|
*/
|
|
1119
1130
|
async writeUninstall(orgId, actor, installId, opts, sw) {
|
|
1120
1131
|
const { via, ...actorOpts } = opts;
|
|
@@ -1315,6 +1326,11 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1315
1326
|
// failed (already rolled back) and a stale installing (a replica crashed midway) are cleaned up
|
|
1316
1327
|
// as a rollback
|
|
1317
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";
|
|
1318
1334
|
const comps = await t
|
|
1319
1335
|
.selectFrom("plugin_install_components")
|
|
1320
1336
|
.select(["kind", "ref", "created", "skill_version"])
|
|
@@ -1357,6 +1373,15 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1357
1373
|
created = now.created;
|
|
1358
1374
|
}
|
|
1359
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
|
+
}
|
|
1360
1385
|
// Skills and agent templates of earlier installs (org copies, now linked to the platform
|
|
1361
1386
|
// components, phase 2) stay: the plugin's components go off in the org with the install
|
|
1362
1387
|
if ((c.kind === "skill" || c.kind === "agent_template") && !rollbackMode)
|
|
@@ -1384,12 +1409,8 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1384
1409
|
keep("wanted_by_scope");
|
|
1385
1410
|
continue;
|
|
1386
1411
|
}
|
|
1387
|
-
// A scope sync uninstall
|
|
1388
|
-
//
|
|
1389
|
-
if (c.kind === "connector" && ctx.via === "scope" && !rollbackMode) {
|
|
1390
|
-
keep("org_owned");
|
|
1391
|
-
continue;
|
|
1392
|
-
}
|
|
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.
|
|
1393
1414
|
const reason = rollbackMode ? "plugin_install_cleanup" : (ctx.reason ?? "plugin_uninstall");
|
|
1394
1415
|
const outcome = await this.decideRemovalIn(orgId, actor, comp, removing, agentRefs, reason);
|
|
1395
1416
|
if (outcome === "removed")
|
|
@@ -1419,12 +1440,12 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1419
1440
|
return "removed";
|
|
1420
1441
|
}
|
|
1421
1442
|
/**
|
|
1422
|
-
* The uninstall rules for one component an install created
|
|
1423
|
-
*
|
|
1424
|
-
* when an agent uses the skill
|
|
1425
|
-
* `removing` has it (`used_by_other_install`, ownership handed over to the oldest
|
|
1426
|
-
* uninstall order doesn't change the result), when a connector still uses the
|
|
1427
|
-
* 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
|
|
1428
1449
|
* (`used_by_plugins`); otherwise removed (`removed`, or `gone` when it no longer existed). Used
|
|
1429
1450
|
* by uninstall and by an upgrade's removals.
|
|
1430
1451
|
*/
|
|
@@ -1677,21 +1698,36 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1677
1698
|
};
|
|
1678
1699
|
}
|
|
1679
1700
|
/**
|
|
1680
|
-
* The org's live
|
|
1681
|
-
* uninstall keeps a connector its agents use, and the plugin's next
|
|
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.
|
|
1682
1704
|
*/
|
|
1683
|
-
async
|
|
1684
|
-
|
|
1705
|
+
async liveTemplateConnectors(orgId, itemId, templateId) {
|
|
1706
|
+
let q = this.db
|
|
1685
1707
|
.selectFrom("connectors as c")
|
|
1686
1708
|
.innerJoin("platform_connector_templates as p", "p.id", "c.platform_template_id")
|
|
1687
|
-
.select("c.id")
|
|
1709
|
+
.select(["p.template_id", "c.id"])
|
|
1688
1710
|
.where("c.org_id", "=", orgId)
|
|
1689
1711
|
.where("c.deleted_at", "is", null)
|
|
1690
|
-
.where("p.catalog_item_id", "=", itemId)
|
|
1691
|
-
|
|
1692
|
-
.
|
|
1693
|
-
|
|
1694
|
-
return
|
|
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));
|
|
1695
1731
|
}
|
|
1696
1732
|
/**
|
|
1697
1733
|
* Creates the connectors an installed plugin is missing (ruling P2-10, `POST …/catalog/:itemId/
|
|
@@ -1887,8 +1923,10 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1887
1923
|
* skills and agent templates are platform components (phase 2): the ones on in the org through
|
|
1888
1924
|
* the plugin (`follow`) go off with it, so the agents using them (by the platform id or an
|
|
1889
1925
|
* unpinned org copy linked to it) are listed, also for the dependency installs it created that
|
|
1890
|
-
* go with it;
|
|
1891
|
-
*
|
|
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).
|
|
1892
1930
|
*/
|
|
1893
1931
|
async previewUninstall(orgId, installId) {
|
|
1894
1932
|
if (!UUID_RE.test(installId))
|
|
@@ -1921,13 +1959,23 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1921
1959
|
};
|
|
1922
1960
|
const kept = [];
|
|
1923
1961
|
/**
|
|
1924
|
-
* The agents using what goes with an install: connectors it
|
|
1925
|
-
*
|
|
1926
|
-
* (`follow`; by the platform id or an unpinned org
|
|
1927
|
-
* 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).
|
|
1928
1967
|
*/
|
|
1929
1968
|
const visit = async (itemId, rows, top, seen) => {
|
|
1930
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
|
+
}
|
|
1931
1979
|
if (!c.created) {
|
|
1932
1980
|
if (top)
|
|
1933
1981
|
kept.push({ kind: c.kind, ref: c.ref, reason: "pre_existing" });
|
|
@@ -1952,17 +2000,7 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1952
2000
|
seen.add(c.ref);
|
|
1953
2001
|
const sub = (await ledger(c.ref)).filter((x) => x.kind !== "skill" && x.kind !== "agent_template");
|
|
1954
2002
|
await visit(dep.item_id, sub, false, seen);
|
|
1955
|
-
continue;
|
|
1956
2003
|
}
|
|
1957
|
-
if (c.kind !== "connector")
|
|
1958
|
-
continue;
|
|
1959
|
-
const users = agents.filter((a) => a.connectors.has(c.ref));
|
|
1960
|
-
if (users.length === 0)
|
|
1961
|
-
continue;
|
|
1962
|
-
if (top)
|
|
1963
|
-
kept.push({ kind: c.kind, ref: c.ref, reason: "used_by_agents" });
|
|
1964
|
-
for (const a of users)
|
|
1965
|
-
use(a, c.kind, c.ref);
|
|
1966
2004
|
}
|
|
1967
2005
|
for (const kind of ["skill", "agent_template"]) {
|
|
1968
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: "需要你的回答";
|
package/dist/channels/phrases.js
CHANGED
|
@@ -96,6 +96,15 @@ export const CHANNEL_PHRASES = {
|
|
|
96
96
|
networkAllowOnce: { zh: "这次允许", en: "Allow this time" },
|
|
97
97
|
/** A network ask's 「always」 button. */
|
|
98
98
|
networkAllowAlways: { zh: "这台设备一直允许", en: "Always allow on this device" },
|
|
99
|
+
/** A git push ask's card title (`{repo}`; sandbox git credentials spec §7). */
|
|
100
|
+
gitTitle: {
|
|
101
|
+
zh: "智能体想推送到 {repo},允许吗?",
|
|
102
|
+
en: "The agent wants to push to {repo}. Allow?",
|
|
103
|
+
},
|
|
104
|
+
/** A git push ask's 「once」 button. */
|
|
105
|
+
gitAllowOnce: { zh: "这次允许", en: "Allow this time" },
|
|
106
|
+
/** A git push ask's 「always」 button: the person's standing allow for the repository. */
|
|
107
|
+
gitAllowAlways: { zh: "这个仓库一直允许", en: "Always allow this repository" },
|
|
99
108
|
/** 提问卡片的随附文本。 */
|
|
100
109
|
questionTitle: { zh: "需要你的回答", en: "Your input is needed" },
|
|
101
110
|
/** 连接卡片的随附文本。 */
|