@opengeni/api-router 0.15.6 → 0.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +1 -1
- package/dist/{chunk-MN7BW3UV.js → chunk-TFHWQL2W.js} +187 -61
- package/dist/chunk-TFHWQL2W.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/mcp/toolspace.d.ts +2 -1
- package/package.json +11 -11
- package/src/integrations/oauth-client.ts +51 -6
- package/src/integrations/slack-interactions.ts +49 -8
- package/src/mcp/server.ts +7 -3
- package/src/mcp/toolspace.ts +68 -30
- package/src/routes/connections.ts +17 -1
- package/src/routes/documents.ts +8 -3
- package/src/routes/scheduled-tasks.ts +7 -3
- package/src/routes/sessions.ts +1 -0
- package/dist/chunk-MN7BW3UV.js.map +0 -1
package/dist/app.js
CHANGED
|
@@ -627,6 +627,12 @@ async function startMcpOAuth(deps, context) {
|
|
|
627
627
|
const providerDomain = officialSlackResource ? "slack.com" : canonicalProviderDomain(context.payload.providerDomain ?? new URL(mcpUrl).hostname);
|
|
628
628
|
const personalSlack = officialSlackResource || providerDomain === "slack.com";
|
|
629
629
|
assertPersonalSlackOAuthStart(settings, context.payload, mcpUrl, personalSlack);
|
|
630
|
+
if (personalSlack && context.payload.ownership === "workspace") {
|
|
631
|
+
throw new HTTPException2(422, {
|
|
632
|
+
message: "Slack's hosted MCP connection is personal; use the OpenGeni Slack bot installation for workspace access"
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
const requestedOwnership = personalSlack ? "personal" : context.payload.ownership ?? "workspace";
|
|
630
636
|
const returnPath = safeReturnPath(context.payload.returnPath ?? "/integrations");
|
|
631
637
|
const baseUrl = integrationBaseUrl(settings.publicBaseUrl, context.requestUrl);
|
|
632
638
|
const redirectUri = `${baseUrl}/v1/integrations/oauth/callback`;
|
|
@@ -637,11 +643,14 @@ async function startMcpOAuth(deps, context) {
|
|
|
637
643
|
providerDomain,
|
|
638
644
|
mcpUrl,
|
|
639
645
|
personalSlack,
|
|
640
|
-
connectionId: context.payload.connectionId
|
|
646
|
+
connectionId: context.payload.connectionId,
|
|
647
|
+
requestedOwnership: context.payload.ownership,
|
|
648
|
+
newConnectionOwnership: requestedOwnership
|
|
641
649
|
});
|
|
642
650
|
if (context.payload.connectionId && !existing) {
|
|
643
651
|
throw new HTTPException2(404, { message: "connection not found" });
|
|
644
652
|
}
|
|
653
|
+
const ownership = existing ? ownershipForConnection(existing.subjectId, context.subjectId) : requestedOwnership;
|
|
645
654
|
const discovery = await discoverMcpOAuth(mcpUrl, settings);
|
|
646
655
|
if (personalSlack && !isLocalTestEnvironment(settings.environment)) {
|
|
647
656
|
assertSlackAuthorizationServer(discovery.as);
|
|
@@ -667,6 +676,7 @@ async function startMcpOAuth(deps, context) {
|
|
|
667
676
|
accountId: context.accountId,
|
|
668
677
|
workspaceId: context.workspaceId,
|
|
669
678
|
subjectId: context.subjectId,
|
|
679
|
+
ownership,
|
|
670
680
|
providerDomain,
|
|
671
681
|
mcpUrl,
|
|
672
682
|
resource,
|
|
@@ -739,6 +749,7 @@ async function completeMcpOAuthCallback(deps, input) {
|
|
|
739
749
|
})
|
|
740
750
|
};
|
|
741
751
|
}
|
|
752
|
+
const ownerSubjectId = state.ownership === "personal" ? state.subjectId : null;
|
|
742
753
|
try {
|
|
743
754
|
const baseUrl = integrationBaseUrl(settings.publicBaseUrl, input.requestUrl);
|
|
744
755
|
const redirectUri = `${baseUrl}/v1/integrations/oauth/callback`;
|
|
@@ -781,7 +792,7 @@ async function completeMcpOAuthCallback(deps, input) {
|
|
|
781
792
|
connectionId: state.connectionId,
|
|
782
793
|
visibleToSubjectId: state.subjectId,
|
|
783
794
|
expectedVersion: state.connectionVersion,
|
|
784
|
-
subjectId:
|
|
795
|
+
subjectId: ownerSubjectId,
|
|
785
796
|
providerDomain: state.providerDomain,
|
|
786
797
|
kind: "oauth2",
|
|
787
798
|
status: "active",
|
|
@@ -793,7 +804,7 @@ async function completeMcpOAuthCallback(deps, input) {
|
|
|
793
804
|
}) : createConnection(db, {
|
|
794
805
|
accountId: state.accountId,
|
|
795
806
|
workspaceId: state.workspaceId,
|
|
796
|
-
subjectId:
|
|
807
|
+
subjectId: ownerSubjectId,
|
|
797
808
|
providerDomain: state.providerDomain,
|
|
798
809
|
kind: "oauth2",
|
|
799
810
|
credentialEncrypted,
|
|
@@ -812,6 +823,7 @@ async function completeMcpOAuthCallback(deps, input) {
|
|
|
812
823
|
redirectTo: callbackReturnPath(state.returnPath, "success", {
|
|
813
824
|
connectionId: connection.id,
|
|
814
825
|
providerDomain: connection.providerDomain,
|
|
826
|
+
ownership: state.ownership,
|
|
815
827
|
...verification.metadata.status === "failed" ? { verification: "failed" } : {}
|
|
816
828
|
})
|
|
817
829
|
};
|
|
@@ -1213,17 +1225,32 @@ async function existingOAuthConnectionForStart(db, input) {
|
|
|
1213
1225
|
input.connectionId,
|
|
1214
1226
|
input.subjectId
|
|
1215
1227
|
);
|
|
1216
|
-
|
|
1228
|
+
if (!connection || connection.kind !== "oauth2") {
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
const ownership = ownershipForConnection(connection.subjectId, input.subjectId);
|
|
1232
|
+
if (input.requestedOwnership && input.requestedOwnership !== ownership) {
|
|
1233
|
+
throw new HTTPException2(409, {
|
|
1234
|
+
message: "connection ownership cannot be changed during OAuth reconnect"
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
return connection.providerDomain === input.providerDomain && (!input.personalSlack || connection.metadata.mcpUrl === input.mcpUrl) ? connection : null;
|
|
1217
1238
|
}
|
|
1218
1239
|
const visible = await listConnectionsMetadata(db, input.workspaceId, input.subjectId);
|
|
1240
|
+
const ownerSubjectId = input.newConnectionOwnership === "personal" ? input.subjectId : null;
|
|
1219
1241
|
const matching = visible.filter(
|
|
1220
|
-
(connection) => connection.subjectId ===
|
|
1242
|
+
(connection) => connection.subjectId === ownerSubjectId && connection.kind === "oauth2" && connection.providerDomain === input.providerDomain && (!input.personalSlack || connection.metadata.mcpUrl === input.mcpUrl)
|
|
1221
1243
|
);
|
|
1222
1244
|
if (input.personalSlack) {
|
|
1223
1245
|
return selectCanonicalPersonalSlackConnection(matching);
|
|
1224
1246
|
}
|
|
1225
1247
|
return matching.find((connection) => connection.status === "active") ?? null;
|
|
1226
1248
|
}
|
|
1249
|
+
function ownershipForConnection(subjectId, authenticatingSubjectId) {
|
|
1250
|
+
if (subjectId === null) return "workspace";
|
|
1251
|
+
if (subjectId === authenticatingSubjectId) return "personal";
|
|
1252
|
+
throw new HTTPException2(404, { message: "connection not found" });
|
|
1253
|
+
}
|
|
1227
1254
|
function buildAuthorizationUrl(input) {
|
|
1228
1255
|
const endpoint = oauthEndpointUrl(input.endpoint, input.settings, "OAuth authorization endpoint");
|
|
1229
1256
|
const url = new URL(endpoint);
|
|
@@ -1254,6 +1281,9 @@ function readOAuthState(state, settings) {
|
|
|
1254
1281
|
accountId: requiredString(payload.accountId, "state.accountId"),
|
|
1255
1282
|
workspaceId: requiredString(payload.workspaceId, "state.workspaceId"),
|
|
1256
1283
|
subjectId: requiredString(payload.subjectId, "state.subjectId"),
|
|
1284
|
+
// OAuth states minted before ownership was explicit were always personal.
|
|
1285
|
+
// Preserve that meaning for in-flight reconnects during a rolling deploy.
|
|
1286
|
+
ownership: connectionOwnership(payload.ownership) ?? "personal",
|
|
1257
1287
|
providerDomain: requiredString(payload.providerDomain, "state.providerDomain"),
|
|
1258
1288
|
mcpUrl: stringValue(payload.mcpUrl) ?? resource,
|
|
1259
1289
|
resource,
|
|
@@ -1297,6 +1327,9 @@ function readOAuthState(state, settings) {
|
|
|
1297
1327
|
...connectionVersion !== void 0 ? { connectionVersion } : {}
|
|
1298
1328
|
};
|
|
1299
1329
|
}
|
|
1330
|
+
function connectionOwnership(value) {
|
|
1331
|
+
return value === "workspace" || value === "personal" ? value : void 0;
|
|
1332
|
+
}
|
|
1300
1333
|
async function clientForState(db, settings, state) {
|
|
1301
1334
|
if (state.clientRegistrationMethod === "cimd") {
|
|
1302
1335
|
return {
|
|
@@ -2669,6 +2702,7 @@ import {
|
|
|
2669
2702
|
requireVariableSetEncryption
|
|
2670
2703
|
} from "@opengeni/core";
|
|
2671
2704
|
import {
|
|
2705
|
+
captureScheduledTaskRestoreState,
|
|
2672
2706
|
createValidatedScheduledTask,
|
|
2673
2707
|
manualScheduledTaskTriggerUsageKey,
|
|
2674
2708
|
manualScheduledTaskTriggerWorkflowId,
|
|
@@ -5998,6 +6032,7 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
|
|
|
5998
6032
|
},
|
|
5999
6033
|
async ({ id, ...raw }) => {
|
|
6000
6034
|
const existing = await requireScheduledTask(deps.db, grant.workspaceId, id);
|
|
6035
|
+
const previous = await captureScheduledTaskRestoreState(deps.db, existing);
|
|
6001
6036
|
const payload = UpdateScheduledTaskRequest.parse(raw);
|
|
6002
6037
|
requireVariableSetsUseForMcpAttachment(grant, payload.variableSetId);
|
|
6003
6038
|
const update = await validatedScheduledTaskUpdate({
|
|
@@ -6013,7 +6048,7 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
|
|
|
6013
6048
|
await syncUpdatedScheduledTask({
|
|
6014
6049
|
db: deps.db,
|
|
6015
6050
|
workflowClient: deps.workflowClient,
|
|
6016
|
-
previous
|
|
6051
|
+
previous,
|
|
6017
6052
|
task
|
|
6018
6053
|
});
|
|
6019
6054
|
return json(task);
|
|
@@ -6027,13 +6062,14 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
|
|
|
6027
6062
|
},
|
|
6028
6063
|
async ({ id }) => {
|
|
6029
6064
|
const existing = await requireScheduledTask(deps.db, grant.workspaceId, id);
|
|
6065
|
+
const previous = await captureScheduledTaskRestoreState(deps.db, existing);
|
|
6030
6066
|
const task = await updateScheduledTask(deps.db, grant.workspaceId, id, {
|
|
6031
6067
|
status: "paused"
|
|
6032
6068
|
});
|
|
6033
6069
|
await syncUpdatedScheduledTask({
|
|
6034
6070
|
db: deps.db,
|
|
6035
6071
|
workflowClient: deps.workflowClient,
|
|
6036
|
-
previous
|
|
6072
|
+
previous,
|
|
6037
6073
|
task
|
|
6038
6074
|
});
|
|
6039
6075
|
return json(task);
|
|
@@ -6047,13 +6083,14 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
|
|
|
6047
6083
|
},
|
|
6048
6084
|
async ({ id }) => {
|
|
6049
6085
|
const existing = await requireScheduledTask(deps.db, grant.workspaceId, id);
|
|
6086
|
+
const previous = await captureScheduledTaskRestoreState(deps.db, existing);
|
|
6050
6087
|
const task = await updateScheduledTask(deps.db, grant.workspaceId, id, {
|
|
6051
6088
|
status: "active"
|
|
6052
6089
|
});
|
|
6053
6090
|
await syncUpdatedScheduledTask({
|
|
6054
6091
|
db: deps.db,
|
|
6055
6092
|
workflowClient: deps.workflowClient,
|
|
6056
|
-
previous
|
|
6093
|
+
previous,
|
|
6057
6094
|
task
|
|
6058
6095
|
});
|
|
6059
6096
|
return json(task);
|
|
@@ -8136,6 +8173,7 @@ import {
|
|
|
8136
8173
|
} from "@opengeni/contracts";
|
|
8137
8174
|
import {
|
|
8138
8175
|
hasPermission as hasPermission5,
|
|
8176
|
+
withFrozenPersonalConnectionDelegations,
|
|
8139
8177
|
settingsWithEnabledCapabilityMcpServers
|
|
8140
8178
|
} from "@opengeni/core";
|
|
8141
8179
|
import {
|
|
@@ -8144,6 +8182,7 @@ import {
|
|
|
8144
8182
|
clearPendingSessionToolspaceCall,
|
|
8145
8183
|
getActiveSessionTurnForExecution,
|
|
8146
8184
|
getSessionRootId,
|
|
8185
|
+
getWorkspaceGrant as getWorkspaceGrant3,
|
|
8147
8186
|
listSessionMcpServerMetadata,
|
|
8148
8187
|
listSessionMcpServersForRun,
|
|
8149
8188
|
registerPendingSessionToolCall,
|
|
@@ -8266,6 +8305,7 @@ async function prepareToolspaceMcpSurface(input) {
|
|
|
8266
8305
|
executionGeneration: activeTurn.executionGeneration
|
|
8267
8306
|
};
|
|
8268
8307
|
const session = await requireSession2(deps.db, grant.workspaceId, sessionId);
|
|
8308
|
+
const personalConnectionDelegations = activeTurn.personalConnectionDelegations;
|
|
8269
8309
|
let rootSessionId = sessionId;
|
|
8270
8310
|
if (deps.connectionCredentials?.mcpCredentials) {
|
|
8271
8311
|
const resolvedRootSessionId = await getSessionRootId(deps.db, grant.workspaceId, sessionId);
|
|
@@ -8300,6 +8340,7 @@ async function prepareToolspaceMcpSurface(input) {
|
|
|
8300
8340
|
rootSessionId,
|
|
8301
8341
|
proxyableIds,
|
|
8302
8342
|
activeTurn,
|
|
8343
|
+
personalConnectionDelegations,
|
|
8303
8344
|
getRegistry: () => getRegistry(attemptAuthority.attemptId)
|
|
8304
8345
|
});
|
|
8305
8346
|
const tools = listing.map(
|
|
@@ -8309,6 +8350,7 @@ async function prepareToolspaceMcpSurface(input) {
|
|
|
8309
8350
|
authority: attemptAuthority,
|
|
8310
8351
|
rootSessionId,
|
|
8311
8352
|
entry,
|
|
8353
|
+
personalConnectionDelegations,
|
|
8312
8354
|
getRegistry
|
|
8313
8355
|
})
|
|
8314
8356
|
);
|
|
@@ -8342,7 +8384,16 @@ async function buildToolspaceRegistry(deps, workspaceId, sessionId, attemptId) {
|
|
|
8342
8384
|
return new Map(withSessionServers.mcpServers.map((server) => [server.id, server]));
|
|
8343
8385
|
}
|
|
8344
8386
|
async function resolveToolListing(input) {
|
|
8345
|
-
const {
|
|
8387
|
+
const {
|
|
8388
|
+
deps,
|
|
8389
|
+
grant,
|
|
8390
|
+
sessionId,
|
|
8391
|
+
rootSessionId,
|
|
8392
|
+
proxyableIds,
|
|
8393
|
+
activeTurn,
|
|
8394
|
+
personalConnectionDelegations,
|
|
8395
|
+
getRegistry
|
|
8396
|
+
} = input;
|
|
8346
8397
|
const cacheKey = await toolListCacheKey(
|
|
8347
8398
|
deps,
|
|
8348
8399
|
grant.workspaceId,
|
|
@@ -8373,7 +8424,8 @@ async function resolveToolListing(input) {
|
|
|
8373
8424
|
config,
|
|
8374
8425
|
sessionId,
|
|
8375
8426
|
rootSessionId,
|
|
8376
|
-
turn: activeTurn
|
|
8427
|
+
turn: activeTurn,
|
|
8428
|
+
personalConnectionDelegations
|
|
8377
8429
|
}).catch((error) => {
|
|
8378
8430
|
deps.observability?.warn("toolspace upstream connection failed", {
|
|
8379
8431
|
serverId,
|
|
@@ -8510,7 +8562,15 @@ async function connectToolspaceServer(input) {
|
|
|
8510
8562
|
};
|
|
8511
8563
|
}
|
|
8512
8564
|
function toolspaceToolFor(input) {
|
|
8513
|
-
const {
|
|
8565
|
+
const {
|
|
8566
|
+
deps,
|
|
8567
|
+
grant,
|
|
8568
|
+
authority,
|
|
8569
|
+
rootSessionId,
|
|
8570
|
+
entry,
|
|
8571
|
+
personalConnectionDelegations,
|
|
8572
|
+
getRegistry
|
|
8573
|
+
} = input;
|
|
8514
8574
|
const { sessionId } = authority;
|
|
8515
8575
|
const { serverId, tool } = entry;
|
|
8516
8576
|
const name = prefixedMcpToolName(serverId, tool.name);
|
|
@@ -8545,7 +8605,8 @@ function toolspaceToolFor(input) {
|
|
|
8545
8605
|
config,
|
|
8546
8606
|
sessionId,
|
|
8547
8607
|
rootSessionId,
|
|
8548
|
-
turn: reservation.turn
|
|
8608
|
+
turn: reservation.turn,
|
|
8609
|
+
personalConnectionDelegations
|
|
8549
8610
|
}).catch(() => null);
|
|
8550
8611
|
if (!connection) {
|
|
8551
8612
|
return mcpError(`upstream tool failed: ${name}`);
|
|
@@ -8763,15 +8824,8 @@ function connectionBrokerFetch(baseFetch, input) {
|
|
|
8763
8824
|
if (!connectionRef) {
|
|
8764
8825
|
return baseFetch;
|
|
8765
8826
|
}
|
|
8766
|
-
const credentialSubjectId = input.turn.initiator.kind === "subject" ? input.turn.initiator.subjectId : void 0;
|
|
8767
|
-
if (connectionRef.subjectScope === "subject" && !credentialSubjectId) {
|
|
8768
|
-
throw new Error(
|
|
8769
|
-
`subject-owned connection for MCP server ${input.config.id} requires a human turn initiator`
|
|
8770
|
-
);
|
|
8771
|
-
}
|
|
8772
8827
|
const hostCredentialPort = input.deps.connectionCredentials?.mcpCredentials;
|
|
8773
|
-
const
|
|
8774
|
-
const resolveCredential = hostCredentialPort ? buildHostConnectionTokenResolver(hostCredentialPort, {
|
|
8828
|
+
const rawResolveCredential = hostCredentialPort ? buildHostConnectionTokenResolver(hostCredentialPort, {
|
|
8775
8829
|
accountId: input.grant.accountId,
|
|
8776
8830
|
workspaceId: input.grant.workspaceId,
|
|
8777
8831
|
sessionId: input.sessionId,
|
|
@@ -8783,18 +8837,40 @@ function connectionBrokerFetch(baseFetch, input) {
|
|
|
8783
8837
|
initiatorContext: input.turn.initiatorContext,
|
|
8784
8838
|
surface: "toolspace"
|
|
8785
8839
|
}) : buildConnectionTokenResolver2(input.deps.db, input.deps.settings);
|
|
8840
|
+
const personalDelegations = input.personalConnectionDelegations ?? [];
|
|
8841
|
+
const delegatedMembershipChecks = /* @__PURE__ */ new Map();
|
|
8842
|
+
const delegatedOwnerHasMembership = async (subjectId) => {
|
|
8843
|
+
const existing = delegatedMembershipChecks.get(subjectId);
|
|
8844
|
+
if (existing) return await existing;
|
|
8845
|
+
const check = getWorkspaceGrant3(input.deps.db, subjectId, input.grant.workspaceId).then(
|
|
8846
|
+
Boolean
|
|
8847
|
+
);
|
|
8848
|
+
delegatedMembershipChecks.set(subjectId, check);
|
|
8849
|
+
return await check;
|
|
8850
|
+
};
|
|
8851
|
+
const resolveCredential = withFrozenPersonalConnectionDelegations({
|
|
8852
|
+
resolveCredential: rawResolveCredential,
|
|
8853
|
+
settings: { mcpServers: [input.config] },
|
|
8854
|
+
personalConnectionDelegations: personalDelegations,
|
|
8855
|
+
ownerHasWorkspaceMembership: delegatedOwnerHasMembership
|
|
8856
|
+
});
|
|
8786
8857
|
return async (requestInput, init) => {
|
|
8787
8858
|
const request = await mcpRequestInfo(requestInput, init);
|
|
8788
8859
|
const destinationUrl = mcpRequestDestinationUrl(requestInput);
|
|
8789
|
-
const
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8860
|
+
const resolverSubjectId = connectionRef.subjectScope !== "subject" && hostCredentialPort ? input.grant.subjectId : void 0;
|
|
8861
|
+
const resolve = async (forceRefresh) => {
|
|
8862
|
+
const result = await resolveCredential({
|
|
8863
|
+
workspaceId: input.grant.workspaceId,
|
|
8864
|
+
serverId: input.config.id,
|
|
8865
|
+
connectionRef,
|
|
8866
|
+
destinationUrl,
|
|
8867
|
+
forceRefresh,
|
|
8868
|
+
...request.toolName ? { toolName: request.toolName } : {},
|
|
8869
|
+
...resolverSubjectId ? { subjectId: resolverSubjectId } : {}
|
|
8870
|
+
});
|
|
8871
|
+
return result;
|
|
8872
|
+
};
|
|
8873
|
+
const first = await resolve(false);
|
|
8798
8874
|
if (first.status === "auth_needed") {
|
|
8799
8875
|
return await authNeededFetchResponse(input, request, first);
|
|
8800
8876
|
}
|
|
@@ -8804,15 +8880,7 @@ function connectionBrokerFetch(baseFetch, input) {
|
|
|
8804
8880
|
);
|
|
8805
8881
|
if (response.status === 401) {
|
|
8806
8882
|
await cancelMcpResponseBody(response);
|
|
8807
|
-
const refreshed = await
|
|
8808
|
-
workspaceId: input.grant.workspaceId,
|
|
8809
|
-
serverId: input.config.id,
|
|
8810
|
-
connectionRef,
|
|
8811
|
-
destinationUrl,
|
|
8812
|
-
forceRefresh: true,
|
|
8813
|
-
...request.toolName ? { toolName: request.toolName } : {},
|
|
8814
|
-
...resolverSubjectId ? { subjectId: resolverSubjectId } : {}
|
|
8815
|
-
});
|
|
8883
|
+
const refreshed = await resolve(true);
|
|
8816
8884
|
if (refreshed.status === "auth_needed") {
|
|
8817
8885
|
return await authNeededFetchResponse(input, request, refreshed);
|
|
8818
8886
|
}
|
|
@@ -8856,7 +8924,7 @@ function authNeededFromStatus(config, first, reason) {
|
|
|
8856
8924
|
reason,
|
|
8857
8925
|
providerDomain: connectionRef.providerDomain,
|
|
8858
8926
|
...connectionRef.provider ? { provider: connectionRef.provider } : {},
|
|
8859
|
-
connectionId: first.connectionId,
|
|
8927
|
+
...connectionRef.subjectScope === "subject" ? {} : { connectionId: first.connectionId },
|
|
8860
8928
|
...connectionRef.scopes ? { scopes: connectionRef.scopes } : {},
|
|
8861
8929
|
...connectionRef.resource ? { resource: connectionRef.resource } : {},
|
|
8862
8930
|
...connectionRef.selectedResources ? { selectedResources: connectionRef.selectedResources } : {}
|
|
@@ -10552,7 +10620,7 @@ import {
|
|
|
10552
10620
|
createConnectionWithSlackBotSuccessAudit,
|
|
10553
10621
|
encryptEnvironmentValue as encryptEnvironmentValue5,
|
|
10554
10622
|
getConnectionMetadata as getConnectionMetadata3,
|
|
10555
|
-
getWorkspaceGrant as
|
|
10623
|
+
getWorkspaceGrant as getWorkspaceGrant5,
|
|
10556
10624
|
listConnectionsMetadata as listConnectionsMetadata3,
|
|
10557
10625
|
recordSlackBotInstallCallbackFailure,
|
|
10558
10626
|
revokeConnection,
|
|
@@ -10585,7 +10653,7 @@ import {
|
|
|
10585
10653
|
decryptEnvironmentValue as decryptEnvironmentValue3,
|
|
10586
10654
|
encryptEnvironmentValue as encryptEnvironmentValue4,
|
|
10587
10655
|
getConnectionMetadata as getConnectionMetadata2,
|
|
10588
|
-
getWorkspaceGrant as
|
|
10656
|
+
getWorkspaceGrant as getWorkspaceGrant4,
|
|
10589
10657
|
loadConnectionCredentialForBroker,
|
|
10590
10658
|
updateConnection as updateConnection2
|
|
10591
10659
|
} from "@opengeni/db";
|
|
@@ -11026,7 +11094,7 @@ function readGoogleDriveOAuthState(raw, settings) {
|
|
|
11026
11094
|
};
|
|
11027
11095
|
}
|
|
11028
11096
|
async function requireGoogleDriveCallbackGrant(deps, state) {
|
|
11029
|
-
const grant = await
|
|
11097
|
+
const grant = await getWorkspaceGrant4(deps.db, state.subjectId, state.workspaceId);
|
|
11030
11098
|
if (!grant || grant.accountId !== state.accountId || !hasPermission7(grant.permissions, "connections:write")) {
|
|
11031
11099
|
throw new HTTPException10(403, {
|
|
11032
11100
|
message: "Google Drive OAuth subject no longer has permission for this workspace"
|
|
@@ -11269,7 +11337,7 @@ function registerConnectionRoutes(app, deps) {
|
|
|
11269
11337
|
const payload = CreateConnectionRequest.parse(await c.req.json());
|
|
11270
11338
|
assertNotReservedSlackBotMetadata(payload.metadata);
|
|
11271
11339
|
const key = requireEnvironmentEncryption4(settings);
|
|
11272
|
-
const subjectId =
|
|
11340
|
+
const subjectId = createConnectionSubjectId(payload, grant.subjectId);
|
|
11273
11341
|
const providerDomain = canonicalProviderDomain(payload.providerDomain);
|
|
11274
11342
|
assertNotDirectPersonalSlackOAuth(providerDomain, payload.kind);
|
|
11275
11343
|
assertNotDirectGoogleDriveOAuth(providerDomain, payload.kind, payload.metadata);
|
|
@@ -11838,7 +11906,7 @@ function slackInstallErrorReason(error) {
|
|
|
11838
11906
|
return "installation_failed";
|
|
11839
11907
|
}
|
|
11840
11908
|
async function requireSlackInstallCallbackGrant(db, state) {
|
|
11841
|
-
const grant = await
|
|
11909
|
+
const grant = await getWorkspaceGrant5(db, state.subjectId, state.workspaceId);
|
|
11842
11910
|
if (!grant || grant.accountId !== state.accountId || !hasPermission8(grant.permissions, "connections:write")) {
|
|
11843
11911
|
throw new SlackInstallCallbackError(
|
|
11844
11912
|
403,
|
|
@@ -11877,6 +11945,18 @@ function writableSubjectId(requested, grantSubjectId) {
|
|
|
11877
11945
|
}
|
|
11878
11946
|
return requested;
|
|
11879
11947
|
}
|
|
11948
|
+
function createConnectionSubjectId(payload, grantSubjectId) {
|
|
11949
|
+
if (payload.ownership === void 0) {
|
|
11950
|
+
return writableSubjectId(payload.subjectId, grantSubjectId);
|
|
11951
|
+
}
|
|
11952
|
+
const subjectId = payload.ownership === "personal" ? grantSubjectId : null;
|
|
11953
|
+
if (payload.subjectId !== void 0 && payload.subjectId !== subjectId) {
|
|
11954
|
+
throw new HTTPException11(422, {
|
|
11955
|
+
message: "ownership and subjectId describe different connection owners"
|
|
11956
|
+
});
|
|
11957
|
+
}
|
|
11958
|
+
return subjectId;
|
|
11959
|
+
}
|
|
11880
11960
|
function encryptCredentialBundle(key, credential) {
|
|
11881
11961
|
return encryptEnvironmentValue5(key, JSON.stringify(credential));
|
|
11882
11962
|
}
|
|
@@ -11919,7 +11999,7 @@ import {
|
|
|
11919
11999
|
ensureDefaultBase,
|
|
11920
12000
|
getDocument,
|
|
11921
12001
|
getDocumentBase,
|
|
11922
|
-
|
|
12002
|
+
listDocumentBasesEnsuringDefault,
|
|
11923
12003
|
listDocuments,
|
|
11924
12004
|
moveDocumentToBase,
|
|
11925
12005
|
queueDocumentForReindex,
|
|
@@ -12597,9 +12677,12 @@ function registerDocumentRoutes(app, deps) {
|
|
|
12597
12677
|
});
|
|
12598
12678
|
app.get("/v1/workspaces/:workspaceId/document-bases", async (c) => {
|
|
12599
12679
|
const workspaceId = c.req.param("workspaceId");
|
|
12600
|
-
await requireAccessGrant5(c, deps, workspaceId, "documents:search");
|
|
12680
|
+
const grant = await requireAccessGrant5(c, deps, workspaceId, "documents:search");
|
|
12601
12681
|
return c.json(
|
|
12602
|
-
(await
|
|
12682
|
+
(await listDocumentBasesEnsuringDefault(db, {
|
|
12683
|
+
accountId: grant.accountId,
|
|
12684
|
+
workspaceId
|
|
12685
|
+
})).map((base) => DocumentBase.parse(base))
|
|
12603
12686
|
);
|
|
12604
12687
|
});
|
|
12605
12688
|
app.get("/v1/workspaces/:workspaceId/document-bases/:baseId", async (c) => {
|
|
@@ -15824,6 +15907,7 @@ import {
|
|
|
15824
15907
|
import { requireAccessGrant as requireAccessGrant13 } from "@opengeni/core";
|
|
15825
15908
|
import { recordWorkspaceUsage as recordWorkspaceUsage4, requireLimit as requireLimit6 } from "@opengeni/core";
|
|
15826
15909
|
import {
|
|
15910
|
+
captureScheduledTaskRestoreState as captureScheduledTaskRestoreState2,
|
|
15827
15911
|
createValidatedScheduledTask as createValidatedScheduledTask3,
|
|
15828
15912
|
manualScheduledTaskTriggerUsageKey as manualScheduledTaskTriggerUsageKey2,
|
|
15829
15913
|
manualScheduledTaskTriggerWorkflowId as manualScheduledTaskTriggerWorkflowId2,
|
|
@@ -15873,6 +15957,7 @@ function registerScheduledTaskRoutes(app, deps) {
|
|
|
15873
15957
|
const grant = await requireAccessGrant13(c, deps, workspaceId, "scheduled_tasks:manage");
|
|
15874
15958
|
const taskId = c.req.param("taskId");
|
|
15875
15959
|
const existing = await requireScheduledTaskForApi(db, workspaceId, taskId);
|
|
15960
|
+
const previous = await captureScheduledTaskRestoreState2(db, existing);
|
|
15876
15961
|
const rawPayload = await c.req.json();
|
|
15877
15962
|
const payload = UpdateScheduledTaskRequest2.parse(rawPayload);
|
|
15878
15963
|
const update = await validatedScheduledTaskUpdate2({
|
|
@@ -15885,23 +15970,25 @@ function registerScheduledTaskRoutes(app, deps) {
|
|
|
15885
15970
|
toolsProvided: scheduledTaskToolsProvided2(rawPayload)
|
|
15886
15971
|
});
|
|
15887
15972
|
const task = await updateScheduledTask2(db, workspaceId, taskId, update);
|
|
15888
|
-
await syncUpdatedScheduledTask2({ db, workflowClient, previous
|
|
15973
|
+
await syncUpdatedScheduledTask2({ db, workflowClient, previous, task });
|
|
15889
15974
|
return c.json(task);
|
|
15890
15975
|
});
|
|
15891
15976
|
app.post("/v1/workspaces/:workspaceId/scheduled-tasks/:taskId/pause", async (c) => {
|
|
15892
15977
|
const workspaceId = c.req.param("workspaceId");
|
|
15893
15978
|
await requireAccessGrant13(c, deps, workspaceId, "scheduled_tasks:manage");
|
|
15894
15979
|
const existing = await requireScheduledTaskForApi(db, workspaceId, c.req.param("taskId"));
|
|
15980
|
+
const previous = await captureScheduledTaskRestoreState2(db, existing);
|
|
15895
15981
|
const task = await updateScheduledTask2(db, workspaceId, existing.id, { status: "paused" });
|
|
15896
|
-
await syncUpdatedScheduledTask2({ db, workflowClient, previous
|
|
15982
|
+
await syncUpdatedScheduledTask2({ db, workflowClient, previous, task });
|
|
15897
15983
|
return c.json(task);
|
|
15898
15984
|
});
|
|
15899
15985
|
app.post("/v1/workspaces/:workspaceId/scheduled-tasks/:taskId/resume", async (c) => {
|
|
15900
15986
|
const workspaceId = c.req.param("workspaceId");
|
|
15901
15987
|
await requireAccessGrant13(c, deps, workspaceId, "scheduled_tasks:manage");
|
|
15902
15988
|
const existing = await requireScheduledTaskForApi(db, workspaceId, c.req.param("taskId"));
|
|
15989
|
+
const previous = await captureScheduledTaskRestoreState2(db, existing);
|
|
15903
15990
|
const task = await updateScheduledTask2(db, workspaceId, existing.id, { status: "active" });
|
|
15904
|
-
await syncUpdatedScheduledTask2({ db, workflowClient, previous
|
|
15991
|
+
await syncUpdatedScheduledTask2({ db, workflowClient, previous, task });
|
|
15905
15992
|
return c.json(task);
|
|
15906
15993
|
});
|
|
15907
15994
|
app.post("/v1/workspaces/:workspaceId/scheduled-tasks/:taskId/trigger", async (c) => {
|
|
@@ -18121,6 +18208,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
18121
18208
|
accountId: grant.accountId,
|
|
18122
18209
|
workspaceId,
|
|
18123
18210
|
sessionId,
|
|
18211
|
+
subjectId: grant.subjectId,
|
|
18124
18212
|
payload: event.payload,
|
|
18125
18213
|
clientEventId: event.clientEventId ?? null
|
|
18126
18214
|
});
|
|
@@ -21560,9 +21648,10 @@ import {
|
|
|
21560
21648
|
deleteSlackBotUserLink,
|
|
21561
21649
|
enqueueSlackInteractionInbox,
|
|
21562
21650
|
getOrCreateSlackInteraction,
|
|
21651
|
+
getLatestSessionModelForSubject,
|
|
21563
21652
|
getSlackBotUserLink,
|
|
21564
21653
|
getSlackInteractionByRoute,
|
|
21565
|
-
getWorkspaceGrant as
|
|
21654
|
+
getWorkspaceGrant as getWorkspaceGrant6,
|
|
21566
21655
|
listSessionEventPage as listSessionEventPage3,
|
|
21567
21656
|
listSessionHumanInputRequests as listSessionHumanInputRequests2,
|
|
21568
21657
|
rekeySlackInteractionRoute,
|
|
@@ -21825,6 +21914,14 @@ async function drainSlackInteractionsOnce(deps) {
|
|
|
21825
21914
|
});
|
|
21826
21915
|
} catch (error) {
|
|
21827
21916
|
const code = safeErrorCode(error);
|
|
21917
|
+
console.error("[slack-interactions] inbox processing failed", {
|
|
21918
|
+
workspaceId: entry.workspaceId,
|
|
21919
|
+
connectionId: entry.connectionId,
|
|
21920
|
+
providerEventId: entry.providerEventId,
|
|
21921
|
+
triggerKind: entry.triggerKind,
|
|
21922
|
+
attemptCount: entry.attemptCount,
|
|
21923
|
+
errorCode: code
|
|
21924
|
+
});
|
|
21828
21925
|
if (entry.attemptCount >= 5 || permanentSlackInteractionError(error) || permanentSlackDeliveryError(error)) {
|
|
21829
21926
|
await settleSlackInteractionInbox(deps.db, {
|
|
21830
21927
|
entry,
|
|
@@ -21929,7 +22026,7 @@ async function processSlackInboxEntry(deps, entry) {
|
|
|
21929
22026
|
});
|
|
21930
22027
|
return;
|
|
21931
22028
|
}
|
|
21932
|
-
const grant = await
|
|
22029
|
+
const grant = await getWorkspaceGrant6(deps.db, link.subjectId, entry.workspaceId, {
|
|
21933
22030
|
principalKind: "human_session"
|
|
21934
22031
|
});
|
|
21935
22032
|
if (!grant || grant.accountId !== entry.accountId) {
|
|
@@ -21958,14 +22055,33 @@ async function processSlackInboxEntry(deps, entry) {
|
|
|
21958
22055
|
await continueSlackSession(deps, grant, interaction, entry);
|
|
21959
22056
|
return;
|
|
21960
22057
|
}
|
|
21961
|
-
const
|
|
21962
|
-
|
|
21963
|
-
|
|
21964
|
-
|
|
21965
|
-
|
|
21966
|
-
|
|
21967
|
-
|
|
21968
|
-
|
|
22058
|
+
const preferredModel = await getLatestSessionModelForSubject(
|
|
22059
|
+
deps.db,
|
|
22060
|
+
entry.workspaceId,
|
|
22061
|
+
grant.subjectId
|
|
22062
|
+
);
|
|
22063
|
+
let session;
|
|
22064
|
+
try {
|
|
22065
|
+
session = await createSessionForRequest3(deps, grant, entry.workspaceId, {
|
|
22066
|
+
requestedSessionId: interaction.sessionReservationId,
|
|
22067
|
+
initialMessage: entry.text,
|
|
22068
|
+
turnInstructions: SLACK_TASK_INSTRUCTIONS,
|
|
22069
|
+
firstPartyMcpTools: [...SLACK_TASK_FIRST_PARTY_MCP_TOOLS],
|
|
22070
|
+
...preferredModel ? { model: preferredModel } : {},
|
|
22071
|
+
idempotencyKey: `slack:${entry.connectionId}:${entry.providerEventId}`,
|
|
22072
|
+
clientEventId: `slack:${entry.providerEventId}`
|
|
22073
|
+
});
|
|
22074
|
+
} catch (error) {
|
|
22075
|
+
if (error instanceof HTTPException32) {
|
|
22076
|
+
await client.postMessage({
|
|
22077
|
+
operationId: deterministicUuid(`slack-admission-failed:${interaction.id}`),
|
|
22078
|
+
channelId: entry.slackChannelId,
|
|
22079
|
+
...entry.triggerKind === "slash_command" ? {} : { threadTimestamp: entry.slackThreadTs ?? entry.slackMessageTs },
|
|
22080
|
+
text: slackAdmissionFailureText(error)
|
|
22081
|
+
});
|
|
22082
|
+
}
|
|
22083
|
+
throw error;
|
|
22084
|
+
}
|
|
21969
22085
|
const bound = await bindSlackInteractionSession(deps.db, {
|
|
21970
22086
|
...interaction,
|
|
21971
22087
|
owningSubjectId: grant.subjectId,
|
|
@@ -22356,9 +22472,19 @@ function safePayloadText(payload, field) {
|
|
|
22356
22472
|
}
|
|
22357
22473
|
function safeErrorCode(error) {
|
|
22358
22474
|
if (error instanceof SlackBotProviderError) return error.code.slice(0, 128);
|
|
22475
|
+
if (error instanceof HTTPException32) return `http_${error.status}`;
|
|
22359
22476
|
const raw = error instanceof Error ? error.name : "slack_interaction_error";
|
|
22360
22477
|
return raw.toLowerCase().replace(/[^a-z0-9_-]/g, "_").slice(0, 128) || "error";
|
|
22361
22478
|
}
|
|
22479
|
+
function slackAdmissionFailureText(error) {
|
|
22480
|
+
if (error.status === 402) {
|
|
22481
|
+
return "OpenGeni could not start this task because the selected model has no available billing source. Open OpenGeni, select a connected subscription model, and try again.";
|
|
22482
|
+
}
|
|
22483
|
+
if (error.status === 429) {
|
|
22484
|
+
return "OpenGeni could not start this task because this workspace has reached a usage limit. Try again later or review the workspace limits in OpenGeni.";
|
|
22485
|
+
}
|
|
22486
|
+
return "OpenGeni could not start this task because the workspace rejected the session settings. Open OpenGeni, select an available model, and try again.";
|
|
22487
|
+
}
|
|
22362
22488
|
var SlackInteractionPermanentError = class extends Error {
|
|
22363
22489
|
};
|
|
22364
22490
|
function permanentSlackInteractionError(error) {
|
|
@@ -23418,4 +23544,4 @@ export {
|
|
|
23418
23544
|
withDefaultEnabledCapabilityMcpTools,
|
|
23419
23545
|
workflowIdForSession2 as workflowIdForSession
|
|
23420
23546
|
};
|
|
23421
|
-
//# sourceMappingURL=chunk-
|
|
23547
|
+
//# sourceMappingURL=chunk-TFHWQL2W.js.map
|