@opengeni/core 0.21.2 → 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 +9 -1
- package/dist/application/new-session-drafts.d.ts +1 -1
- package/dist/application/session-commands.d.ts +9 -4
- 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 +35 -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/memory-slack-publication.d.ts +0 -2
- 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 +27 -3
- package/dist/domain/session-tool-policy.d.ts +2 -2
- package/dist/domain/sessions.d.ts +44 -10
- 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 -25
- package/dist/index.js +3184 -652
- package/dist/index.js.map +1 -1
- package/dist/managed-session.d.ts +53 -0
- package/dist/rigs/index.d.ts +1 -1
- package/dist/rigs/provider-images.d.ts +31 -0
- package/dist/sandbox/fleet.d.ts +9 -1
- package/dist/sandbox/routing.d.ts +1 -0
- package/dist/session-authorization.d.ts +10 -1
- package/dist/workflow-wake-contract.d.ts +7 -0
- package/package.json +18 -10
- package/src/access/index.ts +43 -3
- package/src/application/new-session-drafts.ts +2 -0
- package/src/application/session-commands.ts +217 -116
- package/src/dependencies.ts +30 -2
- package/src/domain/capabilities.ts +672 -218
- 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 +10 -19
- package/src/domain/packs.ts +266 -5
- package/src/domain/personal-connection-delegations.ts +97 -6
- package/src/domain/resources.ts +10 -20
- package/src/domain/scheduled-tasks.ts +365 -25
- package/src/domain/session-tool-policy.ts +8 -2
- package/src/domain/sessions.ts +388 -155
- 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 +14 -0
- package/src/managed-auth-type.ts +4 -3
- package/src/managed-session.ts +36 -0
- package/src/rigs/index.ts +26 -19
- package/src/rigs/provider-images.ts +110 -0
- package/src/sandbox/fleet.ts +56 -1
- package/src/sandbox/routing.ts +32 -3
- package/src/session-authorization.ts +20 -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,16 +89,18 @@ export async function buildCapabilityCatalog(input: {
|
|
|
83
89
|
packInstallations,
|
|
84
90
|
workspacePacks,
|
|
85
91
|
socialConnections,
|
|
86
|
-
bundledSkills,
|
|
87
92
|
curatedLibrarySkills,
|
|
93
|
+
codexAppsCredentialId,
|
|
88
94
|
] = await Promise.all([
|
|
89
95
|
listCapabilityCatalogItems(input.db, input.workspaceId),
|
|
90
96
|
listCapabilityInstallations(input.db, input.workspaceId),
|
|
91
97
|
listPackInstallations(input.db, input.workspaceId),
|
|
92
98
|
listWorkspaceCapabilityPacks(input.db, input.workspaceId),
|
|
93
99
|
listSocialConnections(input.db, input.workspaceId, 500, input.subjectId),
|
|
94
|
-
discoverBundledSkills(),
|
|
95
100
|
discoverCuratedSkillLibraryItems(),
|
|
101
|
+
input.settings.codexConnectedAppsEnabled
|
|
102
|
+
? resolveCodexAppsCredentialIdForRun(input.db, input.workspaceId)
|
|
103
|
+
: Promise.resolve(null),
|
|
96
104
|
]);
|
|
97
105
|
const capabilityInstallationById = new Map(
|
|
98
106
|
capabilityInstallations.map((installation) => [installation.capabilityId, installation]),
|
|
@@ -108,14 +116,28 @@ export async function buildCapabilityCatalog(input: {
|
|
|
108
116
|
packCatalogItem(pack, builtInPackIds.has(pack.id) ? "built_in" : "manual"),
|
|
109
117
|
),
|
|
110
118
|
...configuredMcpCatalogItems(input.settings),
|
|
111
|
-
...
|
|
112
|
-
...bundledSkills,
|
|
119
|
+
...providerIntegrationCatalogItems(socialConnections),
|
|
113
120
|
...curatedLibrarySkills,
|
|
114
121
|
];
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
const codexApps = input.settings.codexConnectedAppsEnabled
|
|
123
|
+
? codexAppsCatalogItem(codexAppsCredentialId !== null)
|
|
124
|
+
: null;
|
|
125
|
+
const items = dedupeCatalogItems([
|
|
126
|
+
...builtIns,
|
|
127
|
+
...persistedItems.filter((item) => !isReservedCodexAppsCatalogItem(item)),
|
|
128
|
+
// Keep the reserved, server-derived item authoritative over any stale
|
|
129
|
+
// legacy catalog row with the same id.
|
|
130
|
+
...(codexApps ? [codexApps] : []),
|
|
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
|
+
})
|
|
119
141
|
.sort(compareCatalogItems);
|
|
120
142
|
return {
|
|
121
143
|
items,
|
|
@@ -123,6 +145,44 @@ export async function buildCapabilityCatalog(input: {
|
|
|
123
145
|
};
|
|
124
146
|
}
|
|
125
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
|
+
|
|
126
186
|
export async function createCatalogItem(input: {
|
|
127
187
|
db: Database;
|
|
128
188
|
accountId: string;
|
|
@@ -142,8 +202,9 @@ export async function createCatalogItem(input: {
|
|
|
142
202
|
}
|
|
143
203
|
if (
|
|
144
204
|
input.payload.kind === "mcp" &&
|
|
145
|
-
|
|
146
|
-
|
|
205
|
+
(id === `mcp:${CODEX_APPS_MCP_SERVER_ID}` ||
|
|
206
|
+
(typeof input.payload.metadata.mcpServerId === "string" &&
|
|
207
|
+
input.payload.metadata.mcpServerId.trim() === CODEX_APPS_MCP_SERVER_ID))
|
|
147
208
|
) {
|
|
148
209
|
throw new HTTPException(422, {
|
|
149
210
|
message: `${CODEX_APPS_MCP_SERVER_ID} is reserved for the canonical Codex Apps service`,
|
|
@@ -278,6 +339,12 @@ export async function enableCapability(input: {
|
|
|
278
339
|
if (!pack) {
|
|
279
340
|
throw new HTTPException(404, { message: "pack not found" });
|
|
280
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
|
+
}
|
|
281
348
|
await assertPackSandboxImageCompatible(input.db, input.workspaceId, pack);
|
|
282
349
|
// The unified capability-enable path accepts an initial variableSet
|
|
283
350
|
// attachment (`payload.variableSetId`), mirroring POST /packs/:id/enable:
|
|
@@ -419,11 +486,15 @@ function normalizedMcpCredentialHeaders(
|
|
|
419
486
|
const seen = new Set<string>();
|
|
420
487
|
for (const [name, value] of entries) {
|
|
421
488
|
if (!mcpCredentialHeaderName.test(name)) {
|
|
422
|
-
throw new HTTPException(422, {
|
|
489
|
+
throw new HTTPException(422, {
|
|
490
|
+
message: `invalid credential header name: ${name}`,
|
|
491
|
+
});
|
|
423
492
|
}
|
|
424
493
|
const lower = name.toLowerCase();
|
|
425
494
|
if (seen.has(lower)) {
|
|
426
|
-
throw new HTTPException(422, {
|
|
495
|
+
throw new HTTPException(422, {
|
|
496
|
+
message: `duplicate credential header name: ${name}`,
|
|
497
|
+
});
|
|
427
498
|
}
|
|
428
499
|
seen.add(lower);
|
|
429
500
|
if (value.length === 0 || value.length > maxMcpCredentialHeaderValueLength) {
|
|
@@ -449,6 +520,15 @@ async function validateMcpCapabilityConnectionRef(
|
|
|
449
520
|
ref: McpServerConnectionRef,
|
|
450
521
|
): Promise<McpServerConnectionRef> {
|
|
451
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
|
+
}
|
|
452
532
|
const normalized: McpServerConnectionRef = {
|
|
453
533
|
providerDomain: ref.providerDomain.trim(),
|
|
454
534
|
subjectScope,
|
|
@@ -458,11 +538,17 @@ async function validateMcpCapabilityConnectionRef(
|
|
|
458
538
|
...(ref.scopes ? { scopes: uniqueStrings(ref.scopes) } : {}),
|
|
459
539
|
...(ref.resource ? { resource: ref.resource } : {}),
|
|
460
540
|
...(ref.selectedResources
|
|
461
|
-
? {
|
|
541
|
+
? {
|
|
542
|
+
selectedResources: ref.selectedResources.map((resource) => ({
|
|
543
|
+
...resource,
|
|
544
|
+
})),
|
|
545
|
+
}
|
|
462
546
|
: {}),
|
|
463
547
|
};
|
|
464
548
|
if (!normalized.providerDomain) {
|
|
465
|
-
throw new HTTPException(422, {
|
|
549
|
+
throw new HTTPException(422, {
|
|
550
|
+
message: "connectionRef.providerDomain is required",
|
|
551
|
+
});
|
|
466
552
|
}
|
|
467
553
|
if (!item.endpointUrl || !item.runtime.mcpServerId) {
|
|
468
554
|
throw new HTTPException(422, {
|
|
@@ -706,6 +792,20 @@ export async function disableCapability(input: {
|
|
|
706
792
|
});
|
|
707
793
|
}
|
|
708
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
|
+
}
|
|
709
809
|
await updatePackInstallationStatus(
|
|
710
810
|
input.db,
|
|
711
811
|
input.workspaceId,
|
|
@@ -723,7 +823,9 @@ export async function disableCapability(input: {
|
|
|
723
823
|
});
|
|
724
824
|
}
|
|
725
825
|
} else if (!(await getCapabilityInstallation(input.db, input.workspaceId, item.id))) {
|
|
726
|
-
throw new HTTPException(409, {
|
|
826
|
+
throw new HTTPException(409, {
|
|
827
|
+
message: "capability is not currently enabled",
|
|
828
|
+
});
|
|
727
829
|
}
|
|
728
830
|
return await disableCapabilityInstallation(input.db, input.workspaceId, item.id);
|
|
729
831
|
}
|
|
@@ -732,17 +834,109 @@ export async function settingsWithEnabledCapabilityMcpServers(
|
|
|
732
834
|
db: Database,
|
|
733
835
|
workspaceId: string,
|
|
734
836
|
settings: Settings,
|
|
837
|
+
options?: {
|
|
838
|
+
subjectId?: string;
|
|
839
|
+
personalConnectionDelegations?: readonly McpPersonalConnectionDelegation[];
|
|
840
|
+
onResolvedApiIntegrations?: (integrations: readonly ApiIntegrationRuntime[]) => void;
|
|
841
|
+
},
|
|
735
842
|
): Promise<Settings> {
|
|
736
|
-
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([
|
|
737
851
|
listEnabledMcpCapabilityServers(db, workspaceId),
|
|
852
|
+
apiIntegrationsPromise,
|
|
738
853
|
resolveCodexAppsCredentialIdForRun(db, workspaceId),
|
|
739
854
|
]);
|
|
855
|
+
options?.onResolvedApiIntegrations?.(apiIntegrations);
|
|
740
856
|
return settingsWithCodexAppsMcpServer(
|
|
741
|
-
|
|
857
|
+
settingsWithApiIntegrationServers(
|
|
858
|
+
settingsWithMcpCapabilityServers(settings, enabled),
|
|
859
|
+
apiIntegrations,
|
|
860
|
+
),
|
|
742
861
|
codexAppsCredentialId !== null,
|
|
743
862
|
);
|
|
744
863
|
}
|
|
745
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
|
+
|
|
746
940
|
/**
|
|
747
941
|
* Resolve executable Apps authority. The connector must remain active and its
|
|
748
942
|
* exact owner must still hold workspace connection-management permission.
|
|
@@ -832,6 +1026,9 @@ export function settingsWithMcpCapabilityServers(
|
|
|
832
1026
|
...(server.allowedTools ? { allowedTools: server.allowedTools } : {}),
|
|
833
1027
|
...(server.timeoutMs ? { timeoutMs: server.timeoutMs } : {}),
|
|
834
1028
|
cacheToolsList: server.cacheToolsList ?? false,
|
|
1029
|
+
...(server.requireApproval !== undefined
|
|
1030
|
+
? { requireApproval: server.requireApproval }
|
|
1031
|
+
: {}),
|
|
835
1032
|
...(headers && headers !== "unavailable" ? { headers } : {}),
|
|
836
1033
|
...(server.connectionRef ? { connectionRef: server.connectionRef } : {}),
|
|
837
1034
|
},
|
|
@@ -917,7 +1114,9 @@ async function fetchMcpRegistryPage(
|
|
|
917
1114
|
try {
|
|
918
1115
|
const response = await fetchImpl(url, { signal: controller.signal });
|
|
919
1116
|
if (!response.ok) {
|
|
920
|
-
throw new HTTPException(502, {
|
|
1117
|
+
throw new HTTPException(502, {
|
|
1118
|
+
message: `MCP registry returned ${response.status}`,
|
|
1119
|
+
});
|
|
921
1120
|
}
|
|
922
1121
|
return (await response.json()) as McpRegistryPage;
|
|
923
1122
|
} catch (error) {
|
|
@@ -925,7 +1124,9 @@ async function fetchMcpRegistryPage(
|
|
|
925
1124
|
throw error;
|
|
926
1125
|
}
|
|
927
1126
|
if (error instanceof Error && error.name === "AbortError") {
|
|
928
|
-
throw new HTTPException(504, {
|
|
1127
|
+
throw new HTTPException(504, {
|
|
1128
|
+
message: "MCP registry request timed out",
|
|
1129
|
+
});
|
|
929
1130
|
}
|
|
930
1131
|
throw new HTTPException(502, {
|
|
931
1132
|
message: `MCP registry request failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -988,6 +1189,7 @@ function packCatalogItem(
|
|
|
988
1189
|
connectors: pack.connectors,
|
|
989
1190
|
knowledge: pack.knowledge,
|
|
990
1191
|
scheduledTaskTemplates: pack.scheduledTaskTemplates,
|
|
1192
|
+
...(pack.variableSet ? { variableSet: pack.variableSet } : {}),
|
|
991
1193
|
// Runtime composition surface only: skill names, never file content.
|
|
992
1194
|
...(pack.sandboxImage ? { sandboxImage: pack.sandboxImage } : {}),
|
|
993
1195
|
...(pack.sandboxProviderImages ? { sandboxProviderImages: pack.sandboxProviderImages } : {}),
|
|
@@ -997,188 +1199,251 @@ function packCatalogItem(
|
|
|
997
1199
|
}
|
|
998
1200
|
|
|
999
1201
|
function configuredMcpCatalogItems(settings: Settings): CapabilityCatalogItem[] {
|
|
1000
|
-
return
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
: "
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
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
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function isReservedCodexAppsCatalogItem(item: CapabilityCatalogItem): boolean {
|
|
1239
|
+
return (
|
|
1240
|
+
item.id === `mcp:${CODEX_APPS_MCP_SERVER_ID}` ||
|
|
1241
|
+
(item.kind === "mcp" &&
|
|
1242
|
+
(item.runtime.mcpServerId === CODEX_APPS_MCP_SERVER_ID ||
|
|
1243
|
+
item.metadata.mcpServerId === CODEX_APPS_MCP_SERVER_ID))
|
|
1025
1244
|
);
|
|
1026
1245
|
}
|
|
1027
1246
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1247
|
+
/**
|
|
1248
|
+
* The Apps MCP is not a configured server and is not a user-installable
|
|
1249
|
+
* capability. It is a deployment-gated, workspace-designated runtime surface,
|
|
1250
|
+
* so project it into the same catalog the web picker already consumes while
|
|
1251
|
+
* keeping its authorization state server-derived and explicit.
|
|
1252
|
+
*/
|
|
1253
|
+
export function codexAppsCatalogItem(available: boolean): CapabilityCatalogItem {
|
|
1254
|
+
return CapabilityCatalogItem.parse({
|
|
1255
|
+
id: `mcp:${CODEX_APPS_MCP_SERVER_ID}`,
|
|
1256
|
+
kind: "mcp",
|
|
1034
1257
|
source: "built_in",
|
|
1035
|
-
name: "
|
|
1258
|
+
name: "Codex Apps",
|
|
1036
1259
|
description:
|
|
1037
|
-
"
|
|
1038
|
-
category: "
|
|
1039
|
-
tags: ["
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
authKind: "
|
|
1045
|
-
tools: [{ kind: "mcp", id:
|
|
1260
|
+
"Use the ChatGPT Apps designated for this workspace. Sessions include this surface by default when it is authorized; explicit policies can opt out.",
|
|
1261
|
+
category: "productivity",
|
|
1262
|
+
tags: ["mcp", "codex", "connected-apps"],
|
|
1263
|
+
providerDomain: "chatgpt.com",
|
|
1264
|
+
surfaceType: "codex_apps",
|
|
1265
|
+
transport: "streamable-http",
|
|
1266
|
+
mcpUrl: CODEX_APPS_MCP_URL,
|
|
1267
|
+
authKind: "none",
|
|
1268
|
+
tools: [{ kind: "mcp", id: CODEX_APPS_MCP_SERVER_ID }],
|
|
1046
1269
|
runtime: {
|
|
1047
|
-
available
|
|
1048
|
-
mcpServerId:
|
|
1049
|
-
|
|
1270
|
+
available,
|
|
1271
|
+
...(available ? { mcpServerId: CODEX_APPS_MCP_SERVER_ID } : {}),
|
|
1272
|
+
transport: "streamable-http",
|
|
1273
|
+
notes: available
|
|
1274
|
+
? "Available through the active workspace Apps designation."
|
|
1275
|
+
: "Unavailable until an active Codex Apps credential is designated for this workspace.",
|
|
1050
1276
|
},
|
|
1051
|
-
enabled:
|
|
1052
|
-
enabledReason:
|
|
1053
|
-
? xConnection.status === "connected"
|
|
1054
|
-
? `${xConnection.ownership} social account connected`
|
|
1055
|
-
: `${xConnection.ownership} social account needs reconnection`
|
|
1056
|
-
: null,
|
|
1277
|
+
enabled: available,
|
|
1278
|
+
enabledReason: available ? "designated Apps credential" : "no active Apps designation",
|
|
1057
1279
|
metadata: {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
ownership: xConnection?.ownership ?? "workspace",
|
|
1061
|
-
firstPartyMcpTools: [
|
|
1062
|
-
"social_connections_list",
|
|
1063
|
-
"social_posts_recent",
|
|
1064
|
-
"social_daily_analysis_context",
|
|
1065
|
-
"social_search_live",
|
|
1066
|
-
"social_mentions_live",
|
|
1067
|
-
"social_thread_fetch",
|
|
1068
|
-
"social_posts_sync",
|
|
1069
|
-
"social_post_reply",
|
|
1070
|
-
],
|
|
1280
|
+
mcpServerId: CODEX_APPS_MCP_SERVER_ID,
|
|
1281
|
+
authorization: "workspace_designation",
|
|
1071
1282
|
},
|
|
1072
1283
|
});
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1284
|
+
}
|
|
1285
|
+
|
|
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",
|
|
1298
|
+
name: "X",
|
|
1299
|
+
description:
|
|
1300
|
+
"Connect one or more X accounts for live search, mentions, thread context, post sync, and permission-controlled replies.",
|
|
1301
|
+
providerDomain: "x.com",
|
|
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
|
+
});
|
|
1372
|
+
});
|
|
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 [
|
|
1107
1383
|
CapabilityCatalogItem.parse({
|
|
1108
|
-
id:
|
|
1109
|
-
name: item.name,
|
|
1110
|
-
description: item.description,
|
|
1111
|
-
category: item.category,
|
|
1112
|
-
tags: item.tags,
|
|
1384
|
+
id: "api:github-app",
|
|
1113
1385
|
kind: "api",
|
|
1114
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" }],
|
|
1115
1397
|
runtime: {
|
|
1116
1398
|
available: true,
|
|
1117
|
-
notes:
|
|
1399
|
+
notes:
|
|
1400
|
+
"GitHub credentials stay host-owned; a workspace owner must approve repository access.",
|
|
1118
1401
|
},
|
|
1402
|
+
lifecycle: {
|
|
1403
|
+
status: "available",
|
|
1404
|
+
readiness: "setup_required",
|
|
1405
|
+
detail: "Connect GitHub from the repository resource flow.",
|
|
1406
|
+
managedBy: "platform",
|
|
1407
|
+
},
|
|
1408
|
+
actions: ["connect", "inspect"],
|
|
1119
1409
|
metadata: {
|
|
1120
|
-
endpointPath:
|
|
1410
|
+
endpointPath: "/v1/workspaces/{workspaceId}/github/app",
|
|
1411
|
+
firstPartyMcpTools: ["github_connect_link", "github_repositories_list"],
|
|
1412
|
+
recommendationOnly: true,
|
|
1121
1413
|
},
|
|
1122
1414
|
}),
|
|
1123
|
-
|
|
1124
|
-
return [x, ...platformApis];
|
|
1415
|
+
];
|
|
1125
1416
|
}
|
|
1126
1417
|
|
|
1127
|
-
function
|
|
1418
|
+
function socialConnectionCounts(
|
|
1128
1419
|
connections: SocialConnection[],
|
|
1129
1420
|
provider: "x" | "reddit",
|
|
1130
|
-
):
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
statusRank(left.status) - statusRank(right.status) ||
|
|
1139
|
-
right.updatedAt.localeCompare(left.updatedAt) ||
|
|
1140
|
-
left.id.localeCompare(right.id),
|
|
1141
|
-
)[0] ?? null
|
|
1142
|
-
);
|
|
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
|
+
};
|
|
1143
1429
|
}
|
|
1144
1430
|
|
|
1145
|
-
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
.map(async (entry) => {
|
|
1156
|
-
const skill = await readSkillMetadata(
|
|
1157
|
-
new URL(`${entry.name}/SKILL.md`, skillsDir),
|
|
1158
|
-
entry.name,
|
|
1159
|
-
);
|
|
1160
|
-
return CapabilityCatalogItem.parse({
|
|
1161
|
-
id: `skill:${entry.name}`,
|
|
1162
|
-
kind: "skill",
|
|
1163
|
-
source: "built_in",
|
|
1164
|
-
name: skill.name,
|
|
1165
|
-
description: skill.description,
|
|
1166
|
-
category: skill.category,
|
|
1167
|
-
tags: ["skill", skill.category],
|
|
1168
|
-
runtime: {
|
|
1169
|
-
available: true,
|
|
1170
|
-
notes: "Bundled into the sandbox skill library.",
|
|
1171
|
-
},
|
|
1172
|
-
metadata: {
|
|
1173
|
-
path: `packages/runtime/src/bundled_hashicorp_terraform_skills/${entry.name}/SKILL.md`,
|
|
1174
|
-
},
|
|
1175
|
-
});
|
|
1176
|
-
}),
|
|
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`,
|
|
1177
1441
|
);
|
|
1178
|
-
return skills;
|
|
1179
|
-
} catch {
|
|
1180
|
-
return [];
|
|
1181
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;
|
|
1182
1447
|
}
|
|
1183
1448
|
|
|
1184
1449
|
/**
|
|
@@ -1227,31 +1492,35 @@ function stringMetadata(value: unknown): string | null {
|
|
|
1227
1492
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
1228
1493
|
}
|
|
1229
1494
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
?
|
|
1253
|
-
:
|
|
1254
|
-
|
|
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
|
+
};
|
|
1255
1524
|
}
|
|
1256
1525
|
|
|
1257
1526
|
export function applyCapabilityEnablement(
|
|
@@ -1270,19 +1539,32 @@ export function applyCapabilityEnablement(
|
|
|
1270
1539
|
enabledReason: enabled ? "enabled" : null,
|
|
1271
1540
|
};
|
|
1272
1541
|
}
|
|
1273
|
-
if (item
|
|
1274
|
-
//
|
|
1275
|
-
//
|
|
1276
|
-
//
|
|
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.
|
|
1546
|
+
return { ...item, connectionRef: null };
|
|
1547
|
+
}
|
|
1548
|
+
if (item.surfaceType === "codex_apps") {
|
|
1549
|
+
// Unlike ordinary built-ins, Apps availability is derived from the exact
|
|
1550
|
+
// active workspace designation above. Do not overwrite it with the
|
|
1551
|
+
// generic source-based "built in" enablement rule.
|
|
1277
1552
|
return { ...item, connectionRef: null };
|
|
1278
1553
|
}
|
|
1279
|
-
if (item.source === "
|
|
1554
|
+
if (item.source === "configured") {
|
|
1280
1555
|
return {
|
|
1281
1556
|
...item,
|
|
1282
1557
|
enabled: true,
|
|
1283
|
-
enabledReason:
|
|
1558
|
+
enabledReason: "managed by deployment",
|
|
1559
|
+
connectionRef: null,
|
|
1284
1560
|
};
|
|
1285
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
|
+
}
|
|
1286
1568
|
if (item.source === "library") {
|
|
1287
1569
|
const enabled =
|
|
1288
1570
|
installation?.status === "active" && skillLibraryInstallationRuntimeReady(item, installation);
|
|
@@ -1295,6 +1577,25 @@ export function applyCapabilityEnablement(
|
|
|
1295
1577
|
}
|
|
1296
1578
|
const activeInstallation = installation?.status === "active";
|
|
1297
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
|
+
}
|
|
1298
1599
|
return {
|
|
1299
1600
|
...item,
|
|
1300
1601
|
enabled,
|
|
@@ -1303,6 +1604,90 @@ export function applyCapabilityEnablement(
|
|
|
1303
1604
|
};
|
|
1304
1605
|
}
|
|
1305
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
|
+
|
|
1306
1691
|
function skillLibraryInstallationRuntimeReady(
|
|
1307
1692
|
item: CapabilityCatalogItem,
|
|
1308
1693
|
installation: CapabilityInstallation,
|
|
@@ -1390,17 +1775,86 @@ function compareCatalogItems(a: CapabilityCatalogItem, b: CapabilityCatalogItem)
|
|
|
1390
1775
|
return `${a.kind}:${a.category}:${a.name}`.localeCompare(`${b.kind}:${b.category}:${b.name}`);
|
|
1391
1776
|
}
|
|
1392
1777
|
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
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();
|
|
1404
1858
|
}
|
|
1405
1859
|
|
|
1406
1860
|
function generatedCapabilityId(payload: CreateCapabilityCatalogItemRequest): string {
|
|
@@ -1606,7 +2060,7 @@ function capabilityInstallationRuntimeReady(
|
|
|
1606
2060
|
}
|
|
1607
2061
|
|
|
1608
2062
|
/**
|
|
1609
|
-
* Checks the
|
|
2063
|
+
* Checks the generic installation projection (header names only) against the
|
|
1610
2064
|
* capability's declared credential requirements.
|
|
1611
2065
|
*/
|
|
1612
2066
|
function storedCredentialHeadersSatisfy(
|