@opengeni/core 3.0.1 → 3.1.2
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/README.md +4 -4
- package/dist/access/index.d.ts +131 -7
- package/dist/access/service-initiator.d.ts +3 -0
- package/dist/application/archived-session-imports.d.ts +6 -0
- package/dist/application/composer-submit.d.ts +1 -1
- package/dist/application/connect-operation.d.ts +6 -0
- package/dist/application/external-workspace-members.d.ts +13 -1
- package/dist/application/modal-native-live-declaration-join.d.ts +37 -0
- package/dist/application/modal-native-original-configuration.d.ts +79 -0
- package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
- package/dist/application/new-session-drafts.d.ts +29 -2
- package/dist/application/organization-privacy.d.ts +57 -0
- package/dist/application/prepared-mcp-connection.d.ts +31 -0
- package/dist/application/sandbox-recovery.d.ts +58 -0
- package/dist/application/session-commands.d.ts +3 -0
- package/dist/application/session-tenancy.d.ts +13 -4
- package/dist/billing/agent-run-admission.d.ts +21 -0
- package/dist/billing/limits.d.ts +27 -1
- package/dist/billing/model-call-admission.d.ts +34 -0
- package/dist/billing/model-usage-settlement.d.ts +112 -0
- package/dist/canonical-human-identities.js +1 -1
- package/dist/{chunk-VCZFFEPB.js → chunk-7T2SZ7BI.js} +16 -2
- package/dist/chunk-7T2SZ7BI.js.map +1 -0
- package/dist/{chunk-ZYCNLYGH.js → chunk-OMJFIIWK.js} +178 -12
- package/dist/chunk-OMJFIIWK.js.map +1 -0
- package/dist/{chunk-H7X52RI2.js → chunk-QI2M2B2K.js} +83 -8
- package/dist/chunk-QI2M2B2K.js.map +1 -0
- package/dist/{chunk-OF65T3PM.js → chunk-TTWTXQB2.js} +94 -6
- package/dist/chunk-TTWTXQB2.js.map +1 -0
- package/dist/codex-model-availability.d.ts +55 -0
- package/dist/default-session-model.d.ts +192 -0
- package/dist/dependencies.d.ts +41 -7
- package/dist/developer-setup-credential-ceiling.d.ts +10 -0
- package/dist/domain/agent-config-resolution.d.ts +49 -0
- package/dist/domain/automations.d.ts +8 -1
- package/dist/domain/capabilities.d.ts +112 -8
- package/dist/domain/connection-account-selection-error.d.ts +8 -0
- package/dist/domain/connector-tool-permissions.d.ts +62 -0
- package/dist/domain/direct-model-provider.d.ts +3 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
- package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
- package/dist/domain/editable-artifacts/ports.d.ts +10 -0
- package/dist/domain/editable-artifacts/service.d.ts +3 -1
- package/dist/domain/editable-artifacts/types.d.ts +1 -1
- package/dist/domain/encryption-key.d.ts +12 -0
- package/dist/domain/insights-timing.d.ts +16 -0
- package/dist/domain/insights-usage.d.ts +27 -0
- package/dist/domain/insights.d.ts +21 -3
- package/dist/domain/knowledge-files.d.ts +1 -1
- package/dist/domain/knowledge-preparation.d.ts +10 -0
- package/dist/domain/knowledge-preparation.test.d.ts +1 -0
- package/dist/domain/knowledge-search.d.ts +15 -76
- package/dist/domain/mcp-account-bindings.d.ts +17 -0
- package/dist/domain/mcp-account-routes.d.ts +21 -0
- package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
- package/dist/domain/organization-integration-catalog.d.ts +15 -0
- package/dist/domain/personal-connection-delegations.d.ts +73 -12
- package/dist/domain/pr-review.d.ts +121 -6
- package/dist/domain/realtime-voice-billing.d.ts +103 -0
- package/dist/domain/resources.d.ts +17 -1
- package/dist/domain/scheduled-task-access.d.ts +265 -0
- package/dist/domain/scheduled-task-account-audit.d.ts +27 -0
- package/dist/domain/scheduled-task-audience.d.ts +70 -0
- package/dist/domain/scheduled-tasks.d.ts +61 -18
- package/dist/domain/session-connection-accounts.d.ts +53 -0
- package/dist/domain/session-file-access.d.ts +4 -0
- package/dist/domain/session-native-connection-replacement.d.ts +14 -0
- package/dist/domain/session-start-timing.d.ts +7 -0
- package/dist/domain/session-tool-policy.d.ts +102 -4
- package/dist/domain/sessions.d.ts +179 -15
- package/dist/domain/site-audience.d.ts +52 -0
- package/dist/domain/skills.d.ts +29 -1
- package/dist/domain/slack-bot.d.ts +46 -1
- package/dist/domain/slack-publication-secret-safety.d.ts +1 -1
- package/dist/domain/vaults/index.d.ts +7 -0
- package/dist/domain/vaults/mapping.d.ts +25 -0
- package/dist/domain/vaults/provider-registry.d.ts +72 -0
- package/dist/domain/vaults/provider-sync.d.ts +176 -0
- package/dist/domain/vaults/provider-writes.d.ts +57 -0
- package/dist/domain/vaults/secret-resolver.d.ts +34 -0
- package/dist/domain/vaults/types.d.ts +24 -0
- package/dist/domain/vaults/vault-service.d.ts +35 -0
- package/dist/domain/voice-input-billing.d.ts +77 -0
- package/dist/domain/web-search-billing.d.ts +51 -0
- package/dist/domain/workspace-credits.d.ts +7 -0
- package/dist/editable-artifact-live/application.d.ts +2 -1
- package/dist/editable-artifact-live/server.d.ts +5 -1
- package/dist/editable-artifact-live/ticket.d.ts +2 -1
- package/dist/editable-artifact-live/types.d.ts +9 -0
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +10 -2
- package/dist/goal-admission.d.ts +627 -0
- package/dist/host-session-usage-limit.d.ts +15 -0
- package/dist/index.d.ts +43 -8
- package/dist/index.js +22682 -11743
- package/dist/index.js.map +1 -1
- package/dist/managed-session.d.ts +110 -7
- package/dist/model-call.d.ts +58 -0
- package/dist/model-catalog.d.ts +29 -3
- package/dist/model-provider-credentials.d.ts +43 -0
- package/dist/product-usage-metrics.d.ts +30 -0
- package/dist/rigs/index.d.ts +1 -13
- package/dist/sandbox/fleet.d.ts +58 -4
- package/dist/sandbox/routing.d.ts +42 -2
- package/dist/sandbox/runtime-settings.d.ts +27 -17
- package/dist/session-authorization.d.ts +10 -0
- package/dist/session-usage-limit-metrics.d.ts +60 -0
- package/dist/session-usage-limits.d.ts +89 -0
- package/dist/site-agent-clamp.d.ts +71 -0
- package/dist/site-session-origin.d.ts +39 -2
- package/dist/transcription.d.ts +120 -3
- package/dist/turn-surface.d.ts +20 -0
- package/dist/user-presence.d.ts +32 -0
- package/dist/workflow-wake-contract.d.ts +13 -1
- package/package.json +15 -22
- package/src/access/index.ts +961 -38
- package/src/access/service-initiator.ts +50 -0
- package/src/application/api-integration-servers.ts +9 -0
- package/src/application/archived-session-imports.ts +161 -0
- package/src/application/composer-submit.ts +3 -2
- package/src/application/connect-authority.ts +3 -3
- package/src/application/connect-operation.ts +37 -3
- package/src/application/external-continuation.ts +20 -3
- package/src/application/external-link-work-admission.ts +3 -0
- package/src/application/external-workspace-members.ts +51 -3
- package/src/application/modal-native-live-declaration-join.ts +196 -0
- package/src/application/modal-native-original-configuration.ts +261 -0
- package/src/application/modal-native-worker-host-transport.ts +263 -0
- package/src/application/new-session-drafts.ts +172 -55
- package/src/application/organization-privacy.ts +161 -0
- package/src/application/prepared-mcp-connection.ts +133 -0
- package/src/application/sandbox-recovery.ts +78 -0
- package/src/application/session-commands.ts +73 -1
- package/src/application/session-mcp-credential-rotation.ts +61 -18
- package/src/application/session-tenancy.ts +262 -44
- package/src/application/user-resource-grants.ts +1 -29
- package/src/billing/agent-run-admission.ts +117 -0
- package/src/billing/limits.ts +138 -14
- package/src/billing/model-call-admission.ts +118 -0
- package/src/billing/model-usage-settlement.ts +472 -0
- package/src/codex-model-availability.ts +264 -0
- package/src/default-session-model.ts +692 -0
- package/src/dependencies.ts +48 -6
- package/src/developer-setup-credential-ceiling.ts +60 -0
- package/src/domain/agent-config-resolution.ts +148 -0
- package/src/domain/automations.ts +76 -2
- package/src/domain/capabilities.ts +830 -181
- package/src/domain/connection-account-selection-error.ts +19 -0
- package/src/domain/connector-tool-permissions.ts +572 -0
- package/src/domain/direct-model-provider.ts +62 -0
- package/src/domain/editable-artifacts/agent-application.ts +44 -6
- package/src/domain/editable-artifacts/durable-export.ts +100 -5
- package/src/domain/editable-artifacts/in-memory.ts +6 -0
- package/src/domain/editable-artifacts/ports.ts +11 -0
- package/src/domain/editable-artifacts/service.ts +20 -0
- package/src/domain/editable-artifacts/types.ts +1 -1
- package/src/domain/encryption-key.ts +26 -0
- package/src/domain/file-owner.ts +16 -1
- package/src/domain/github-action-policies.ts +1 -1
- package/src/domain/github-skill-source.ts +3 -1
- package/src/domain/insights-timing.ts +95 -0
- package/src/domain/insights-usage.ts +67 -0
- package/src/domain/insights.ts +167 -43
- package/src/domain/knowledge-files.ts +10 -3
- package/src/domain/knowledge-messages.ts +1 -0
- package/src/domain/knowledge-preparation.test.ts +276 -0
- package/src/domain/knowledge-preparation.ts +147 -0
- package/src/domain/knowledge-search.test.ts +617 -5
- package/src/domain/knowledge-search.ts +240 -23
- package/src/domain/mcp-account-bindings.ts +169 -0
- package/src/domain/mcp-account-routes.ts +152 -0
- package/src/domain/native-mcp-connection-admission.ts +14 -0
- package/src/domain/organization-integration-catalog.ts +88 -0
- package/src/domain/personal-connection-delegations.ts +399 -257
- package/src/domain/pr-review.ts +24 -8
- package/src/domain/realtime-voice-billing.ts +425 -0
- package/src/domain/resources.ts +50 -4
- package/src/domain/scheduled-task-access.ts +1089 -0
- package/src/domain/scheduled-task-account-audit.ts +90 -0
- package/src/domain/scheduled-task-audience.ts +249 -0
- package/src/domain/scheduled-tasks.ts +1313 -374
- package/src/domain/session-connection-accounts.ts +239 -0
- package/src/domain/session-file-access.ts +47 -0
- package/src/domain/session-native-connection-replacement.ts +64 -0
- package/src/domain/session-start-timing.ts +60 -0
- package/src/domain/session-tool-policy.ts +489 -14
- package/src/domain/sessions.ts +2254 -717
- package/src/domain/site-audience.ts +130 -0
- package/src/domain/skills.ts +9 -0
- package/src/domain/slack-bot.ts +141 -4
- package/src/domain/slack-publication-secret-safety.ts +1 -1
- package/src/domain/vaults/index.ts +19 -0
- package/src/domain/vaults/mapping.ts +190 -0
- package/src/domain/vaults/provider-registry.ts +201 -0
- package/src/domain/vaults/provider-sync.ts +531 -0
- package/src/domain/vaults/provider-writes.ts +288 -0
- package/src/domain/vaults/secret-resolver.ts +464 -0
- package/src/domain/vaults/types.ts +26 -0
- package/src/domain/vaults/vault-service.ts +1316 -0
- package/src/domain/voice-input-billing.ts +330 -0
- package/src/domain/web-search-billing.ts +169 -0
- package/src/domain/workspace-credits.ts +13 -0
- package/src/editable-artifact-live/application.ts +8 -1
- package/src/editable-artifact-live/server.ts +154 -6
- package/src/editable-artifact-live/ticket.ts +86 -1
- package/src/editable-artifact-live/types.ts +10 -0
- package/src/goal-admission.ts +220 -0
- package/src/host-session-usage-limit.ts +25 -0
- package/src/index.ts +75 -11
- package/src/managed-session.ts +137 -6
- package/src/model-call.ts +79 -0
- package/src/model-catalog.ts +345 -91
- package/src/model-provider-credentials.ts +394 -0
- package/src/product-usage-metrics.ts +101 -0
- package/src/rigs/index.ts +23 -111
- package/src/sandbox/fleet.ts +253 -28
- package/src/sandbox/routing.ts +204 -43
- package/src/sandbox/runtime-settings.ts +159 -85
- package/src/session-authorization.ts +172 -23
- package/src/session-usage-limit-metrics.ts +95 -0
- package/src/session-usage-limits.ts +415 -0
- package/src/site-agent-clamp.ts +226 -0
- package/src/site-session-origin.ts +84 -5
- package/src/transcription.ts +123 -4
- package/src/turn-surface.ts +47 -0
- package/src/user-presence.ts +134 -0
- package/src/workflow-wake-contract.ts +14 -1
- package/dist/application/host-mcp-owner.d.ts +0 -6
- package/dist/application/host-mcp-resolvers.d.ts +0 -13
- package/dist/chunk-5WZYFL45.js +0 -266
- package/dist/chunk-5WZYFL45.js.map +0 -1
- package/dist/chunk-H7X52RI2.js.map +0 -1
- package/dist/chunk-OF65T3PM.js.map +0 -1
- package/dist/chunk-VCZFFEPB.js.map +0 -1
- package/dist/chunk-ZYCNLYGH.js.map +0 -1
- package/dist/domain/connection-authority.d.ts +0 -97
- package/dist/domain/connection-authority.js +0 -169
- package/dist/domain/connection-authority.js.map +0 -1
- package/dist/domain/environments.d.ts +0 -33
- package/dist/domain/host-mcp-authority-source-admission.d.ts +0 -10
- package/dist/domain/host-mcp-task-admission.d.ts +0 -18
- package/dist/domain/packs.d.ts +0 -59
- package/dist/domain/product-integration-pack.d.ts +0 -44
- package/dist/domain/product-integration-skill.gen.d.ts +0 -5
- package/dist/remote-mcp-credentials.d.ts +0 -23
- package/dist/remote-mcp-credentials.js +0 -11
- package/dist/remote-mcp-credentials.js.map +0 -1
- package/src/application/host-mcp-owner.ts +0 -41
- package/src/application/host-mcp-resolvers.ts +0 -84
- package/src/domain/connection-authority.ts +0 -329
- package/src/domain/environments.ts +0 -120
- package/src/domain/host-mcp-authority-source-admission.ts +0 -25
- package/src/domain/host-mcp-task-admission.ts +0 -121
- package/src/domain/packs.ts +0 -699
- package/src/domain/product-integration-pack.ts +0 -42
- package/src/domain/product-integration-skill.gen.ts +0 -52
- package/src/remote-mcp-credentials.ts +0 -361
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { McpServerConfig, Settings } from "@opengeni/config";
|
|
2
2
|
import type {
|
|
3
3
|
AccessGrant,
|
|
4
|
+
ConnectionAccountIntent,
|
|
4
5
|
ConnectionMetadata,
|
|
5
|
-
|
|
6
|
+
McpConnectionAccountBinding,
|
|
7
|
+
McpConnectionAccountSelection,
|
|
6
8
|
McpPersonalConnectionDelegation,
|
|
7
9
|
McpServerConnectionRef,
|
|
8
10
|
ResourceRef,
|
|
@@ -24,17 +26,25 @@ import {
|
|
|
24
26
|
import {
|
|
25
27
|
getPersonalGitHubRepositorySelectionState,
|
|
26
28
|
getSessionTurnPersonalConnectionDelegations,
|
|
29
|
+
getSessionTurnMcpAccountBindings,
|
|
30
|
+
getSessionTurnConnectionAccountIntent,
|
|
27
31
|
getConnectionMetadata,
|
|
28
32
|
getSocialConnection,
|
|
29
33
|
listConnectionsMetadata,
|
|
30
34
|
listSocialConnections,
|
|
31
|
-
|
|
35
|
+
listOwnedConnectionAccounts,
|
|
32
36
|
namedSubjectHasLiveWorkspaceAuthority,
|
|
33
37
|
type Database,
|
|
34
38
|
type ResolveConnectionCredentialInput,
|
|
35
39
|
type ResolveConnectionCredentialResult,
|
|
36
40
|
} from "@opengeni/db";
|
|
37
41
|
import { personalGitHubRepositoryResources } from "./resources";
|
|
42
|
+
import { ConnectionAccountSelectionError } from "./connection-account-selection-error";
|
|
43
|
+
export { ConnectionAccountSelectionError } from "./connection-account-selection-error";
|
|
44
|
+
import {
|
|
45
|
+
mcpAccountBindingsFromVisibleConnections,
|
|
46
|
+
personalDelegationsForAccountBindings,
|
|
47
|
+
} from "./mcp-account-bindings";
|
|
38
48
|
|
|
39
49
|
export type PersonalConnectionDelegationSource =
|
|
40
50
|
| {
|
|
@@ -157,6 +167,67 @@ export function personalConnectionDelegationSourceForGrant(
|
|
|
157
167
|
return { kind: "subject", subjectId: grant.subjectId, accountId: grant.accountId };
|
|
158
168
|
}
|
|
159
169
|
|
|
170
|
+
/** Account-picker inventory: shared workspace accounts and this sender's own
|
|
171
|
+
* personal accounts. Visibility is never an execution grant or a credential
|
|
172
|
+
* response; every selected connection is validated again at admission/use. */
|
|
173
|
+
export async function listOwnConnectionAccountsForGrant(
|
|
174
|
+
db: Database,
|
|
175
|
+
grant: AccessGrant,
|
|
176
|
+
options: { includeInactive?: boolean } = {},
|
|
177
|
+
): Promise<ConnectionMetadata[]> {
|
|
178
|
+
const source = personalConnectionDelegationSourceForGrant(grant);
|
|
179
|
+
const workspace = (await listConnectionsMetadata(db, grant.workspaceId, null)).filter(
|
|
180
|
+
(connection) =>
|
|
181
|
+
connection.subjectId === null &&
|
|
182
|
+
connection.workspaceId === grant.workspaceId &&
|
|
183
|
+
connection.accountId === grant.accountId &&
|
|
184
|
+
(options.includeInactive === true || connection.status === "active"),
|
|
185
|
+
);
|
|
186
|
+
if (source.kind !== "subject") return workspace;
|
|
187
|
+
const personal = await listOwnConnectionMetadata(db, {
|
|
188
|
+
...source,
|
|
189
|
+
workspaceId: grant.workspaceId,
|
|
190
|
+
includeInactive: options.includeInactive === true,
|
|
191
|
+
});
|
|
192
|
+
return [...workspace, ...personal];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function listOwnConnectionMetadata(
|
|
196
|
+
db: Database,
|
|
197
|
+
input: {
|
|
198
|
+
accountId: string;
|
|
199
|
+
workspaceId: string;
|
|
200
|
+
subjectId: string;
|
|
201
|
+
includeInactive?: boolean;
|
|
202
|
+
/** Read only these owned accounts (a live target fence); omit for all. */
|
|
203
|
+
connectionIds?: ReadonlySet<string>;
|
|
204
|
+
},
|
|
205
|
+
): Promise<ConnectionMetadata[]> {
|
|
206
|
+
const accounts = (await listOwnedConnectionAccounts(db, input)).filter(
|
|
207
|
+
(account) => !input.connectionIds || input.connectionIds.has(account.connectionId),
|
|
208
|
+
);
|
|
209
|
+
const connections: ConnectionMetadata[] = [];
|
|
210
|
+
// The caller may supply a transaction handle (for example, credential
|
|
211
|
+
// rotation). Each metadata read opens a nested RLS savepoint on that same
|
|
212
|
+
// connection, so these reads must not overlap.
|
|
213
|
+
for (const account of accounts) {
|
|
214
|
+
const connection = await getConnectionMetadata(
|
|
215
|
+
db,
|
|
216
|
+
account.originWorkspaceId,
|
|
217
|
+
account.connectionId,
|
|
218
|
+
input.subjectId,
|
|
219
|
+
);
|
|
220
|
+
if (
|
|
221
|
+
connection?.accountId === input.accountId &&
|
|
222
|
+
connection.subjectId === input.subjectId &&
|
|
223
|
+
connection.authorityId != null &&
|
|
224
|
+
(input.includeInactive === true || connection.status === "active")
|
|
225
|
+
)
|
|
226
|
+
connections.push(connection);
|
|
227
|
+
}
|
|
228
|
+
return connections;
|
|
229
|
+
}
|
|
230
|
+
|
|
160
231
|
export async function authorizedSocialConnectionsForGrant(input: {
|
|
161
232
|
db: Database;
|
|
162
233
|
grant: AccessGrant;
|
|
@@ -215,74 +286,12 @@ export async function authorizedSocialConnectionsForGrant(input: {
|
|
|
215
286
|
return [...workspace.map((connection) => ({ connection, subjectId: null })), ...personal];
|
|
216
287
|
}
|
|
217
288
|
|
|
218
|
-
|
|
289
|
+
/** Historical native Atlassian grants no longer authorize API execution. */
|
|
290
|
+
export async function authorizedAtlassianConnectionsForGrant(_input: {
|
|
219
291
|
db: Database;
|
|
220
292
|
grant: AccessGrant;
|
|
221
293
|
}): Promise<AuthorizedAtlassianConnection[]> {
|
|
222
|
-
|
|
223
|
-
(connection) =>
|
|
224
|
-
connection.subjectId === null &&
|
|
225
|
-
connection.status === "active" &&
|
|
226
|
-
sameProviderDomain(connection.providerDomain, "api.atlassian.com"),
|
|
227
|
-
);
|
|
228
|
-
const source = personalConnectionDelegationSourceForGrant(input.grant);
|
|
229
|
-
if (source.kind === "none") {
|
|
230
|
-
return workspace.map((connection) => ({ connection, subjectId: null }));
|
|
231
|
-
}
|
|
232
|
-
if (source.kind === "subject") {
|
|
233
|
-
const visible = await listConnectionsMetadata(
|
|
234
|
-
input.db,
|
|
235
|
-
input.grant.workspaceId,
|
|
236
|
-
source.subjectId,
|
|
237
|
-
);
|
|
238
|
-
return visible
|
|
239
|
-
.filter(
|
|
240
|
-
(connection) =>
|
|
241
|
-
connection.status === "active" &&
|
|
242
|
-
sameProviderDomain(connection.providerDomain, "api.atlassian.com"),
|
|
243
|
-
)
|
|
244
|
-
.map((connection) => ({
|
|
245
|
-
connection,
|
|
246
|
-
subjectId: connection.subjectId === null ? null : source.subjectId,
|
|
247
|
-
}));
|
|
248
|
-
}
|
|
249
|
-
const delegations = (
|
|
250
|
-
await getSessionTurnPersonalConnectionDelegations(
|
|
251
|
-
input.db,
|
|
252
|
-
input.grant.workspaceId,
|
|
253
|
-
source.sessionId,
|
|
254
|
-
source.turnId,
|
|
255
|
-
)
|
|
256
|
-
).filter((item) => item.connectionType === "atlassian");
|
|
257
|
-
const personal: AuthorizedAtlassianConnection[] = [];
|
|
258
|
-
for (const delegation of delegations) {
|
|
259
|
-
if (
|
|
260
|
-
!(await ownerStillBelongsToWorkspace(input.db, {
|
|
261
|
-
accountId: input.grant.accountId,
|
|
262
|
-
workspaceId: input.grant.workspaceId,
|
|
263
|
-
subjectId: delegation.ownerSubjectId,
|
|
264
|
-
}))
|
|
265
|
-
) {
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
const connection = await getConnectionMetadata(
|
|
269
|
-
input.db,
|
|
270
|
-
input.grant.workspaceId,
|
|
271
|
-
delegation.connectionId,
|
|
272
|
-
delegation.ownerSubjectId,
|
|
273
|
-
);
|
|
274
|
-
if (
|
|
275
|
-
!connection ||
|
|
276
|
-
connection.subjectId !== delegation.ownerSubjectId ||
|
|
277
|
-
connection.status !== "active" ||
|
|
278
|
-
!sameProviderDomain(connection.providerDomain, delegation.providerDomain) ||
|
|
279
|
-
!sameProviderDomain(connection.providerDomain, "api.atlassian.com")
|
|
280
|
-
) {
|
|
281
|
-
continue;
|
|
282
|
-
}
|
|
283
|
-
personal.push({ connection, subjectId: delegation.ownerSubjectId });
|
|
284
|
-
}
|
|
285
|
-
return [...workspace.map((connection) => ({ connection, subjectId: null })), ...personal];
|
|
294
|
+
return [];
|
|
286
295
|
}
|
|
287
296
|
|
|
288
297
|
export function selectedPersonalConnectionServers(
|
|
@@ -301,31 +310,30 @@ function isNativeSubjectConnectionRef(
|
|
|
301
310
|
return ref?.subjectScope === "subject" && ref.authoritySource !== "host";
|
|
302
311
|
}
|
|
303
312
|
|
|
304
|
-
function canonicalPersonalConnections(connections: ConnectionMetadata[]): ConnectionMetadata[] {
|
|
305
|
-
return [...connections].sort((left, right) => {
|
|
306
|
-
const active = Number(right.status === "active") - Number(left.status === "active");
|
|
307
|
-
if (active !== 0) return active;
|
|
308
|
-
const updated = Date.parse(right.updatedAt) - Date.parse(left.updatedAt);
|
|
309
|
-
if (updated !== 0) return updated;
|
|
310
|
-
const created = Date.parse(right.createdAt) - Date.parse(left.createdAt);
|
|
311
|
-
if (created !== 0) return created;
|
|
312
|
-
return right.id.localeCompare(left.id);
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
|
|
316
313
|
function sameProviderDomain(left: string, right: string): boolean {
|
|
317
314
|
return left.toLowerCase() === right.toLowerCase();
|
|
318
315
|
}
|
|
319
316
|
|
|
317
|
+
function canonicalConnectionResource(value: string): string {
|
|
318
|
+
try {
|
|
319
|
+
const url = new URL(value);
|
|
320
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return value.trim();
|
|
321
|
+
url.hash = "";
|
|
322
|
+
url.pathname = url.pathname.replace(/\/+$/, "") || "/";
|
|
323
|
+
return url.toString();
|
|
324
|
+
} catch {
|
|
325
|
+
return value.trim();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
320
329
|
export function personalConnectionDelegationsFromVisibleConnections(input: {
|
|
321
330
|
servers: McpServerConfig[];
|
|
322
331
|
subjectId: string;
|
|
323
332
|
connections: ConnectionMetadata[];
|
|
324
|
-
authoritySelections?:
|
|
325
|
-
rejectUnselectedActivatedConnections?: boolean;
|
|
333
|
+
authoritySelections?: McpConnectionAccountSelection[];
|
|
326
334
|
}): McpPersonalConnectionDelegation[] {
|
|
327
335
|
const delegations: McpPersonalConnectionDelegation[] = [];
|
|
328
|
-
const connections =
|
|
336
|
+
const connections = input.connections;
|
|
329
337
|
const selections = new Map(
|
|
330
338
|
(input.authoritySelections ?? []).map((selection) => [selection.serverId, selection]),
|
|
331
339
|
);
|
|
@@ -337,52 +345,51 @@ export function personalConnectionDelegationsFromVisibleConnections(input: {
|
|
|
337
345
|
(candidate) =>
|
|
338
346
|
candidate.subjectId === input.subjectId &&
|
|
339
347
|
candidate.status === "active" &&
|
|
348
|
+
candidate.authorityId !== undefined &&
|
|
349
|
+
candidate.authorityId !== null &&
|
|
340
350
|
sameProviderDomain(candidate.providerDomain, ref.providerDomain) &&
|
|
341
351
|
(!ref.kind || candidate.kind === ref.kind) &&
|
|
352
|
+
(!ref.resource ||
|
|
353
|
+
(typeof candidate.metadata.resource === "string" &&
|
|
354
|
+
canonicalConnectionResource(candidate.metadata.resource) ===
|
|
355
|
+
canonicalConnectionResource(ref.resource))) &&
|
|
342
356
|
(!ref.connectionId || candidate.id === ref.connectionId),
|
|
343
357
|
);
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
358
|
+
if (!selection && eligible.length > 1) {
|
|
359
|
+
throw new ConnectionAccountSelectionError(
|
|
360
|
+
`Choose an account for ${server.id}: multiple connected accounts match.`,
|
|
361
|
+
{
|
|
362
|
+
version: 1,
|
|
363
|
+
reason: "ambiguous_account",
|
|
364
|
+
accounts: [{ serverId: server.id, connectionId: null, reason: "selection_unavailable" }],
|
|
365
|
+
},
|
|
366
|
+
);
|
|
367
|
+
}
|
|
348
368
|
const connection = selection
|
|
349
369
|
? eligible.find((candidate) => candidate.id === selection.connectionId)
|
|
350
370
|
: eligible[0];
|
|
351
371
|
if (!connection) continue;
|
|
352
|
-
|
|
353
|
-
if (
|
|
354
|
-
!selection ||
|
|
355
|
-
selection.connectionId !== connection.id ||
|
|
356
|
-
selection.userDelegation.authorityId !== connection.authorityId
|
|
357
|
-
) {
|
|
358
|
-
if (input.rejectUnselectedActivatedConnections) {
|
|
359
|
-
throw new Error(
|
|
360
|
-
`scheduled connection authority selection is required for activated server ${server.id}`,
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
// Activated organization-user connections are never admitted through
|
|
364
|
-
// the legacy owner tuple. The caller must select one exact opaque grant.
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
selections.delete(server.id);
|
|
368
|
-
} else if (selection) {
|
|
369
|
-
throw new Error(`connection authority is not available for legacy server ${server.id}`);
|
|
370
|
-
}
|
|
372
|
+
selections.delete(server.id);
|
|
371
373
|
delegations.push({
|
|
372
374
|
serverId: server.id,
|
|
373
375
|
connectionId: connection.id,
|
|
374
|
-
|
|
376
|
+
originWorkspaceId: connection.workspaceId,
|
|
375
377
|
ownerSubjectId: input.subjectId,
|
|
376
378
|
providerDomain: connection.providerDomain,
|
|
377
379
|
kind: connection.kind,
|
|
378
|
-
...(selection ? { userDelegation: selection.userDelegation } : {}),
|
|
379
380
|
});
|
|
380
381
|
}
|
|
381
382
|
if (selections.size > 0) {
|
|
382
|
-
throw new
|
|
383
|
-
`
|
|
384
|
-
|
|
385
|
-
|
|
383
|
+
throw new ConnectionAccountSelectionError(
|
|
384
|
+
`Selected account is unavailable for: ${[...selections.keys()].join(", ")}`,
|
|
385
|
+
{
|
|
386
|
+
version: 1,
|
|
387
|
+
reason: "selected_account_unavailable",
|
|
388
|
+
accounts: [...selections.values()].map((selection) => ({
|
|
389
|
+
...selection,
|
|
390
|
+
reason: "selection_unavailable",
|
|
391
|
+
})),
|
|
392
|
+
},
|
|
386
393
|
);
|
|
387
394
|
}
|
|
388
395
|
return delegations;
|
|
@@ -393,39 +400,27 @@ export function personalConnectionDelegationsFromParent(input: {
|
|
|
393
400
|
parentDelegations: McpPersonalConnectionDelegation[];
|
|
394
401
|
personalGitHubResources?: ResourceRef[];
|
|
395
402
|
targetSessionId?: string;
|
|
396
|
-
rejectActivatedConnections?: boolean;
|
|
397
403
|
}): McpPersonalConnectionDelegation[] {
|
|
398
|
-
// Every successor is new logical work. `once` remains bound to the original
|
|
399
|
-
// accepted turn, `session` may be re-admitted only into that exact session,
|
|
400
|
-
// and `always` may cross to another authorized session.
|
|
401
|
-
const childEligible = (delegation: McpPersonalConnectionDelegation) => {
|
|
402
|
-
const authority = delegation.userDelegation;
|
|
403
|
-
if (!authority) return true;
|
|
404
|
-
if (authority.mode === "once") return false;
|
|
405
|
-
if (authority.mode === "session") return authority.sessionId === input.targetSessionId;
|
|
406
|
-
return true;
|
|
407
|
-
};
|
|
408
404
|
const mcp = input.servers.flatMap((server) => {
|
|
409
405
|
const ref = server.connectionRef;
|
|
410
406
|
if (!isNativeSubjectConnectionRef(ref)) return [];
|
|
411
407
|
const delegation = input.parentDelegations.find(
|
|
412
408
|
(candidate) =>
|
|
409
|
+
candidate.connectionType !== "atlassian" &&
|
|
413
410
|
candidate.serverId === server.id &&
|
|
414
411
|
sameProviderDomain(candidate.providerDomain, ref.providerDomain) &&
|
|
415
412
|
(!ref.kind || !candidate.kind || candidate.kind === ref.kind),
|
|
416
413
|
);
|
|
417
|
-
return delegation
|
|
414
|
+
return delegation ? [{ ...delegation }] : [];
|
|
418
415
|
});
|
|
419
416
|
const projected = [
|
|
420
417
|
...mcp,
|
|
421
418
|
...input.parentDelegations
|
|
422
419
|
.filter(
|
|
423
420
|
(item) =>
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
item.connectionType === "github_personal" ||
|
|
428
|
-
item.serverId === GOOGLE_DRIVE_PUBLICATION_SERVER_ID),
|
|
421
|
+
item.connectionType === "social" ||
|
|
422
|
+
item.connectionType === "github_personal" ||
|
|
423
|
+
item.serverId === GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
|
|
429
424
|
)
|
|
430
425
|
.map((item) => ({ ...item })),
|
|
431
426
|
];
|
|
@@ -464,14 +459,6 @@ export function personalConnectionDelegationsFromParent(input: {
|
|
|
464
459
|
) {
|
|
465
460
|
throw new Error("agent-created personal GitHub repository authority is unavailable");
|
|
466
461
|
}
|
|
467
|
-
if (
|
|
468
|
-
input.rejectActivatedConnections &&
|
|
469
|
-
inherited.some((delegation) => delegation.userDelegation)
|
|
470
|
-
) {
|
|
471
|
-
throw new Error(
|
|
472
|
-
"scheduled connection authority is not available until task occurrence authority is activated",
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
462
|
return inherited;
|
|
476
463
|
}
|
|
477
464
|
|
|
@@ -484,14 +471,14 @@ export function personalConnectionDelegationsFromParent(input: {
|
|
|
484
471
|
export function googleDrivePublicationDelegationFromVisibleConnections(input: {
|
|
485
472
|
subjectId: string;
|
|
486
473
|
connections: ConnectionMetadata[];
|
|
487
|
-
authoritySelection?:
|
|
488
|
-
rejectUnselectedActivatedConnection?: boolean;
|
|
474
|
+
authoritySelection?: McpConnectionAccountSelection;
|
|
489
475
|
}): McpPersonalConnectionDelegation | null {
|
|
490
476
|
const selection = input.authoritySelection;
|
|
491
477
|
const eligible = input.connections.filter((connection) => {
|
|
492
478
|
if (
|
|
493
479
|
connection.subjectId !== input.subjectId ||
|
|
494
480
|
connection.status !== "active" ||
|
|
481
|
+
!connection.authorityId ||
|
|
495
482
|
connection.kind !== "oauth2" ||
|
|
496
483
|
!sameProviderDomain(connection.providerDomain, GOOGLE_DRIVE_PROVIDER_DOMAIN) ||
|
|
497
484
|
!googleDriveScopesAllowCapability(connection.grantedScopes, "publish_file")
|
|
@@ -510,26 +497,25 @@ export function googleDrivePublicationDelegationFromVisibleConnections(input: {
|
|
|
510
497
|
? eligible.filter((connection) => connection.id === selection.connectionId)
|
|
511
498
|
: eligible;
|
|
512
499
|
if (selection && selectedEligible.length !== 1) {
|
|
513
|
-
throw new
|
|
500
|
+
throw new ConnectionAccountSelectionError(
|
|
501
|
+
"Google Drive publication authority selection is unavailable",
|
|
502
|
+
{
|
|
503
|
+
version: 1,
|
|
504
|
+
reason: "selected_account_unavailable",
|
|
505
|
+
accounts: [
|
|
506
|
+
{
|
|
507
|
+
serverId: GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
|
|
508
|
+
connectionId: selection.connectionId,
|
|
509
|
+
reason: "selection_unavailable",
|
|
510
|
+
},
|
|
511
|
+
],
|
|
512
|
+
},
|
|
513
|
+
);
|
|
514
514
|
}
|
|
515
515
|
if (!selection && selectedEligible.length !== 1) return null;
|
|
516
516
|
const connection = selectedEligible[0]!;
|
|
517
|
-
if (
|
|
518
|
-
|
|
519
|
-
!selection ||
|
|
520
|
-
selection.serverId !== GOOGLE_DRIVE_PUBLICATION_SERVER_ID ||
|
|
521
|
-
selection.connectionId !== connection.id ||
|
|
522
|
-
selection.userDelegation.authorityId !== connection.authorityId
|
|
523
|
-
) {
|
|
524
|
-
if (input.rejectUnselectedActivatedConnection) {
|
|
525
|
-
throw new Error(
|
|
526
|
-
"scheduled connection authority selection is required for activated Google Drive publication",
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
} else if (selection) {
|
|
532
|
-
throw new Error("connection authority is not available for legacy Google Drive publication");
|
|
517
|
+
if (selection && selection.serverId !== GOOGLE_DRIVE_PUBLICATION_SERVER_ID) {
|
|
518
|
+
throw new Error("Google Drive publication account selection is invalid");
|
|
533
519
|
}
|
|
534
520
|
// Freeze the exact output destination on the accepted delegation: a later
|
|
535
521
|
// connection-settings change must never redirect an already-accepted turn's
|
|
@@ -542,44 +528,20 @@ export function googleDrivePublicationDelegationFromVisibleConnections(input: {
|
|
|
542
528
|
return {
|
|
543
529
|
serverId: GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
|
|
544
530
|
connectionId: connection.id,
|
|
545
|
-
|
|
531
|
+
originWorkspaceId: connection.workspaceId,
|
|
546
532
|
ownerSubjectId: input.subjectId,
|
|
547
533
|
providerDomain: connection.providerDomain,
|
|
548
534
|
kind: connection.kind,
|
|
549
|
-
...(selection ? { userDelegation: selection.userDelegation } : {}),
|
|
550
535
|
outputDestination: frozenMetadata.outputDestination,
|
|
551
536
|
};
|
|
552
537
|
}
|
|
553
538
|
|
|
554
|
-
|
|
539
|
+
/** Native Atlassian credentials remain stored but are not new turn authority. */
|
|
540
|
+
export function personalAtlassianDelegationsFromVisibleConnections(_input: {
|
|
555
541
|
subjectId: string;
|
|
556
542
|
connections: ConnectionMetadata[];
|
|
557
|
-
rejectActivatedConnections?: boolean;
|
|
558
543
|
}): McpPersonalConnectionDelegation[] {
|
|
559
|
-
|
|
560
|
-
(connection) =>
|
|
561
|
-
connection.subjectId === input.subjectId &&
|
|
562
|
-
connection.status === "active" &&
|
|
563
|
-
sameProviderDomain(connection.providerDomain, "api.atlassian.com"),
|
|
564
|
-
);
|
|
565
|
-
if (input.rejectActivatedConnections && eligible.some((connection) => connection.authorityId)) {
|
|
566
|
-
throw new Error(
|
|
567
|
-
"scheduled connection authority selection is required for activated Atlassian access",
|
|
568
|
-
);
|
|
569
|
-
}
|
|
570
|
-
// Atlassian's first-party multi-call adapter is a named successor. Until it
|
|
571
|
-
// can consume an accepted snapshot, common-authority rows are omitted rather
|
|
572
|
-
// than falling through the legacy owner-tuple resolver.
|
|
573
|
-
return eligible
|
|
574
|
-
.filter((connection) => !connection.authorityId)
|
|
575
|
-
.map((connection) => ({
|
|
576
|
-
serverId: `atlassian:${connection.id}`,
|
|
577
|
-
connectionId: connection.id,
|
|
578
|
-
ownerSubjectId: input.subjectId,
|
|
579
|
-
providerDomain: connection.providerDomain,
|
|
580
|
-
kind: connection.kind,
|
|
581
|
-
connectionType: "atlassian" as const,
|
|
582
|
-
}));
|
|
544
|
+
return [];
|
|
583
545
|
}
|
|
584
546
|
|
|
585
547
|
async function personalGitHubDelegationFromVisibleConnections(input: {
|
|
@@ -588,7 +550,7 @@ async function personalGitHubDelegationFromVisibleConnections(input: {
|
|
|
588
550
|
subjectId: string;
|
|
589
551
|
connections: ConnectionMetadata[];
|
|
590
552
|
resources: ResourceRef[];
|
|
591
|
-
authoritySelection?:
|
|
553
|
+
authoritySelection?: McpConnectionAccountSelection;
|
|
592
554
|
}): Promise<McpPersonalConnectionDelegation | null> {
|
|
593
555
|
const resources = personalGitHubRepositoryResources(input.resources);
|
|
594
556
|
if (resources.length === 0) {
|
|
@@ -602,27 +564,41 @@ async function personalGitHubDelegationFromVisibleConnections(input: {
|
|
|
602
564
|
throw new Error("accepted work may use only one personal GitHub account");
|
|
603
565
|
}
|
|
604
566
|
const authoritySelection = input.authoritySelection;
|
|
605
|
-
|
|
606
|
-
throw new Error("personal GitHub repository resources require explicit connection authority");
|
|
607
|
-
}
|
|
567
|
+
const credentialBindingId = [...bindingIds][0]!;
|
|
608
568
|
const connection = input.connections.find(
|
|
609
569
|
(candidate) =>
|
|
610
|
-
candidate.id === authoritySelection.connectionId &&
|
|
570
|
+
(!authoritySelection || candidate.id === authoritySelection.connectionId) &&
|
|
611
571
|
candidate.subjectId === input.subjectId &&
|
|
612
572
|
candidate.status === "active" &&
|
|
613
|
-
isPersonalGitHubConnection(candidate)
|
|
573
|
+
isPersonalGitHubConnection(candidate) &&
|
|
574
|
+
PersonalGitHubConnectionMetadata.safeParse(candidate.metadata).data?.credentialBindingId ===
|
|
575
|
+
credentialBindingId,
|
|
614
576
|
);
|
|
615
577
|
if (
|
|
616
578
|
!connection ||
|
|
617
579
|
!connection.authorityId ||
|
|
618
|
-
connection.authorityId !== authoritySelection.userDelegation.authorityId ||
|
|
619
580
|
connection.grantedScopes.length !== 1 ||
|
|
620
581
|
connection.grantedScopes[0] !== "repo"
|
|
621
582
|
) {
|
|
583
|
+
if (authoritySelection) {
|
|
584
|
+
throw new ConnectionAccountSelectionError(
|
|
585
|
+
"personal GitHub connection authority selection is unavailable",
|
|
586
|
+
{
|
|
587
|
+
version: 1,
|
|
588
|
+
reason: "selected_account_unavailable",
|
|
589
|
+
accounts: [
|
|
590
|
+
{
|
|
591
|
+
serverId: PERSONAL_GITHUB_CONNECTION_SURFACE_ID,
|
|
592
|
+
connectionId: authoritySelection.connectionId,
|
|
593
|
+
reason: "selection_unavailable",
|
|
594
|
+
},
|
|
595
|
+
],
|
|
596
|
+
},
|
|
597
|
+
);
|
|
598
|
+
}
|
|
622
599
|
throw new Error("personal GitHub connection authority selection is unavailable");
|
|
623
600
|
}
|
|
624
601
|
const metadata = PersonalGitHubConnectionMetadata.parse(connection.metadata);
|
|
625
|
-
const credentialBindingId = [...bindingIds][0]!;
|
|
626
602
|
if (metadata.credentialBindingId !== credentialBindingId) {
|
|
627
603
|
throw new Error("personal GitHub credential binding does not match the selected connection");
|
|
628
604
|
}
|
|
@@ -669,7 +645,6 @@ async function personalGitHubDelegationFromVisibleConnections(input: {
|
|
|
669
645
|
providerDomain: "github.com",
|
|
670
646
|
kind: "oauth2",
|
|
671
647
|
connectionType: "github_personal",
|
|
672
|
-
userDelegation: authoritySelection.userDelegation,
|
|
673
648
|
personalGitHubRepositorySelection: {
|
|
674
649
|
credentialBindingId,
|
|
675
650
|
connectionAuthorityGeneration: selection.connectionAuthorityGeneration,
|
|
@@ -689,13 +664,12 @@ export function personalConnectionDelegationsEqual(
|
|
|
689
664
|
const other = byServer.get(delegation.serverId);
|
|
690
665
|
return (
|
|
691
666
|
other?.connectionId === delegation.connectionId &&
|
|
667
|
+
other.canonicalServerId === delegation.canonicalServerId &&
|
|
692
668
|
other.originWorkspaceId === delegation.originWorkspaceId &&
|
|
693
669
|
other.ownerSubjectId === delegation.ownerSubjectId &&
|
|
694
670
|
sameProviderDomain(other.providerDomain, delegation.providerDomain) &&
|
|
695
671
|
other.kind === delegation.kind &&
|
|
696
672
|
other.connectionType === delegation.connectionType &&
|
|
697
|
-
JSON.stringify(other.userDelegation ?? null) ===
|
|
698
|
-
JSON.stringify(delegation.userDelegation ?? null) &&
|
|
699
673
|
JSON.stringify(other.personalGitHubRepositorySelection ?? null) ===
|
|
700
674
|
JSON.stringify(delegation.personalGitHubRepositorySelection ?? null)
|
|
701
675
|
);
|
|
@@ -776,11 +750,7 @@ export function withFrozenPersonalConnectionDelegations(input: {
|
|
|
776
750
|
: publicationDelegations.length === 1
|
|
777
751
|
? publicationDelegations[0]!
|
|
778
752
|
: null;
|
|
779
|
-
if (
|
|
780
|
-
!delegation ||
|
|
781
|
-
(!delegation.userDelegation &&
|
|
782
|
-
!(await input.ownerHasWorkspaceMembership(delegation.ownerSubjectId)))
|
|
783
|
-
) {
|
|
753
|
+
if (!delegation || !(await input.ownerHasWorkspaceMembership(delegation.ownerSubjectId))) {
|
|
784
754
|
return personalAuthorityUnavailable(request);
|
|
785
755
|
}
|
|
786
756
|
effectiveRequest = {
|
|
@@ -802,6 +772,214 @@ export function withFrozenPersonalConnectionDelegations(input: {
|
|
|
802
772
|
};
|
|
803
773
|
}
|
|
804
774
|
|
|
775
|
+
/** Freeze all native account routes without conflating workspace ownership
|
|
776
|
+
* with the exact causal human required for personal connections. */
|
|
777
|
+
export async function freezeConnectionAccounts(
|
|
778
|
+
input: Parameters<typeof freezePersonalConnectionDelegations>[0] & { accountId: string },
|
|
779
|
+
): Promise<{
|
|
780
|
+
mcpAccountBindings: McpConnectionAccountBinding[] | null;
|
|
781
|
+
personalConnectionDelegations: McpPersonalConnectionDelegation[];
|
|
782
|
+
/**
|
|
783
|
+
* What the request decided, to be stored on the accepted turn: whose
|
|
784
|
+
* personal accounts it may use and which accounts were explicitly picked.
|
|
785
|
+
* Agent-created work carries its parent turn's intent (null when the parent
|
|
786
|
+
* predates intent).
|
|
787
|
+
*/
|
|
788
|
+
connectionAccountIntent: ConnectionAccountIntent | null;
|
|
789
|
+
}> {
|
|
790
|
+
const selectedIds = new Set(input.tools.map((tool) => tool.id));
|
|
791
|
+
const servers = input.settings.mcpServers.filter(
|
|
792
|
+
(server) =>
|
|
793
|
+
selectedIds.has(server.id) &&
|
|
794
|
+
server.connectionRef &&
|
|
795
|
+
server.connectionRef.authoritySource !== "host",
|
|
796
|
+
);
|
|
797
|
+
const serverIds = new Set(servers.map((server) => server.id));
|
|
798
|
+
let mcpAccountBindings: McpConnectionAccountBinding[];
|
|
799
|
+
let connectionAccountIntent: ConnectionAccountIntent | null;
|
|
800
|
+
if (input.source.kind === "turn") {
|
|
801
|
+
if (input.authoritySelections?.length) {
|
|
802
|
+
throw new ConnectionAccountSelectionError(
|
|
803
|
+
"Agent-created work inherits the exact parent accounts",
|
|
804
|
+
{ version: 1, reason: "parent_accounts_required", accounts: [] },
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
connectionAccountIntent = await getSessionTurnConnectionAccountIntent(
|
|
808
|
+
input.db,
|
|
809
|
+
input.workspaceId,
|
|
810
|
+
input.source.sessionId,
|
|
811
|
+
input.source.turnId,
|
|
812
|
+
);
|
|
813
|
+
const inherited = await getSessionTurnMcpAccountBindings(
|
|
814
|
+
input.db,
|
|
815
|
+
input.workspaceId,
|
|
816
|
+
input.source.sessionId,
|
|
817
|
+
input.source.turnId,
|
|
818
|
+
);
|
|
819
|
+
if (inherited === null) {
|
|
820
|
+
return {
|
|
821
|
+
mcpAccountBindings: null,
|
|
822
|
+
personalConnectionDelegations: await freezePersonalConnectionDelegations(input),
|
|
823
|
+
connectionAccountIntent,
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
mcpAccountBindings = inherited.filter((binding) => serverIds.has(binding.canonicalServerId));
|
|
827
|
+
} else {
|
|
828
|
+
connectionAccountIntent = {
|
|
829
|
+
version: 1,
|
|
830
|
+
principal:
|
|
831
|
+
input.source.kind === "subject"
|
|
832
|
+
? { kind: "subject", subjectId: input.source.subjectId }
|
|
833
|
+
: { kind: "none" },
|
|
834
|
+
selections: [...(input.authoritySelections ?? [])],
|
|
835
|
+
};
|
|
836
|
+
mcpAccountBindings = mcpAccountBindingsFromVisibleConnections({
|
|
837
|
+
accountId: input.accountId,
|
|
838
|
+
workspaceId: input.workspaceId,
|
|
839
|
+
subjectId: input.source.kind === "subject" ? input.source.subjectId : null,
|
|
840
|
+
servers,
|
|
841
|
+
connections: await visibleMcpAccountConnections(input.db, {
|
|
842
|
+
accountId: input.accountId,
|
|
843
|
+
workspaceId: input.workspaceId,
|
|
844
|
+
source: input.source,
|
|
845
|
+
}),
|
|
846
|
+
...(input.authoritySelections
|
|
847
|
+
? {
|
|
848
|
+
selections: input.authoritySelections.filter((selection) =>
|
|
849
|
+
serverIds.has(selection.serverId),
|
|
850
|
+
),
|
|
851
|
+
}
|
|
852
|
+
: {}),
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
// Dedicated first-party publication/repository/social surfaces keep their
|
|
856
|
+
// existing selection and authority contracts; they are not generic MCP routes.
|
|
857
|
+
const special = await freezePersonalConnectionDelegations({
|
|
858
|
+
...input,
|
|
859
|
+
settings: {
|
|
860
|
+
...input.settings,
|
|
861
|
+
mcpServers: input.settings.mcpServers.filter((server) => !serverIds.has(server.id)),
|
|
862
|
+
},
|
|
863
|
+
...(input.authoritySelections
|
|
864
|
+
? {
|
|
865
|
+
authoritySelections: input.authoritySelections.filter(
|
|
866
|
+
(selection) => !serverIds.has(selection.serverId),
|
|
867
|
+
),
|
|
868
|
+
}
|
|
869
|
+
: {}),
|
|
870
|
+
});
|
|
871
|
+
return {
|
|
872
|
+
mcpAccountBindings,
|
|
873
|
+
personalConnectionDelegations: [
|
|
874
|
+
...personalDelegationsForAccountBindings(mcpAccountBindings),
|
|
875
|
+
...special,
|
|
876
|
+
],
|
|
877
|
+
connectionAccountIntent,
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/** Shared workspace accounts plus the entitled owner's own active accounts. */
|
|
882
|
+
export async function visibleMcpAccountConnections(
|
|
883
|
+
db: Database,
|
|
884
|
+
input: {
|
|
885
|
+
accountId: string;
|
|
886
|
+
workspaceId: string;
|
|
887
|
+
source: Exclude<PersonalConnectionDelegationSource, { kind: "turn" }>;
|
|
888
|
+
},
|
|
889
|
+
): Promise<ConnectionMetadata[]> {
|
|
890
|
+
const personal =
|
|
891
|
+
input.source.kind === "subject" &&
|
|
892
|
+
(await ownerStillBelongsToWorkspace(db, {
|
|
893
|
+
accountId: input.accountId,
|
|
894
|
+
workspaceId: input.workspaceId,
|
|
895
|
+
subjectId: input.source.subjectId,
|
|
896
|
+
}))
|
|
897
|
+
? await listOwnConnectionMetadata(db, {
|
|
898
|
+
accountId: input.accountId,
|
|
899
|
+
workspaceId: input.workspaceId,
|
|
900
|
+
subjectId: input.source.subjectId,
|
|
901
|
+
})
|
|
902
|
+
: [];
|
|
903
|
+
const workspace = await listConnectionsMetadata(db, input.workspaceId, null);
|
|
904
|
+
return [...workspace.filter((connection) => connection.subjectId === null), ...personal];
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* `visibleMcpAccountConnections` restricted to exact connection ids, for a live
|
|
909
|
+
* target fence. The same visibility rules apply (shared workspace accounts;
|
|
910
|
+
* the entitled owner's own active accounts while the owner still belongs to
|
|
911
|
+
* the workspace), but unrelated accounts are never read. An id read on the
|
|
912
|
+
* wrong side, or no longer visible, is simply absent.
|
|
913
|
+
*/
|
|
914
|
+
export async function visibleMcpAccountConnectionsByIds(
|
|
915
|
+
db: Database,
|
|
916
|
+
input: {
|
|
917
|
+
accountId: string;
|
|
918
|
+
workspaceId: string;
|
|
919
|
+
source: Exclude<PersonalConnectionDelegationSource, { kind: "turn" }>;
|
|
920
|
+
workspaceConnectionIds: readonly string[];
|
|
921
|
+
personalConnectionIds: readonly string[];
|
|
922
|
+
},
|
|
923
|
+
): Promise<ConnectionMetadata[]> {
|
|
924
|
+
const workspace: ConnectionMetadata[] = [];
|
|
925
|
+
// `db` may be one bounded transaction: these reads must not overlap.
|
|
926
|
+
for (const connectionId of new Set(input.workspaceConnectionIds)) {
|
|
927
|
+
const connection = await getConnectionMetadata(db, input.workspaceId, connectionId, null);
|
|
928
|
+
if (connection && connection.subjectId === null) workspace.push(connection);
|
|
929
|
+
}
|
|
930
|
+
const personalIds = new Set(input.personalConnectionIds);
|
|
931
|
+
const personal =
|
|
932
|
+
input.source.kind === "subject" &&
|
|
933
|
+
personalIds.size > 0 &&
|
|
934
|
+
(await ownerStillBelongsToWorkspace(db, {
|
|
935
|
+
accountId: input.accountId,
|
|
936
|
+
workspaceId: input.workspaceId,
|
|
937
|
+
subjectId: input.source.subjectId,
|
|
938
|
+
}))
|
|
939
|
+
? await listOwnConnectionMetadata(db, {
|
|
940
|
+
accountId: input.accountId,
|
|
941
|
+
workspaceId: input.workspaceId,
|
|
942
|
+
subjectId: input.source.subjectId,
|
|
943
|
+
connectionIds: personalIds,
|
|
944
|
+
})
|
|
945
|
+
: [];
|
|
946
|
+
return [...workspace, ...personal];
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Every account a fresh, selection-free freeze would bind for the generic MCP
|
|
951
|
+
* routes among `tools`: exactly what `freezeConnectionAccounts` binds when no
|
|
952
|
+
* account is chosen. The same entitlement contract applies: `source` is a
|
|
953
|
+
* grant-derived or frozen-owner subject, never a looked-up one. Read-only; it
|
|
954
|
+
* never persists a delegation.
|
|
955
|
+
*/
|
|
956
|
+
export async function availableMcpAccountBindings(input: {
|
|
957
|
+
db: Database;
|
|
958
|
+
accountId: string;
|
|
959
|
+
workspaceId: string;
|
|
960
|
+
settings: Pick<Settings, "mcpServers">;
|
|
961
|
+
tools: ToolRef[];
|
|
962
|
+
source: Exclude<PersonalConnectionDelegationSource, { kind: "turn" }>;
|
|
963
|
+
/** Result of `visibleMcpAccountConnections` for the same source, when already read. */
|
|
964
|
+
connections?: ConnectionMetadata[];
|
|
965
|
+
}): Promise<McpConnectionAccountBinding[]> {
|
|
966
|
+
const selectedIds = new Set(input.tools.map((tool) => tool.id));
|
|
967
|
+
const servers = input.settings.mcpServers.filter(
|
|
968
|
+
(server) =>
|
|
969
|
+
selectedIds.has(server.id) &&
|
|
970
|
+
server.connectionRef &&
|
|
971
|
+
server.connectionRef.authoritySource !== "host",
|
|
972
|
+
);
|
|
973
|
+
if (servers.length === 0) return [];
|
|
974
|
+
return mcpAccountBindingsFromVisibleConnections({
|
|
975
|
+
accountId: input.accountId,
|
|
976
|
+
workspaceId: input.workspaceId,
|
|
977
|
+
subjectId: input.source.kind === "subject" ? input.source.subjectId : null,
|
|
978
|
+
servers,
|
|
979
|
+
connections: input.connections ?? (await visibleMcpAccountConnections(input.db, input)),
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
|
|
805
983
|
export async function freezePersonalConnectionDelegations(input: {
|
|
806
984
|
db: Database;
|
|
807
985
|
workspaceId: string;
|
|
@@ -809,8 +987,7 @@ export async function freezePersonalConnectionDelegations(input: {
|
|
|
809
987
|
tools: ToolRef[];
|
|
810
988
|
resources?: ResourceRef[];
|
|
811
989
|
source: PersonalConnectionDelegationSource;
|
|
812
|
-
authoritySelections?:
|
|
813
|
-
rejectUnselectedActivatedConnections?: boolean;
|
|
990
|
+
authoritySelections?: McpConnectionAccountSelection[];
|
|
814
991
|
/** Exact first-party export tool + permission gate, not broad opengeni attachment. */
|
|
815
992
|
googleDrivePublicationEnabled?: boolean;
|
|
816
993
|
/** Exact first-party Atlassian tool + permission gate. */
|
|
@@ -853,16 +1030,13 @@ export async function freezePersonalConnectionDelegations(input: {
|
|
|
853
1030
|
),
|
|
854
1031
|
personalGitHubResources,
|
|
855
1032
|
...(input.targetSessionId ? { targetSessionId: input.targetSessionId } : {}),
|
|
856
|
-
...(input.rejectUnselectedActivatedConnections !== undefined
|
|
857
|
-
? { rejectActivatedConnections: input.rejectUnselectedActivatedConnections }
|
|
858
|
-
: {}),
|
|
859
1033
|
});
|
|
860
1034
|
return inherited.filter((item) => {
|
|
861
1035
|
if (item.serverId === GOOGLE_DRIVE_PUBLICATION_SERVER_ID) {
|
|
862
1036
|
return input.googleDrivePublicationEnabled === true;
|
|
863
1037
|
}
|
|
864
1038
|
if (item.connectionType === "atlassian") {
|
|
865
|
-
return
|
|
1039
|
+
return false;
|
|
866
1040
|
}
|
|
867
1041
|
if (item.connectionType === "social") {
|
|
868
1042
|
return includeFirstPartyConnections;
|
|
@@ -876,54 +1050,23 @@ export async function freezePersonalConnectionDelegations(input: {
|
|
|
876
1050
|
workspaceId: input.workspaceId,
|
|
877
1051
|
subjectId: ownerSubjectId,
|
|
878
1052
|
});
|
|
879
|
-
|
|
880
|
-
input.
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
const portableSelections = await Promise.all(
|
|
885
|
-
(input.authoritySelections ?? []).map(async (selection) => {
|
|
886
|
-
const originWorkspaceId = await resolvePersonalConnectionAuthoritySelectionOrigin(input.db, {
|
|
887
|
-
accountId: selection.userDelegation.organizationId,
|
|
888
|
-
targetWorkspaceId: input.workspaceId,
|
|
889
|
-
subjectId: ownerSubjectId,
|
|
890
|
-
connectionId: selection.connectionId,
|
|
891
|
-
delegation: selection.userDelegation,
|
|
892
|
-
});
|
|
893
|
-
if (!originWorkspaceId) {
|
|
894
|
-
throw new Error(`connection authority selection is unavailable: ${selection.serverId}`);
|
|
895
|
-
}
|
|
896
|
-
const connection = await getConnectionMetadata(
|
|
897
|
-
input.db,
|
|
898
|
-
originWorkspaceId,
|
|
899
|
-
selection.connectionId,
|
|
900
|
-
ownerSubjectId,
|
|
1053
|
+
if (!membership) {
|
|
1054
|
+
if ((input.authoritySelections?.length ?? 0) > 0) {
|
|
1055
|
+
throw new ConnectionAccountSelectionError(
|
|
1056
|
+
"Selected connection account requires live owner workspace access",
|
|
1057
|
+
{ version: 1, reason: "owner_access_unavailable", accounts: [] },
|
|
901
1058
|
);
|
|
902
|
-
|
|
903
|
-
!connection ||
|
|
904
|
-
connection.workspaceId !== originWorkspaceId ||
|
|
905
|
-
connection.subjectId !== ownerSubjectId ||
|
|
906
|
-
connection.authorityId !== selection.userDelegation.authorityId
|
|
907
|
-
) {
|
|
908
|
-
throw new Error(`connection authority selection is unavailable: ${selection.serverId}`);
|
|
909
|
-
}
|
|
910
|
-
return connection;
|
|
911
|
-
}),
|
|
912
|
-
);
|
|
913
|
-
if (!membership && portableSelections.length === 0) {
|
|
1059
|
+
}
|
|
914
1060
|
if (personalGitHubResources.length > 0) {
|
|
915
1061
|
throw new Error("personal GitHub repository authority requires live causal user authority");
|
|
916
1062
|
}
|
|
917
1063
|
return [];
|
|
918
1064
|
}
|
|
919
|
-
const visibleConnections =
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
]),
|
|
925
|
-
).values(),
|
|
926
|
-
];
|
|
1065
|
+
const visibleConnections = await listOwnConnectionMetadata(input.db, {
|
|
1066
|
+
accountId: input.source.accountId,
|
|
1067
|
+
workspaceId: input.workspaceId,
|
|
1068
|
+
subjectId: ownerSubjectId,
|
|
1069
|
+
});
|
|
927
1070
|
const selectedMcpServerIds = new Set(servers.map((server) => server.id));
|
|
928
1071
|
const supportedSelectionIds = new Set(selectedMcpServerIds);
|
|
929
1072
|
if (input.googleDrivePublicationEnabled) {
|
|
@@ -936,10 +1079,18 @@ export async function freezePersonalConnectionDelegations(input: {
|
|
|
936
1079
|
(selection) => !supportedSelectionIds.has(selection.serverId),
|
|
937
1080
|
);
|
|
938
1081
|
if (unsupportedSelections.length > 0) {
|
|
939
|
-
throw new
|
|
1082
|
+
throw new ConnectionAccountSelectionError(
|
|
940
1083
|
`connection authority selection did not match a selected MCP server: ${unsupportedSelections
|
|
941
1084
|
.map((selection) => selection.serverId)
|
|
942
1085
|
.join(", ")}`,
|
|
1086
|
+
{
|
|
1087
|
+
version: 1,
|
|
1088
|
+
reason: "selected_account_unavailable",
|
|
1089
|
+
accounts: unsupportedSelections.map((selection) => ({
|
|
1090
|
+
...selection,
|
|
1091
|
+
reason: "connector_unavailable",
|
|
1092
|
+
})),
|
|
1093
|
+
},
|
|
943
1094
|
);
|
|
944
1095
|
}
|
|
945
1096
|
const mcp = personalConnectionDelegationsFromVisibleConnections({
|
|
@@ -949,9 +1100,6 @@ export async function freezePersonalConnectionDelegations(input: {
|
|
|
949
1100
|
authoritySelections: (input.authoritySelections ?? []).filter((selection) =>
|
|
950
1101
|
selectedMcpServerIds.has(selection.serverId),
|
|
951
1102
|
),
|
|
952
|
-
...(input.rejectUnselectedActivatedConnections !== undefined
|
|
953
|
-
? { rejectUnselectedActivatedConnections: input.rejectUnselectedActivatedConnections }
|
|
954
|
-
: {}),
|
|
955
1103
|
});
|
|
956
1104
|
const personalGitHubAuthoritySelection = (input.authoritySelections ?? []).find(
|
|
957
1105
|
(selection) => selection.serverId === PERSONAL_GITHUB_CONNECTION_SURFACE_ID,
|
|
@@ -986,9 +1134,6 @@ export async function freezePersonalConnectionDelegations(input: {
|
|
|
986
1134
|
? personalAtlassianDelegationsFromVisibleConnections({
|
|
987
1135
|
subjectId: ownerSubjectId,
|
|
988
1136
|
connections: visibleConnections,
|
|
989
|
-
...(input.rejectUnselectedActivatedConnections !== undefined
|
|
990
|
-
? { rejectActivatedConnections: input.rejectUnselectedActivatedConnections }
|
|
991
|
-
: {}),
|
|
992
1137
|
})
|
|
993
1138
|
: [];
|
|
994
1139
|
const googleDriveAuthoritySelection = input.googleDrivePublicationEnabled
|
|
@@ -1003,9 +1148,6 @@ export async function freezePersonalConnectionDelegations(input: {
|
|
|
1003
1148
|
...(googleDriveAuthoritySelection
|
|
1004
1149
|
? { authoritySelection: googleDriveAuthoritySelection }
|
|
1005
1150
|
: {}),
|
|
1006
|
-
...(input.rejectUnselectedActivatedConnections !== undefined
|
|
1007
|
-
? { rejectUnselectedActivatedConnection: input.rejectUnselectedActivatedConnections }
|
|
1008
|
-
: {}),
|
|
1009
1151
|
})
|
|
1010
1152
|
: null;
|
|
1011
1153
|
return [
|