@opengeni/core 0.21.10 → 0.24.1
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/access/index.d.ts +1 -1
- package/dist/application/new-session-drafts.d.ts +1 -1
- package/dist/application/session-commands.d.ts +2 -2
- package/dist/billing/limits.d.ts +1 -1
- package/dist/chunk-6WKPWE5S.js +2380 -0
- package/dist/chunk-6WKPWE5S.js.map +1 -0
- package/dist/chunk-JJU6I5XD.js +2713 -0
- package/dist/chunk-JJU6I5XD.js.map +1 -0
- package/dist/chunk-NYPMQ7NO.js +2270 -0
- package/dist/chunk-NYPMQ7NO.js.map +1 -0
- package/dist/dependencies.d.ts +26 -6
- package/dist/domain/capabilities.d.ts +28 -3
- package/dist/domain/company-profile-durable-learning-adapter.d.ts +39 -0
- package/dist/domain/durable-learning-slack-publication.d.ts +72 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +192 -0
- package/dist/domain/editable-artifacts/durable-export-postgres.d.ts +4 -0
- package/dist/domain/editable-artifacts/durable-export.d.ts +212 -0
- package/dist/domain/editable-artifacts/errors.d.ts +47 -0
- package/dist/domain/editable-artifacts/hash.d.ts +11 -0
- package/dist/domain/editable-artifacts/in-memory.d.ts +99 -0
- package/dist/domain/editable-artifacts/index.d.ts +14 -0
- package/dist/domain/editable-artifacts/ports.d.ts +341 -0
- package/dist/domain/editable-artifacts/postgres-adapters.d.ts +13 -0
- package/dist/domain/editable-artifacts/service.d.ts +105 -0
- package/dist/domain/editable-artifacts/snapshot-compaction.d.ts +44 -0
- package/dist/domain/editable-artifacts/snapshot-verifier-genesis.d.ts +50 -0
- package/dist/domain/editable-artifacts/snapshot-verifier-materialization.d.ts +21 -0
- package/dist/domain/editable-artifacts/snapshot-verifier.d.ts +118 -0
- package/dist/domain/editable-artifacts/types.d.ts +410 -0
- package/dist/domain/memory-slack-delivery.d.ts +19 -0
- package/dist/domain/packs.d.ts +29 -1
- package/dist/domain/personal-connection-delegations.d.ts +9 -1
- package/dist/domain/scheduled-tasks.d.ts +2 -2
- package/dist/domain/session-tool-policy.d.ts +2 -2
- package/dist/domain/sessions.d.ts +14 -6
- package/dist/domain/skill-imports.d.ts +32 -0
- package/dist/domain/slack-publication-secret-safety.d.ts +8 -0
- package/dist/domain/timeline-annotations.d.ts +5 -0
- package/dist/domain/video-generation-capabilities.d.ts +9 -0
- package/dist/domain/video-generation.d.ts +47 -0
- package/dist/editable-artifact-live/application.d.ts +69 -0
- package/dist/editable-artifact-live/errors.d.ts +1 -0
- package/dist/editable-artifact-live/index.d.ts +8 -0
- package/dist/editable-artifact-live/outbox-dispatcher.d.ts +162 -0
- package/dist/editable-artifact-live/ports.d.ts +100 -0
- package/dist/editable-artifact-live/server.d.ts +71 -0
- package/dist/editable-artifact-live/ticket.d.ts +40 -0
- package/dist/editable-artifact-live/types.d.ts +213 -0
- package/dist/editable-artifact-live/wire.d.ts +34 -0
- package/dist/editable-artifact-live.d.ts +2 -0
- package/dist/editable-artifact-live.js +58 -0
- package/dist/editable-artifact-live.js.map +1 -0
- package/dist/editable-artifacts.d.ts +2 -0
- package/dist/editable-artifacts.js +179 -0
- package/dist/editable-artifacts.js.map +1 -0
- package/dist/index.d.ts +35 -26
- package/dist/index.js +2792 -609
- package/dist/index.js.map +1 -1
- package/dist/managed-session.d.ts +1 -1
- package/dist/rigs/index.d.ts +1 -1
- package/dist/rigs/provider-images.d.ts +31 -0
- package/dist/sandbox/routing.d.ts +1 -0
- package/dist/session-authorization.d.ts +1 -1
- package/dist/workflow-wake-contract.d.ts +7 -0
- package/package.json +18 -10
- package/src/application/new-session-drafts.ts +2 -0
- package/src/application/session-commands.ts +188 -107
- package/src/dependencies.ts +30 -2
- package/src/domain/capabilities.ts +604 -220
- package/src/domain/company-profile-durable-learning-adapter.ts +221 -0
- package/src/domain/durable-learning-slack-publication.ts +202 -0
- package/src/domain/editable-artifacts/agent-application.ts +687 -0
- package/src/domain/editable-artifacts/durable-export-postgres.ts +139 -0
- package/src/domain/editable-artifacts/durable-export.ts +734 -0
- package/src/domain/editable-artifacts/errors.ts +115 -0
- package/src/domain/editable-artifacts/hash.ts +131 -0
- package/src/domain/editable-artifacts/in-memory.ts +1662 -0
- package/src/domain/editable-artifacts/index.ts +14 -0
- package/src/domain/editable-artifacts/ports.ts +462 -0
- package/src/domain/editable-artifacts/postgres-adapters.ts +49 -0
- package/src/domain/editable-artifacts/service.ts +2402 -0
- package/src/domain/editable-artifacts/snapshot-compaction.ts +246 -0
- package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +235 -0
- package/src/domain/editable-artifacts/snapshot-verifier-materialization.ts +74 -0
- package/src/domain/editable-artifacts/snapshot-verifier.ts +687 -0
- package/src/domain/editable-artifacts/types.ts +835 -0
- package/src/domain/insights.ts +136 -33
- package/src/domain/memory-slack-delivery.ts +204 -0
- package/src/domain/memory-slack-publication.ts +5 -2
- package/src/domain/packs.ts +266 -5
- package/src/domain/personal-connection-delegations.ts +97 -6
- package/src/domain/scheduled-tasks.ts +155 -29
- package/src/domain/session-tool-policy.ts +8 -2
- package/src/domain/sessions.ts +274 -131
- package/src/domain/skill-imports.ts +350 -0
- package/src/domain/slack-publication-secret-safety.ts +42 -0
- package/src/domain/timeline-annotations.ts +204 -0
- package/src/domain/video-generation-capabilities.ts +69 -0
- package/src/domain/video-generation.ts +249 -0
- package/src/editable-artifact-live/application.ts +182 -0
- package/src/editable-artifact-live/errors.ts +4 -0
- package/src/editable-artifact-live/index.ts +8 -0
- package/src/editable-artifact-live/outbox-dispatcher.ts +916 -0
- package/src/editable-artifact-live/ports.ts +139 -0
- package/src/editable-artifact-live/server.ts +1707 -0
- package/src/editable-artifact-live/ticket.ts +217 -0
- package/src/editable-artifact-live/types.ts +286 -0
- package/src/editable-artifact-live/wire.ts +95 -0
- package/src/editable-artifact-live.ts +2 -0
- package/src/editable-artifacts.ts +2 -0
- package/src/index.ts +13 -0
- package/src/rigs/index.ts +26 -19
- package/src/rigs/provider-images.ts +110 -0
- package/src/sandbox/routing.ts +6 -3
- package/src/session-authorization.ts +3 -0
- package/src/workflow-wake-contract.ts +8 -0
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
2
1
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
2
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
4
3
|
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
5
4
|
import { environmentsEncryptionKeyBytes, type Settings } from "@opengeni/config";
|
|
6
5
|
import {
|
|
7
6
|
CapabilityCatalogItem,
|
|
7
|
+
capabilityCatalogItemIsTrustedForExposure,
|
|
8
8
|
type AccessGrant,
|
|
9
|
+
type CapabilityAction,
|
|
9
10
|
type CapabilityCatalogResponse,
|
|
10
11
|
type CapabilityInstallation,
|
|
11
12
|
type CreateCapabilityCatalogItemRequest,
|
|
12
13
|
type EnableCapabilityRequest,
|
|
13
14
|
type McpServerConnectionRef,
|
|
15
|
+
type McpPersonalConnectionDelegation,
|
|
14
16
|
type SocialConnection,
|
|
15
17
|
} from "@opengeni/contracts";
|
|
16
18
|
import {
|
|
@@ -38,12 +40,14 @@ import {
|
|
|
38
40
|
listCapabilityInstallations,
|
|
39
41
|
listConnectionsMetadata,
|
|
40
42
|
listEnabledMcpCapabilityServers,
|
|
43
|
+
listInstalledApiIntegrations,
|
|
41
44
|
listPackInstallations,
|
|
42
45
|
listSocialConnections,
|
|
43
46
|
mcpServerIdForCapability,
|
|
44
47
|
updatePackInstallationStatus,
|
|
45
48
|
upsertCapabilityCatalogItem,
|
|
46
49
|
type Database,
|
|
50
|
+
type ApiIntegrationRuntime,
|
|
47
51
|
type EnabledMcpCapabilityServer,
|
|
48
52
|
} from "@opengeni/db";
|
|
49
53
|
import { HTTPException } from "hono/http-exception";
|
|
@@ -56,6 +60,7 @@ import {
|
|
|
56
60
|
import { validateVariableSetAttachment } from "./environments";
|
|
57
61
|
import {
|
|
58
62
|
assertPackSandboxImageCompatible,
|
|
63
|
+
capabilityPackRequiresInstallationPlan,
|
|
59
64
|
listCapabilityPacks,
|
|
60
65
|
listWorkspaceCapabilityPacks,
|
|
61
66
|
resolveCapabilityPack,
|
|
@@ -68,6 +73,7 @@ const mcpRegistryMaxPages = 3;
|
|
|
68
73
|
const mcpCapabilityProbeTimeoutMs = 15000;
|
|
69
74
|
const maxMcpCredentialHeaders = 16;
|
|
70
75
|
const maxMcpCredentialHeaderValueLength = 4096;
|
|
76
|
+
const officialGmailMcpUrl = "https://gmailmcp.googleapis.com/mcp/v1";
|
|
71
77
|
// RFC 9110 field-name token characters.
|
|
72
78
|
const mcpCredentialHeaderName = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
73
79
|
|
|
@@ -83,7 +89,6 @@ export async function buildCapabilityCatalog(input: {
|
|
|
83
89
|
packInstallations,
|
|
84
90
|
workspacePacks,
|
|
85
91
|
socialConnections,
|
|
86
|
-
bundledSkills,
|
|
87
92
|
curatedLibrarySkills,
|
|
88
93
|
codexAppsCredentialId,
|
|
89
94
|
] = await Promise.all([
|
|
@@ -92,7 +97,6 @@ export async function buildCapabilityCatalog(input: {
|
|
|
92
97
|
listPackInstallations(input.db, input.workspaceId),
|
|
93
98
|
listWorkspaceCapabilityPacks(input.db, input.workspaceId),
|
|
94
99
|
listSocialConnections(input.db, input.workspaceId, 500, input.subjectId),
|
|
95
|
-
discoverBundledSkills(),
|
|
96
100
|
discoverCuratedSkillLibraryItems(),
|
|
97
101
|
input.settings.codexConnectedAppsEnabled
|
|
98
102
|
? resolveCodexAppsCredentialIdForRun(input.db, input.workspaceId)
|
|
@@ -112,8 +116,7 @@ export async function buildCapabilityCatalog(input: {
|
|
|
112
116
|
packCatalogItem(pack, builtInPackIds.has(pack.id) ? "built_in" : "manual"),
|
|
113
117
|
),
|
|
114
118
|
...configuredMcpCatalogItems(input.settings),
|
|
115
|
-
...
|
|
116
|
-
...bundledSkills,
|
|
119
|
+
...providerIntegrationCatalogItems(socialConnections),
|
|
117
120
|
...curatedLibrarySkills,
|
|
118
121
|
];
|
|
119
122
|
const codexApps = input.settings.codexConnectedAppsEnabled
|
|
@@ -126,9 +129,15 @@ export async function buildCapabilityCatalog(input: {
|
|
|
126
129
|
// legacy catalog row with the same id.
|
|
127
130
|
...(codexApps ? [codexApps] : []),
|
|
128
131
|
])
|
|
129
|
-
.map((item) =>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
+
.map((item) => {
|
|
133
|
+
const runtimeItem = applyInstalledArtifactRuntime(item);
|
|
134
|
+
const projected = applyCapabilityEnablement(
|
|
135
|
+
runtimeItem,
|
|
136
|
+
capabilityInstallationById.get(runtimeItem.id),
|
|
137
|
+
activePackIds,
|
|
138
|
+
);
|
|
139
|
+
return applyCapabilityLifecycle(projected);
|
|
140
|
+
})
|
|
132
141
|
.sort(compareCatalogItems);
|
|
133
142
|
return {
|
|
134
143
|
items,
|
|
@@ -136,6 +145,44 @@ export async function buildCapabilityCatalog(input: {
|
|
|
136
145
|
};
|
|
137
146
|
}
|
|
138
147
|
|
|
148
|
+
function applyInstalledArtifactRuntime(item: CapabilityCatalogItem): CapabilityCatalogItem {
|
|
149
|
+
if (
|
|
150
|
+
item.kind === "api" &&
|
|
151
|
+
item.metadata.platformVersion === 2 &&
|
|
152
|
+
typeof item.metadata.pluginVersionId === "string" &&
|
|
153
|
+
typeof item.metadata.apiFacetId === "string" &&
|
|
154
|
+
typeof item.metadata.revisionId === "string" &&
|
|
155
|
+
typeof item.metadata.serverId === "string"
|
|
156
|
+
) {
|
|
157
|
+
return {
|
|
158
|
+
...item,
|
|
159
|
+
runtime: {
|
|
160
|
+
available: true,
|
|
161
|
+
mcpServerId: item.metadata.serverId,
|
|
162
|
+
transport: "local-adapter",
|
|
163
|
+
notes: "Available through an immutable workspace API Integration revision.",
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (
|
|
168
|
+
item.kind !== "skill" ||
|
|
169
|
+
item.metadata.platformVersion !== 2 ||
|
|
170
|
+
typeof item.metadata.pluginVersionId !== "string" ||
|
|
171
|
+
typeof item.metadata.facetId !== "string" ||
|
|
172
|
+
typeof item.metadata.sourceCommit !== "string" ||
|
|
173
|
+
typeof item.metadata.contentSha256 !== "string"
|
|
174
|
+
) {
|
|
175
|
+
return item;
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
...item,
|
|
179
|
+
runtime: {
|
|
180
|
+
available: true,
|
|
181
|
+
notes: "Installed from an immutable workspace Skill artifact.",
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
139
186
|
export async function createCatalogItem(input: {
|
|
140
187
|
db: Database;
|
|
141
188
|
accountId: string;
|
|
@@ -292,6 +339,12 @@ export async function enableCapability(input: {
|
|
|
292
339
|
if (!pack) {
|
|
293
340
|
throw new HTTPException(404, { message: "pack not found" });
|
|
294
341
|
}
|
|
342
|
+
if (capabilityPackRequiresInstallationPlan(pack)) {
|
|
343
|
+
throw new HTTPException(409, {
|
|
344
|
+
message:
|
|
345
|
+
"This Pack uses component or Rig requirements. Preview and install it through the Pack installation flow.",
|
|
346
|
+
});
|
|
347
|
+
}
|
|
295
348
|
await assertPackSandboxImageCompatible(input.db, input.workspaceId, pack);
|
|
296
349
|
// The unified capability-enable path accepts an initial variableSet
|
|
297
350
|
// attachment (`payload.variableSetId`), mirroring POST /packs/:id/enable:
|
|
@@ -433,11 +486,15 @@ function normalizedMcpCredentialHeaders(
|
|
|
433
486
|
const seen = new Set<string>();
|
|
434
487
|
for (const [name, value] of entries) {
|
|
435
488
|
if (!mcpCredentialHeaderName.test(name)) {
|
|
436
|
-
throw new HTTPException(422, {
|
|
489
|
+
throw new HTTPException(422, {
|
|
490
|
+
message: `invalid credential header name: ${name}`,
|
|
491
|
+
});
|
|
437
492
|
}
|
|
438
493
|
const lower = name.toLowerCase();
|
|
439
494
|
if (seen.has(lower)) {
|
|
440
|
-
throw new HTTPException(422, {
|
|
495
|
+
throw new HTTPException(422, {
|
|
496
|
+
message: `duplicate credential header name: ${name}`,
|
|
497
|
+
});
|
|
441
498
|
}
|
|
442
499
|
seen.add(lower);
|
|
443
500
|
if (value.length === 0 || value.length > maxMcpCredentialHeaderValueLength) {
|
|
@@ -463,6 +520,15 @@ async function validateMcpCapabilityConnectionRef(
|
|
|
463
520
|
ref: McpServerConnectionRef,
|
|
464
521
|
): Promise<McpServerConnectionRef> {
|
|
465
522
|
const subjectScope = ref.subjectScope ?? "workspace";
|
|
523
|
+
const personalOnly =
|
|
524
|
+
item.metadata.connectionOwnership === "personal_only" ||
|
|
525
|
+
item.endpointUrl?.replace(/\/+$/, "") === officialGmailMcpUrl;
|
|
526
|
+
if (personalOnly && subjectScope !== "subject") {
|
|
527
|
+
throw new HTTPException(422, {
|
|
528
|
+
message:
|
|
529
|
+
"this capability requires a personal connection; each workspace member must connect their own account",
|
|
530
|
+
});
|
|
531
|
+
}
|
|
466
532
|
const normalized: McpServerConnectionRef = {
|
|
467
533
|
providerDomain: ref.providerDomain.trim(),
|
|
468
534
|
subjectScope,
|
|
@@ -472,11 +538,17 @@ async function validateMcpCapabilityConnectionRef(
|
|
|
472
538
|
...(ref.scopes ? { scopes: uniqueStrings(ref.scopes) } : {}),
|
|
473
539
|
...(ref.resource ? { resource: ref.resource } : {}),
|
|
474
540
|
...(ref.selectedResources
|
|
475
|
-
? {
|
|
541
|
+
? {
|
|
542
|
+
selectedResources: ref.selectedResources.map((resource) => ({
|
|
543
|
+
...resource,
|
|
544
|
+
})),
|
|
545
|
+
}
|
|
476
546
|
: {}),
|
|
477
547
|
};
|
|
478
548
|
if (!normalized.providerDomain) {
|
|
479
|
-
throw new HTTPException(422, {
|
|
549
|
+
throw new HTTPException(422, {
|
|
550
|
+
message: "connectionRef.providerDomain is required",
|
|
551
|
+
});
|
|
480
552
|
}
|
|
481
553
|
if (!item.endpointUrl || !item.runtime.mcpServerId) {
|
|
482
554
|
throw new HTTPException(422, {
|
|
@@ -720,6 +792,20 @@ export async function disableCapability(input: {
|
|
|
720
792
|
});
|
|
721
793
|
}
|
|
722
794
|
if (item.kind === "pack") {
|
|
795
|
+
const packInstallation = await getPackInstallation(
|
|
796
|
+
input.db,
|
|
797
|
+
input.workspaceId,
|
|
798
|
+
packIdFromCapabilityId(item.id),
|
|
799
|
+
);
|
|
800
|
+
if (
|
|
801
|
+
packInstallation &&
|
|
802
|
+
(packInstallation.manifestDigest !== null || packInstallation.manifestSnapshot !== null)
|
|
803
|
+
) {
|
|
804
|
+
throw new HTTPException(409, {
|
|
805
|
+
message:
|
|
806
|
+
"This Pack owns explicit components. Uninstall it through the Pack uninstall preview flow.",
|
|
807
|
+
});
|
|
808
|
+
}
|
|
723
809
|
await updatePackInstallationStatus(
|
|
724
810
|
input.db,
|
|
725
811
|
input.workspaceId,
|
|
@@ -737,7 +823,9 @@ export async function disableCapability(input: {
|
|
|
737
823
|
});
|
|
738
824
|
}
|
|
739
825
|
} else if (!(await getCapabilityInstallation(input.db, input.workspaceId, item.id))) {
|
|
740
|
-
throw new HTTPException(409, {
|
|
826
|
+
throw new HTTPException(409, {
|
|
827
|
+
message: "capability is not currently enabled",
|
|
828
|
+
});
|
|
741
829
|
}
|
|
742
830
|
return await disableCapabilityInstallation(input.db, input.workspaceId, item.id);
|
|
743
831
|
}
|
|
@@ -746,17 +834,109 @@ export async function settingsWithEnabledCapabilityMcpServers(
|
|
|
746
834
|
db: Database,
|
|
747
835
|
workspaceId: string,
|
|
748
836
|
settings: Settings,
|
|
837
|
+
options?: {
|
|
838
|
+
subjectId?: string;
|
|
839
|
+
personalConnectionDelegations?: readonly McpPersonalConnectionDelegation[];
|
|
840
|
+
onResolvedApiIntegrations?: (integrations: readonly ApiIntegrationRuntime[]) => void;
|
|
841
|
+
},
|
|
749
842
|
): Promise<Settings> {
|
|
750
|
-
const
|
|
843
|
+
const apiIntegrationsPromise = options?.subjectId
|
|
844
|
+
? listInstalledApiIntegrations(db, workspaceId, options.subjectId)
|
|
845
|
+
: listInstalledApiIntegrationsForDelegations(
|
|
846
|
+
db,
|
|
847
|
+
workspaceId,
|
|
848
|
+
options?.personalConnectionDelegations ?? [],
|
|
849
|
+
);
|
|
850
|
+
const [enabled, apiIntegrations, codexAppsCredentialId] = await Promise.all([
|
|
751
851
|
listEnabledMcpCapabilityServers(db, workspaceId),
|
|
852
|
+
apiIntegrationsPromise,
|
|
752
853
|
resolveCodexAppsCredentialIdForRun(db, workspaceId),
|
|
753
854
|
]);
|
|
855
|
+
options?.onResolvedApiIntegrations?.(apiIntegrations);
|
|
754
856
|
return settingsWithCodexAppsMcpServer(
|
|
755
|
-
|
|
857
|
+
settingsWithApiIntegrationServers(
|
|
858
|
+
settingsWithMcpCapabilityServers(settings, enabled),
|
|
859
|
+
apiIntegrations,
|
|
860
|
+
),
|
|
756
861
|
codexAppsCredentialId !== null,
|
|
757
862
|
);
|
|
758
863
|
}
|
|
759
864
|
|
|
865
|
+
export function apiIntegrationsMatchingDelegations(
|
|
866
|
+
integrations: readonly ApiIntegrationRuntime[],
|
|
867
|
+
delegations: readonly McpPersonalConnectionDelegation[],
|
|
868
|
+
): ApiIntegrationRuntime[] {
|
|
869
|
+
const exact = new Set(
|
|
870
|
+
delegations.map((delegation) =>
|
|
871
|
+
[
|
|
872
|
+
delegation.serverId,
|
|
873
|
+
delegation.connectionId,
|
|
874
|
+
delegation.providerDomain.toLowerCase(),
|
|
875
|
+
delegation.kind ?? "",
|
|
876
|
+
].join("\u0000"),
|
|
877
|
+
),
|
|
878
|
+
);
|
|
879
|
+
return integrations.filter((integration) => {
|
|
880
|
+
const ref = integration.connectionRef;
|
|
881
|
+
if (!ref || ref.subjectScope !== "subject" || !ref.connectionId) return false;
|
|
882
|
+
return exact.has(
|
|
883
|
+
[
|
|
884
|
+
integration.serverId,
|
|
885
|
+
ref.connectionId,
|
|
886
|
+
ref.providerDomain.toLowerCase(),
|
|
887
|
+
ref.kind ?? "",
|
|
888
|
+
].join("\u0000"),
|
|
889
|
+
);
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
async function listInstalledApiIntegrationsForDelegations(
|
|
894
|
+
db: Database,
|
|
895
|
+
workspaceId: string,
|
|
896
|
+
delegations: readonly McpPersonalConnectionDelegation[],
|
|
897
|
+
): Promise<ApiIntegrationRuntime[]> {
|
|
898
|
+
const workspace = await listInstalledApiIntegrations(db, workspaceId);
|
|
899
|
+
if (delegations.length === 0) return workspace;
|
|
900
|
+
const owners = [...new Set(delegations.map((delegation) => delegation.ownerSubjectId))];
|
|
901
|
+
const delegatedByOwner = await Promise.all(
|
|
902
|
+
owners.map(async (subjectId) =>
|
|
903
|
+
apiIntegrationsMatchingDelegations(
|
|
904
|
+
await listInstalledApiIntegrations(db, workspaceId, subjectId),
|
|
905
|
+
delegations.filter((delegation) => delegation.ownerSubjectId === subjectId),
|
|
906
|
+
),
|
|
907
|
+
),
|
|
908
|
+
);
|
|
909
|
+
const byServerId = new Map(workspace.map((integration) => [integration.serverId, integration]));
|
|
910
|
+
for (const integration of delegatedByOwner.flat())
|
|
911
|
+
byServerId.set(integration.serverId, integration);
|
|
912
|
+
return [...byServerId.values()];
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
export function settingsWithApiIntegrationServers(
|
|
916
|
+
settings: Settings,
|
|
917
|
+
integrations: readonly ApiIntegrationRuntime[],
|
|
918
|
+
): Settings {
|
|
919
|
+
if (integrations.length === 0) return settings;
|
|
920
|
+
const existingIds = new Set(settings.mcpServers.map((server) => server.id));
|
|
921
|
+
const dynamicServers = integrations
|
|
922
|
+
.filter((integration) => !existingIds.has(integration.serverId))
|
|
923
|
+
.map((integration) => ({
|
|
924
|
+
id: integration.serverId,
|
|
925
|
+
name: integration.name,
|
|
926
|
+
// Local adapters never send MCP traffic to this URL. Keeping the exact
|
|
927
|
+
// provider base URL in Settings preserves destination identity for policy,
|
|
928
|
+
// diagnostics, and the stable session-MCP approval fallback.
|
|
929
|
+
url: integration.baseUrl,
|
|
930
|
+
allowedTools: [...integration.allowedTools],
|
|
931
|
+
cacheToolsList: true,
|
|
932
|
+
requireApproval: integration.requireApproval,
|
|
933
|
+
...(integration.connectionRef ? { connectionRef: integration.connectionRef } : {}),
|
|
934
|
+
}));
|
|
935
|
+
return dynamicServers.length
|
|
936
|
+
? { ...settings, mcpServers: [...settings.mcpServers, ...dynamicServers] }
|
|
937
|
+
: settings;
|
|
938
|
+
}
|
|
939
|
+
|
|
760
940
|
/**
|
|
761
941
|
* Resolve executable Apps authority. The connector must remain active and its
|
|
762
942
|
* exact owner must still hold workspace connection-management permission.
|
|
@@ -846,6 +1026,9 @@ export function settingsWithMcpCapabilityServers(
|
|
|
846
1026
|
...(server.allowedTools ? { allowedTools: server.allowedTools } : {}),
|
|
847
1027
|
...(server.timeoutMs ? { timeoutMs: server.timeoutMs } : {}),
|
|
848
1028
|
cacheToolsList: server.cacheToolsList ?? false,
|
|
1029
|
+
...(server.requireApproval !== undefined
|
|
1030
|
+
? { requireApproval: server.requireApproval }
|
|
1031
|
+
: {}),
|
|
849
1032
|
...(headers && headers !== "unavailable" ? { headers } : {}),
|
|
850
1033
|
...(server.connectionRef ? { connectionRef: server.connectionRef } : {}),
|
|
851
1034
|
},
|
|
@@ -931,7 +1114,9 @@ async function fetchMcpRegistryPage(
|
|
|
931
1114
|
try {
|
|
932
1115
|
const response = await fetchImpl(url, { signal: controller.signal });
|
|
933
1116
|
if (!response.ok) {
|
|
934
|
-
throw new HTTPException(502, {
|
|
1117
|
+
throw new HTTPException(502, {
|
|
1118
|
+
message: `MCP registry returned ${response.status}`,
|
|
1119
|
+
});
|
|
935
1120
|
}
|
|
936
1121
|
return (await response.json()) as McpRegistryPage;
|
|
937
1122
|
} catch (error) {
|
|
@@ -939,7 +1124,9 @@ async function fetchMcpRegistryPage(
|
|
|
939
1124
|
throw error;
|
|
940
1125
|
}
|
|
941
1126
|
if (error instanceof Error && error.name === "AbortError") {
|
|
942
|
-
throw new HTTPException(504, {
|
|
1127
|
+
throw new HTTPException(504, {
|
|
1128
|
+
message: "MCP registry request timed out",
|
|
1129
|
+
});
|
|
943
1130
|
}
|
|
944
1131
|
throw new HTTPException(502, {
|
|
945
1132
|
message: `MCP registry request failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -1002,6 +1189,7 @@ function packCatalogItem(
|
|
|
1002
1189
|
connectors: pack.connectors,
|
|
1003
1190
|
knowledge: pack.knowledge,
|
|
1004
1191
|
scheduledTaskTemplates: pack.scheduledTaskTemplates,
|
|
1192
|
+
...(pack.variableSet ? { variableSet: pack.variableSet } : {}),
|
|
1005
1193
|
// Runtime composition surface only: skill names, never file content.
|
|
1006
1194
|
...(pack.sandboxImage ? { sandboxImage: pack.sandboxImage } : {}),
|
|
1007
1195
|
...(pack.sandboxProviderImages ? { sandboxProviderImages: pack.sandboxProviderImages } : {}),
|
|
@@ -1011,34 +1199,40 @@ function packCatalogItem(
|
|
|
1011
1199
|
}
|
|
1012
1200
|
|
|
1013
1201
|
function configuredMcpCatalogItems(settings: Settings): CapabilityCatalogItem[] {
|
|
1014
|
-
return
|
|
1015
|
-
.
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1202
|
+
return (
|
|
1203
|
+
settings.mcpServers
|
|
1204
|
+
// OpenGeni, Files, and Document Search are native runtime surfaces. They
|
|
1205
|
+
// remain available to sessions through configuration, but are not things a
|
|
1206
|
+
// user installs, connects, or enables in the Capabilities control center.
|
|
1207
|
+
.filter(
|
|
1208
|
+
(server) =>
|
|
1209
|
+
server.id !== CODEX_APPS_MCP_SERVER_ID && !firstPartyMcpServerIds.has(server.id),
|
|
1210
|
+
)
|
|
1211
|
+
.map((server) =>
|
|
1212
|
+
CapabilityCatalogItem.parse({
|
|
1213
|
+
id: `mcp:${server.id}`,
|
|
1214
|
+
kind: "mcp",
|
|
1215
|
+
source: "configured",
|
|
1216
|
+
name: server.name ?? server.id,
|
|
1217
|
+
description: null,
|
|
1218
|
+
category: "configured",
|
|
1219
|
+
tags: ["mcp", ...(server.allowedTools?.length ? ["limited-tools"] : [])],
|
|
1220
|
+
endpointUrl: server.url,
|
|
1221
|
+
tools: [{ kind: "mcp", id: server.id }],
|
|
1222
|
+
runtime: {
|
|
1223
|
+
available: true,
|
|
1224
|
+
mcpServerId: server.id,
|
|
1225
|
+
transport: "streamable-http",
|
|
1226
|
+
notes: "Managed by this OpenGeni deployment through OPENGENI_MCP_SERVERS.",
|
|
1227
|
+
},
|
|
1228
|
+
metadata: {
|
|
1229
|
+
mcpServerId: server.id,
|
|
1230
|
+
allowedTools: server.allowedTools ?? [],
|
|
1231
|
+
cacheToolsList: server.cacheToolsList,
|
|
1232
|
+
},
|
|
1233
|
+
}),
|
|
1234
|
+
)
|
|
1235
|
+
);
|
|
1042
1236
|
}
|
|
1043
1237
|
|
|
1044
1238
|
function isReservedCodexAppsCatalogItem(item: CapabilityCatalogItem): boolean {
|
|
@@ -1089,160 +1283,167 @@ export function codexAppsCatalogItem(available: boolean): CapabilityCatalogItem
|
|
|
1089
1283
|
});
|
|
1090
1284
|
}
|
|
1091
1285
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1286
|
+
type SocialProviderIntegrationDefinition = {
|
|
1287
|
+
provider: "x" | "reddit";
|
|
1288
|
+
name: string;
|
|
1289
|
+
description: string;
|
|
1290
|
+
providerDomain: string;
|
|
1291
|
+
homepageUrl: string;
|
|
1292
|
+
tags: string[];
|
|
1293
|
+
};
|
|
1294
|
+
|
|
1295
|
+
const SOCIAL_PROVIDER_INTEGRATIONS: readonly SocialProviderIntegrationDefinition[] = [
|
|
1296
|
+
{
|
|
1297
|
+
provider: "x",
|
|
1099
1298
|
name: "X",
|
|
1100
1299
|
description:
|
|
1101
|
-
"Connect
|
|
1102
|
-
category: "social-media",
|
|
1103
|
-
tags: ["api", "x", "twitter", "social", "marketing"],
|
|
1104
|
-
homepageUrl: "https://x.com",
|
|
1105
|
-
authModel: "oauth2_authorization_code_pkce",
|
|
1300
|
+
"Connect one or more X accounts for live search, mentions, thread context, post sync, and permission-controlled replies.",
|
|
1106
1301
|
providerDomain: "x.com",
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1302
|
+
homepageUrl: "https://x.com",
|
|
1303
|
+
tags: ["api", "x", "twitter", "social", "marketing"],
|
|
1304
|
+
},
|
|
1305
|
+
{
|
|
1306
|
+
provider: "reddit",
|
|
1307
|
+
name: "Reddit",
|
|
1308
|
+
description:
|
|
1309
|
+
"Connect one or more Reddit accounts for search, mentions, thread context, account sync, and permission-controlled replies.",
|
|
1310
|
+
providerDomain: "reddit.com",
|
|
1311
|
+
homepageUrl: "https://www.reddit.com",
|
|
1312
|
+
tags: ["api", "reddit", "social", "community", "marketing"],
|
|
1313
|
+
},
|
|
1314
|
+
];
|
|
1315
|
+
|
|
1316
|
+
const SOCIAL_PROVIDER_TOOL_NAMES = {
|
|
1317
|
+
x: [
|
|
1318
|
+
"x_accounts_list",
|
|
1319
|
+
"x_search_live",
|
|
1320
|
+
"x_mentions_live",
|
|
1321
|
+
"x_thread_fetch",
|
|
1322
|
+
"x_posts_sync",
|
|
1323
|
+
"x_post_reply",
|
|
1324
|
+
],
|
|
1325
|
+
reddit: [
|
|
1326
|
+
"reddit_accounts_list",
|
|
1327
|
+
"reddit_search_live",
|
|
1328
|
+
"reddit_mentions_live",
|
|
1329
|
+
"reddit_thread_fetch",
|
|
1330
|
+
"reddit_posts_sync",
|
|
1331
|
+
"reddit_post_reply",
|
|
1332
|
+
],
|
|
1333
|
+
} as const;
|
|
1334
|
+
|
|
1335
|
+
function providerIntegrationCatalogItems(
|
|
1336
|
+
socialConnections: SocialConnection[],
|
|
1337
|
+
): CapabilityCatalogItem[] {
|
|
1338
|
+
return SOCIAL_PROVIDER_INTEGRATIONS.map((definition) => {
|
|
1339
|
+
const counts = socialConnectionCounts(socialConnections, definition.provider);
|
|
1340
|
+
const enabled = counts.connected + counts.needsReauth > 0;
|
|
1341
|
+
return CapabilityCatalogItem.parse({
|
|
1342
|
+
id: `api:${definition.provider}`,
|
|
1343
|
+
kind: "api",
|
|
1344
|
+
source: "built_in",
|
|
1345
|
+
name: definition.name,
|
|
1346
|
+
description: definition.description,
|
|
1347
|
+
category: "social-media",
|
|
1348
|
+
tags: definition.tags,
|
|
1349
|
+
homepageUrl: definition.homepageUrl,
|
|
1350
|
+
authModel: "oauth2_authorization_code_pkce",
|
|
1351
|
+
providerDomain: definition.providerDomain,
|
|
1352
|
+
surfaceType: "provider_integration",
|
|
1353
|
+
authKind: "oauth2",
|
|
1354
|
+
tools: [{ kind: "mcp", id: "opengeni" }],
|
|
1355
|
+
runtime: {
|
|
1356
|
+
available: true,
|
|
1357
|
+
mcpServerId: "opengeni",
|
|
1358
|
+
notes:
|
|
1359
|
+
"OpenGeni's social provider adapter routes every call through an exact visible account Connection.",
|
|
1360
|
+
},
|
|
1361
|
+
enabled,
|
|
1362
|
+
enabledReason: socialConnectionSummary(counts),
|
|
1363
|
+
provenance: "OpenGeni provider adapter",
|
|
1364
|
+
metadata: {
|
|
1365
|
+
providerAdapter: "social",
|
|
1366
|
+
provider: definition.provider,
|
|
1367
|
+
connectionCounts: counts,
|
|
1368
|
+
runtimeNamespace: "social",
|
|
1369
|
+
firstPartyMcpTools: SOCIAL_PROVIDER_TOOL_NAMES[definition.provider],
|
|
1370
|
+
},
|
|
1371
|
+
});
|
|
1136
1372
|
});
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
id: "api:documents",
|
|
1148
|
-
name: "Document Knowledge Base",
|
|
1149
|
-
description: "Upload, index, search, and attach knowledge bases to agents.",
|
|
1150
|
-
category: "knowledge",
|
|
1151
|
-
tags: ["api", "documents", "knowledge"],
|
|
1152
|
-
endpointPath: "/v1/workspaces/{workspaceId}/document-bases",
|
|
1153
|
-
},
|
|
1154
|
-
{
|
|
1155
|
-
id: "api:social",
|
|
1156
|
-
name: "Social Accounts",
|
|
1157
|
-
description: "Connect social accounts and ingest posts for marketing agents.",
|
|
1158
|
-
category: "marketing",
|
|
1159
|
-
tags: ["api", "social", "marketing"],
|
|
1160
|
-
endpointPath: "/v1/workspaces/{workspaceId}/social/connections",
|
|
1161
|
-
},
|
|
1162
|
-
{
|
|
1163
|
-
id: "api:scheduled-tasks",
|
|
1164
|
-
name: "Scheduled Tasks",
|
|
1165
|
-
description: "Run agents once, on intervals, or on calendar schedules.",
|
|
1166
|
-
category: "automation",
|
|
1167
|
-
tags: ["api", "schedules", "agents"],
|
|
1168
|
-
endpointPath: "/v1/workspaces/{workspaceId}/scheduled-tasks",
|
|
1169
|
-
},
|
|
1170
|
-
].map((item) =>
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* Native product connection recommendations are intentionally separate from
|
|
1377
|
+
* the installable catalog returned to the Capabilities UI. Agents may still
|
|
1378
|
+
* recommend the owning product flow without manufacturing an enabled catalog
|
|
1379
|
+
* row for GitHub resources, Documents, schedules, or other platform features.
|
|
1380
|
+
*/
|
|
1381
|
+
export function nativeConnectionCapabilityRecommendations(): CapabilityCatalogItem[] {
|
|
1382
|
+
return [
|
|
1171
1383
|
CapabilityCatalogItem.parse({
|
|
1172
|
-
id:
|
|
1173
|
-
name: item.name,
|
|
1174
|
-
description: item.description,
|
|
1175
|
-
category: item.category,
|
|
1176
|
-
tags: item.tags,
|
|
1384
|
+
id: "api:github-app",
|
|
1177
1385
|
kind: "api",
|
|
1178
1386
|
source: "built_in",
|
|
1387
|
+
name: "GitHub App",
|
|
1388
|
+
description: "Connect repositories through OpenGeni's GitHub resource picker.",
|
|
1389
|
+
category: "source-control",
|
|
1390
|
+
tags: ["github", "repositories", "source-control"],
|
|
1391
|
+
homepageUrl: "https://github.com",
|
|
1392
|
+
providerDomain: "github.com",
|
|
1393
|
+
authModel: "github_app_owner_consent",
|
|
1394
|
+
authKind: "oauth2",
|
|
1395
|
+
surfaceType: "first_party_github",
|
|
1396
|
+
tools: [{ kind: "mcp", id: "opengeni" }],
|
|
1179
1397
|
runtime: {
|
|
1180
1398
|
available: true,
|
|
1181
|
-
notes:
|
|
1399
|
+
notes:
|
|
1400
|
+
"GitHub credentials stay host-owned; a workspace owner must approve repository access.",
|
|
1401
|
+
},
|
|
1402
|
+
lifecycle: {
|
|
1403
|
+
status: "available",
|
|
1404
|
+
readiness: "setup_required",
|
|
1405
|
+
detail: "Connect GitHub from the repository resource flow.",
|
|
1406
|
+
managedBy: "platform",
|
|
1182
1407
|
},
|
|
1408
|
+
actions: ["connect", "inspect"],
|
|
1183
1409
|
metadata: {
|
|
1184
|
-
endpointPath:
|
|
1410
|
+
endpointPath: "/v1/workspaces/{workspaceId}/github/app",
|
|
1411
|
+
firstPartyMcpTools: ["github_connect_link", "github_repositories_list"],
|
|
1412
|
+
recommendationOnly: true,
|
|
1185
1413
|
},
|
|
1186
1414
|
}),
|
|
1187
|
-
|
|
1188
|
-
return [x, ...platformApis];
|
|
1415
|
+
];
|
|
1189
1416
|
}
|
|
1190
1417
|
|
|
1191
|
-
function
|
|
1418
|
+
function socialConnectionCounts(
|
|
1192
1419
|
connections: SocialConnection[],
|
|
1193
1420
|
provider: "x" | "reddit",
|
|
1194
|
-
):
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
statusRank(left.status) - statusRank(right.status) ||
|
|
1203
|
-
right.updatedAt.localeCompare(left.updatedAt) ||
|
|
1204
|
-
left.id.localeCompare(right.id),
|
|
1205
|
-
)[0] ?? null
|
|
1206
|
-
);
|
|
1421
|
+
): { connected: number; needsReauth: number; disabled: number; total: number } {
|
|
1422
|
+
const matching = connections.filter((connection) => connection.provider === provider);
|
|
1423
|
+
return {
|
|
1424
|
+
connected: matching.filter((connection) => connection.status === "connected").length,
|
|
1425
|
+
needsReauth: matching.filter((connection) => connection.status === "needs_reauth").length,
|
|
1426
|
+
disabled: matching.filter((connection) => connection.status === "disabled").length,
|
|
1427
|
+
total: matching.length,
|
|
1428
|
+
};
|
|
1207
1429
|
}
|
|
1208
1430
|
|
|
1209
|
-
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
.map(async (entry) => {
|
|
1220
|
-
const skill = await readSkillMetadata(
|
|
1221
|
-
new URL(`${entry.name}/SKILL.md`, skillsDir),
|
|
1222
|
-
entry.name,
|
|
1223
|
-
);
|
|
1224
|
-
return CapabilityCatalogItem.parse({
|
|
1225
|
-
id: `skill:${entry.name}`,
|
|
1226
|
-
kind: "skill",
|
|
1227
|
-
source: "built_in",
|
|
1228
|
-
name: skill.name,
|
|
1229
|
-
description: skill.description,
|
|
1230
|
-
category: skill.category,
|
|
1231
|
-
tags: ["skill", skill.category],
|
|
1232
|
-
runtime: {
|
|
1233
|
-
available: true,
|
|
1234
|
-
notes: "Bundled into the sandbox skill library.",
|
|
1235
|
-
},
|
|
1236
|
-
metadata: {
|
|
1237
|
-
path: `packages/runtime/src/bundled_hashicorp_terraform_skills/${entry.name}/SKILL.md`,
|
|
1238
|
-
},
|
|
1239
|
-
});
|
|
1240
|
-
}),
|
|
1431
|
+
function socialConnectionSummary(counts: ReturnType<typeof socialConnectionCounts>): string | null {
|
|
1432
|
+
const parts: string[] = [];
|
|
1433
|
+
if (counts.connected > 0) {
|
|
1434
|
+
parts.push(`${counts.connected} connected account${counts.connected === 1 ? "" : "s"}`);
|
|
1435
|
+
}
|
|
1436
|
+
if (counts.needsReauth > 0) {
|
|
1437
|
+
parts.push(
|
|
1438
|
+
`${counts.needsReauth} account${counts.needsReauth === 1 ? "" : "s"} need${
|
|
1439
|
+
counts.needsReauth === 1 ? "s" : ""
|
|
1440
|
+
} reconnection`,
|
|
1241
1441
|
);
|
|
1242
|
-
return skills;
|
|
1243
|
-
} catch {
|
|
1244
|
-
return [];
|
|
1245
1442
|
}
|
|
1443
|
+
if (counts.disabled > 0) {
|
|
1444
|
+
parts.push(`${counts.disabled} disconnected account${counts.disabled === 1 ? "" : "s"}`);
|
|
1445
|
+
}
|
|
1446
|
+
return parts.length > 0 ? parts.join("; ") : null;
|
|
1246
1447
|
}
|
|
1247
1448
|
|
|
1248
1449
|
/**
|
|
@@ -1291,31 +1492,35 @@ function stringMetadata(value: unknown): string | null {
|
|
|
1291
1492
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
1292
1493
|
}
|
|
1293
1494
|
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
const
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
?
|
|
1317
|
-
:
|
|
1318
|
-
|
|
1495
|
+
function isSocialProviderIntegration(item: CapabilityCatalogItem): boolean {
|
|
1496
|
+
return (
|
|
1497
|
+
item.surfaceType === "first_party_social" ||
|
|
1498
|
+
(item.surfaceType === "provider_integration" && item.metadata.providerAdapter === "social")
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function socialProviderConnectionCounts(item: CapabilityCatalogItem): {
|
|
1503
|
+
connected: number;
|
|
1504
|
+
needsReauth: number;
|
|
1505
|
+
} {
|
|
1506
|
+
const value = item.metadata.connectionCounts;
|
|
1507
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1508
|
+
return {
|
|
1509
|
+
connected: item.enabled && !item.enabledReason?.includes("reconnection") ? 1 : 0,
|
|
1510
|
+
needsReauth: item.enabledReason?.includes("reconnection") ? 1 : 0,
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
const record = value as Record<string, unknown>;
|
|
1514
|
+
return {
|
|
1515
|
+
connected:
|
|
1516
|
+
typeof record.connected === "number" && Number.isInteger(record.connected)
|
|
1517
|
+
? Math.max(0, record.connected)
|
|
1518
|
+
: 0,
|
|
1519
|
+
needsReauth:
|
|
1520
|
+
typeof record.needsReauth === "number" && Number.isInteger(record.needsReauth)
|
|
1521
|
+
? Math.max(0, record.needsReauth)
|
|
1522
|
+
: 0,
|
|
1523
|
+
};
|
|
1319
1524
|
}
|
|
1320
1525
|
|
|
1321
1526
|
export function applyCapabilityEnablement(
|
|
@@ -1334,10 +1539,10 @@ export function applyCapabilityEnablement(
|
|
|
1334
1539
|
enabledReason: enabled ? "enabled" : null,
|
|
1335
1540
|
};
|
|
1336
1541
|
}
|
|
1337
|
-
if (item
|
|
1338
|
-
//
|
|
1339
|
-
//
|
|
1340
|
-
//
|
|
1542
|
+
if (isSocialProviderIntegration(item)) {
|
|
1543
|
+
// Provider-integration state is derived from every authoritative visible
|
|
1544
|
+
// social Connection while the catalog is built. The catalog summary never
|
|
1545
|
+
// publishes a personal connection UUID or collapses many accounts to one.
|
|
1341
1546
|
return { ...item, connectionRef: null };
|
|
1342
1547
|
}
|
|
1343
1548
|
if (item.surfaceType === "codex_apps") {
|
|
@@ -1346,13 +1551,20 @@ export function applyCapabilityEnablement(
|
|
|
1346
1551
|
// generic source-based "built in" enablement rule.
|
|
1347
1552
|
return { ...item, connectionRef: null };
|
|
1348
1553
|
}
|
|
1349
|
-
if (item.source === "
|
|
1554
|
+
if (item.source === "configured") {
|
|
1350
1555
|
return {
|
|
1351
1556
|
...item,
|
|
1352
1557
|
enabled: true,
|
|
1353
|
-
enabledReason:
|
|
1558
|
+
enabledReason: "managed by deployment",
|
|
1559
|
+
connectionRef: null,
|
|
1354
1560
|
};
|
|
1355
1561
|
}
|
|
1562
|
+
if (item.source === "built_in") {
|
|
1563
|
+
// "Built in" describes provenance, not lifecycle. Native product
|
|
1564
|
+
// surfaces and first-party connectors must project their authoritative
|
|
1565
|
+
// state explicitly above rather than becoming enabled by taxonomy.
|
|
1566
|
+
return { ...item, connectionRef: null };
|
|
1567
|
+
}
|
|
1356
1568
|
if (item.source === "library") {
|
|
1357
1569
|
const enabled =
|
|
1358
1570
|
installation?.status === "active" && skillLibraryInstallationRuntimeReady(item, installation);
|
|
@@ -1365,6 +1577,25 @@ export function applyCapabilityEnablement(
|
|
|
1365
1577
|
}
|
|
1366
1578
|
const activeInstallation = installation?.status === "active";
|
|
1367
1579
|
const enabled = !!activeInstallation && capabilityInstallationRuntimeReady(item, installation);
|
|
1580
|
+
if (item.kind === "api" && item.metadata.platformVersion === 2) {
|
|
1581
|
+
const connectionBound = installation?.metadata.connectionBound === true;
|
|
1582
|
+
const providerDomain = stringMetadata(installation?.metadata.providerDomain);
|
|
1583
|
+
const connectionKind = stringMetadata(installation?.metadata.connectionKind);
|
|
1584
|
+
const connectionOwnership = stringMetadata(installation?.metadata.connectionOwnership);
|
|
1585
|
+
return {
|
|
1586
|
+
...item,
|
|
1587
|
+
enabled,
|
|
1588
|
+
enabledReason: enabled ? "installed immutable Integration revision" : null,
|
|
1589
|
+
connectionRef:
|
|
1590
|
+
enabled && connectionBound && providerDomain && connectionKind
|
|
1591
|
+
? {
|
|
1592
|
+
providerDomain,
|
|
1593
|
+
kind: connectionKind,
|
|
1594
|
+
...(connectionOwnership === "subject" ? { subjectScope: "subject" as const } : {}),
|
|
1595
|
+
}
|
|
1596
|
+
: null,
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1368
1599
|
return {
|
|
1369
1600
|
...item,
|
|
1370
1601
|
enabled,
|
|
@@ -1373,6 +1604,90 @@ export function applyCapabilityEnablement(
|
|
|
1373
1604
|
};
|
|
1374
1605
|
}
|
|
1375
1606
|
|
|
1607
|
+
function applyCapabilityLifecycle(item: CapabilityCatalogItem): CapabilityCatalogItem {
|
|
1608
|
+
if (item.source === "configured") {
|
|
1609
|
+
return {
|
|
1610
|
+
...item,
|
|
1611
|
+
lifecycle: {
|
|
1612
|
+
status: "managed",
|
|
1613
|
+
readiness: item.runtime.available ? "ready" : "unavailable",
|
|
1614
|
+
detail: item.runtime.notes,
|
|
1615
|
+
managedBy: "deployment",
|
|
1616
|
+
},
|
|
1617
|
+
actions: ["inspect"],
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
if (!item.runtime.available) {
|
|
1622
|
+
return {
|
|
1623
|
+
...item,
|
|
1624
|
+
lifecycle: {
|
|
1625
|
+
status: "unavailable",
|
|
1626
|
+
readiness: "unavailable",
|
|
1627
|
+
detail: item.runtime.notes,
|
|
1628
|
+
managedBy: item.source === "built_in" ? "platform" : null,
|
|
1629
|
+
},
|
|
1630
|
+
actions: ["inspect"],
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
if (isSocialProviderIntegration(item)) {
|
|
1635
|
+
const counts = socialProviderConnectionCounts(item);
|
|
1636
|
+
const needsAttention = counts.needsReauth > 0;
|
|
1637
|
+
const connected = counts.connected > 0;
|
|
1638
|
+
return {
|
|
1639
|
+
...item,
|
|
1640
|
+
lifecycle: {
|
|
1641
|
+
status: needsAttention ? "needs_attention" : connected ? "connected" : "available",
|
|
1642
|
+
readiness: needsAttention ? "attention" : connected ? "ready" : "setup_required",
|
|
1643
|
+
detail: item.enabledReason,
|
|
1644
|
+
managedBy: null,
|
|
1645
|
+
},
|
|
1646
|
+
actions: needsAttention
|
|
1647
|
+
? ["repair", "connect", "disconnect", "inspect"]
|
|
1648
|
+
: connected
|
|
1649
|
+
? ["connect", "configure", "disconnect", "inspect"]
|
|
1650
|
+
: ["connect", "inspect"],
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
if (item.surfaceType === "codex_apps") {
|
|
1655
|
+
return {
|
|
1656
|
+
...item,
|
|
1657
|
+
lifecycle: {
|
|
1658
|
+
status: item.enabled ? "connected" : "available",
|
|
1659
|
+
readiness: item.enabled ? "ready" : "setup_required",
|
|
1660
|
+
detail: item.enabledReason,
|
|
1661
|
+
managedBy: "platform",
|
|
1662
|
+
},
|
|
1663
|
+
actions: item.enabled ? ["configure", "disconnect", "inspect"] : ["connect", "inspect"],
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
const installed = item.enabled;
|
|
1668
|
+
const actions: CapabilityAction[] = installed
|
|
1669
|
+
? item.kind === "pack" || item.kind === "skill" || item.kind === "plugin"
|
|
1670
|
+
? ["configure", "update", "uninstall", "inspect"]
|
|
1671
|
+
: ["configure", "disconnect", "inspect"]
|
|
1672
|
+
: item.kind === "mcp" || item.kind === "api"
|
|
1673
|
+
? ["connect", "inspect"]
|
|
1674
|
+
: ["install", "inspect"];
|
|
1675
|
+
return {
|
|
1676
|
+
...item,
|
|
1677
|
+
lifecycle: {
|
|
1678
|
+
status: installed
|
|
1679
|
+
? item.kind === "mcp" || item.kind === "api"
|
|
1680
|
+
? "ready"
|
|
1681
|
+
: "installed"
|
|
1682
|
+
: "available",
|
|
1683
|
+
readiness: installed ? "ready" : "setup_required",
|
|
1684
|
+
detail: item.enabledReason,
|
|
1685
|
+
managedBy: item.source === "built_in" ? "platform" : "workspace",
|
|
1686
|
+
},
|
|
1687
|
+
actions,
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1376
1691
|
function skillLibraryInstallationRuntimeReady(
|
|
1377
1692
|
item: CapabilityCatalogItem,
|
|
1378
1693
|
installation: CapabilityInstallation,
|
|
@@ -1460,17 +1775,86 @@ function compareCatalogItems(a: CapabilityCatalogItem, b: CapabilityCatalogItem)
|
|
|
1460
1775
|
return `${a.kind}:${a.category}:${a.name}`.localeCompare(`${b.kind}:${b.category}:${b.name}`);
|
|
1461
1776
|
}
|
|
1462
1777
|
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1778
|
+
export type CapabilityCatalogSearchMatch = {
|
|
1779
|
+
item: CapabilityCatalogItem;
|
|
1780
|
+
score: number;
|
|
1781
|
+
matchedOn: Array<"name" | "provider" | "tag" | "category" | "description" | "id">;
|
|
1782
|
+
};
|
|
1783
|
+
|
|
1784
|
+
/**
|
|
1785
|
+
* Deterministically rank the already-merged workspace catalog for an agent.
|
|
1786
|
+
* The caller still owns live authorization checks (GitHub binding, OAuth row,
|
|
1787
|
+
* and so on); this function searches metadata only and never probes a provider
|
|
1788
|
+
* or exposes credential/setup prose to the model.
|
|
1789
|
+
*/
|
|
1790
|
+
export function searchCapabilityCatalogItems(
|
|
1791
|
+
items: readonly CapabilityCatalogItem[],
|
|
1792
|
+
query: string,
|
|
1793
|
+
limit = 8,
|
|
1794
|
+
): CapabilityCatalogSearchMatch[] {
|
|
1795
|
+
const phrase = normalizeCapabilitySearchText(query);
|
|
1796
|
+
const tokens = [...new Set(phrase.split(" ").filter(Boolean))].slice(0, 24);
|
|
1797
|
+
if (tokens.length === 0) return [];
|
|
1798
|
+
const boundedLimit = Math.min(20, Math.max(1, Math.trunc(limit)));
|
|
1799
|
+
const weightedFields = (item: CapabilityCatalogItem) =>
|
|
1800
|
+
[
|
|
1801
|
+
["name", item.name, 100],
|
|
1802
|
+
["provider", item.providerDomain ?? "", 80],
|
|
1803
|
+
["tag", item.tags.join(" "), 50],
|
|
1804
|
+
["category", item.category, 30],
|
|
1805
|
+
["id", item.id, 25],
|
|
1806
|
+
["description", item.description ?? "", 15],
|
|
1807
|
+
] as const;
|
|
1808
|
+
|
|
1809
|
+
return items
|
|
1810
|
+
.filter((item) => capabilityCatalogItemIsTrustedForExposure(item))
|
|
1811
|
+
.flatMap((item): CapabilityCatalogSearchMatch[] => {
|
|
1812
|
+
let score = 0;
|
|
1813
|
+
const matchedOn = new Set<CapabilityCatalogSearchMatch["matchedOn"][number]>();
|
|
1814
|
+
for (const [field, raw, weight] of weightedFields(item)) {
|
|
1815
|
+
const value = normalizeCapabilitySearchText(raw);
|
|
1816
|
+
if (!value) continue;
|
|
1817
|
+
const words = value.split(" ");
|
|
1818
|
+
if (value.includes(phrase)) {
|
|
1819
|
+
score += Math.round(weight * 1.5);
|
|
1820
|
+
matchedOn.add(field);
|
|
1821
|
+
}
|
|
1822
|
+
for (const token of tokens) {
|
|
1823
|
+
if (words.includes(token)) {
|
|
1824
|
+
score += weight;
|
|
1825
|
+
matchedOn.add(field);
|
|
1826
|
+
} else if (words.some((word) => word.startsWith(token) || token.startsWith(word))) {
|
|
1827
|
+
score += Math.round(weight * 0.7);
|
|
1828
|
+
matchedOn.add(field);
|
|
1829
|
+
} else if (value.includes(token)) {
|
|
1830
|
+
score += Math.round(weight * 0.4);
|
|
1831
|
+
matchedOn.add(field);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
if (score === 0) return [];
|
|
1836
|
+
if (item.enabled) score += 12;
|
|
1837
|
+
if (item.tier === "verified") score += 8;
|
|
1838
|
+
if (item.source === "built_in") score += 6;
|
|
1839
|
+
return [{ item, score, matchedOn: [...matchedOn] }];
|
|
1840
|
+
})
|
|
1841
|
+
.sort(
|
|
1842
|
+
(left, right) =>
|
|
1843
|
+
right.score - left.score ||
|
|
1844
|
+
Number(right.item.enabled) - Number(left.item.enabled) ||
|
|
1845
|
+
left.item.name.localeCompare(right.item.name) ||
|
|
1846
|
+
left.item.id.localeCompare(right.item.id),
|
|
1847
|
+
)
|
|
1848
|
+
.slice(0, boundedLimit);
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
function normalizeCapabilitySearchText(value: string): string {
|
|
1852
|
+
return value
|
|
1853
|
+
.normalize("NFKD")
|
|
1854
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
1855
|
+
.toLowerCase()
|
|
1856
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
1857
|
+
.trim();
|
|
1474
1858
|
}
|
|
1475
1859
|
|
|
1476
1860
|
function generatedCapabilityId(payload: CreateCapabilityCatalogItemRequest): string {
|