@opengeni/core 5.1.1 → 5.2.0-canary.37090756480001
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/default-session-model.d.ts +4 -3
- package/dist/dependencies.d.ts +2 -0
- package/dist/domain/connector-tool-permissions.d.ts +5 -1
- package/dist/domain/direct-model-provider.d.ts +3 -0
- package/dist/domain/insights.d.ts +11 -2
- package/dist/domain/knowledge-messages.d.ts +2 -2
- package/dist/domain/pr-review.d.ts +4 -4
- package/dist/domain/session-tool-policy.d.ts +7 -2
- package/dist/domain/sessions.d.ts +8 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +662 -486
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
- package/src/access/index.ts +10 -3
- package/src/default-session-model.ts +19 -4
- package/src/dependencies.ts +2 -0
- package/src/domain/connector-tool-permissions.ts +40 -10
- package/src/domain/direct-model-provider.ts +62 -0
- package/src/domain/insights.ts +91 -8
- package/src/domain/scheduled-tasks.ts +2 -3
- package/src/domain/session-tool-policy.ts +27 -1
- package/src/domain/sessions.ts +23 -18
- package/src/index.ts +1 -0
- package/src/model-catalog.ts +29 -16
package/dist/index.js
CHANGED
|
@@ -419,6 +419,57 @@ async function readSessionAttachmentFiles(deps, grant, sessionId, fileIds) {
|
|
|
419
419
|
return [...ordinary, ...shared];
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
+
// src/domain/direct-model-provider.ts
|
|
423
|
+
import { DirectModelProviderMetadata } from "@opengeni/contracts";
|
|
424
|
+
import { HTTPException } from "hono/http-exception";
|
|
425
|
+
async function verifyDirectModelAccess(input, apiKey, fetchImpl = fetch) {
|
|
426
|
+
const config = DirectModelProviderMetadata.parse(input);
|
|
427
|
+
const baseUrl = config.provider === "openai" ? "https://api.openai.com/v1" : config.endpoint;
|
|
428
|
+
let response;
|
|
429
|
+
try {
|
|
430
|
+
response = await fetchImpl(`${baseUrl}/responses`, {
|
|
431
|
+
method: "POST",
|
|
432
|
+
redirect: "error",
|
|
433
|
+
signal: AbortSignal.timeout(15e3),
|
|
434
|
+
headers: {
|
|
435
|
+
"content-type": "application/json",
|
|
436
|
+
...config.provider === "openai" ? { authorization: `Bearer ${apiKey}` } : { "api-key": apiKey }
|
|
437
|
+
},
|
|
438
|
+
body: JSON.stringify({
|
|
439
|
+
model: config.model,
|
|
440
|
+
input: "Reply OK.",
|
|
441
|
+
max_output_tokens: 32,
|
|
442
|
+
store: false
|
|
443
|
+
})
|
|
444
|
+
});
|
|
445
|
+
} catch {
|
|
446
|
+
throw new HTTPException(502, {
|
|
447
|
+
message: "We couldn\u2019t reach your model provider. Check the endpoint and try again."
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (response.status === 401 || response.status === 403) {
|
|
451
|
+
throw new HTTPException(422, {
|
|
452
|
+
message: "Your provider didn\u2019t accept this API key. Check the key and its permissions."
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
if (response.status === 404 || response.status === 400) {
|
|
456
|
+
throw new HTTPException(422, {
|
|
457
|
+
message: config.provider === "azure_openai" ? "Azure couldn\u2019t use this deployment. Check the endpoint and deployment name." : "OpenAI couldn\u2019t use this model. Check the model name and your account\u2019s access."
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
if (response.status === 429) {
|
|
461
|
+
throw new HTTPException(422, {
|
|
462
|
+
message: "Your provider\u2019s quota or rate limit blocked the check. Check your provider billing or try again shortly."
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
const body = response.ok ? await response.json().catch(() => null) : null;
|
|
466
|
+
if (!body || !["completed", "incomplete"].includes(body.status)) {
|
|
467
|
+
throw new HTTPException(502, {
|
|
468
|
+
message: "Your provider couldn\u2019t complete the connection check. Try again shortly."
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
422
473
|
// src/domain/skills.ts
|
|
423
474
|
import { validateSkillTextFiles } from "@opengeni/contracts";
|
|
424
475
|
import { buildPortableSkillArtifact } from "@opengeni/runtime/skill-library";
|
|
@@ -509,9 +560,9 @@ import {
|
|
|
509
560
|
} from "@opengeni/contracts";
|
|
510
561
|
|
|
511
562
|
// src/domain/connection-account-selection-error.ts
|
|
512
|
-
import { HTTPException } from "hono/http-exception";
|
|
563
|
+
import { HTTPException as HTTPException2 } from "hono/http-exception";
|
|
513
564
|
import { ConnectionAccountSelectionDiagnostic } from "@opengeni/contracts";
|
|
514
|
-
var ConnectionAccountSelectionError = class extends
|
|
565
|
+
var ConnectionAccountSelectionError = class extends HTTPException2 {
|
|
515
566
|
name = "ConnectionAccountSelectionError";
|
|
516
567
|
diagnostic;
|
|
517
568
|
constructor(message, diagnostic = {
|
|
@@ -677,7 +728,7 @@ import {
|
|
|
677
728
|
mcpServerIdForCapability,
|
|
678
729
|
upsertCapabilityCatalogItem
|
|
679
730
|
} from "@opengeni/db";
|
|
680
|
-
import { HTTPException as
|
|
731
|
+
import { HTTPException as HTTPException6 } from "hono/http-exception";
|
|
681
732
|
import {
|
|
682
733
|
withOrganizationIntegrationAcquisition,
|
|
683
734
|
withOrganizationIntegrationPolicyFence
|
|
@@ -699,6 +750,7 @@ import {
|
|
|
699
750
|
bootstrapWorkspace,
|
|
700
751
|
ensureManagedAccessForUser,
|
|
701
752
|
ensureExternalIdentity,
|
|
753
|
+
lockExternalWorkspaceMembershipLifecycle,
|
|
702
754
|
resolveExternalIdentityLink,
|
|
703
755
|
managedPersonalWorkspacePermissions,
|
|
704
756
|
nestedPostgresSqlState,
|
|
@@ -710,22 +762,22 @@ import {
|
|
|
710
762
|
requireWorkspace,
|
|
711
763
|
resolveNamedManagedPersonalWorkspaceGrant
|
|
712
764
|
} from "@opengeni/db";
|
|
713
|
-
import { HTTPException as
|
|
765
|
+
import { HTTPException as HTTPException4 } from "hono/http-exception";
|
|
714
766
|
|
|
715
767
|
// src/access/service-initiator.ts
|
|
716
768
|
import { ServiceTurnInitiatorContext } from "@opengeni/contracts";
|
|
717
|
-
import { HTTPException as
|
|
769
|
+
import { HTTPException as HTTPException3 } from "hono/http-exception";
|
|
718
770
|
function serviceInitiatorFromHeaders(headers) {
|
|
719
771
|
const name = headers.get("x-opengeni-service-initiator");
|
|
720
772
|
const rawContext = headers.get("x-opengeni-service-context");
|
|
721
773
|
if (name === null && rawContext === null) return null;
|
|
722
774
|
if (headers.has("x-opengeni-external-actor")) {
|
|
723
|
-
throw new
|
|
775
|
+
throw new HTTPException3(422, {
|
|
724
776
|
message: "service initiator and external actor (asUser) are mutually exclusive"
|
|
725
777
|
});
|
|
726
778
|
}
|
|
727
779
|
if (name === null || !/^[a-z0-9][a-z0-9:._-]{0,63}$/.test(name)) {
|
|
728
|
-
throw new
|
|
780
|
+
throw new HTTPException3(422, {
|
|
729
781
|
message: "x-opengeni-service-initiator must match ^[a-z0-9][a-z0-9:._-]{0,63}$"
|
|
730
782
|
});
|
|
731
783
|
}
|
|
@@ -742,7 +794,7 @@ function serviceInitiatorFromHeaders(headers) {
|
|
|
742
794
|
context = parsed;
|
|
743
795
|
if (!ServiceTurnInitiatorContext.safeParse(context).success) throw new Error("reserved");
|
|
744
796
|
} catch {
|
|
745
|
-
throw new
|
|
797
|
+
throw new HTTPException3(422, {
|
|
746
798
|
message: "x-opengeni-service-context must be a JSON object of flat string/number/boolean values, at most 2048 UTF-8 bytes, without reserved OpenGeni provenance fields"
|
|
747
799
|
});
|
|
748
800
|
}
|
|
@@ -773,12 +825,12 @@ function isDeveloperSetupDelegatedPermissionAllowed(permission) {
|
|
|
773
825
|
}
|
|
774
826
|
function requireApiKeyManagementContext(context) {
|
|
775
827
|
if (developerSetupApiKeyContexts.has(context)) {
|
|
776
|
-
throw new
|
|
828
|
+
throw new HTTPException4(403, { message: "Developer setup keys cannot manage API keys" });
|
|
777
829
|
}
|
|
778
830
|
}
|
|
779
831
|
function requireApiKeyDelegationContext(context, permissions) {
|
|
780
832
|
if ((hasPermission(permissions, "api_keys:manage") || hasPermission(permissions, "members:manage")) && developerSetupApiKeyContexts.has(context)) {
|
|
781
|
-
throw new
|
|
833
|
+
throw new HTTPException4(403, {
|
|
782
834
|
message: "Setup credentials cannot delegate API key management"
|
|
783
835
|
});
|
|
784
836
|
}
|
|
@@ -899,7 +951,7 @@ async function requireAccessContext(c, deps) {
|
|
|
899
951
|
}
|
|
900
952
|
const context = await pending;
|
|
901
953
|
if (!context) {
|
|
902
|
-
throw new
|
|
954
|
+
throw new HTTPException4(401, { message: "authentication required" });
|
|
903
955
|
}
|
|
904
956
|
return context;
|
|
905
957
|
}
|
|
@@ -909,7 +961,7 @@ async function requireAccessGrant(c, deps, workspaceId, permission) {
|
|
|
909
961
|
async function requireFreshAccessGrant(c, deps, workspaceId, permission) {
|
|
910
962
|
const context = await resolveAccessContext(c, deps);
|
|
911
963
|
if (!context) {
|
|
912
|
-
throw new
|
|
964
|
+
throw new HTTPException4(401, { message: "authentication required" });
|
|
913
965
|
}
|
|
914
966
|
return (await accessGrantAuthorization(context, deps, workspaceId, permission)).grant;
|
|
915
967
|
}
|
|
@@ -956,7 +1008,7 @@ function accessGrantAuthorizationFromContext(context, grant) {
|
|
|
956
1008
|
function requireAccountAdminAuthorizationStamp(authorization) {
|
|
957
1009
|
const { grant, accountGrant } = authorization;
|
|
958
1010
|
if (!resolvedAccessGrantAuthorizations.has(authorization) || !authorization.contextIntegrity || authorization.authenticatedSubjectId !== grant.subjectId || accountGrant?.accountId !== grant.accountId || accountGrant.subjectId !== grant.subjectId || !accountGrant.permissions.includes("account:admin")) {
|
|
959
|
-
throw new
|
|
1011
|
+
throw new HTTPException4(403, { message: "missing permission: account:admin" });
|
|
960
1012
|
}
|
|
961
1013
|
return Object.freeze({
|
|
962
1014
|
authorizationId: crypto.randomUUID(),
|
|
@@ -968,24 +1020,24 @@ function requireAccountAdminAuthorizationStamp(authorization) {
|
|
|
968
1020
|
function requireResolvedAccessGrantAuthorization(authorization, workspaceId) {
|
|
969
1021
|
const { grant } = authorization;
|
|
970
1022
|
if (!resolvedAccessGrantAuthorizations.has(authorization) || !authorization.contextIntegrity || authorization.authenticatedSubjectId !== grant.subjectId || grant.workspaceId !== workspaceId) {
|
|
971
|
-
throw new
|
|
1023
|
+
throw new HTTPException4(403, { message: "workspace access authorization is invalid" });
|
|
972
1024
|
}
|
|
973
1025
|
return grant;
|
|
974
1026
|
}
|
|
975
1027
|
async function requireCanonicalLocalAccountAdministrator(c, deps, accountId) {
|
|
976
1028
|
if (c.req.header("authorization")) {
|
|
977
|
-
throw new
|
|
1029
|
+
throw new HTTPException4(401, { message: "organization administrator session required" });
|
|
978
1030
|
}
|
|
979
1031
|
const context = await requireAccessContext(c, deps);
|
|
980
1032
|
const grant = context.workspaceGrants.find((candidate) => candidate.accountId === accountId);
|
|
981
1033
|
if (!grant) {
|
|
982
|
-
throw new
|
|
1034
|
+
throw new HTTPException4(403, {
|
|
983
1035
|
message: "local organization administration is not authorized"
|
|
984
1036
|
});
|
|
985
1037
|
}
|
|
986
1038
|
const authorization = accessGrantAuthorizationFromContext(context, grant);
|
|
987
1039
|
if (!authorization.canonicalLocalHumanSession) {
|
|
988
|
-
throw new
|
|
1040
|
+
throw new HTTPException4(401, { message: "organization administrator session required" });
|
|
989
1041
|
}
|
|
990
1042
|
requireAccountAdminAuthorizationStamp(authorization);
|
|
991
1043
|
return { subjectId: context.subjectId, authorization };
|
|
@@ -1005,7 +1057,7 @@ async function requireWorkspaceSettingsGrant(c, deps, workspaceId) {
|
|
|
1005
1057
|
})) {
|
|
1006
1058
|
return grant;
|
|
1007
1059
|
}
|
|
1008
|
-
throw new
|
|
1060
|
+
throw new HTTPException4(403, {
|
|
1009
1061
|
message: "workspace settings require a workspace administrator or Personal workspace owner"
|
|
1010
1062
|
});
|
|
1011
1063
|
}
|
|
@@ -1027,7 +1079,7 @@ async function accessGrantAuthorization(context, deps, workspaceId, permission)
|
|
|
1027
1079
|
})
|
|
1028
1080
|
);
|
|
1029
1081
|
if (!grant2 || grant2.accountId !== external.identity.accountId) {
|
|
1030
|
-
throw new
|
|
1082
|
+
throw new HTTPException4(403, { message: "external workspace access denied" });
|
|
1031
1083
|
}
|
|
1032
1084
|
grant2.permissions = Permission.options.filter(
|
|
1033
1085
|
(value) => hasPermission(grant2.permissions, value) && hasPermission(external.permissions, value) && (!external.linked || hasPermission(external.linked.link.permissions, value))
|
|
@@ -1050,7 +1102,7 @@ async function accessGrantAuthorization(context, deps, workspaceId, permission)
|
|
|
1050
1102
|
const authority = accountScopedApiKeyWorkspaceAuthority(context);
|
|
1051
1103
|
const workspace = await requireWorkspace(deps.db, workspaceId);
|
|
1052
1104
|
if (!authority || workspace.accountId !== authority.accountId || workspace.kind !== "shared") {
|
|
1053
|
-
throw new
|
|
1105
|
+
throw new HTTPException4(403, {
|
|
1054
1106
|
message: "Developer setup requires a shared organization workspace"
|
|
1055
1107
|
});
|
|
1056
1108
|
}
|
|
@@ -1065,7 +1117,7 @@ async function accessGrantAuthorization(context, deps, workspaceId, permission)
|
|
|
1065
1117
|
if (!grant) {
|
|
1066
1118
|
const workspace = await requireWorkspace(deps.db, workspaceId).catch(() => null);
|
|
1067
1119
|
if (!workspace) {
|
|
1068
|
-
throw new
|
|
1120
|
+
throw new HTTPException4(404, { message: "workspace not found" });
|
|
1069
1121
|
}
|
|
1070
1122
|
const authority = accountScopedApiKeyWorkspaceAuthority(context);
|
|
1071
1123
|
const requiredWorkspacePermission = permission ?? "workspace:read";
|
|
@@ -1080,7 +1132,7 @@ async function accessGrantAuthorization(context, deps, workspaceId, permission)
|
|
|
1080
1132
|
...apiKeyServiceContexts.get(context)
|
|
1081
1133
|
};
|
|
1082
1134
|
} else {
|
|
1083
|
-
throw new
|
|
1135
|
+
throw new HTTPException4(403, { message: "workspace access denied" });
|
|
1084
1136
|
}
|
|
1085
1137
|
}
|
|
1086
1138
|
if (permission) {
|
|
@@ -1105,23 +1157,23 @@ function hostedHumanSessionPrincipalKind(context) {
|
|
|
1105
1157
|
function requirePermission(grant, permission) {
|
|
1106
1158
|
if (!hasPermission(grant.permissions, permission)) {
|
|
1107
1159
|
if (permission === "variable-sets:use") {
|
|
1108
|
-
throw new
|
|
1160
|
+
throw new HTTPException4(403, {
|
|
1109
1161
|
message: "missing permission: variable-sets:use (deprecated alias: environments:use)"
|
|
1110
1162
|
});
|
|
1111
1163
|
}
|
|
1112
1164
|
if (permission === "variable-sets:manage") {
|
|
1113
|
-
throw new
|
|
1165
|
+
throw new HTTPException4(403, {
|
|
1114
1166
|
message: "missing permission: variable-sets:manage (deprecated alias: environments:manage)"
|
|
1115
1167
|
});
|
|
1116
1168
|
}
|
|
1117
|
-
throw new
|
|
1169
|
+
throw new HTTPException4(403, {
|
|
1118
1170
|
message: `missing permission: ${permission}`
|
|
1119
1171
|
});
|
|
1120
1172
|
}
|
|
1121
1173
|
}
|
|
1122
1174
|
function requireLiteralPermission(grant, permission) {
|
|
1123
1175
|
if (!hasLiteralPermission(grant.permissions, permission)) {
|
|
1124
|
-
throw new
|
|
1176
|
+
throw new HTTPException4(403, {
|
|
1125
1177
|
message: `missing literal permission: ${permission}`
|
|
1126
1178
|
});
|
|
1127
1179
|
}
|
|
@@ -1141,13 +1193,13 @@ async function resolveAccessContext(c, deps) {
|
|
|
1141
1193
|
const service = serviceInitiatorFromHeaders(c.req.raw.headers);
|
|
1142
1194
|
if (service) {
|
|
1143
1195
|
if (deps.settings.productAccessMode === "local") {
|
|
1144
|
-
throw new
|
|
1196
|
+
throw new HTTPException4(422, {
|
|
1145
1197
|
message: "service initiator headers require an organization or workspace API key"
|
|
1146
1198
|
});
|
|
1147
1199
|
}
|
|
1148
1200
|
const context = await apiKeyAccessContext(c, deps, deps.settings.productAccessMode);
|
|
1149
1201
|
if (!context) {
|
|
1150
|
-
throw new
|
|
1202
|
+
throw new HTTPException4(422, {
|
|
1151
1203
|
message: "service initiator headers require an organization or workspace API key"
|
|
1152
1204
|
});
|
|
1153
1205
|
}
|
|
@@ -1155,7 +1207,7 @@ async function resolveAccessContext(c, deps) {
|
|
|
1155
1207
|
}
|
|
1156
1208
|
if (c.req.header("x-opengeni-external-actor") !== void 0) {
|
|
1157
1209
|
if (deps.settings.productAccessMode === "local") {
|
|
1158
|
-
throw new
|
|
1210
|
+
throw new HTTPException4(401, {
|
|
1159
1211
|
message: "external actors require organization key authentication"
|
|
1160
1212
|
});
|
|
1161
1213
|
}
|
|
@@ -1255,32 +1307,35 @@ async function apiKeyAccessContext(c, deps, mode) {
|
|
|
1255
1307
|
const externalHeader = c.req.header("x-opengeni-external-actor");
|
|
1256
1308
|
const service = serviceInitiatorFromHeaders(c.req.raw.headers);
|
|
1257
1309
|
if (service && apiKey.credentialKind !== "organization" && apiKey.credentialKind !== "workspace") {
|
|
1258
|
-
throw new
|
|
1310
|
+
throw new HTTPException4(422, {
|
|
1259
1311
|
message: "service initiator headers require an organization or workspace API key"
|
|
1260
1312
|
});
|
|
1261
1313
|
}
|
|
1262
1314
|
if (externalHeader !== void 0) {
|
|
1263
1315
|
if (apiKey.workspaceId !== null || apiKey.credentialKind !== "organization") {
|
|
1264
|
-
throw new
|
|
1316
|
+
throw new HTTPException4(403, { message: "external actors require an organization key" });
|
|
1265
1317
|
}
|
|
1266
1318
|
let selection;
|
|
1267
1319
|
try {
|
|
1268
1320
|
if (externalHeader.length > 16384) throw new Error("oversize");
|
|
1269
1321
|
selection = ExternalActorSelection.parse(JSON.parse(decodeURIComponent(externalHeader)));
|
|
1270
1322
|
} catch {
|
|
1271
|
-
throw new
|
|
1323
|
+
throw new HTTPException4(400, { message: "invalid external actor selection" });
|
|
1272
1324
|
}
|
|
1273
1325
|
let identity;
|
|
1274
1326
|
try {
|
|
1275
|
-
identity = await
|
|
1276
|
-
|
|
1277
|
-
|
|
1327
|
+
identity = await withAccountRls(deps.db, apiKey.accountId, async (tx) => {
|
|
1328
|
+
await lockExternalWorkspaceMembershipLifecycle(tx, apiKey.accountId);
|
|
1329
|
+
return await ensureExternalIdentity(tx, {
|
|
1330
|
+
accountId: apiKey.accountId,
|
|
1331
|
+
...selection.identity
|
|
1332
|
+
});
|
|
1278
1333
|
});
|
|
1279
1334
|
} catch (error) {
|
|
1280
1335
|
if (nestedPostgresSqlState(error) === "42501") {
|
|
1281
|
-
throw new
|
|
1336
|
+
throw new HTTPException4(403, { message: "external identity is unavailable" });
|
|
1282
1337
|
}
|
|
1283
|
-
throw new
|
|
1338
|
+
throw new HTTPException4(503, { message: "external identity authority is unavailable" });
|
|
1284
1339
|
}
|
|
1285
1340
|
const linked = selection.mode === "linked_native" ? await resolveExternalIdentityLink(deps.db, {
|
|
1286
1341
|
identity,
|
|
@@ -1288,7 +1343,7 @@ async function apiKeyAccessContext(c, deps, mode) {
|
|
|
1288
1343
|
expectedRevision: selection.expectedLinkRevision
|
|
1289
1344
|
}) : null;
|
|
1290
1345
|
if (selection.mode === "linked_native" && !linked)
|
|
1291
|
-
throw new
|
|
1346
|
+
throw new HTTPException4(403, { message: "Native identity link is unavailable or changed" });
|
|
1292
1347
|
const effectiveSubjectId = linked?.link.nativeSubjectId ?? identity.subjectId;
|
|
1293
1348
|
const context2 = {
|
|
1294
1349
|
mode,
|
|
@@ -1483,10 +1538,10 @@ function preferredFikenConnection(connections) {
|
|
|
1483
1538
|
import { listSkillLibraryEntries } from "@opengeni/runtime/skill-library";
|
|
1484
1539
|
|
|
1485
1540
|
// src/domain/native-mcp-connection-admission.ts
|
|
1486
|
-
import { HTTPException as
|
|
1541
|
+
import { HTTPException as HTTPException5 } from "hono/http-exception";
|
|
1487
1542
|
function assertNativeMcpConnectionRef(connectionRef) {
|
|
1488
1543
|
if (connectionRef?.authoritySource === "host") {
|
|
1489
|
-
throw new
|
|
1544
|
+
throw new HTTPException5(422, {
|
|
1490
1545
|
message: "host-owned MCP connection refs are no longer supported; select an ordinary native connection"
|
|
1491
1546
|
});
|
|
1492
1547
|
}
|
|
@@ -1569,22 +1624,22 @@ async function buildCapabilityCatalog(input) {
|
|
|
1569
1624
|
async function createCatalogItem(input) {
|
|
1570
1625
|
const id = input.payload.id?.trim() || generatedCapabilityId(input.payload);
|
|
1571
1626
|
if (id.startsWith("skill:")) {
|
|
1572
|
-
throw new
|
|
1627
|
+
throw new HTTPException6(422, {
|
|
1573
1628
|
message: "Skills are installed through the Skill library or source import flow"
|
|
1574
1629
|
});
|
|
1575
1630
|
}
|
|
1576
1631
|
if (id.startsWith("api:")) {
|
|
1577
|
-
throw new
|
|
1632
|
+
throw new HTTPException6(422, {
|
|
1578
1633
|
message: "API Integrations are installed from typed Integration Definitions"
|
|
1579
1634
|
});
|
|
1580
1635
|
}
|
|
1581
1636
|
if (id.startsWith("plugin:")) {
|
|
1582
|
-
throw new
|
|
1637
|
+
throw new HTTPException6(422, {
|
|
1583
1638
|
message: "Plugins are installed through the Plugin Package flow"
|
|
1584
1639
|
});
|
|
1585
1640
|
}
|
|
1586
1641
|
if (input.payload.kind === "mcp" && (id === `mcp:${CODEX_APPS_MCP_SERVER_ID}` || typeof input.payload.metadata.mcpServerId === "string" && input.payload.metadata.mcpServerId.trim() === CODEX_APPS_MCP_SERVER_ID)) {
|
|
1587
|
-
throw new
|
|
1642
|
+
throw new HTTPException6(422, {
|
|
1588
1643
|
message: `${CODEX_APPS_MCP_SERVER_ID} is reserved for the canonical Codex Apps service`
|
|
1589
1644
|
});
|
|
1590
1645
|
}
|
|
@@ -1623,26 +1678,26 @@ async function prepareCapabilityEnable(input) {
|
|
|
1623
1678
|
input.capabilityId
|
|
1624
1679
|
);
|
|
1625
1680
|
if (isReservedCodexAppsCatalogItem(item))
|
|
1626
|
-
throw new
|
|
1681
|
+
throw new HTTPException6(422, {
|
|
1627
1682
|
message: "Codex Apps use the dedicated account designation flow"
|
|
1628
1683
|
});
|
|
1629
1684
|
if (item.kind === "skill") {
|
|
1630
|
-
throw new
|
|
1685
|
+
throw new HTTPException6(409, {
|
|
1631
1686
|
message: "Install Skills through the Skill library or source import flow"
|
|
1632
1687
|
});
|
|
1633
1688
|
}
|
|
1634
1689
|
if (item.kind === "api") {
|
|
1635
|
-
throw new
|
|
1690
|
+
throw new HTTPException6(409, {
|
|
1636
1691
|
message: "Install API Integrations through the Integration Definitions flow"
|
|
1637
1692
|
});
|
|
1638
1693
|
}
|
|
1639
1694
|
if (item.kind === "plugin") {
|
|
1640
|
-
throw new
|
|
1695
|
+
throw new HTTPException6(409, {
|
|
1641
1696
|
message: "Install Plugins through the Plugin Package flow"
|
|
1642
1697
|
});
|
|
1643
1698
|
}
|
|
1644
1699
|
if (item.kind === "mcp" && !item.runtime.available) {
|
|
1645
|
-
throw new
|
|
1700
|
+
throw new HTTPException6(422, {
|
|
1646
1701
|
message: "MCP capabilities need a remote streamable HTTP endpoint before they can be enabled"
|
|
1647
1702
|
});
|
|
1648
1703
|
}
|
|
@@ -1676,7 +1731,7 @@ async function prepareCapabilityEnable(input) {
|
|
|
1676
1731
|
installationConfig
|
|
1677
1732
|
);
|
|
1678
1733
|
if (!current || !isDeepStrictEqual(current, unchanged)) {
|
|
1679
|
-
throw new
|
|
1734
|
+
throw new HTTPException6(409, {
|
|
1680
1735
|
message: "Capability installation changed; reload before reconciling"
|
|
1681
1736
|
});
|
|
1682
1737
|
}
|
|
@@ -1777,7 +1832,7 @@ async function resolveMcpCredentialHeaders(input, item) {
|
|
|
1777
1832
|
])
|
|
1778
1833
|
);
|
|
1779
1834
|
} catch {
|
|
1780
|
-
throw new
|
|
1835
|
+
throw new HTTPException6(422, {
|
|
1781
1836
|
message: `stored credential headers for "${item.name}" could not be decrypted; supply them again in the enable request "headers" field`
|
|
1782
1837
|
});
|
|
1783
1838
|
}
|
|
@@ -1788,31 +1843,31 @@ function normalizedMcpCredentialHeaders(headers) {
|
|
|
1788
1843
|
return null;
|
|
1789
1844
|
}
|
|
1790
1845
|
if (entries.length > maxMcpCredentialHeaders) {
|
|
1791
|
-
throw new
|
|
1846
|
+
throw new HTTPException6(422, {
|
|
1792
1847
|
message: `an MCP capability supports at most ${maxMcpCredentialHeaders} credential headers`
|
|
1793
1848
|
});
|
|
1794
1849
|
}
|
|
1795
1850
|
const seen = /* @__PURE__ */ new Set();
|
|
1796
1851
|
for (const [name, value] of entries) {
|
|
1797
1852
|
if (!mcpCredentialHeaderName.test(name)) {
|
|
1798
|
-
throw new
|
|
1853
|
+
throw new HTTPException6(422, {
|
|
1799
1854
|
message: `invalid credential header name: ${name}`
|
|
1800
1855
|
});
|
|
1801
1856
|
}
|
|
1802
1857
|
const lower = name.toLowerCase();
|
|
1803
1858
|
if (seen.has(lower)) {
|
|
1804
|
-
throw new
|
|
1859
|
+
throw new HTTPException6(422, {
|
|
1805
1860
|
message: `duplicate credential header name: ${name}`
|
|
1806
1861
|
});
|
|
1807
1862
|
}
|
|
1808
1863
|
seen.add(lower);
|
|
1809
1864
|
if (value.length === 0 || value.length > maxMcpCredentialHeaderValueLength) {
|
|
1810
|
-
throw new
|
|
1865
|
+
throw new HTTPException6(422, {
|
|
1811
1866
|
message: `credential header ${name} must be 1-${maxMcpCredentialHeaderValueLength} characters`
|
|
1812
1867
|
});
|
|
1813
1868
|
}
|
|
1814
1869
|
if (/[\u0000-\u0008\u000A-\u001F\u007F]/.test(value)) {
|
|
1815
|
-
throw new
|
|
1870
|
+
throw new HTTPException6(422, {
|
|
1816
1871
|
message: `credential header ${name} contains forbidden control characters`
|
|
1817
1872
|
});
|
|
1818
1873
|
}
|
|
@@ -1838,12 +1893,12 @@ async function validateMcpCapabilityConnectionRef(input, item, ref) {
|
|
|
1838
1893
|
} : {}
|
|
1839
1894
|
};
|
|
1840
1895
|
if (!normalized.providerDomain) {
|
|
1841
|
-
throw new
|
|
1896
|
+
throw new HTTPException6(422, {
|
|
1842
1897
|
message: "connectionRef.providerDomain is required"
|
|
1843
1898
|
});
|
|
1844
1899
|
}
|
|
1845
1900
|
if (!item.endpointUrl || !item.runtime.mcpServerId) {
|
|
1846
|
-
throw new
|
|
1901
|
+
throw new HTTPException6(422, {
|
|
1847
1902
|
message: "MCP capabilities need a remote streamable HTTP endpoint before they can use a connectionRef"
|
|
1848
1903
|
});
|
|
1849
1904
|
}
|
|
@@ -1868,27 +1923,27 @@ async function validateMcpCapabilityConnectionRef(input, item, ref) {
|
|
|
1868
1923
|
) ?? null;
|
|
1869
1924
|
}
|
|
1870
1925
|
if (!connection) {
|
|
1871
|
-
throw new
|
|
1926
|
+
throw new HTTPException6(422, {
|
|
1872
1927
|
message: "connectionRef does not reference a visible active connection"
|
|
1873
1928
|
});
|
|
1874
1929
|
}
|
|
1875
1930
|
if (subjectScope === "subject" && connection.subjectId !== input.grant.subjectId || subjectScope === "workspace" && connection.subjectId !== null) {
|
|
1876
|
-
throw new
|
|
1931
|
+
throw new HTTPException6(422, {
|
|
1877
1932
|
message: `connectionRef does not reference a ${subjectScope}-owned connection`
|
|
1878
1933
|
});
|
|
1879
1934
|
}
|
|
1880
1935
|
if (connection.status !== "active") {
|
|
1881
|
-
throw new
|
|
1936
|
+
throw new HTTPException6(422, {
|
|
1882
1937
|
message: `connectionRef.connectionId is not active (${connection.status})`
|
|
1883
1938
|
});
|
|
1884
1939
|
}
|
|
1885
1940
|
if (connection.providerDomain !== normalized.providerDomain) {
|
|
1886
|
-
throw new
|
|
1941
|
+
throw new HTTPException6(422, {
|
|
1887
1942
|
message: "connectionRef.providerDomain does not match the referenced connection"
|
|
1888
1943
|
});
|
|
1889
1944
|
}
|
|
1890
1945
|
if (normalized.kind && connection.kind !== normalized.kind) {
|
|
1891
|
-
throw new
|
|
1946
|
+
throw new HTTPException6(422, {
|
|
1892
1947
|
message: "connectionRef.kind does not match the referenced connection"
|
|
1893
1948
|
});
|
|
1894
1949
|
}
|
|
@@ -1915,12 +1970,12 @@ function assertRequiredMcpCredentialHeaders(item, headers, connectionRef) {
|
|
|
1915
1970
|
const names = new Set(Object.keys(headers ?? {}).map((name) => name.toLowerCase()));
|
|
1916
1971
|
const missing = required.filter((name) => !names.has(name.toLowerCase()));
|
|
1917
1972
|
if (missing.length > 0) {
|
|
1918
|
-
throw new
|
|
1973
|
+
throw new HTTPException6(422, {
|
|
1919
1974
|
message: `MCP capability "${item.name}" requires credential header(s) ${missing.join(", ")}; pass them in the enable request "headers" field`
|
|
1920
1975
|
});
|
|
1921
1976
|
}
|
|
1922
1977
|
if (item.authModel && names.size === 0) {
|
|
1923
|
-
throw new
|
|
1978
|
+
throw new HTTPException6(422, {
|
|
1924
1979
|
message: `MCP capability "${item.name}" requires credentials; pass them in the enable request "headers" field`
|
|
1925
1980
|
});
|
|
1926
1981
|
}
|
|
@@ -1935,7 +1990,7 @@ function requiredCapabilityHeaders(metadata) {
|
|
|
1935
1990
|
function requireCapabilityHeaderEncryption(settings) {
|
|
1936
1991
|
const key = environmentsEncryptionKeyBytes(settings);
|
|
1937
1992
|
if (!key) {
|
|
1938
|
-
throw new
|
|
1993
|
+
throw new HTTPException6(503, {
|
|
1939
1994
|
message: "MCP credential headers require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"
|
|
1940
1995
|
});
|
|
1941
1996
|
}
|
|
@@ -1946,7 +2001,7 @@ async function validateMcpCapabilityConnection(item, probe = probeStreamableHttp
|
|
|
1946
2001
|
return {};
|
|
1947
2002
|
}
|
|
1948
2003
|
if (!item.endpointUrl || !item.runtime.mcpServerId) {
|
|
1949
|
-
throw new
|
|
2004
|
+
throw new HTTPException6(422, {
|
|
1950
2005
|
message: "MCP capabilities need a remote streamable HTTP endpoint before they can be enabled"
|
|
1951
2006
|
});
|
|
1952
2007
|
}
|
|
@@ -1967,7 +2022,7 @@ async function validateMcpCapabilityConnection(item, probe = probeStreamableHttp
|
|
|
1967
2022
|
}
|
|
1968
2023
|
};
|
|
1969
2024
|
} catch (error) {
|
|
1970
|
-
throw new
|
|
2025
|
+
throw new HTTPException6(422, {
|
|
1971
2026
|
message: `MCP capability "${item.name}" could not be enabled because ${mcpProbeErrorMessage(error, item.endpointUrl)}`
|
|
1972
2027
|
});
|
|
1973
2028
|
}
|
|
@@ -2032,27 +2087,27 @@ async function disableCapability(input) {
|
|
|
2032
2087
|
input.capabilityId
|
|
2033
2088
|
);
|
|
2034
2089
|
if (item.kind === "skill") {
|
|
2035
|
-
throw new
|
|
2090
|
+
throw new HTTPException6(409, {
|
|
2036
2091
|
message: "Uninstall Skills through the Skill uninstall preview flow"
|
|
2037
2092
|
});
|
|
2038
2093
|
}
|
|
2039
2094
|
if (item.kind === "api") {
|
|
2040
|
-
throw new
|
|
2095
|
+
throw new HTTPException6(409, {
|
|
2041
2096
|
message: "Remove API Integrations through the Integration instance flow"
|
|
2042
2097
|
});
|
|
2043
2098
|
}
|
|
2044
2099
|
if (item.kind === "plugin") {
|
|
2045
|
-
throw new
|
|
2100
|
+
throw new HTTPException6(409, {
|
|
2046
2101
|
message: "Remove Plugins through the Plugin Package flow"
|
|
2047
2102
|
});
|
|
2048
2103
|
}
|
|
2049
2104
|
if (item.source === "built_in" || item.source === "configured") {
|
|
2050
|
-
throw new
|
|
2105
|
+
throw new HTTPException6(409, {
|
|
2051
2106
|
message: "built-in and configured capabilities are always available; remove them from configuration to disable them"
|
|
2052
2107
|
});
|
|
2053
2108
|
}
|
|
2054
2109
|
if (!await getCapabilityInstallation(input.db, input.workspaceId, item.id)) {
|
|
2055
|
-
throw new
|
|
2110
|
+
throw new HTTPException6(409, {
|
|
2056
2111
|
message: "capability is not currently enabled"
|
|
2057
2112
|
});
|
|
2058
2113
|
}
|
|
@@ -2249,21 +2304,21 @@ async function fetchMcpRegistryPage(url, options = {}) {
|
|
|
2249
2304
|
try {
|
|
2250
2305
|
const response = await fetchImpl(url, { signal: controller.signal });
|
|
2251
2306
|
if (!response.ok) {
|
|
2252
|
-
throw new
|
|
2307
|
+
throw new HTTPException6(502, {
|
|
2253
2308
|
message: `MCP registry returned ${response.status}`
|
|
2254
2309
|
});
|
|
2255
2310
|
}
|
|
2256
2311
|
return await response.json();
|
|
2257
2312
|
} catch (error) {
|
|
2258
|
-
if (error instanceof
|
|
2313
|
+
if (error instanceof HTTPException6) {
|
|
2259
2314
|
throw error;
|
|
2260
2315
|
}
|
|
2261
2316
|
if (error instanceof Error && error.name === "AbortError") {
|
|
2262
|
-
throw new
|
|
2317
|
+
throw new HTTPException6(504, {
|
|
2263
2318
|
message: "MCP registry request timed out"
|
|
2264
2319
|
});
|
|
2265
2320
|
}
|
|
2266
|
-
throw new
|
|
2321
|
+
throw new HTTPException6(502, {
|
|
2267
2322
|
message: `MCP registry request failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2268
2323
|
});
|
|
2269
2324
|
} finally {
|
|
@@ -2274,7 +2329,7 @@ async function requireCatalogItem(db, workspaceId, settings, capabilityId) {
|
|
|
2274
2329
|
const catalog = await buildCapabilityCatalog({ db, workspaceId, settings });
|
|
2275
2330
|
const item = catalog.items.find((candidate) => candidate.id === capabilityId) ?? await getCapabilityCatalogItem(db, workspaceId, capabilityId);
|
|
2276
2331
|
if (!item) {
|
|
2277
|
-
throw new
|
|
2332
|
+
throw new HTTPException6(404, { message: "capability not found" });
|
|
2278
2333
|
}
|
|
2279
2334
|
return item;
|
|
2280
2335
|
}
|
|
@@ -3002,13 +3057,13 @@ import {
|
|
|
3002
3057
|
requireFileForSubject,
|
|
3003
3058
|
withSessionRlsActorContext as withSessionRlsActorContext3
|
|
3004
3059
|
} from "@opengeni/db";
|
|
3005
|
-
import { HTTPException as
|
|
3060
|
+
import { HTTPException as HTTPException7 } from "hono/http-exception";
|
|
3006
3061
|
function validateToolRefs(tools, settings) {
|
|
3007
3062
|
const mcpServerIds = new Set(settings.mcpServers.map((server) => server.id));
|
|
3008
3063
|
const out = [];
|
|
3009
3064
|
for (const tool of tools) {
|
|
3010
3065
|
if (tool.kind !== "mcp") {
|
|
3011
|
-
throw new
|
|
3066
|
+
throw new HTTPException7(422, {
|
|
3012
3067
|
message: `unsupported tool kind: ${tool.kind}`
|
|
3013
3068
|
});
|
|
3014
3069
|
}
|
|
@@ -3017,7 +3072,7 @@ function validateToolRefs(tools, settings) {
|
|
|
3017
3072
|
if (optional) {
|
|
3018
3073
|
continue;
|
|
3019
3074
|
}
|
|
3020
|
-
throw new
|
|
3075
|
+
throw new HTTPException7(422, { message: `unknown MCP server id: ${tool.id}` });
|
|
3021
3076
|
}
|
|
3022
3077
|
out.push({
|
|
3023
3078
|
kind: "mcp",
|
|
@@ -3058,7 +3113,7 @@ function assertToolRefsSubset(requested, allowed, message = "requested tools exc
|
|
|
3058
3113
|
const allowedIds = new Set(allowed.map((tool) => `${tool.kind}:${tool.id}`));
|
|
3059
3114
|
const widened = requested.find((tool) => !allowedIds.has(`${tool.kind}:${tool.id}`));
|
|
3060
3115
|
if (widened) {
|
|
3061
|
-
throw new
|
|
3116
|
+
throw new HTTPException7(403, { message: `${message}: ${widened.id}` });
|
|
3062
3117
|
}
|
|
3063
3118
|
}
|
|
3064
3119
|
function validateToolRefsForSessionPolicy(input) {
|
|
@@ -3087,7 +3142,7 @@ function normalizeResources(resources) {
|
|
|
3087
3142
|
try {
|
|
3088
3143
|
normalizedUri = normalizeRepositoryTransportUri(resource.uri);
|
|
3089
3144
|
} catch (error) {
|
|
3090
|
-
throw new
|
|
3145
|
+
throw new HTTPException7(422, {
|
|
3091
3146
|
message: error instanceof Error ? error.message : "invalid repository URI"
|
|
3092
3147
|
});
|
|
3093
3148
|
}
|
|
@@ -3096,14 +3151,14 @@ function normalizeResources(resources) {
|
|
|
3096
3151
|
);
|
|
3097
3152
|
const credentialBindingId = gitCredentialBindingIdForRepository(resource, credentialProvider);
|
|
3098
3153
|
if ((resource.credentialBindingId || resource.connectionId || resource.access) && !credentialProvider) {
|
|
3099
|
-
throw new
|
|
3154
|
+
throw new HTTPException7(422, {
|
|
3100
3155
|
message: "repository credential bindings and access intent require a Git provider"
|
|
3101
3156
|
});
|
|
3102
3157
|
}
|
|
3103
3158
|
if (credentialProvider && credentialBindingId) {
|
|
3104
3159
|
const boundProvider = credentialBindingProviders.get(credentialBindingId);
|
|
3105
3160
|
if (boundProvider && boundProvider !== credentialProvider) {
|
|
3106
|
-
throw new
|
|
3161
|
+
throw new HTTPException7(422, {
|
|
3107
3162
|
message: `credential binding ${credentialBindingId} is assigned to multiple Git providers`
|
|
3108
3163
|
});
|
|
3109
3164
|
}
|
|
@@ -3132,7 +3187,7 @@ function normalizeResources(resources) {
|
|
|
3132
3187
|
const mountCollisionKey = normalized.mountPath ? resourceMountPathCollisionKey(normalized.mountPath) : void 0;
|
|
3133
3188
|
const mounted = mountCollisionKey ? mountPaths.get(mountCollisionKey) : void 0;
|
|
3134
3189
|
if (mounted && mounted !== key) {
|
|
3135
|
-
throw new
|
|
3190
|
+
throw new HTTPException7(422, {
|
|
3136
3191
|
message: `duplicate resource mount path: ${normalized.mountPath}`
|
|
3137
3192
|
});
|
|
3138
3193
|
}
|
|
@@ -3142,7 +3197,7 @@ function normalizeResources(resources) {
|
|
|
3142
3197
|
const identity = resourceIdentityKey(normalized);
|
|
3143
3198
|
const seenIdentity = identities.get(identity);
|
|
3144
3199
|
if (seenIdentity && seenIdentity !== key) {
|
|
3145
|
-
throw new
|
|
3200
|
+
throw new HTTPException7(422, {
|
|
3146
3201
|
message: `duplicate resource with different settings: ${identity}`
|
|
3147
3202
|
});
|
|
3148
3203
|
}
|
|
@@ -3159,7 +3214,7 @@ function mergeResourceRefs(existing, additions) {
|
|
|
3159
3214
|
return mergeContractResourceRefs(existing, additions, { rejectConflicts: true });
|
|
3160
3215
|
} catch (error) {
|
|
3161
3216
|
if (error instanceof ResourceRefConflictError) {
|
|
3162
|
-
throw new
|
|
3217
|
+
throw new HTTPException7(422, { message: error.message });
|
|
3163
3218
|
}
|
|
3164
3219
|
throw error;
|
|
3165
3220
|
}
|
|
@@ -3183,14 +3238,14 @@ function personalGitHubRepositoryResources(resources) {
|
|
|
3183
3238
|
for (const resource of resources) {
|
|
3184
3239
|
if (!isPersonalGitHubRepositoryCandidate(resource)) {
|
|
3185
3240
|
if (resource.kind === "repository" && resource.connectionType === "github_personal") {
|
|
3186
|
-
throw new
|
|
3241
|
+
throw new HTTPException7(422, {
|
|
3187
3242
|
message: "personal GitHub repository resources require the dedicated GitHub provider"
|
|
3188
3243
|
});
|
|
3189
3244
|
}
|
|
3190
3245
|
continue;
|
|
3191
3246
|
}
|
|
3192
3247
|
if (!PERSONAL_GITHUB_BINDING_ID_PATTERN.test(resource.credentialBindingId) || typeof resource.repositoryId !== "string" || !/^[1-9]\d*$/u.test(resource.repositoryId) || resource.access !== "read" && resource.access !== "write" || resource.installationId !== void 0 || resource.githubInstallationId !== void 0 || resource.githubRepositoryId !== void 0 || resource.connectionId !== void 0 || resource.projectId !== void 0) {
|
|
3193
|
-
throw new
|
|
3248
|
+
throw new HTTPException7(422, {
|
|
3194
3249
|
message: "personal GitHub repository resources require one opaque binding, provider repository id, and explicit read or write access"
|
|
3195
3250
|
});
|
|
3196
3251
|
}
|
|
@@ -3198,10 +3253,10 @@ function personalGitHubRepositoryResources(resources) {
|
|
|
3198
3253
|
try {
|
|
3199
3254
|
uri = new URL(resource.uri);
|
|
3200
3255
|
} catch {
|
|
3201
|
-
throw new
|
|
3256
|
+
throw new HTTPException7(422, { message: "personal GitHub repository URI is invalid" });
|
|
3202
3257
|
}
|
|
3203
3258
|
if (uri.protocol !== "https:" || uri.hostname !== "github.com" || uri.port !== "" || uri.username !== "" || uri.password !== "" || uri.search !== "" || uri.hash !== "" || uri.pathname.endsWith(".git") || uri.pathname.split("/").filter(Boolean).length !== 2 || resource.uri !== `${uri.origin}${uri.pathname.replace(/\/$/u, "")}`) {
|
|
3204
|
-
throw new
|
|
3259
|
+
throw new HTTPException7(422, {
|
|
3205
3260
|
message: "personal GitHub repository resources require a canonical GitHub HTTPS URI"
|
|
3206
3261
|
});
|
|
3207
3262
|
}
|
|
@@ -3211,7 +3266,7 @@ function personalGitHubRepositoryResources(resources) {
|
|
|
3211
3266
|
for (const resource of selected) {
|
|
3212
3267
|
const key = `${resource.credentialBindingId}\0${resource.repositoryId}`;
|
|
3213
3268
|
if (identities.has(key)) {
|
|
3214
|
-
throw new
|
|
3269
|
+
throw new HTTPException7(422, {
|
|
3215
3270
|
message: "personal GitHub repository resources must not contain duplicates"
|
|
3216
3271
|
});
|
|
3217
3272
|
}
|
|
@@ -3222,7 +3277,7 @@ function personalGitHubRepositoryResources(resources) {
|
|
|
3222
3277
|
function validateGitHubRepositorySelectionShape(resources) {
|
|
3223
3278
|
const installationIds = validateGitHubRepositorySelectionShapes(resources);
|
|
3224
3279
|
if (installationIds.length > 1) {
|
|
3225
|
-
throw new
|
|
3280
|
+
throw new HTTPException7(422, {
|
|
3226
3281
|
message: "GitHub App repository resources must belong to one installation"
|
|
3227
3282
|
});
|
|
3228
3283
|
}
|
|
@@ -3247,7 +3302,7 @@ function gitHubRepositorySelections(resources) {
|
|
|
3247
3302
|
const installationId = positiveInteger(installationRaw);
|
|
3248
3303
|
const repositoryId = positiveInteger(repositoryRaw);
|
|
3249
3304
|
if (!installationId || !repositoryId) {
|
|
3250
|
-
throw new
|
|
3305
|
+
throw new HTTPException7(422, {
|
|
3251
3306
|
message: "GitHub App repository resources require positive github_installation_id and github_repository_id"
|
|
3252
3307
|
});
|
|
3253
3308
|
}
|
|
@@ -3269,14 +3324,14 @@ async function validateGitHubRepositorySelection(db, workspaceId, resources) {
|
|
|
3269
3324
|
installationId,
|
|
3270
3325
|
repositoryIds
|
|
3271
3326
|
)) {
|
|
3272
|
-
throw new
|
|
3327
|
+
throw new HTTPException7(422, {
|
|
3273
3328
|
message: "GitHub App repository resources must be authorized for a GitHub App installation linked to this workspace"
|
|
3274
3329
|
});
|
|
3275
3330
|
}
|
|
3276
3331
|
}
|
|
3277
3332
|
}
|
|
3278
3333
|
function isAuthoritativeGitHubRepositorySelectionError(error) {
|
|
3279
|
-
return error instanceof
|
|
3334
|
+
return error instanceof HTTPException7 && error.status === 422;
|
|
3280
3335
|
}
|
|
3281
3336
|
async function validateFileResources(db, accountId, workspaceId, subjectId, resources, privateFileContext) {
|
|
3282
3337
|
return withSessionRlsActorContext3(
|
|
@@ -3291,7 +3346,7 @@ async function validateFileResources(db, accountId, workspaceId, subjectId, reso
|
|
|
3291
3346
|
continue;
|
|
3292
3347
|
}
|
|
3293
3348
|
if (fileIds.has(resource.fileId)) {
|
|
3294
|
-
throw new
|
|
3349
|
+
throw new HTTPException7(422, { message: `duplicate file resource: ${resource.fileId}` });
|
|
3295
3350
|
}
|
|
3296
3351
|
fileIds.add(resource.fileId);
|
|
3297
3352
|
const file = await requireFileForSubject(db, {
|
|
@@ -3301,10 +3356,10 @@ async function validateFileResources(db, accountId, workspaceId, subjectId, reso
|
|
|
3301
3356
|
fileId: resource.fileId
|
|
3302
3357
|
}).catch(() => null);
|
|
3303
3358
|
if (!file) {
|
|
3304
|
-
throw new
|
|
3359
|
+
throw new HTTPException7(422, { message: `unknown file resource: ${resource.fileId}` });
|
|
3305
3360
|
}
|
|
3306
3361
|
if (file.status !== "ready") {
|
|
3307
|
-
throw new
|
|
3362
|
+
throw new HTTPException7(422, {
|
|
3308
3363
|
message: `file resource ${resource.fileId} is ${file.status}`
|
|
3309
3364
|
});
|
|
3310
3365
|
}
|
|
@@ -3317,7 +3372,7 @@ function normalizeMountPath(path) {
|
|
|
3317
3372
|
return normalizeResourceMountPath(path);
|
|
3318
3373
|
} catch (error) {
|
|
3319
3374
|
if (!(error instanceof ResourceMountPathError)) throw error;
|
|
3320
|
-
throw new
|
|
3375
|
+
throw new HTTPException7(422, { message: `invalid resource mount path: ${path}` });
|
|
3321
3376
|
}
|
|
3322
3377
|
}
|
|
3323
3378
|
function positiveInteger(value) {
|
|
@@ -4035,17 +4090,18 @@ async function freezePersonalConnectionDelegations(input) {
|
|
|
4035
4090
|
}
|
|
4036
4091
|
|
|
4037
4092
|
// src/domain/sessions.ts
|
|
4093
|
+
import { isDirectModelId, ModelUnavailableError } from "@opengeni/contracts";
|
|
4038
4094
|
import { acceptSessionFileAttachments } from "@opengeni/db";
|
|
4039
4095
|
|
|
4040
4096
|
// src/domain/knowledge.ts
|
|
4041
|
-
import { HTTPException as
|
|
4097
|
+
import { HTTPException as HTTPException8 } from "hono/http-exception";
|
|
4042
4098
|
async function knowledgeContextForGateway(deps, grant) {
|
|
4043
4099
|
if (!hasPermission(grant.permissions, "documents:search"))
|
|
4044
|
-
throw new
|
|
4100
|
+
throw new HTTPException8(403, { message: "Missing permission: documents:search" });
|
|
4045
4101
|
if (grant.principalKind === "agent_attempt") {
|
|
4046
4102
|
const sessionId = grant.metadata?.sessionId;
|
|
4047
4103
|
if (typeof sessionId !== "string")
|
|
4048
|
-
throw new
|
|
4104
|
+
throw new HTTPException8(403, { message: "Knowledge attempt unavailable" });
|
|
4049
4105
|
const attempt = await requireLiveAgentAttemptAuthorization(deps.db, grant, sessionId);
|
|
4050
4106
|
return {
|
|
4051
4107
|
accountId: grant.accountId,
|
|
@@ -4075,12 +4131,12 @@ async function knowledgeContextForGateway(deps, grant) {
|
|
|
4075
4131
|
async function knowledgeContextForAccess(deps, access, permission) {
|
|
4076
4132
|
const { grant } = access;
|
|
4077
4133
|
if (!hasPermission(grant.permissions, permission)) {
|
|
4078
|
-
throw new
|
|
4134
|
+
throw new HTTPException8(403, { message: `Missing permission: ${permission}` });
|
|
4079
4135
|
}
|
|
4080
4136
|
if (grant.principalKind === "agent_attempt") {
|
|
4081
4137
|
const sessionId = grant.metadata?.sessionId;
|
|
4082
4138
|
if (typeof sessionId !== "string")
|
|
4083
|
-
throw new
|
|
4139
|
+
throw new HTTPException8(403, { message: "Knowledge attempt unavailable" });
|
|
4084
4140
|
const attempt = await requireLiveAgentAttemptAuthorization(deps.db, grant, sessionId);
|
|
4085
4141
|
return {
|
|
4086
4142
|
accountId: grant.accountId,
|
|
@@ -4115,7 +4171,7 @@ async function knowledgeContextForAccess(deps, access, permission) {
|
|
|
4115
4171
|
};
|
|
4116
4172
|
}
|
|
4117
4173
|
if (grant.principalKind !== "human_session" || !access.contextIntegrity || access.authenticatedSubjectId !== grant.subjectId || grant.serviceInitiator || grant.serviceInitiatorContext || grant.metadata?.attemptId !== void 0 || grant.metadata?.turnId !== void 0) {
|
|
4118
|
-
throw new
|
|
4174
|
+
throw new HTTPException8(403, {
|
|
4119
4175
|
message: "Knowledge management requires an authenticated human or agent attempt"
|
|
4120
4176
|
});
|
|
4121
4177
|
}
|
|
@@ -4308,14 +4364,14 @@ import { CODEX_MODEL_ID_PREFIX, isCodexBilledModel } from "@opengeni/codex";
|
|
|
4308
4364
|
import {
|
|
4309
4365
|
readTurnExecutionPolicyV1
|
|
4310
4366
|
} from "@opengeni/contracts";
|
|
4311
|
-
import { HTTPException as
|
|
4367
|
+
import { HTTPException as HTTPException9 } from "hono/http-exception";
|
|
4312
4368
|
function requireDeveloperSetupDelegatedPermissions(policy, permissions) {
|
|
4313
4369
|
if (policy.credentialRestriction !== "developer_setup") return;
|
|
4314
4370
|
const forbidden = permissions?.find(
|
|
4315
4371
|
(permission) => !isDeveloperSetupDelegatedPermissionAllowed(permission)
|
|
4316
4372
|
);
|
|
4317
4373
|
if (forbidden) {
|
|
4318
|
-
throw new
|
|
4374
|
+
throw new HTTPException9(403, {
|
|
4319
4375
|
message: `Developer setup cannot delegate first-party MCP permission: ${forbidden}`
|
|
4320
4376
|
});
|
|
4321
4377
|
}
|
|
@@ -4328,7 +4384,7 @@ function withDeveloperSetupCredentialRestriction(policy, source) {
|
|
|
4328
4384
|
try {
|
|
4329
4385
|
inherited = readTurnExecutionPolicyV1(metadata);
|
|
4330
4386
|
} catch {
|
|
4331
|
-
throw new
|
|
4387
|
+
throw new HTTPException9(409, { message: "The frozen session credential ceiling is invalid" });
|
|
4332
4388
|
}
|
|
4333
4389
|
if (inherited.kind === "valid" && inherited.policy.credentialRestriction === "developer_setup") {
|
|
4334
4390
|
restricted = true;
|
|
@@ -4444,14 +4500,14 @@ import {
|
|
|
4444
4500
|
publishDurableSessionEvents as publishDurableSessionEvents2,
|
|
4445
4501
|
publishDurableWorkspaceControlEvent
|
|
4446
4502
|
} from "@opengeni/events";
|
|
4447
|
-
import { HTTPException as
|
|
4503
|
+
import { HTTPException as HTTPException17 } from "hono/http-exception";
|
|
4448
4504
|
|
|
4449
4505
|
// src/domain/external-creation-attribution.ts
|
|
4450
|
-
import { HTTPException as
|
|
4506
|
+
import { HTTPException as HTTPException10 } from "hono/http-exception";
|
|
4451
4507
|
var EXTERNAL_CREATION_ATTRIBUTION_KEY = "opengeniExternalCreationAttribution";
|
|
4452
4508
|
function externalCreationMetadata(metadata, authorization, grant) {
|
|
4453
4509
|
if (metadata && Object.hasOwn(metadata, EXTERNAL_CREATION_ATTRIBUTION_KEY)) {
|
|
4454
|
-
throw new
|
|
4510
|
+
throw new HTTPException10(422, {
|
|
4455
4511
|
message: `${EXTERNAL_CREATION_ATTRIBUTION_KEY} is server-owned`
|
|
4456
4512
|
});
|
|
4457
4513
|
}
|
|
@@ -4476,12 +4532,12 @@ import {
|
|
|
4476
4532
|
resolveExternalIdentityLink as resolveExternalIdentityLink2,
|
|
4477
4533
|
getWorkspaceGrant as getWorkspaceGrant3,
|
|
4478
4534
|
lockActiveExternalOrganizationKey,
|
|
4479
|
-
lockExternalWorkspaceMembershipLifecycle,
|
|
4535
|
+
lockExternalWorkspaceMembershipLifecycle as lockExternalWorkspaceMembershipLifecycle2,
|
|
4480
4536
|
managedPersonalWorkspacePermissions as managedPersonalWorkspacePermissions2,
|
|
4481
4537
|
nestedPostgresSqlState as nestedPostgresSqlState2,
|
|
4482
4538
|
withWorkspaceSubjectRls as withWorkspaceSubjectRls2
|
|
4483
4539
|
} from "@opengeni/db";
|
|
4484
|
-
import { HTTPException as
|
|
4540
|
+
import { HTTPException as HTTPException11 } from "hono/http-exception";
|
|
4485
4541
|
function externalContinuationCommitAuthorizer(authorization) {
|
|
4486
4542
|
const continuation = authorization ? externalActorContinuationForAuthorization(authorization) : null;
|
|
4487
4543
|
if (!continuation || !authorization) return void 0;
|
|
@@ -4496,7 +4552,7 @@ function externalContinuationCommitAuthorizer(authorization) {
|
|
|
4496
4552
|
await requireExternalContinuationAuthority(tx, continuation, scope, permissions);
|
|
4497
4553
|
} catch (error) {
|
|
4498
4554
|
const denied2 = nestedPostgresSqlState2(error) === "42501" || error instanceof Error && error.message === "External continuation authority unavailable";
|
|
4499
|
-
throw new
|
|
4555
|
+
throw new HTTPException11(denied2 ? 403 : 503, {
|
|
4500
4556
|
message: denied2 ? "external authority changed" : "external authority is unavailable",
|
|
4501
4557
|
cause: error
|
|
4502
4558
|
});
|
|
@@ -4510,7 +4566,7 @@ async function requireExternalContinuationAuthority(tx, raw, scope, permission)
|
|
|
4510
4566
|
throw new Error("External continuation authority unavailable");
|
|
4511
4567
|
};
|
|
4512
4568
|
if (actor.accountId !== scope.accountId || actor.effectiveSubjectId !== scope.subjectId) deny();
|
|
4513
|
-
await
|
|
4569
|
+
await lockExternalWorkspaceMembershipLifecycle2(tx, scope.accountId);
|
|
4514
4570
|
const permissions = await lockActiveExternalOrganizationKey(
|
|
4515
4571
|
tx,
|
|
4516
4572
|
scope.accountId,
|
|
@@ -4608,7 +4664,7 @@ import {
|
|
|
4608
4664
|
recordUsageEvent,
|
|
4609
4665
|
sumUsageQuantity
|
|
4610
4666
|
} from "@opengeni/db";
|
|
4611
|
-
import { HTTPException as
|
|
4667
|
+
import { HTTPException as HTTPException12 } from "hono/http-exception";
|
|
4612
4668
|
function paidDocumentEmbedding(settings) {
|
|
4613
4669
|
return settings.documentEmbeddingBillingMode === "credits" && settings.documentEmbeddingProvider === "openai";
|
|
4614
4670
|
}
|
|
@@ -4637,7 +4693,7 @@ async function requireLimit(deps, input) {
|
|
|
4637
4693
|
if (decision.allowed) {
|
|
4638
4694
|
return;
|
|
4639
4695
|
}
|
|
4640
|
-
throw new
|
|
4696
|
+
throw new HTTPException12(
|
|
4641
4697
|
decision.code === "insufficient_credits" || decision.code === "allowance_exhausted" ? 402 : 429,
|
|
4642
4698
|
{
|
|
4643
4699
|
message: decision.message,
|
|
@@ -6140,6 +6196,7 @@ async function provisionSandbox(services, ctx, input) {
|
|
|
6140
6196
|
// src/model-catalog.ts
|
|
6141
6197
|
import { modelAllowedByConnections } from "@opengeni/db";
|
|
6142
6198
|
import {
|
|
6199
|
+
withDirectModelProviders,
|
|
6143
6200
|
applyModelCatalogDocument,
|
|
6144
6201
|
CLAUDE_CONNECTION_KINDS,
|
|
6145
6202
|
claudeProviderId,
|
|
@@ -6170,6 +6227,7 @@ import {
|
|
|
6170
6227
|
evaluateWorkspaceModelPolicy
|
|
6171
6228
|
} from "@opengeni/contracts";
|
|
6172
6229
|
import {
|
|
6230
|
+
listConnectionsMetadata as listConnectionsMetadata3,
|
|
6173
6231
|
getDeploymentModelCatalog,
|
|
6174
6232
|
listWorkspaceProviderCustomModels,
|
|
6175
6233
|
getWorkspaceProviderCustomModelForExecution,
|
|
@@ -6302,7 +6360,8 @@ async function resolveWorkspaceCatalogSettings(db, envSettings, input) {
|
|
|
6302
6360
|
organizationGatewayCustomModels,
|
|
6303
6361
|
organizationOpenRouterCustomModels,
|
|
6304
6362
|
retainedOrganizationGatewayCustomModels,
|
|
6305
|
-
retainedOrganizationOpenRouterCustomModels
|
|
6363
|
+
retainedOrganizationOpenRouterCustomModels,
|
|
6364
|
+
directConnections
|
|
6306
6365
|
] = await Promise.all([
|
|
6307
6366
|
resolveCatalogSettings(db, envSettings),
|
|
6308
6367
|
listWorkspaceGatewayCustomModels(db, {
|
|
@@ -6360,7 +6419,8 @@ async function resolveWorkspaceCatalogSettings(db, envSettings, input) {
|
|
|
6360
6419
|
upstreamModelId
|
|
6361
6420
|
})
|
|
6362
6421
|
)
|
|
6363
|
-
) : Promise.resolve([])
|
|
6422
|
+
) : Promise.resolve([]),
|
|
6423
|
+
supportsOrganizationProviderReads ? listConnectionsMetadata3(db, input.workspaceId, null) : Promise.resolve([])
|
|
6364
6424
|
]);
|
|
6365
6425
|
const includeRetainedModels = (activeModels, retainedModels) => {
|
|
6366
6426
|
const customModels = [...activeModels];
|
|
@@ -6434,22 +6494,25 @@ async function resolveWorkspaceCatalogSettings(db, envSettings, input) {
|
|
|
6434
6494
|
}
|
|
6435
6495
|
return {
|
|
6436
6496
|
...resolved,
|
|
6437
|
-
settings:
|
|
6497
|
+
settings: withDirectModelProviders(
|
|
6438
6498
|
withClaudeConnectionCatalog(
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6499
|
+
withClaudeConnectionCatalog(
|
|
6500
|
+
withOrganizationOpenRouterCatalogProvider(
|
|
6501
|
+
withOrganizationGatewayCatalogProvider(
|
|
6502
|
+
withWorkspaceOpenRouterCatalogProvider(
|
|
6503
|
+
withWorkspaceGatewayCatalogProvider(resolved.settings, gatewayCustomModels),
|
|
6504
|
+
openRouterCustomModels
|
|
6505
|
+
),
|
|
6506
|
+
organizationGatewayModels
|
|
6444
6507
|
),
|
|
6445
|
-
|
|
6508
|
+
organizationOpenRouterModels
|
|
6446
6509
|
),
|
|
6447
|
-
|
|
6510
|
+
claudeConnections
|
|
6448
6511
|
),
|
|
6449
|
-
|
|
6512
|
+
workspaceClaudeConnections,
|
|
6513
|
+
"workspace"
|
|
6450
6514
|
),
|
|
6451
|
-
|
|
6452
|
-
"workspace"
|
|
6515
|
+
directConnections
|
|
6453
6516
|
)
|
|
6454
6517
|
};
|
|
6455
6518
|
}
|
|
@@ -6525,7 +6588,7 @@ function credentialReadinessFor(input) {
|
|
|
6525
6588
|
const claudeKind = CLAUDE_CONNECTION_KINDS.find(
|
|
6526
6589
|
(kind) => claudeProviderId(kind, "workspace") === input.model.providerId
|
|
6527
6590
|
);
|
|
6528
|
-
const connectionActive = claudeKind ? input.workspaceClaudeConnections?.[claudeKind]?.active === true : input.model.providerId === WORKSPACE_OPENROUTER_PROVIDER_ID ? input.workspaceOpenRouterConnectionActive : input.workspaceGatewayConnectionActive;
|
|
6591
|
+
const connectionActive = input.provider?.kind === "direct-openai-workspace" || input.provider?.kind === "direct-azure-workspace" ? true : claudeKind ? input.workspaceClaudeConnections?.[claudeKind]?.active === true : input.model.providerId === WORKSPACE_OPENROUTER_PROVIDER_ID ? input.workspaceOpenRouterConnectionActive : input.workspaceGatewayConnectionActive;
|
|
6529
6592
|
return connectionActive ? { status: "ready", reason: null, basis: "connection", checkedAt: null } : {
|
|
6530
6593
|
status: "not_ready",
|
|
6531
6594
|
reason: "needs_reauth",
|
|
@@ -6868,8 +6931,19 @@ function findSelection(selections, modelId) {
|
|
|
6868
6931
|
function creditsCandidate(input) {
|
|
6869
6932
|
const fallbackEffort = input.settings.openaiReasoningEffort;
|
|
6870
6933
|
const deployment = findSelection(input.selections, input.settings.openaiModel);
|
|
6871
|
-
if (deployment?.availability.selectable && deployment.model.cost === "credits") return null;
|
|
6872
6934
|
const configured = findSelection(input.selections, input.settings.creditsDefaultModel);
|
|
6935
|
+
if (deployment?.availability.selectable && deployment.model.cost === "credits") {
|
|
6936
|
+
if (configured?.model.id !== deployment.model.id) return null;
|
|
6937
|
+
return {
|
|
6938
|
+
model: deployment.model.id,
|
|
6939
|
+
reasoningEffort: clampReasoningEffortForConfiguredModel(
|
|
6940
|
+
deployment.model,
|
|
6941
|
+
input.settings.creditsDefaultReasoningEffort,
|
|
6942
|
+
fallbackEffort
|
|
6943
|
+
),
|
|
6944
|
+
source: "credits"
|
|
6945
|
+
};
|
|
6946
|
+
}
|
|
6873
6947
|
if (configured?.availability.selectable && configured.model.cost === "credits") {
|
|
6874
6948
|
return {
|
|
6875
6949
|
model: configured.model.id,
|
|
@@ -7138,13 +7212,13 @@ import {
|
|
|
7138
7212
|
resolveWorkspaceAgentDefaults,
|
|
7139
7213
|
resolveWorkspaceAgentHumanInputEnabled
|
|
7140
7214
|
} from "@opengeni/contracts";
|
|
7141
|
-
import { HTTPException as
|
|
7215
|
+
import { HTTPException as HTTPException13 } from "hono/http-exception";
|
|
7142
7216
|
function withAgentConfigHttpErrors(fn) {
|
|
7143
7217
|
try {
|
|
7144
7218
|
return fn();
|
|
7145
7219
|
} catch (error) {
|
|
7146
7220
|
if (error instanceof AgentConfigError) {
|
|
7147
|
-
throw new
|
|
7221
|
+
throw new HTTPException13(422, { message: error.message, cause: error });
|
|
7148
7222
|
}
|
|
7149
7223
|
throw error;
|
|
7150
7224
|
}
|
|
@@ -7209,7 +7283,7 @@ function applySessionAgentConfigWriteThrough(input) {
|
|
|
7209
7283
|
}
|
|
7210
7284
|
function requireAgentConfigAdmission(settings, agent) {
|
|
7211
7285
|
if (agent !== void 0 && settings.agentConfigAdmissionEnabled !== true) {
|
|
7212
|
-
throw new
|
|
7286
|
+
throw new HTTPException13(422, {
|
|
7213
7287
|
message: "agent configuration is not enabled on this deployment",
|
|
7214
7288
|
cause: new AgentConfigError(
|
|
7215
7289
|
"agent_config_not_enabled",
|
|
@@ -7368,6 +7442,21 @@ function resolveTurnToolPolicy(input) {
|
|
|
7368
7442
|
defaultMcpServerIds: scheduledIds ?? input.defaultMcpServerIds ?? []
|
|
7369
7443
|
});
|
|
7370
7444
|
}
|
|
7445
|
+
async function workspaceSessionToolPolicyContext(db, workspaceId, settings, subjectId) {
|
|
7446
|
+
const [runtimeSettings, workspace] = await Promise.all([
|
|
7447
|
+
settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings, {
|
|
7448
|
+
...subjectId ? { subjectId } : {}
|
|
7449
|
+
}),
|
|
7450
|
+
requireWorkspace2(db, workspaceId)
|
|
7451
|
+
]);
|
|
7452
|
+
return {
|
|
7453
|
+
workspaceServerIds: sortedIds(runtimeSettings.mcpServers.map((server) => server.id)),
|
|
7454
|
+
workspaceDefaultServerIds: workspaceSessionToolPolicyDefaultServerIdsFor(
|
|
7455
|
+
runtimeSettings.mcpServers,
|
|
7456
|
+
workspace.settings
|
|
7457
|
+
)
|
|
7458
|
+
};
|
|
7459
|
+
}
|
|
7371
7460
|
async function workspaceSessionToolPolicyServerIds(db, workspaceId, settings, subjectId) {
|
|
7372
7461
|
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings, {
|
|
7373
7462
|
...subjectId ? { subjectId } : {}
|
|
@@ -7529,11 +7618,12 @@ function sessionEffectiveToolProjectionInput(session, toolRefs, context) {
|
|
|
7529
7618
|
"browser_screenshot",
|
|
7530
7619
|
"browser_clipboard",
|
|
7531
7620
|
"browser_debug",
|
|
7621
|
+
"browser_downloads",
|
|
7532
7622
|
"computer_targets",
|
|
7533
7623
|
"computer_observe",
|
|
7534
7624
|
"computer_clipboard"
|
|
7535
7625
|
].includes(name);
|
|
7536
|
-
if (permissionAllowed(readOnly ? "sessions:read" : "sessions:control")) {
|
|
7626
|
+
if (permissionAllowed(readOnly ? "sessions:read" : "sessions:control") && (name !== "browser_download_save" || permissionAllowed("files:upload"))) {
|
|
7537
7627
|
firstPartyModelNames.set(name, `interaction__${name}`);
|
|
7538
7628
|
if (!firstPartyMcpTools.includes(name) && resolveAgentToolFamilies(session.agent).allowsFirstPartyTool(name) && session.firstPartyMcpTools.includes(name))
|
|
7539
7629
|
firstPartyMcpTools.push(name);
|
|
@@ -7629,11 +7719,11 @@ import {
|
|
|
7629
7719
|
numberTimelineAnnotations
|
|
7630
7720
|
} from "@opengeni/contracts";
|
|
7631
7721
|
import { getSessionEvent } from "@opengeni/db";
|
|
7632
|
-
import { HTTPException as
|
|
7722
|
+
import { HTTPException as HTTPException14 } from "hono/http-exception";
|
|
7633
7723
|
var SOURCE_CONTEXT_BYTES = 160;
|
|
7634
7724
|
var ANSI_SEQUENCE = new RegExp("\\u001B\\[[0-?]*[ -/]*[@-~]", "g");
|
|
7635
7725
|
function invalidAnnotationSource() {
|
|
7636
|
-
throw new
|
|
7726
|
+
throw new HTTPException14(422, { message: "Invalid timeline annotation source" });
|
|
7637
7727
|
}
|
|
7638
7728
|
function safeJson(value) {
|
|
7639
7729
|
try {
|
|
@@ -7778,12 +7868,12 @@ import {
|
|
|
7778
7868
|
variableSetVariableNameReservation
|
|
7779
7869
|
} from "@opengeni/contracts";
|
|
7780
7870
|
import { getVariableSet, recordAuditEvent } from "@opengeni/db";
|
|
7781
|
-
import { HTTPException as
|
|
7871
|
+
import { HTTPException as HTTPException15 } from "hono/http-exception";
|
|
7782
7872
|
var MAX_ENVIRONMENTS_PER_WORKSPACE = 25;
|
|
7783
7873
|
var MAX_VARIABLES_PER_ENVIRONMENT = 100;
|
|
7784
7874
|
function assertAllowedVariableSetVariableName(name) {
|
|
7785
7875
|
if (variableSetVariableNameReservation(name)) {
|
|
7786
|
-
throw new
|
|
7876
|
+
throw new HTTPException15(422, {
|
|
7787
7877
|
message: `reserved variable set variable name / reserved environment variable name: ${name}`
|
|
7788
7878
|
});
|
|
7789
7879
|
}
|
|
@@ -7792,7 +7882,7 @@ var assertAllowedEnvironmentVariableName = assertAllowedVariableSetVariableName;
|
|
|
7792
7882
|
function requireVariableSetEncryption(settings) {
|
|
7793
7883
|
const key = environmentsEncryptionKeyBytes2(settings);
|
|
7794
7884
|
if (!key) {
|
|
7795
|
-
throw new
|
|
7885
|
+
throw new HTTPException15(503, {
|
|
7796
7886
|
message: "variable sets require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"
|
|
7797
7887
|
});
|
|
7798
7888
|
}
|
|
@@ -7810,7 +7900,7 @@ async function requireVariableSetForApi(db, grant, variableSetId) {
|
|
|
7810
7900
|
variableSetId
|
|
7811
7901
|
);
|
|
7812
7902
|
if (!variableSet) {
|
|
7813
|
-
throw new
|
|
7903
|
+
throw new HTTPException15(404, { message: "variableSet not found" });
|
|
7814
7904
|
}
|
|
7815
7905
|
return variableSet;
|
|
7816
7906
|
}
|
|
@@ -7824,7 +7914,7 @@ async function validateVariableSetAttachment(deps, grant, workspaceId, variableS
|
|
|
7824
7914
|
variableSetId
|
|
7825
7915
|
);
|
|
7826
7916
|
if (!variableSet) {
|
|
7827
|
-
throw new
|
|
7917
|
+
throw new HTTPException15(422, { message: "unknown variableSetId" });
|
|
7828
7918
|
}
|
|
7829
7919
|
return variableSet;
|
|
7830
7920
|
}
|
|
@@ -7855,7 +7945,7 @@ import {
|
|
|
7855
7945
|
import {
|
|
7856
7946
|
getConnectionMetadata as getConnectionMetadata3
|
|
7857
7947
|
} from "@opengeni/db";
|
|
7858
|
-
import { HTTPException as
|
|
7948
|
+
import { HTTPException as HTTPException16 } from "hono/http-exception";
|
|
7859
7949
|
function openGeniSlackBotMetadata(metadata) {
|
|
7860
7950
|
const parsed = OpenGeniSlackBotConnectionMetadata.safeParse(metadata);
|
|
7861
7951
|
return parsed.success ? parsed.data : null;
|
|
@@ -7874,12 +7964,12 @@ function hasReservedOpenGeniSlackBotSessionMetadata(metadata) {
|
|
|
7874
7964
|
async function requireOpenGeniSlackBotConnection(db, workspaceId, connectionId) {
|
|
7875
7965
|
const connection = await getConnectionMetadata3(db, workspaceId, connectionId, null);
|
|
7876
7966
|
if (!connection || !isOpenGeniSlackBotConnection(connection)) {
|
|
7877
|
-
throw new
|
|
7967
|
+
throw new HTTPException16(422, {
|
|
7878
7968
|
message: "slackBotConnectionId must reference an OpenGeni Slack bot connection"
|
|
7879
7969
|
});
|
|
7880
7970
|
}
|
|
7881
7971
|
if (connection.status !== "active") {
|
|
7882
|
-
throw new
|
|
7972
|
+
throw new HTTPException16(422, {
|
|
7883
7973
|
message: `OpenGeni Slack bot connection is not active (${connection.status})`
|
|
7884
7974
|
});
|
|
7885
7975
|
}
|
|
@@ -7911,12 +8001,12 @@ async function validateScheduledTaskSlackChannel(input) {
|
|
|
7911
8001
|
const nextChannel = input.next.slackBotChannelId ?? null;
|
|
7912
8002
|
if (!nextChannel) return;
|
|
7913
8003
|
if (!input.next.slackBotConnectionId) {
|
|
7914
|
-
throw new
|
|
8004
|
+
throw new HTTPException16(422, {
|
|
7915
8005
|
message: "slackBotChannelId requires slackBotConnectionId"
|
|
7916
8006
|
});
|
|
7917
8007
|
}
|
|
7918
8008
|
if (input.runMode === "existing_session") {
|
|
7919
|
-
throw new
|
|
8009
|
+
throw new HTTPException16(422, {
|
|
7920
8010
|
message: "Slack channel posting needs a task that starts its own chats"
|
|
7921
8011
|
});
|
|
7922
8012
|
}
|
|
@@ -7924,18 +8014,18 @@ async function validateScheduledTaskSlackChannel(input) {
|
|
|
7924
8014
|
return;
|
|
7925
8015
|
}
|
|
7926
8016
|
if (!isAuthenticatedPersonAuthorization(input.authorization)) {
|
|
7927
|
-
throw new
|
|
8017
|
+
throw new HTTPException16(403, {
|
|
7928
8018
|
message: "Only a person can choose the Slack channel a scheduled task posts to"
|
|
7929
8019
|
});
|
|
7930
8020
|
}
|
|
7931
8021
|
requirePermission(input.grant, "connections:write");
|
|
7932
8022
|
if (input.reusableSessionCanPost && !await input.reusableSessionCanPost()) {
|
|
7933
|
-
throw new
|
|
8023
|
+
throw new HTTPException16(409, {
|
|
7934
8024
|
message: "this task continues a chat that was created without Slack posting; recreate the task to post from it"
|
|
7935
8025
|
});
|
|
7936
8026
|
}
|
|
7937
8027
|
if (!input.verifySlackChannel) {
|
|
7938
|
-
throw new
|
|
8028
|
+
throw new HTTPException16(422, {
|
|
7939
8029
|
message: "Choose the Slack channel in the schedule editor"
|
|
7940
8030
|
});
|
|
7941
8031
|
}
|
|
@@ -8284,14 +8374,14 @@ function withoutExcludedMcpServers(tools, excludedIds = []) {
|
|
|
8284
8374
|
function defaultPolicyExclusions(ids = []) {
|
|
8285
8375
|
const sorted = [...new Set(ids)].filter((id) => id !== "opengeni").sort();
|
|
8286
8376
|
if (sorted.length > 64 || sorted.some((id) => id.length > 200 || !/^[A-Za-z0-9_-]+$/.test(id))) {
|
|
8287
|
-
throw new
|
|
8377
|
+
throw new HTTPException17(422, {
|
|
8288
8378
|
message: "connector exclusions must contain at most 64 valid MCP server IDs"
|
|
8289
8379
|
});
|
|
8290
8380
|
}
|
|
8291
8381
|
return sorted.length ? { excludedMcpServerIds: sorted } : {};
|
|
8292
8382
|
}
|
|
8293
8383
|
function isCatalogOverlayModel(modelId) {
|
|
8294
|
-
return modelId?.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX2) === true || modelId?.startsWith(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX2) === true || modelId?.startsWith(ORGANIZATION_GATEWAY_MODEL_ID_PREFIX2) === true || modelId?.startsWith(ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX2) === true || CLAUDE_CONNECTION_KINDS3.some(
|
|
8384
|
+
return modelId != null && isDirectModelId(modelId) || modelId?.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX2) === true || modelId?.startsWith(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX2) === true || modelId?.startsWith(ORGANIZATION_GATEWAY_MODEL_ID_PREFIX2) === true || modelId?.startsWith(ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX2) === true || CLAUDE_CONNECTION_KINDS3.some(
|
|
8295
8385
|
(kind) => ["workspace", "organization"].some(
|
|
8296
8386
|
(scope) => modelId?.startsWith(claudeProviderId2(kind, scope) + "/")
|
|
8297
8387
|
)
|
|
@@ -8301,25 +8391,25 @@ var sessionMcpCredentialHeaderName = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
|
8301
8391
|
async function requireAtomicPersonalResourceAttachment(deps, authorization, workspaceId, intent, existingSession) {
|
|
8302
8392
|
if (!intent) return;
|
|
8303
8393
|
if (!authorization) {
|
|
8304
|
-
throw new
|
|
8394
|
+
throw new HTTPException17(403, {
|
|
8305
8395
|
message: "Personal resources require the owning managed-human session."
|
|
8306
8396
|
});
|
|
8307
8397
|
}
|
|
8308
8398
|
try {
|
|
8309
8399
|
requireVerifiedOwningUser(authorization, workspaceId);
|
|
8310
8400
|
} catch (error) {
|
|
8311
|
-
throw new
|
|
8401
|
+
throw new HTTPException17(403, {
|
|
8312
8402
|
message: "Personal resources require the owning managed-human session.",
|
|
8313
8403
|
cause: error
|
|
8314
8404
|
});
|
|
8315
8405
|
}
|
|
8316
8406
|
if (!await sessionTenancyProductActivated2(deps.db, workspaceId)) {
|
|
8317
|
-
throw new
|
|
8407
|
+
throw new HTTPException17(409, {
|
|
8318
8408
|
message: "Session tenancy is not activated for this organization."
|
|
8319
8409
|
});
|
|
8320
8410
|
}
|
|
8321
8411
|
if (existingSession && intent.expectedAuthorityEpoch === void 0) {
|
|
8322
|
-
throw new
|
|
8412
|
+
throw new HTTPException17(422, {
|
|
8323
8413
|
message: "Personal-resource attachment requires expectedAuthorityEpoch."
|
|
8324
8414
|
});
|
|
8325
8415
|
}
|
|
@@ -8350,7 +8440,7 @@ function resolveFirstPartyMcpToolsForCreate(requested, parentStored, policy = {
|
|
|
8350
8440
|
const parentCeiling = effectiveFirstPartyMcpToolCeiling(parentStored, policy);
|
|
8351
8441
|
const widened = requested.find((tool) => !parentCeiling.has(tool));
|
|
8352
8442
|
if (widened) {
|
|
8353
|
-
throw new
|
|
8443
|
+
throw new HTTPException17(403, {
|
|
8354
8444
|
message: `child first-party MCP tools may only narrow the parent session selection: ${widened}`
|
|
8355
8445
|
});
|
|
8356
8446
|
}
|
|
@@ -8383,7 +8473,7 @@ function initiatingHumanForAllowance(frozen) {
|
|
|
8383
8473
|
function serviceInitiatorForGrant(grant) {
|
|
8384
8474
|
if (!grant.serviceInitiator) {
|
|
8385
8475
|
if (grant.serviceInitiatorContext) {
|
|
8386
|
-
throw new
|
|
8476
|
+
throw new HTTPException17(403, {
|
|
8387
8477
|
message: "service initiator context requires a signed service initiator"
|
|
8388
8478
|
});
|
|
8389
8479
|
}
|
|
@@ -8391,13 +8481,13 @@ function serviceInitiatorForGrant(grant) {
|
|
|
8391
8481
|
}
|
|
8392
8482
|
const initiator = ServiceTurnInitiator.safeParse(grant.serviceInitiator);
|
|
8393
8483
|
if (!initiator.success) {
|
|
8394
|
-
throw new
|
|
8484
|
+
throw new HTTPException17(403, {
|
|
8395
8485
|
message: "a delegated command initiator must be a bounded service principal"
|
|
8396
8486
|
});
|
|
8397
8487
|
}
|
|
8398
8488
|
const context = ServiceTurnInitiatorContext2.safeParse(grant.serviceInitiatorContext ?? {});
|
|
8399
8489
|
if (!context.success) {
|
|
8400
|
-
throw new
|
|
8490
|
+
throw new HTTPException17(403, {
|
|
8401
8491
|
message: "delegated service initiator context is invalid or reserved"
|
|
8402
8492
|
});
|
|
8403
8493
|
}
|
|
@@ -8405,7 +8495,7 @@ function serviceInitiatorForGrant(grant) {
|
|
|
8405
8495
|
const callerAttemptId = grant.metadata?.["attemptId"];
|
|
8406
8496
|
const callerExecutionGeneration = grant.metadata?.["executionGeneration"];
|
|
8407
8497
|
if (callerTurnId !== void 0 || callerAttemptId !== void 0 || callerExecutionGeneration !== void 0) {
|
|
8408
|
-
throw new
|
|
8498
|
+
throw new HTTPException17(403, {
|
|
8409
8499
|
message: "a service initiator cannot replace an exact agent-attempt initiator"
|
|
8410
8500
|
});
|
|
8411
8501
|
}
|
|
@@ -8423,7 +8513,7 @@ function creationInitiatorForGrant(grant) {
|
|
|
8423
8513
|
const hasCallerTurnClaim = callerTurnId !== void 0 || callerAttemptId !== void 0 || callerExecutionGeneration !== void 0;
|
|
8424
8514
|
if (hasCallerTurnClaim) {
|
|
8425
8515
|
if (typeof callerSessionId !== "string" || typeof callerTurnId !== "string" || typeof callerAttemptId !== "string" || typeof callerExecutionGeneration !== "number" || !Number.isSafeInteger(callerExecutionGeneration) || callerExecutionGeneration < 1) {
|
|
8426
|
-
throw new
|
|
8516
|
+
throw new HTTPException17(403, {
|
|
8427
8517
|
message: "caller attempt claims are incomplete"
|
|
8428
8518
|
});
|
|
8429
8519
|
}
|
|
@@ -8464,31 +8554,31 @@ function normalizedSessionMcpCredentialHeaders(headers) {
|
|
|
8464
8554
|
}
|
|
8465
8555
|
const entries = Object.entries(headers).map(([name, value]) => [name.trim(), value]).filter(([name]) => name.length > 0);
|
|
8466
8556
|
if (entries.length > maxSessionMcpCredentialHeaders) {
|
|
8467
|
-
throw new
|
|
8557
|
+
throw new HTTPException17(422, {
|
|
8468
8558
|
message: `a session MCP server supports at most ${maxSessionMcpCredentialHeaders} credential headers`
|
|
8469
8559
|
});
|
|
8470
8560
|
}
|
|
8471
8561
|
const seen = /* @__PURE__ */ new Set();
|
|
8472
8562
|
for (const [name, value] of entries) {
|
|
8473
8563
|
if (!sessionMcpCredentialHeaderName.test(name)) {
|
|
8474
|
-
throw new
|
|
8564
|
+
throw new HTTPException17(422, {
|
|
8475
8565
|
message: `invalid credential header name: ${name}`
|
|
8476
8566
|
});
|
|
8477
8567
|
}
|
|
8478
8568
|
const lower = name.toLowerCase();
|
|
8479
8569
|
if (seen.has(lower)) {
|
|
8480
|
-
throw new
|
|
8570
|
+
throw new HTTPException17(422, {
|
|
8481
8571
|
message: `duplicate credential header name: ${name}`
|
|
8482
8572
|
});
|
|
8483
8573
|
}
|
|
8484
8574
|
seen.add(lower);
|
|
8485
8575
|
if (value.length === 0 || value.length > maxSessionMcpCredentialHeaderValueLength) {
|
|
8486
|
-
throw new
|
|
8576
|
+
throw new HTTPException17(422, {
|
|
8487
8577
|
message: `credential header ${name} must be 1-${maxSessionMcpCredentialHeaderValueLength} characters`
|
|
8488
8578
|
});
|
|
8489
8579
|
}
|
|
8490
8580
|
if (/[\u0000-\u0008\u000A-\u001F\u007F]/.test(value)) {
|
|
8491
|
-
throw new
|
|
8581
|
+
throw new HTTPException17(422, {
|
|
8492
8582
|
message: `credential header ${name} contains forbidden control characters`
|
|
8493
8583
|
});
|
|
8494
8584
|
}
|
|
@@ -8556,13 +8646,13 @@ function validateSessionMcpServersForCreate(settings, grant, servers) {
|
|
|
8556
8646
|
for (const server of servers) {
|
|
8557
8647
|
assertNativeMcpConnectionRef(server.connectionRef);
|
|
8558
8648
|
if (seenIds.has(server.id)) {
|
|
8559
|
-
throw new
|
|
8649
|
+
throw new HTTPException17(422, {
|
|
8560
8650
|
message: `duplicate session MCP server id: ${server.id}`
|
|
8561
8651
|
});
|
|
8562
8652
|
}
|
|
8563
8653
|
seenIds.add(server.id);
|
|
8564
8654
|
if (reservedSessionMcpServerIds.has(server.id) || existingIds.has(server.id)) {
|
|
8565
|
-
throw new
|
|
8655
|
+
throw new HTTPException17(422, {
|
|
8566
8656
|
message: `MCP server id already exists: ${server.id}`
|
|
8567
8657
|
});
|
|
8568
8658
|
}
|
|
@@ -8604,13 +8694,13 @@ function validateInheritedSessionMcpServersForCreate(servers) {
|
|
|
8604
8694
|
const seenIds = /* @__PURE__ */ new Set();
|
|
8605
8695
|
for (const server of servers) {
|
|
8606
8696
|
if (seenIds.has(server.id)) {
|
|
8607
|
-
throw new
|
|
8697
|
+
throw new HTTPException17(422, {
|
|
8608
8698
|
message: `duplicate inherited session MCP server id: ${server.id}`
|
|
8609
8699
|
});
|
|
8610
8700
|
}
|
|
8611
8701
|
seenIds.add(server.id);
|
|
8612
8702
|
if (reservedSessionMcpServerIds.has(server.id)) {
|
|
8613
|
-
throw new
|
|
8703
|
+
throw new HTTPException17(422, {
|
|
8614
8704
|
message: `reserved inherited session MCP server id: ${server.id}`
|
|
8615
8705
|
});
|
|
8616
8706
|
}
|
|
@@ -8642,13 +8732,13 @@ function validateSessionMcpCredentialUpdates(input) {
|
|
|
8642
8732
|
const seenIds = /* @__PURE__ */ new Set();
|
|
8643
8733
|
const encryptedUpdates = input.updates.map((update) => {
|
|
8644
8734
|
if (seenIds.has(update.id)) {
|
|
8645
|
-
throw new
|
|
8735
|
+
throw new HTTPException17(422, {
|
|
8646
8736
|
message: `duplicate session MCP credential update id: ${update.id}`
|
|
8647
8737
|
});
|
|
8648
8738
|
}
|
|
8649
8739
|
seenIds.add(update.id);
|
|
8650
8740
|
if (!knownIds.has(update.id)) {
|
|
8651
|
-
throw new
|
|
8741
|
+
throw new HTTPException17(422, {
|
|
8652
8742
|
message: `unknown session MCP server id: ${update.id}`
|
|
8653
8743
|
});
|
|
8654
8744
|
}
|
|
@@ -8715,7 +8805,7 @@ async function createAndStartSessionWithOutcome(input) {
|
|
|
8715
8805
|
let targetSeededBeforeCreateCommit = false;
|
|
8716
8806
|
const beforeCreateCommit = requiresActiveWorkspaceCustomModel || input.consumeNewSessionDraft || input.beforeCreateCommit || preflightTarget || targetPreflightFailureMessage ? async (tx, sessionId, context) => {
|
|
8717
8807
|
if (targetPreflightFailureMessage && context?.created !== false) {
|
|
8718
|
-
throw new
|
|
8808
|
+
throw new HTTPException17(422, {
|
|
8719
8809
|
message: targetPreflightFailureMessage
|
|
8720
8810
|
});
|
|
8721
8811
|
}
|
|
@@ -8733,9 +8823,7 @@ async function createAndStartSessionWithOutcome(input) {
|
|
|
8733
8823
|
reference
|
|
8734
8824
|
});
|
|
8735
8825
|
if (!active) {
|
|
8736
|
-
throw
|
|
8737
|
-
message: `model is not available: ${input.model}`
|
|
8738
|
-
});
|
|
8826
|
+
throw modelUnavailableHttpException(input.model);
|
|
8739
8827
|
}
|
|
8740
8828
|
}
|
|
8741
8829
|
if (input.consumeNewSessionDraft && context?.created !== false) {
|
|
@@ -8758,7 +8846,7 @@ async function createAndStartSessionWithOutcome(input) {
|
|
|
8758
8846
|
workingDir: preflightTarget.workingDir
|
|
8759
8847
|
});
|
|
8760
8848
|
if (!seeded.swapped) {
|
|
8761
|
-
throw new
|
|
8849
|
+
throw new HTTPException17(422, {
|
|
8762
8850
|
message: `cannot target sandbox ${seedTargetForNewSession.sandboxId}: target authority changed during session creation`
|
|
8763
8851
|
});
|
|
8764
8852
|
}
|
|
@@ -8962,7 +9050,7 @@ async function finishStartSession(input, session) {
|
|
|
8962
9050
|
input.seedTargetSandbox.workingDir ?? null
|
|
8963
9051
|
);
|
|
8964
9052
|
if (!seeded.swapped) {
|
|
8965
|
-
throw new
|
|
9053
|
+
throw new HTTPException17(422, {
|
|
8966
9054
|
message: `cannot target sandbox ${input.seedTargetSandbox.sandboxId}: ${seeded.reason ?? "target is not attachable"}`
|
|
8967
9055
|
});
|
|
8968
9056
|
}
|
|
@@ -9025,6 +9113,10 @@ async function finishStartSession(input, session) {
|
|
|
9025
9113
|
function workflowIdForSession(sessionId) {
|
|
9026
9114
|
return `session-${sessionId}`;
|
|
9027
9115
|
}
|
|
9116
|
+
function modelUnavailableHttpException(model) {
|
|
9117
|
+
const cause = new ModelUnavailableError(model);
|
|
9118
|
+
return new HTTPException17(422, { message: cause.message, cause });
|
|
9119
|
+
}
|
|
9028
9120
|
function canonicalConfiguredModel(settings, model) {
|
|
9029
9121
|
if (model === null || model === void 0) {
|
|
9030
9122
|
return model;
|
|
@@ -9034,7 +9126,7 @@ function canonicalConfiguredModel(settings, model) {
|
|
|
9034
9126
|
if (settings.codexSubscriptionEnabled && configuredAllowedModels(withCodexCatalogProvider3(settings)).includes(canonicalModel)) {
|
|
9035
9127
|
return canonicalModel;
|
|
9036
9128
|
}
|
|
9037
|
-
throw
|
|
9129
|
+
throw modelUnavailableHttpException(model);
|
|
9038
9130
|
}
|
|
9039
9131
|
if (configuredAllowedModels(settings).includes(canonicalModel)) {
|
|
9040
9132
|
return canonicalModel;
|
|
@@ -9045,7 +9137,7 @@ function canonicalConfiguredModel(settings, model) {
|
|
|
9045
9137
|
if (settings.supergrokSubscriptionEnabled && canonicalModel.startsWith(XAI_SUBSCRIPTION_MODEL_ID_PREFIX)) {
|
|
9046
9138
|
return canonicalModel;
|
|
9047
9139
|
}
|
|
9048
|
-
throw
|
|
9140
|
+
throw modelUnavailableHttpException(model);
|
|
9049
9141
|
}
|
|
9050
9142
|
function assertConfiguredModel(settings, model) {
|
|
9051
9143
|
canonicalConfiguredModel(settings, model);
|
|
@@ -9086,7 +9178,7 @@ async function assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model
|
|
|
9086
9178
|
modelId: canonicalModel
|
|
9087
9179
|
});
|
|
9088
9180
|
if (!verdict.allowed) {
|
|
9089
|
-
throw new
|
|
9181
|
+
throw new HTTPException17(422, {
|
|
9090
9182
|
message: verdict.reason === "provider" ? `model "${canonicalModel}" is not allowed by this workspace's model policy: provider "${providerId}" is not in the allowed providers` : `model "${canonicalModel}" is not allowed by this workspace's model policy`
|
|
9091
9183
|
});
|
|
9092
9184
|
}
|
|
@@ -9094,10 +9186,10 @@ async function assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model
|
|
|
9094
9186
|
async function requireQueuedTurnForApi(db, workspaceId, sessionId, turnId) {
|
|
9095
9187
|
const turn = await getSessionTurn(db, workspaceId, turnId);
|
|
9096
9188
|
if (!turn || turn.sessionId !== sessionId) {
|
|
9097
|
-
throw new
|
|
9189
|
+
throw new HTTPException17(404, { message: "session turn not found" });
|
|
9098
9190
|
}
|
|
9099
9191
|
if (turn.status !== "queued") {
|
|
9100
|
-
throw new
|
|
9192
|
+
throw new HTTPException17(409, {
|
|
9101
9193
|
message: `turn is ${turn.status}; only queued turns can be changed`
|
|
9102
9194
|
});
|
|
9103
9195
|
}
|
|
@@ -9208,7 +9300,7 @@ async function postUserMessageTurn(input) {
|
|
|
9208
9300
|
assertSessionAllowsProductModel(sessionForModelGate, effectiveModelForGate);
|
|
9209
9301
|
} catch (error) {
|
|
9210
9302
|
if (error instanceof CodexCompactionV2ProviderLockedError) {
|
|
9211
|
-
throw new
|
|
9303
|
+
throw new HTTPException17(422, { message: error.message, cause: error });
|
|
9212
9304
|
}
|
|
9213
9305
|
throw error;
|
|
9214
9306
|
}
|
|
@@ -9275,9 +9367,7 @@ async function postUserMessageTurn(input) {
|
|
|
9275
9367
|
reference
|
|
9276
9368
|
});
|
|
9277
9369
|
if (!active) {
|
|
9278
|
-
throw
|
|
9279
|
-
message: `model is not available: ${freshWorkspaceCustomModel}`
|
|
9280
|
-
});
|
|
9370
|
+
throw modelUnavailableHttpException(freshWorkspaceCustomModel);
|
|
9281
9371
|
}
|
|
9282
9372
|
}
|
|
9283
9373
|
} : {},
|
|
@@ -9290,19 +9380,19 @@ async function postUserMessageTurn(input) {
|
|
|
9290
9380
|
throw error;
|
|
9291
9381
|
}
|
|
9292
9382
|
if (error instanceof PersonalResourceAttachmentAcceptanceError) {
|
|
9293
|
-
throw new
|
|
9383
|
+
throw new HTTPException17(
|
|
9294
9384
|
error.kind === "invalid" ? 422 : error.kind === "forbidden" ? 403 : 409,
|
|
9295
9385
|
{ message: error.message, cause: error }
|
|
9296
9386
|
);
|
|
9297
9387
|
}
|
|
9298
9388
|
if (error instanceof QueueCommandConflictError || error instanceof SessionControlConflictError) {
|
|
9299
|
-
throw new
|
|
9389
|
+
throw new HTTPException17(409, { message: error.message });
|
|
9300
9390
|
}
|
|
9301
9391
|
if (error instanceof Error && error.message.includes("cancelled")) {
|
|
9302
|
-
throw new
|
|
9392
|
+
throw new HTTPException17(409, { message: error.message });
|
|
9303
9393
|
}
|
|
9304
9394
|
if (error instanceof Error && error.message.startsWith("Unknown session MCP server")) {
|
|
9305
|
-
throw new
|
|
9395
|
+
throw new HTTPException17(422, { message: error.message });
|
|
9306
9396
|
}
|
|
9307
9397
|
throw error;
|
|
9308
9398
|
}
|
|
@@ -9358,28 +9448,28 @@ function resolveSessionCreateScope(input) {
|
|
|
9358
9448
|
} else {
|
|
9359
9449
|
const agentAccess = requested.agentAccessProvided ? requested.agentAccess : parent.agentAccess;
|
|
9360
9450
|
if (AGENT_ACCESS_WIDTH[agentAccess] > AGENT_ACCESS_WIDTH[parent.agentAccess]) {
|
|
9361
|
-
throw new
|
|
9451
|
+
throw new HTTPException17(403, {
|
|
9362
9452
|
message: "child agent access may only narrow the parent session"
|
|
9363
9453
|
});
|
|
9364
9454
|
}
|
|
9365
9455
|
if (requested.endUserProvided) {
|
|
9366
9456
|
const same = requested.scopeSubjectId !== null && parent.scopeSubjectId !== null && requested.scopeSubjectId === parent.scopeSubjectId;
|
|
9367
9457
|
if (!same) {
|
|
9368
|
-
throw new
|
|
9458
|
+
throw new HTTPException17(403, {
|
|
9369
9459
|
message: "child end-user label must equal the parent session label"
|
|
9370
9460
|
});
|
|
9371
9461
|
}
|
|
9372
9462
|
}
|
|
9373
9463
|
const memoryScope = requested.memoryScopeProvided ? requested.memoryScope : parent.memoryScope;
|
|
9374
9464
|
if (MEMORY_SCOPE_WIDTH[memoryScope] > MEMORY_SCOPE_WIDTH[parent.memoryScope]) {
|
|
9375
|
-
throw new
|
|
9465
|
+
throw new HTTPException17(403, {
|
|
9376
9466
|
message: "child memory scope may only narrow the parent session"
|
|
9377
9467
|
});
|
|
9378
9468
|
}
|
|
9379
9469
|
resolved = { agentAccess, scopeSubjectId: parent.scopeSubjectId, memoryScope };
|
|
9380
9470
|
}
|
|
9381
9471
|
if (resolved.memoryScope === "user" && resolved.scopeSubjectId === null) {
|
|
9382
|
-
throw new
|
|
9472
|
+
throw new HTTPException17(422, { message: 'memoryScope "user" requires an authenticated user' });
|
|
9383
9473
|
}
|
|
9384
9474
|
return resolved;
|
|
9385
9475
|
}
|
|
@@ -9433,7 +9523,7 @@ async function retryFailedSession(deps, grant, workspaceId, sessionId, request)
|
|
|
9433
9523
|
assertSessionAllowsProductModel(session, model);
|
|
9434
9524
|
} catch (error) {
|
|
9435
9525
|
if (error instanceof CodexCompactionV2ProviderLockedError)
|
|
9436
|
-
throw new
|
|
9526
|
+
throw new HTTPException17(422, { message: error.message, cause: error });
|
|
9437
9527
|
throw error;
|
|
9438
9528
|
}
|
|
9439
9529
|
await assertWorkspaceModelPolicyAllows(deps.db, settings, workspaceId, model);
|
|
@@ -9544,7 +9634,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9544
9634
|
const creationMetadata = externalCreationMetadata(payload.metadata, authorization, grant);
|
|
9545
9635
|
const externalBeforeCreateCommit = externalContinuationCommitAuthorizer(authorization);
|
|
9546
9636
|
if (hasReservedOpenGeniSlackBotSessionMetadata(payload.metadata)) {
|
|
9547
|
-
throw new
|
|
9637
|
+
throw new HTTPException17(422, {
|
|
9548
9638
|
message: `${OPENGENI_SLACK_BOT_SESSION_METADATA_KEY2} is reserved for scheduler routing`
|
|
9549
9639
|
});
|
|
9550
9640
|
}
|
|
@@ -9552,13 +9642,13 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9552
9642
|
const visibilityProvided = hasOwnProperty(rawPayload, "visibility");
|
|
9553
9643
|
if (payload.visibility === "private" && !grant.metadata?.["sessionId"]) {
|
|
9554
9644
|
if (!authorization) {
|
|
9555
|
-
throw new
|
|
9645
|
+
throw new HTTPException17(403, {
|
|
9556
9646
|
message: "managed human session required"
|
|
9557
9647
|
});
|
|
9558
9648
|
}
|
|
9559
9649
|
await requireManagedHumanPrivateSessionCreate(unresolvedDeps, authorization, workspaceId);
|
|
9560
9650
|
if (payload.sandbox === "shared" || typeof payload.sandbox === "object") {
|
|
9561
|
-
throw new
|
|
9651
|
+
throw new HTTPException17(422, {
|
|
9562
9652
|
message: "Only-me sessions require their own sandbox"
|
|
9563
9653
|
});
|
|
9564
9654
|
}
|
|
@@ -9573,14 +9663,14 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9573
9663
|
});
|
|
9574
9664
|
} catch (error) {
|
|
9575
9665
|
if (error instanceof SessionAuthorizationDeniedError) {
|
|
9576
|
-
throw new
|
|
9666
|
+
throw new HTTPException17(403, { message: error.message, cause: error });
|
|
9577
9667
|
}
|
|
9578
9668
|
throw error;
|
|
9579
9669
|
}
|
|
9580
9670
|
}
|
|
9581
9671
|
const parentSession = parentSessionId ? await getSession3(db, workspaceId, parentSessionId) : null;
|
|
9582
9672
|
if (parentSessionId && !parentSession) {
|
|
9583
|
-
throw new
|
|
9673
|
+
throw new HTTPException17(404, {
|
|
9584
9674
|
message: `parent session not found in workspace: ${parentSessionId}`
|
|
9585
9675
|
});
|
|
9586
9676
|
}
|
|
@@ -9588,7 +9678,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9588
9678
|
const workspaceSessionToolDefaults = resolveWorkspaceSessionToolDefaults2(workspace.settings);
|
|
9589
9679
|
const parentAuthority = parentSession ? await getSessionAuthorityProjection4(db, workspaceId, parentSession.id) : null;
|
|
9590
9680
|
if (parentSession && !parentAuthority) {
|
|
9591
|
-
throw new
|
|
9681
|
+
throw new HTTPException17(403, {
|
|
9592
9682
|
message: "parent session authority is unavailable"
|
|
9593
9683
|
});
|
|
9594
9684
|
}
|
|
@@ -9600,7 +9690,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9600
9690
|
parentVisibility: parentAuthority?.visibility ?? null
|
|
9601
9691
|
});
|
|
9602
9692
|
} catch (error) {
|
|
9603
|
-
throw new
|
|
9693
|
+
throw new HTTPException17(422, {
|
|
9604
9694
|
message: error instanceof Error ? error.message : "invalid child visibility"
|
|
9605
9695
|
});
|
|
9606
9696
|
}
|
|
@@ -9611,7 +9701,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9611
9701
|
parentSession?.bundledSkillIds
|
|
9612
9702
|
);
|
|
9613
9703
|
} catch (error) {
|
|
9614
|
-
throw new
|
|
9704
|
+
throw new HTTPException17(422, {
|
|
9615
9705
|
message: error instanceof Error ? error.message : "Invalid bundled Skill selection"
|
|
9616
9706
|
});
|
|
9617
9707
|
}
|
|
@@ -9653,7 +9743,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9653
9743
|
creationInitiator.actor.attemptId
|
|
9654
9744
|
) : null;
|
|
9655
9745
|
if (creationInitiator.actor && (!parentCallingTurn || parentCallingTurn.sessionId !== parentSession?.id)) {
|
|
9656
|
-
throw new
|
|
9746
|
+
throw new HTTPException17(403, {
|
|
9657
9747
|
message: "caller attempt does not belong to the parent session"
|
|
9658
9748
|
});
|
|
9659
9749
|
}
|
|
@@ -9713,12 +9803,12 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9713
9803
|
}
|
|
9714
9804
|
} catch (error) {
|
|
9715
9805
|
if (error instanceof SessionIdConflictError) {
|
|
9716
|
-
throw new
|
|
9806
|
+
throw new HTTPException17(409, {
|
|
9717
9807
|
message: "requested session id is already in use"
|
|
9718
9808
|
});
|
|
9719
9809
|
}
|
|
9720
9810
|
if (error instanceof SessionCreateIdempotencyConflictError) {
|
|
9721
|
-
throw new
|
|
9811
|
+
throw new HTTPException17(409, { message: error.message, cause: error });
|
|
9722
9812
|
}
|
|
9723
9813
|
throw error;
|
|
9724
9814
|
}
|
|
@@ -9777,7 +9867,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9777
9867
|
}
|
|
9778
9868
|
);
|
|
9779
9869
|
} catch (error) {
|
|
9780
|
-
throw new
|
|
9870
|
+
throw new HTTPException17(422, {
|
|
9781
9871
|
message: error instanceof Error ? error.message : "invalid child goal root constraints"
|
|
9782
9872
|
});
|
|
9783
9873
|
}
|
|
@@ -9824,7 +9914,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9824
9914
|
for (const [id, policy] of Object.entries(requestedMcpPolicies)) {
|
|
9825
9915
|
const inherited = inheritedServers.find((server) => server.id === id);
|
|
9826
9916
|
if (!inherited || sessionMcpServers.runtimeServers.some((server) => server.id === id)) {
|
|
9827
|
-
throw new
|
|
9917
|
+
throw new HTTPException17(422, {
|
|
9828
9918
|
message: `MCP approval policy must name an enabled inherited capability: ${id}${/^api_(openapi|graphql)_/.test(id) ? "; API Integration tool approval is set at install with autoApprovedTools" : ""}`
|
|
9829
9919
|
});
|
|
9830
9920
|
}
|
|
@@ -9845,12 +9935,12 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9845
9935
|
for (const capabilityId of selectedInstalledSkillIds) {
|
|
9846
9936
|
const installed = installedById.get(capabilityId);
|
|
9847
9937
|
if (!installed) {
|
|
9848
|
-
throw new
|
|
9938
|
+
throw new HTTPException17(422, {
|
|
9849
9939
|
message: `Session-selected Skill is not installed in this workspace: ${capabilityId}`
|
|
9850
9940
|
});
|
|
9851
9941
|
}
|
|
9852
9942
|
if (installed.activationMode !== "session_selected") {
|
|
9853
|
-
throw new
|
|
9943
|
+
throw new HTTPException17(422, {
|
|
9854
9944
|
message: `Installed Skill does not require explicit session selection: ${capabilityId}`
|
|
9855
9945
|
});
|
|
9856
9946
|
}
|
|
@@ -9865,13 +9955,13 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9865
9955
|
try {
|
|
9866
9956
|
skills = SessionSkills.parse([...inheritedOrSubmittedSkills, ...selectedInstalledSkills]);
|
|
9867
9957
|
} catch (error) {
|
|
9868
|
-
throw new
|
|
9958
|
+
throw new HTTPException17(422, {
|
|
9869
9959
|
message: error instanceof Error ? error.message : "invalid session Skill selection"
|
|
9870
9960
|
});
|
|
9871
9961
|
}
|
|
9872
9962
|
const toolsProvided = hasOwnProperty(rawPayload, "tools");
|
|
9873
9963
|
if (toolsProvided && payload.excludedMcpServerIds !== void 0) {
|
|
9874
|
-
throw new
|
|
9964
|
+
throw new HTTPException17(422, {
|
|
9875
9965
|
message: "connector exclusions require workspace-default tools"
|
|
9876
9966
|
});
|
|
9877
9967
|
}
|
|
@@ -9933,7 +10023,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9933
10023
|
}
|
|
9934
10024
|
if (payload.excludedMcpServerIds !== void 0) {
|
|
9935
10025
|
if (toolPolicy.mode !== "workspace_default") {
|
|
9936
|
-
throw new
|
|
10026
|
+
throw new HTTPException17(403, {
|
|
9937
10027
|
message: "connector exclusions require workspace-default tools"
|
|
9938
10028
|
});
|
|
9939
10029
|
}
|
|
@@ -9967,7 +10057,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9967
10057
|
const captureLinkedAuthority = prepareExternalLinkTurnAdmission(authorization);
|
|
9968
10058
|
await validateGitHubRepositorySelection(db, workspaceId, resources);
|
|
9969
10059
|
if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
|
|
9970
|
-
throw new
|
|
10060
|
+
throw new HTTPException17(503, {
|
|
9971
10061
|
message: "object storage is not configured"
|
|
9972
10062
|
});
|
|
9973
10063
|
}
|
|
@@ -9994,7 +10084,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
9994
10084
|
const rig = await getRig2(db, grant, requestedRigId);
|
|
9995
10085
|
if (!rig || !rig.activeVersion) {
|
|
9996
10086
|
if (payload.rigId) {
|
|
9997
|
-
throw new
|
|
10087
|
+
throw new HTTPException17(422, {
|
|
9998
10088
|
message: rig ? `sandbox environment ${payload.rigId} has no active version to bind` : `unknown rigId: ${payload.rigId}`
|
|
9999
10089
|
});
|
|
10000
10090
|
}
|
|
@@ -10015,7 +10105,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10015
10105
|
if (payload.channelId) {
|
|
10016
10106
|
const channel = await getChannel(db, workspaceId, payload.channelId);
|
|
10017
10107
|
if (!channel) {
|
|
10018
|
-
throw new
|
|
10108
|
+
throw new HTTPException17(422, {
|
|
10019
10109
|
message: `unknown channelId: ${payload.channelId}`
|
|
10020
10110
|
});
|
|
10021
10111
|
}
|
|
@@ -10034,15 +10124,14 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10034
10124
|
...xaiProviderAccountAuthoritySnapshot ? { xaiAuthoritySnapshot: xaiProviderAccountAuthoritySnapshot } : {}
|
|
10035
10125
|
});
|
|
10036
10126
|
if (!admissibleWorkspaceModel(selections, model)) {
|
|
10037
|
-
throw new
|
|
10127
|
+
throw new HTTPException17(422, { message: `model is not selectable: ${model}` });
|
|
10038
10128
|
}
|
|
10039
10129
|
}
|
|
10040
10130
|
const inheritedReasoningEffort = parentCallingTurn?.reasoningEffort ?? parentSession?.reasoningEffort ?? resolvedDefault?.reasoningEffort ?? settings.openaiReasoningEffort;
|
|
10041
|
-
const inheritedLatencyMode = parentCallingTurn?.latencyMode ?? parentSession?.latencyMode ?? "standard";
|
|
10042
10131
|
const reasoningEffort = payload.reasoningEffort ?? inheritedReasoningEffort;
|
|
10043
|
-
const latencyMode = payload.latencyMode ??
|
|
10132
|
+
const latencyMode = payload.latencyMode ?? "standard";
|
|
10044
10133
|
if (payload.expectedNewSessionDraftRevision !== void 0 && payload.rigId === null) {
|
|
10045
|
-
throw new
|
|
10134
|
+
throw new HTTPException17(409, {
|
|
10046
10135
|
message: "The submitted session options are not represented by the new-session draft"
|
|
10047
10136
|
});
|
|
10048
10137
|
}
|
|
@@ -10080,7 +10169,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10080
10169
|
reasoningEffort,
|
|
10081
10170
|
reasoningSource: payload.reasoningEffort === void 0 ? inheritedFromParent ? "continuation" : "deployment" : "explicit",
|
|
10082
10171
|
latencyMode,
|
|
10083
|
-
latencyModeSource: payload.latencyMode === void 0 ?
|
|
10172
|
+
latencyModeSource: payload.latencyMode === void 0 ? "deployment" : "explicit"
|
|
10084
10173
|
}),
|
|
10085
10174
|
{ grant, authorization, trustedMetadata: [parentSession?.metadata] }
|
|
10086
10175
|
);
|
|
@@ -10089,7 +10178,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10089
10178
|
if (parentFirstPartyMcpPermissions && payload.firstPartyMcpPermissions?.some(
|
|
10090
10179
|
(permission) => !hasPermission(parentFirstPartyMcpPermissions, permission)
|
|
10091
10180
|
)) {
|
|
10092
|
-
throw new
|
|
10181
|
+
throw new HTTPException17(403, {
|
|
10093
10182
|
message: "child first-party MCP permissions may only narrow the parent session grant"
|
|
10094
10183
|
});
|
|
10095
10184
|
}
|
|
@@ -10097,19 +10186,19 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10097
10186
|
(permission) => hasPermission(grant.permissions, permission)
|
|
10098
10187
|
) : null);
|
|
10099
10188
|
if (firstPartyMcpPermissions && firstPartyMcpPermissions.length === 0) {
|
|
10100
|
-
throw new
|
|
10189
|
+
throw new HTTPException17(422, {
|
|
10101
10190
|
message: "firstPartyMcpPermissions must not be empty; omit it for the default worker permission set"
|
|
10102
10191
|
});
|
|
10103
10192
|
}
|
|
10104
10193
|
for (const permission of firstPartyMcpPermissions ?? []) {
|
|
10105
10194
|
if (!hasPermission(grant.permissions, permission)) {
|
|
10106
|
-
throw new
|
|
10195
|
+
throw new HTTPException17(403, {
|
|
10107
10196
|
message: `cannot grant first-party MCP permission beyond the creating grant: ${permission}`
|
|
10108
10197
|
});
|
|
10109
10198
|
}
|
|
10110
10199
|
}
|
|
10111
10200
|
if (effectiveGoal && firstPartyMcpPermissions && !firstPartyMcpPermissions.includes("goals:manage")) {
|
|
10112
|
-
throw new
|
|
10201
|
+
throw new HTTPException17(422, {
|
|
10113
10202
|
message: "goal-bearing sessions require goals:manage in the resulting first-party MCP permission set"
|
|
10114
10203
|
});
|
|
10115
10204
|
}
|
|
@@ -10118,7 +10207,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10118
10207
|
(tool) => !deploymentFirstPartyMcpToolPolicy.allowed.includes(tool)
|
|
10119
10208
|
);
|
|
10120
10209
|
if (disallowedFirstPartyMcpTool) {
|
|
10121
|
-
throw new
|
|
10210
|
+
throw new HTTPException17(422, {
|
|
10122
10211
|
message: `first-party MCP tool is disabled by deployment policy: ${disallowedFirstPartyMcpTool}`
|
|
10123
10212
|
});
|
|
10124
10213
|
}
|
|
@@ -10170,13 +10259,13 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10170
10259
|
(name) => !firstPartyMcpTools.includes(name)
|
|
10171
10260
|
);
|
|
10172
10261
|
if (missingGoalTools.length > 0) {
|
|
10173
|
-
throw new
|
|
10262
|
+
throw new HTTPException17(422, {
|
|
10174
10263
|
message: `goal-bearing sessions require first-party MCP tools: ${missingGoalTools.join(", ")}`
|
|
10175
10264
|
});
|
|
10176
10265
|
}
|
|
10177
10266
|
}
|
|
10178
10267
|
if (payload.targetSandboxId && payload.sandbox !== void 0 && payload.sandbox !== "new") {
|
|
10179
|
-
throw new
|
|
10268
|
+
throw new HTTPException17(422, {
|
|
10180
10269
|
message: "targetSandboxId requires an own sandbox (omit sandbox or pass 'new'); it cannot join a shared group"
|
|
10181
10270
|
});
|
|
10182
10271
|
}
|
|
@@ -10190,7 +10279,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10190
10279
|
const rigVersionMatchesGroup = (memberRigVersionId) => memberRigVersionId === frozenRigVersionId;
|
|
10191
10280
|
if (sandboxChoice === "shared") {
|
|
10192
10281
|
if (!parentSessionId) {
|
|
10193
|
-
throw new
|
|
10282
|
+
throw new HTTPException17(422, {
|
|
10194
10283
|
message: "sandbox:'shared' requires a parent session (spawn from inside a session); use 'new' for a top-level create."
|
|
10195
10284
|
});
|
|
10196
10285
|
}
|
|
@@ -10213,7 +10302,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10213
10302
|
}
|
|
10214
10303
|
if (variableSetMismatch || rigMismatch) {
|
|
10215
10304
|
if (payload.sandbox === "shared") {
|
|
10216
|
-
throw new
|
|
10305
|
+
throw new HTTPException17(422, {
|
|
10217
10306
|
message: variableSetMismatch ? "sandbox:'shared' requires the same variableSet / same environment as the creator's box (the box variable set/environment is fixed at creation); omit sandbox or pass 'new' when attaching a different variableSet/environment." : "sandbox:'shared' requires the same sandbox environment as the creator's box (setup is fixed at creation); omit sandbox or pass 'new' when selecting a different sandbox environment."
|
|
10218
10307
|
});
|
|
10219
10308
|
}
|
|
@@ -10229,7 +10318,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10229
10318
|
} else if (typeof sandboxChoice === "object") {
|
|
10230
10319
|
const member = await getAnySessionInGroup(db, workspaceId, sandboxChoice.groupId);
|
|
10231
10320
|
if (!member) {
|
|
10232
|
-
throw new
|
|
10321
|
+
throw new HTTPException17(404, {
|
|
10233
10322
|
message: `sandbox group not found in workspace: ${sandboxChoice.groupId}`
|
|
10234
10323
|
});
|
|
10235
10324
|
}
|
|
@@ -10242,7 +10331,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10242
10331
|
if (!memberVariableSetSelections.every(
|
|
10243
10332
|
(memberVariableSetIds) => variableSetsMatchGroup(memberVariableSetIds)
|
|
10244
10333
|
)) {
|
|
10245
|
-
throw new
|
|
10334
|
+
throw new HTTPException17(422, {
|
|
10246
10335
|
message: `sandbox group ${sandboxChoice.groupId} runs a different variableSet / different environment (the box variable set/environment is fixed at creation); create with the group's variableSet/environment or omit sandbox for an own box.`
|
|
10247
10336
|
});
|
|
10248
10337
|
}
|
|
@@ -10254,7 +10343,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10254
10343
|
if (!memberRigVersionIds.every(
|
|
10255
10344
|
(memberRigVersionId) => rigVersionMatchesGroup(memberRigVersionId)
|
|
10256
10345
|
)) {
|
|
10257
|
-
throw new
|
|
10346
|
+
throw new HTTPException17(422, {
|
|
10258
10347
|
message: `sandbox group ${sandboxChoice.groupId} uses a different sandbox environment (setup is fixed at creation); select the group's sandbox environment or omit sandbox for a separate box.`
|
|
10259
10348
|
});
|
|
10260
10349
|
}
|
|
@@ -10264,12 +10353,12 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10264
10353
|
inheritedSandboxOs = member.sandboxOs;
|
|
10265
10354
|
}
|
|
10266
10355
|
if (payload.workingDir !== void 0 && !payload.targetSandboxId) {
|
|
10267
|
-
throw new
|
|
10356
|
+
throw new HTTPException17(422, {
|
|
10268
10357
|
message: "workingDir requires targetSandboxId (it is the targeted machine's working directory)"
|
|
10269
10358
|
});
|
|
10270
10359
|
}
|
|
10271
10360
|
if (inheritedBackend === void 0 && !payload.targetSandboxId && (payload.sandboxBackend ?? settings.sandboxBackend) === "selfhosted") {
|
|
10272
|
-
throw new
|
|
10361
|
+
throw new HTTPException17(422, {
|
|
10273
10362
|
message: "selfhosted sessions require targetSandboxId; select an online Connected Machine"
|
|
10274
10363
|
});
|
|
10275
10364
|
}
|
|
@@ -10302,7 +10391,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10302
10391
|
workingDir: payload.workingDir ?? null
|
|
10303
10392
|
} : inheritedActiveTarget;
|
|
10304
10393
|
if (effectiveSandboxBackend === "selfhosted" && effectiveSeedTarget === null && managedSessionGroupBackend(settings.sandboxBackend, effectiveSandboxBackend) === null) {
|
|
10305
|
-
throw new
|
|
10394
|
+
throw new HTTPException17(422, {
|
|
10306
10395
|
message: "self-hosted execution runs on a Connected Machine, but no machine was selected or inherited; connect the parent session to a machine or provide machineTarget"
|
|
10307
10396
|
});
|
|
10308
10397
|
}
|
|
@@ -10333,7 +10422,7 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10333
10422
|
const initialLearning = payload.agentLearning && Object.keys(payload.agentLearning).length ? payload.agentLearning : null;
|
|
10334
10423
|
const initialLearningContext = initialLearning && authorization ? await knowledgeContextForAccess(deps, authorization, "sessions:control") : null;
|
|
10335
10424
|
if (initialLearning && initialLearningContext?.actor.kind !== "human") {
|
|
10336
|
-
throw new
|
|
10425
|
+
throw new HTTPException17(403, {
|
|
10337
10426
|
message: "Chat learning settings require authenticated human session control"
|
|
10338
10427
|
});
|
|
10339
10428
|
}
|
|
@@ -10480,27 +10569,27 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
10480
10569
|
});
|
|
10481
10570
|
} catch (error) {
|
|
10482
10571
|
if (error instanceof PersonalResourceAttachmentAcceptanceError) {
|
|
10483
|
-
throw new
|
|
10572
|
+
throw new HTTPException17(
|
|
10484
10573
|
error.kind === "invalid" ? 422 : error.kind === "forbidden" ? 403 : 409,
|
|
10485
10574
|
{ message: error.message, cause: error }
|
|
10486
10575
|
);
|
|
10487
10576
|
}
|
|
10488
10577
|
if (error instanceof AgentCommandAuthorityError) {
|
|
10489
|
-
throw new
|
|
10578
|
+
throw new HTTPException17(403, { message: error.message });
|
|
10490
10579
|
}
|
|
10491
10580
|
if (error instanceof SessionIdConflictError) {
|
|
10492
|
-
throw new
|
|
10581
|
+
throw new HTTPException17(409, {
|
|
10493
10582
|
message: "requested session id is already in use"
|
|
10494
10583
|
});
|
|
10495
10584
|
}
|
|
10496
10585
|
if (error instanceof NewSessionDraftConflictError) {
|
|
10497
|
-
throw new
|
|
10586
|
+
throw new HTTPException17(409, {
|
|
10498
10587
|
message: error.message,
|
|
10499
10588
|
cause: error
|
|
10500
10589
|
});
|
|
10501
10590
|
}
|
|
10502
10591
|
if (error instanceof SessionCreateIdempotencyConflictError) {
|
|
10503
|
-
throw new
|
|
10592
|
+
throw new HTTPException17(409, { message: error.message, cause: error });
|
|
10504
10593
|
}
|
|
10505
10594
|
throw error;
|
|
10506
10595
|
}
|
|
@@ -10664,7 +10753,7 @@ async function acceptSessionUserMessageInFileScope(deps, grant, workspaceId, ses
|
|
|
10664
10753
|
assertSessionAllowsProductModel(existingSession, effectiveModel);
|
|
10665
10754
|
} catch (error) {
|
|
10666
10755
|
if (error instanceof CodexCompactionV2ProviderLockedError) {
|
|
10667
|
-
throw new
|
|
10756
|
+
throw new HTTPException17(422, { message: error.message, cause: error });
|
|
10668
10757
|
}
|
|
10669
10758
|
throw error;
|
|
10670
10759
|
}
|
|
@@ -10690,7 +10779,7 @@ async function acceptSessionUserMessageInFileScope(deps, grant, workspaceId, ses
|
|
|
10690
10779
|
(resource) => !acceptedResources.has(stableJson3(resource))
|
|
10691
10780
|
);
|
|
10692
10781
|
if (unacceptedDraftResource) {
|
|
10693
|
-
throw new
|
|
10782
|
+
throw new HTTPException17(422, {
|
|
10694
10783
|
message: "composer draft resources must be included in the accepted resource set"
|
|
10695
10784
|
});
|
|
10696
10785
|
}
|
|
@@ -10742,7 +10831,7 @@ async function acceptSessionUserMessageInFileScope(deps, grant, workspaceId, ses
|
|
|
10742
10831
|
model: effectiveModel
|
|
10743
10832
|
});
|
|
10744
10833
|
if (requestedResources.some((resource) => resource.kind === "file") && !objectStorage) {
|
|
10745
|
-
throw new
|
|
10834
|
+
throw new HTTPException17(503, {
|
|
10746
10835
|
message: "object storage is not configured"
|
|
10747
10836
|
});
|
|
10748
10837
|
}
|
|
@@ -10978,7 +11067,7 @@ async function setSessionModel(deps, grant, sessionId, request, surface = "core"
|
|
|
10978
11067
|
latencyModeSource: "session"
|
|
10979
11068
|
});
|
|
10980
11069
|
} catch (error) {
|
|
10981
|
-
throw new
|
|
11070
|
+
throw new HTTPException17(422, {
|
|
10982
11071
|
message: error instanceof Error ? error.message : "Invalid model settings",
|
|
10983
11072
|
cause: error
|
|
10984
11073
|
});
|
|
@@ -11013,7 +11102,7 @@ async function updateSessionMcpApprovalPolicy(deps, grant, sessionId, serverId,
|
|
|
11013
11102
|
async (_session, context) => {
|
|
11014
11103
|
const result = await context.updateSessionMcpApprovalPolicy(serverId, normalizedPolicy);
|
|
11015
11104
|
if (!result.server) {
|
|
11016
|
-
throw new
|
|
11105
|
+
throw new HTTPException17(404, {
|
|
11017
11106
|
message: "session MCP server not found"
|
|
11018
11107
|
});
|
|
11019
11108
|
}
|
|
@@ -11134,7 +11223,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
11134
11223
|
(tool) => !validatedIds.has(`${tool.kind}:${tool.id}`)
|
|
11135
11224
|
);
|
|
11136
11225
|
if (unknown) {
|
|
11137
|
-
throw new
|
|
11226
|
+
throw new HTTPException17(422, {
|
|
11138
11227
|
message: `unknown MCP server id: ${unknown.id}`
|
|
11139
11228
|
});
|
|
11140
11229
|
}
|
|
@@ -11171,7 +11260,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
11171
11260
|
if (session.parentSessionId) {
|
|
11172
11261
|
const parent = await context.getLockedSession(session.parentSessionId);
|
|
11173
11262
|
if (!parent) {
|
|
11174
|
-
throw new
|
|
11263
|
+
throw new HTTPException17(409, {
|
|
11175
11264
|
message: "parent session is no longer available"
|
|
11176
11265
|
});
|
|
11177
11266
|
}
|
|
@@ -11184,7 +11273,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
11184
11273
|
);
|
|
11185
11274
|
if (requestedMode === "workspace_default") {
|
|
11186
11275
|
if (!parentTracksWorkspaceDefaults) {
|
|
11187
|
-
throw new
|
|
11276
|
+
throw new HTTPException17(403, {
|
|
11188
11277
|
message: "a child may adopt workspace defaults only while its parent tracks workspace defaults"
|
|
11189
11278
|
});
|
|
11190
11279
|
}
|
|
@@ -11210,7 +11299,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
11210
11299
|
(tool) => !parentFirstPartySet.has(tool)
|
|
11211
11300
|
);
|
|
11212
11301
|
if (widenedFirstPartyTool) {
|
|
11213
|
-
throw new
|
|
11302
|
+
throw new HTTPException17(403, {
|
|
11214
11303
|
message: `session OpenGeni tools may only narrow the parent policy: ${widenedFirstPartyTool}`
|
|
11215
11304
|
});
|
|
11216
11305
|
}
|
|
@@ -11231,7 +11320,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
11231
11320
|
}
|
|
11232
11321
|
if (connectorOnlyEdit) {
|
|
11233
11322
|
if (session.toolPolicy.mode !== "workspace_default") {
|
|
11234
|
-
throw new
|
|
11323
|
+
throw new HTTPException17(409, {
|
|
11235
11324
|
message: "adopt workspace defaults before editing connector exclusions"
|
|
11236
11325
|
});
|
|
11237
11326
|
}
|
|
@@ -11285,7 +11374,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
11285
11374
|
if (nextPolicy.mode === "workspace_default" && (session.toolPolicy.excludedMcpServerIds ?? []).some(
|
|
11286
11375
|
(id) => !nextPolicy.excludedMcpServerIds?.includes(id)
|
|
11287
11376
|
)) {
|
|
11288
|
-
throw new
|
|
11377
|
+
throw new HTTPException17(403, {
|
|
11289
11378
|
message: "an agent may not remove session connector exclusions"
|
|
11290
11379
|
});
|
|
11291
11380
|
}
|
|
@@ -11302,7 +11391,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
11302
11391
|
(tool) => !currentFirstPartyCeiling.has(tool)
|
|
11303
11392
|
);
|
|
11304
11393
|
if (widenedFirstPartyTool) {
|
|
11305
|
-
throw new
|
|
11394
|
+
throw new HTTPException17(403, {
|
|
11306
11395
|
message: `an agent may only narrow its session OpenGeni tools: ${widenedFirstPartyTool}`
|
|
11307
11396
|
});
|
|
11308
11397
|
}
|
|
@@ -11406,7 +11495,7 @@ async function updateSessionAgent(deps, grant, sessionId, request) {
|
|
|
11406
11495
|
if (session.parentSessionId) {
|
|
11407
11496
|
parent = await context.getLockedSession(session.parentSessionId);
|
|
11408
11497
|
if (!parent) {
|
|
11409
|
-
throw new
|
|
11498
|
+
throw new HTTPException17(409, { message: "parent session is no longer available" });
|
|
11410
11499
|
}
|
|
11411
11500
|
parentCeiling = parent.agent?.capabilities ?? legacySessionAgentCapabilities(deps.settings, parent, workspace.settings);
|
|
11412
11501
|
}
|
|
@@ -11515,7 +11604,7 @@ async function updateSessionAgent(deps, grant, sessionId, request) {
|
|
|
11515
11604
|
toolPolicy = writeThrough.toolPolicy;
|
|
11516
11605
|
const nextInstructions = request.agent.instructions !== void 0 ? request.agent.instructions : session.instructions ?? null;
|
|
11517
11606
|
if (goalOpen && !writeThrough.firstPartyMcpTools.includes("goal_complete")) {
|
|
11518
|
-
throw new
|
|
11607
|
+
throw new HTTPException17(422, {
|
|
11519
11608
|
message: "a session with an open goal must keep its goal tools"
|
|
11520
11609
|
});
|
|
11521
11610
|
}
|
|
@@ -11588,13 +11677,13 @@ async function readSessionLineage(deps, grant, sessionId) {
|
|
|
11588
11677
|
if (authorization?.relatedSessionAccess === "target") {
|
|
11589
11678
|
const session = await getSession3(deps.db, grant.workspaceId, sessionId);
|
|
11590
11679
|
if (!session) {
|
|
11591
|
-
throw new
|
|
11680
|
+
throw new HTTPException17(404, { message: "session not found" });
|
|
11592
11681
|
}
|
|
11593
11682
|
return { ancestors: [], children: [], truncated: false };
|
|
11594
11683
|
}
|
|
11595
11684
|
const lineage = await getSessionLineage(deps.db, grant.workspaceId, sessionId);
|
|
11596
11685
|
if (!lineage) {
|
|
11597
|
-
throw new
|
|
11686
|
+
throw new HTTPException17(404, { message: "session not found" });
|
|
11598
11687
|
}
|
|
11599
11688
|
return lineage;
|
|
11600
11689
|
}
|
|
@@ -12110,7 +12199,7 @@ function filenameForMimeType(mimeType) {
|
|
|
12110
12199
|
}
|
|
12111
12200
|
|
|
12112
12201
|
// src/application/external-workspace-members.ts
|
|
12113
|
-
import { HTTPException as
|
|
12202
|
+
import { HTTPException as HTTPException18 } from "hono/http-exception";
|
|
12114
12203
|
import {
|
|
12115
12204
|
AddExternalWorkspaceMemberRequest,
|
|
12116
12205
|
ExternalIdentityReference,
|
|
@@ -12121,7 +12210,7 @@ import {
|
|
|
12121
12210
|
ensureExternalIdentity as ensureExternalIdentity3,
|
|
12122
12211
|
grantWorkspaceAccess,
|
|
12123
12212
|
listWorkspaceMembers,
|
|
12124
|
-
lockExternalWorkspaceMembershipLifecycle as
|
|
12213
|
+
lockExternalWorkspaceMembershipLifecycle as lockExternalWorkspaceMembershipLifecycle3,
|
|
12125
12214
|
requireWorkspace as requireWorkspace5,
|
|
12126
12215
|
setRlsContext,
|
|
12127
12216
|
withWorkspaceSubjectRls as withWorkspaceSubjectRls3,
|
|
@@ -12149,12 +12238,12 @@ async function addExternalWorkspaceMemberForRequest(c, deps, workspaceId, input)
|
|
|
12149
12238
|
requireApiKeyDelegationContext(context, payload.permissions);
|
|
12150
12239
|
const authority = accountScopedApiKeyWorkspaceAuthority(context);
|
|
12151
12240
|
if (!authority)
|
|
12152
|
-
throw new
|
|
12241
|
+
throw new HTTPException18(403, {
|
|
12153
12242
|
message: "external onboarding requires an organization service key"
|
|
12154
12243
|
});
|
|
12155
12244
|
const grant = await requireFreshAccessGrant(c, deps, workspaceId, "members:manage");
|
|
12156
12245
|
if (grant.accountId !== authority.accountId || payload.permissions.some((permission) => !hasPermission(grant.permissions, permission))) {
|
|
12157
|
-
throw new
|
|
12246
|
+
throw new HTTPException18(403, { message: "membership exceeds key authority" });
|
|
12158
12247
|
}
|
|
12159
12248
|
if (payload.operationId) {
|
|
12160
12249
|
try {
|
|
@@ -12172,7 +12261,7 @@ async function addExternalWorkspaceMemberForRequest(c, deps, workspaceId, input)
|
|
|
12172
12261
|
}
|
|
12173
12262
|
}
|
|
12174
12263
|
return withWorkspaceSubjectRls3(deps.db, workspaceId, grant.subjectId, async (tx) => {
|
|
12175
|
-
await
|
|
12264
|
+
await lockExternalWorkspaceMembershipLifecycle3(tx, grant.accountId);
|
|
12176
12265
|
const live = await requireFreshAccessGrant(
|
|
12177
12266
|
c,
|
|
12178
12267
|
{ ...deps, db: tx },
|
|
@@ -12180,11 +12269,11 @@ async function addExternalWorkspaceMemberForRequest(c, deps, workspaceId, input)
|
|
|
12180
12269
|
"members:manage"
|
|
12181
12270
|
);
|
|
12182
12271
|
if (live.accountId !== grant.accountId || live.subjectId !== grant.subjectId || payload.permissions.some((permission) => !hasPermission(live.permissions, permission))) {
|
|
12183
|
-
throw new
|
|
12272
|
+
throw new HTTPException18(403, { message: "membership authority changed" });
|
|
12184
12273
|
}
|
|
12185
12274
|
const workspace = await requireWorkspace5(tx, workspaceId);
|
|
12186
12275
|
if (workspace.kind !== "shared" || workspace.accountId !== authority.accountId)
|
|
12187
|
-
throw new
|
|
12276
|
+
throw new HTTPException18(403, {
|
|
12188
12277
|
message: "external onboarding requires a shared organization workspace"
|
|
12189
12278
|
});
|
|
12190
12279
|
const identity = await ensureExternalIdentity3(tx, {
|
|
@@ -12198,7 +12287,7 @@ async function addExternalWorkspaceMemberForRequest(c, deps, workspaceId, input)
|
|
|
12198
12287
|
const permissions = [...new Set(payload.permissions)];
|
|
12199
12288
|
if (existing) {
|
|
12200
12289
|
if (existing.permissions.length !== permissions.length || existing.permissions.some((permission) => !permissions.includes(permission))) {
|
|
12201
|
-
throw new
|
|
12290
|
+
throw new HTTPException18(409, {
|
|
12202
12291
|
message: "existing membership differs; onboarding does not overwrite permissions"
|
|
12203
12292
|
});
|
|
12204
12293
|
}
|
|
@@ -12217,7 +12306,7 @@ async function addExternalWorkspaceMemberForRequest(c, deps, workspaceId, input)
|
|
|
12217
12306
|
function rethrowExternalWorkspaceOperation(error) {
|
|
12218
12307
|
const status = organizationMembershipHttpStatus(nestedPostgresSqlState4(error));
|
|
12219
12308
|
if (status)
|
|
12220
|
-
throw new
|
|
12309
|
+
throw new HTTPException18(status, {
|
|
12221
12310
|
message: status === 409 ? "External membership operation changed or was cancelled" : "External membership operation is not available"
|
|
12222
12311
|
});
|
|
12223
12312
|
throw error;
|
|
@@ -12226,7 +12315,7 @@ async function externalService(c, deps, organizationId) {
|
|
|
12226
12315
|
const context = await requireAccessContext(c, deps);
|
|
12227
12316
|
const authority = accountScopedApiKeyWorkspaceAuthority(context);
|
|
12228
12317
|
if (!authority || authority.accountId !== organizationId || !hasPermission(authority.permissions, "members:manage")) {
|
|
12229
|
-
throw new
|
|
12318
|
+
throw new HTTPException18(403, {
|
|
12230
12319
|
message: "External membership operation requires an organization service key"
|
|
12231
12320
|
});
|
|
12232
12321
|
}
|
|
@@ -12236,7 +12325,7 @@ async function lookupExternalIdentityForRequest(c, deps, organizationId, input)
|
|
|
12236
12325
|
const service = await externalService(c, deps, organizationId);
|
|
12237
12326
|
const identity = ExternalIdentityReference.safeParse(input);
|
|
12238
12327
|
if (!identity.success)
|
|
12239
|
-
throw new
|
|
12328
|
+
throw new HTTPException18(422, { message: "Invalid external identity reference" });
|
|
12240
12329
|
try {
|
|
12241
12330
|
return await lookupExternalIdentity(deps.db, service, identity.data);
|
|
12242
12331
|
} catch (error) {
|
|
@@ -12247,7 +12336,7 @@ async function cancelExternalWorkspaceMemberGrantForRequest(c, deps, organizatio
|
|
|
12247
12336
|
const service = await externalService(c, deps, organizationId);
|
|
12248
12337
|
const request = CancelExternalWorkspaceMemberGrantRequest.safeParse(input);
|
|
12249
12338
|
if (!request.success)
|
|
12250
|
-
throw new
|
|
12339
|
+
throw new HTTPException18(422, { message: "Invalid external grant cancellation" });
|
|
12251
12340
|
try {
|
|
12252
12341
|
return await cancelExternalWorkspaceMemberGrant(
|
|
12253
12342
|
deps.db,
|
|
@@ -12262,7 +12351,7 @@ async function updateExternalWorkspaceMemberForRequest(c, deps, organizationId,
|
|
|
12262
12351
|
const service = await externalService(c, deps, organizationId);
|
|
12263
12352
|
const request = UpdateExternalWorkspaceMemberRequest.safeParse(input);
|
|
12264
12353
|
if (!request.success)
|
|
12265
|
-
throw new
|
|
12354
|
+
throw new HTTPException18(422, {
|
|
12266
12355
|
message: `Invalid external member update: ${request.error.issues.slice(0, 5).map((issue) => `${issue.path.map(String).join(".") || "request"}: ${issue.message}`).join("; ")}`
|
|
12267
12356
|
});
|
|
12268
12357
|
requireApiKeyDelegationContext(await requireAccessContext(c, deps), request.data.permissions);
|
|
@@ -12278,7 +12367,7 @@ async function updateExternalWorkspaceMemberForRequest(c, deps, organizationId,
|
|
|
12278
12367
|
}
|
|
12279
12368
|
|
|
12280
12369
|
// src/application/external-identity-lifecycle.ts
|
|
12281
|
-
import { HTTPException as
|
|
12370
|
+
import { HTTPException as HTTPException19 } from "hono/http-exception";
|
|
12282
12371
|
import { UpdateExternalIdentityMembershipRequest } from "@opengeni/contracts/external-identities";
|
|
12283
12372
|
import { updateOrganizationMember } from "@opengeni/db";
|
|
12284
12373
|
async function updateExternalIdentityMembershipForRequest(context, deps, organizationId, membershipId, input) {
|
|
@@ -12288,13 +12377,13 @@ async function updateExternalIdentityMembershipForRequest(context, deps, organiz
|
|
|
12288
12377
|
(grant) => grant.accountId === organizationId && grant.subjectId === access.subjectId
|
|
12289
12378
|
);
|
|
12290
12379
|
if (!service || service.accountId !== organizationId || !account?.permissions.includes("account:admin")) {
|
|
12291
|
-
throw new
|
|
12380
|
+
throw new HTTPException19(403, {
|
|
12292
12381
|
message: "external identity administration requires an organization service key"
|
|
12293
12382
|
});
|
|
12294
12383
|
}
|
|
12295
12384
|
const parsed = UpdateExternalIdentityMembershipRequest.safeParse(input);
|
|
12296
12385
|
if (!parsed.success)
|
|
12297
|
-
throw new
|
|
12386
|
+
throw new HTTPException19(422, { message: "invalid external identity transition" });
|
|
12298
12387
|
return updateOrganizationMember(deps.db, {
|
|
12299
12388
|
organizationId,
|
|
12300
12389
|
membershipId,
|
|
@@ -12316,7 +12405,7 @@ import {
|
|
|
12316
12405
|
rotateSessionMcpCredentialsAtomically,
|
|
12317
12406
|
SessionMcpCredentialRotationError
|
|
12318
12407
|
} from "@opengeni/db/session-mcp-credential-rotation";
|
|
12319
|
-
import { HTTPException as
|
|
12408
|
+
import { HTTPException as HTTPException20 } from "hono/http-exception";
|
|
12320
12409
|
|
|
12321
12410
|
// src/domain/session-native-connection-replacement.ts
|
|
12322
12411
|
import { connectionMetadataMatchesBinding } from "@opengeni/db/session-mcp-credential-rotation";
|
|
@@ -12360,7 +12449,7 @@ function sessionMcpRotationFingerprint(key, scope, request) {
|
|
|
12360
12449
|
}
|
|
12361
12450
|
function requireRotationGrant(grant) {
|
|
12362
12451
|
if (grantHasAgentAttemptAuthority(grant))
|
|
12363
|
-
throw new
|
|
12452
|
+
throw new HTTPException20(403, { message: "host authorization required" });
|
|
12364
12453
|
requirePermission(grant, "sessions:control");
|
|
12365
12454
|
requirePermission(grant, "mcp_servers:attach");
|
|
12366
12455
|
}
|
|
@@ -12372,7 +12461,7 @@ async function rotateSessionMcpCredentialsForRequest(deps, authorization, sessio
|
|
|
12372
12461
|
requireRotationGrant(grant);
|
|
12373
12462
|
const parsed = RotateSessionMcpCredentialsRequest.safeParse(raw);
|
|
12374
12463
|
if (!parsed.success)
|
|
12375
|
-
throw new
|
|
12464
|
+
throw new HTTPException20(422, { message: "invalid credential rotation request" });
|
|
12376
12465
|
const encryptionKey = requireVariableSetEncryption(deps.settings);
|
|
12377
12466
|
let request;
|
|
12378
12467
|
try {
|
|
@@ -12395,7 +12484,7 @@ async function rotateSessionMcpCredentialsForRequest(deps, authorization, sessio
|
|
|
12395
12484
|
if (new Set(request.updates.map((update) => update.id)).size !== request.updates.length)
|
|
12396
12485
|
throw new Error();
|
|
12397
12486
|
} catch {
|
|
12398
|
-
throw new
|
|
12487
|
+
throw new HTTPException20(422, { message: "invalid credential rotation headers or server IDs" });
|
|
12399
12488
|
}
|
|
12400
12489
|
const scope = {
|
|
12401
12490
|
accountId: grant.accountId,
|
|
@@ -12446,17 +12535,17 @@ async function rotateSessionMcpCredentialsForRequest(deps, authorization, sessio
|
|
|
12446
12535
|
await externalAuthorize?.(tx);
|
|
12447
12536
|
const fresh = await reauthorize(tx);
|
|
12448
12537
|
if (fresh.accountId !== grant.accountId || fresh.workspaceId !== grant.workspaceId || fresh.subjectId !== grant.subjectId || fresh.principalKind !== grant.principalKind)
|
|
12449
|
-
throw new
|
|
12538
|
+
throw new HTTPException20(403, { message: "credential rotation authority changed" });
|
|
12450
12539
|
requireRotationGrant(fresh);
|
|
12451
12540
|
if (fresh.principalKind === "human_session" && !externalAuthorize) {
|
|
12452
12541
|
const live = await getWorkspaceGrant4(tx, fresh.subjectId, fresh.workspaceId);
|
|
12453
12542
|
if (live) {
|
|
12454
12543
|
requireRotationGrant(live);
|
|
12455
12544
|
} else if (!authorization.canonicalManagedHumanSession) {
|
|
12456
|
-
throw new
|
|
12545
|
+
throw new HTTPException20(403, { message: "credential rotation authority changed" });
|
|
12457
12546
|
}
|
|
12458
12547
|
if (!await subjectHasLiveWorkspaceAuthorityInScope(tx, scope)) {
|
|
12459
|
-
throw new
|
|
12548
|
+
throw new HTTPException20(403, { message: "credential rotation authority changed" });
|
|
12460
12549
|
}
|
|
12461
12550
|
}
|
|
12462
12551
|
await requireSessionAuthorization({ ...deps, db: tx }, fresh, {
|
|
@@ -12468,17 +12557,17 @@ async function rotateSessionMcpCredentialsForRequest(deps, authorization, sessio
|
|
|
12468
12557
|
}
|
|
12469
12558
|
});
|
|
12470
12559
|
} catch (error) {
|
|
12471
|
-
if (error instanceof
|
|
12472
|
-
throw new
|
|
12560
|
+
if (error instanceof HTTPException20)
|
|
12561
|
+
throw new HTTPException20(error.status, { message: "credential rotation access denied" });
|
|
12473
12562
|
if (error instanceof SessionAuthorizationDeniedError)
|
|
12474
|
-
throw new
|
|
12563
|
+
throw new HTTPException20(403, { message: "credential rotation access denied" });
|
|
12475
12564
|
if (error instanceof SessionAuthorizationUnavailableError)
|
|
12476
|
-
throw new
|
|
12565
|
+
throw new HTTPException20(503, { message: "credential rotation authorization unavailable" });
|
|
12477
12566
|
if (error instanceof SessionMcpCredentialRotationError) {
|
|
12478
12567
|
const status = error.code === "receipt_key_unavailable" ? 503 : error.code === "authority_revoked" ? 403 : error.code === "not_found" ? 404 : error.code === "invalid_request" || error.code === "brokered_server" || error.code === "connection_unavailable" ? 422 : 409;
|
|
12479
|
-
throw new
|
|
12568
|
+
throw new HTTPException20(status, { message: `credential_rotation_${error.code}` });
|
|
12480
12569
|
}
|
|
12481
|
-
throw new
|
|
12570
|
+
throw new HTTPException20(503, { message: "credential rotation unavailable" });
|
|
12482
12571
|
}
|
|
12483
12572
|
}
|
|
12484
12573
|
|
|
@@ -12486,19 +12575,19 @@ async function rotateSessionMcpCredentialsForRequest(deps, authorization, sessio
|
|
|
12486
12575
|
import {
|
|
12487
12576
|
getWorkspaceGrant as getWorkspaceGrant5,
|
|
12488
12577
|
resolveNamedManagedPersonalWorkspaceGrant as resolveNamedManagedPersonalWorkspaceGrant2,
|
|
12489
|
-
lockExternalWorkspaceMembershipLifecycle as
|
|
12578
|
+
lockExternalWorkspaceMembershipLifecycle as lockExternalWorkspaceMembershipLifecycle4,
|
|
12490
12579
|
withWorkspaceSubjectRls as withWorkspaceSubjectRls4,
|
|
12491
12580
|
lockConnectionSetupKey,
|
|
12492
12581
|
nestedPostgresSqlState as nestedPostgresSqlState5
|
|
12493
12582
|
} from "@opengeni/db";
|
|
12494
|
-
import { HTTPException as
|
|
12583
|
+
import { HTTPException as HTTPException21 } from "hono/http-exception";
|
|
12495
12584
|
async function requireConnectOwnerAuthority(db, state, permission = "connections:write", origin = null) {
|
|
12496
12585
|
async function requireExternal(continuation) {
|
|
12497
12586
|
try {
|
|
12498
12587
|
await requireExternalContinuationAuthority(db, continuation, state, permission);
|
|
12499
12588
|
} catch (error) {
|
|
12500
12589
|
const denied2 = nestedPostgresSqlState5(error) === "42501" || error instanceof Error && error.message === "External continuation authority unavailable";
|
|
12501
|
-
throw new
|
|
12590
|
+
throw new HTTPException21(denied2 ? 403 : 503, {
|
|
12502
12591
|
message: denied2 ? "Connection origin authority changed" : "Connection origin authority unavailable",
|
|
12503
12592
|
cause: error
|
|
12504
12593
|
});
|
|
@@ -12510,19 +12599,19 @@ async function requireConnectOwnerAuthority(db, state, permission = "connections
|
|
|
12510
12599
|
return;
|
|
12511
12600
|
}
|
|
12512
12601
|
if (state.subjectId.startsWith("external_user:"))
|
|
12513
|
-
throw new
|
|
12602
|
+
throw new HTTPException21(403, { message: "External Connect continuation required" });
|
|
12514
12603
|
if (state.subjectId.startsWith("api_key:")) {
|
|
12515
12604
|
const permissions = await lockConnectionSetupKey(db, state);
|
|
12516
12605
|
if (!permissions || !hasPermission(permissions, permission))
|
|
12517
|
-
throw new
|
|
12606
|
+
throw new HTTPException21(403, { message: "Connection API key authority changed" });
|
|
12518
12607
|
return;
|
|
12519
12608
|
}
|
|
12520
12609
|
await withWorkspaceSubjectRls4(db, state.workspaceId, state.subjectId, async (tx) => {
|
|
12521
|
-
await
|
|
12610
|
+
await lockExternalWorkspaceMembershipLifecycle4(tx, state.accountId);
|
|
12522
12611
|
const membership = await getWorkspaceGrant5(tx, state.subjectId, state.workspaceId);
|
|
12523
12612
|
const grant = membership?.accountId === state.accountId ? membership : state.personalOwnerVerified ? await resolveNamedManagedPersonalWorkspaceGrant2(tx, state) : null;
|
|
12524
12613
|
if (!grant || grant.accountId !== state.accountId || !hasPermission(grant.permissions, permission))
|
|
12525
|
-
throw new
|
|
12614
|
+
throw new HTTPException21(403, { message: "Connection owner authority changed" });
|
|
12526
12615
|
});
|
|
12527
12616
|
}
|
|
12528
12617
|
|
|
@@ -12585,7 +12674,7 @@ import {
|
|
|
12585
12674
|
PORTABLE_SKILL_MAX_FILES,
|
|
12586
12675
|
PORTABLE_SKILL_MAX_TOTAL_BYTES
|
|
12587
12676
|
} from "@opengeni/runtime/skill-library";
|
|
12588
|
-
import { HTTPException as
|
|
12677
|
+
import { HTTPException as HTTPException22 } from "hono/http-exception";
|
|
12589
12678
|
var githubSegment = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$/u;
|
|
12590
12679
|
var gitCommit = /^[0-9a-f]{40,64}$/u;
|
|
12591
12680
|
var maxConcurrentBlobReads = 8;
|
|
@@ -12597,7 +12686,7 @@ async function resolveSkillImport(rawUrl, client) {
|
|
|
12597
12686
|
}
|
|
12598
12687
|
const sourceCommit = await client.resolveCommit(parsed.owner, parsed.repository, parsed.ref);
|
|
12599
12688
|
if (!gitCommit.test(sourceCommit)) {
|
|
12600
|
-
throw new
|
|
12689
|
+
throw new HTTPException22(422, { message: "GitHub returned an invalid source commit" });
|
|
12601
12690
|
}
|
|
12602
12691
|
const tree = await client.listTree(parsed.owner, parsed.repository, sourceCommit);
|
|
12603
12692
|
const blobs = /* @__PURE__ */ new Map();
|
|
@@ -12613,12 +12702,12 @@ async function resolveSkillImport(rawUrl, client) {
|
|
|
12613
12702
|
const entries = skillFilesUnderRoot(tree, sourcePath);
|
|
12614
12703
|
const declaredBytes = entries.reduce((sum, entry) => sum + (entry.size ?? 0), 0);
|
|
12615
12704
|
if (entries.length > PORTABLE_SKILL_MAX_FILES) {
|
|
12616
|
-
throw new
|
|
12705
|
+
throw new HTTPException22(422, {
|
|
12617
12706
|
message: `Skill contains more than ${PORTABLE_SKILL_MAX_FILES} files`
|
|
12618
12707
|
});
|
|
12619
12708
|
}
|
|
12620
12709
|
if (declaredBytes > PORTABLE_SKILL_MAX_TOTAL_BYTES) {
|
|
12621
|
-
throw new
|
|
12710
|
+
throw new HTTPException22(422, {
|
|
12622
12711
|
message: `Skill exceeds ${PORTABLE_SKILL_MAX_TOTAL_BYTES} bytes`
|
|
12623
12712
|
});
|
|
12624
12713
|
}
|
|
@@ -12628,7 +12717,7 @@ async function resolveSkillImport(rawUrl, client) {
|
|
|
12628
12717
|
try {
|
|
12629
12718
|
content = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
12630
12719
|
} catch {
|
|
12631
|
-
throw new
|
|
12720
|
+
throw new HTTPException22(422, {
|
|
12632
12721
|
message: `Skill file is not valid UTF-8 text: ${relativeSkillPath(entry.path, sourcePath)}`
|
|
12633
12722
|
});
|
|
12634
12723
|
}
|
|
@@ -12641,14 +12730,14 @@ function buildResolvedImport(parsed, files, sourcePath, commit) {
|
|
|
12641
12730
|
try {
|
|
12642
12731
|
artifact = buildPortableSkillArtifact2(files);
|
|
12643
12732
|
} catch (error) {
|
|
12644
|
-
throw new
|
|
12733
|
+
throw new HTTPException22(422, {
|
|
12645
12734
|
message: error instanceof Error ? error.message : "Skill artifact is invalid"
|
|
12646
12735
|
});
|
|
12647
12736
|
}
|
|
12648
12737
|
const repositoryUrl = `https://github.com/${parsed.owner}/${parsed.repository}`;
|
|
12649
12738
|
const sourceCommit = commit ?? artifact.contentSha256;
|
|
12650
12739
|
if (!commit && artifact.name.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "") !== parsed.skillSlug.toLowerCase()) {
|
|
12651
|
-
throw new
|
|
12740
|
+
throw new HTTPException22(422, {
|
|
12652
12741
|
message: "The downloaded skill does not match the requested name"
|
|
12653
12742
|
});
|
|
12654
12743
|
}
|
|
@@ -12705,10 +12794,10 @@ function parseSkillSource(rawUrl) {
|
|
|
12705
12794
|
try {
|
|
12706
12795
|
url = new URL(rawUrl);
|
|
12707
12796
|
} catch {
|
|
12708
|
-
throw new
|
|
12797
|
+
throw new HTTPException22(422, { message: "Enter a valid GitHub or skills.sh URL" });
|
|
12709
12798
|
}
|
|
12710
12799
|
if (url.protocol !== "https:" || url.username || url.password || url.hash) {
|
|
12711
|
-
throw new
|
|
12800
|
+
throw new HTTPException22(422, {
|
|
12712
12801
|
message: "Skill imports require a credential-free HTTPS URL without a fragment"
|
|
12713
12802
|
});
|
|
12714
12803
|
}
|
|
@@ -12716,18 +12805,18 @@ function parseSkillSource(rawUrl) {
|
|
|
12716
12805
|
try {
|
|
12717
12806
|
segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
|
|
12718
12807
|
} catch {
|
|
12719
|
-
throw new
|
|
12808
|
+
throw new HTTPException22(422, { message: "The Skill URL contains invalid encoding" });
|
|
12720
12809
|
}
|
|
12721
12810
|
if (url.hostname === "skills.sh" || url.hostname === "www.skills.sh") {
|
|
12722
12811
|
if (segments.length !== 3) {
|
|
12723
|
-
throw new
|
|
12812
|
+
throw new HTTPException22(422, {
|
|
12724
12813
|
message: "A skills.sh URL must identify one owner, repository, and Skill"
|
|
12725
12814
|
});
|
|
12726
12815
|
}
|
|
12727
12816
|
const [owner2, repository2, skillSlug] = segments;
|
|
12728
12817
|
assertGitHubRepository(owner2, repository2);
|
|
12729
12818
|
if (!skillSlug || !githubSegment.test(skillSlug)) {
|
|
12730
|
-
throw new
|
|
12819
|
+
throw new HTTPException22(422, { message: "The skills.sh Skill name is invalid" });
|
|
12731
12820
|
}
|
|
12732
12821
|
return {
|
|
12733
12822
|
source: "skills_sh",
|
|
@@ -12740,12 +12829,12 @@ function parseSkillSource(rawUrl) {
|
|
|
12740
12829
|
};
|
|
12741
12830
|
}
|
|
12742
12831
|
if (url.hostname !== "github.com" && url.hostname !== "www.github.com") {
|
|
12743
|
-
throw new
|
|
12832
|
+
throw new HTTPException22(422, {
|
|
12744
12833
|
message: "Only github.com and skills.sh imports are supported"
|
|
12745
12834
|
});
|
|
12746
12835
|
}
|
|
12747
12836
|
if (segments.length < 2) {
|
|
12748
|
-
throw new
|
|
12837
|
+
throw new HTTPException22(422, { message: "A GitHub URL must identify a repository" });
|
|
12749
12838
|
}
|
|
12750
12839
|
const owner = segments[0];
|
|
12751
12840
|
const repository = stripGitSuffix(segments[1]);
|
|
@@ -12763,15 +12852,15 @@ function parseSkillSource(rawUrl) {
|
|
|
12763
12852
|
}
|
|
12764
12853
|
const mode = segments[2];
|
|
12765
12854
|
if (mode !== "tree" && mode !== "blob") {
|
|
12766
|
-
throw new
|
|
12855
|
+
throw new HTTPException22(422, {
|
|
12767
12856
|
message: "Use a GitHub repository, tree, folder, or SKILL.md URL"
|
|
12768
12857
|
});
|
|
12769
12858
|
}
|
|
12770
12859
|
const ref = segments[3];
|
|
12771
|
-
if (!ref) throw new
|
|
12860
|
+
if (!ref) throw new HTTPException22(422, { message: "The GitHub URL is missing a revision" });
|
|
12772
12861
|
const pathSegments = segments.slice(4);
|
|
12773
12862
|
if (pathSegments.length === 0 && mode === "blob") {
|
|
12774
|
-
throw new
|
|
12863
|
+
throw new HTTPException22(422, { message: "The GitHub URL is missing a Skill folder" });
|
|
12775
12864
|
}
|
|
12776
12865
|
const folderSegments = mode === "blob" && pathSegments.at(-1)?.toLowerCase() === "skill.md" ? pathSegments.slice(0, -1) : pathSegments;
|
|
12777
12866
|
const requestedPath = folderSegments.length === 0 ? "." : normalizeGitHubPath(folderSegments);
|
|
@@ -12792,7 +12881,7 @@ async function selectSkillRoot(source, tree, readBlob, sourceCommit) {
|
|
|
12792
12881
|
if (source.requestedPath) {
|
|
12793
12882
|
const root = source.requestedPath;
|
|
12794
12883
|
if (!skillFiles.includes(root)) {
|
|
12795
|
-
throw new
|
|
12884
|
+
throw new HTTPException22(422, {
|
|
12796
12885
|
message: `No top-level SKILL.md was found in ${root}`
|
|
12797
12886
|
});
|
|
12798
12887
|
}
|
|
@@ -12801,7 +12890,7 @@ async function selectSkillRoot(source, tree, readBlob, sourceCommit) {
|
|
|
12801
12890
|
let candidates = skillFiles;
|
|
12802
12891
|
if (source.skillSlug) {
|
|
12803
12892
|
if (skillFiles.length > PORTABLE_SKILL_MAX_FILES) {
|
|
12804
|
-
throw new
|
|
12893
|
+
throw new HTTPException22(422, {
|
|
12805
12894
|
message: "Too many Skill candidates; paste the exact GitHub folder URL"
|
|
12806
12895
|
});
|
|
12807
12896
|
}
|
|
@@ -12810,14 +12899,14 @@ async function selectSkillRoot(source, tree, readBlob, sourceCommit) {
|
|
|
12810
12899
|
const matches = await mapConcurrent(skillFiles, maxConcurrentBlobReads, async (root) => {
|
|
12811
12900
|
const entry = entriesByPath.get(root === "." ? "SKILL.md" : `${root}/SKILL.md`);
|
|
12812
12901
|
if ((entry.size ?? 0) > PORTABLE_SKILL_MAX_FILE_BYTES) {
|
|
12813
|
-
throw new
|
|
12902
|
+
throw new HTTPException22(422, {
|
|
12814
12903
|
message: "Skill metadata is too large; paste the exact GitHub folder URL"
|
|
12815
12904
|
});
|
|
12816
12905
|
}
|
|
12817
12906
|
const bytes = await readBlob(entry.sha);
|
|
12818
12907
|
metadataBytes += bytes.byteLength;
|
|
12819
12908
|
if (bytes.byteLength > PORTABLE_SKILL_MAX_FILE_BYTES || metadataBytes > PORTABLE_SKILL_MAX_TOTAL_BYTES) {
|
|
12820
|
-
throw new
|
|
12909
|
+
throw new HTTPException22(422, {
|
|
12821
12910
|
message: "Skill metadata is too large; paste the exact GitHub folder URL"
|
|
12822
12911
|
});
|
|
12823
12912
|
}
|
|
@@ -12825,7 +12914,7 @@ async function selectSkillRoot(source, tree, readBlob, sourceCommit) {
|
|
|
12825
12914
|
try {
|
|
12826
12915
|
markdown = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
12827
12916
|
} catch {
|
|
12828
|
-
throw new
|
|
12917
|
+
throw new HTTPException22(422, {
|
|
12829
12918
|
message: "Skill metadata is not valid UTF-8; paste the exact GitHub folder URL"
|
|
12830
12919
|
});
|
|
12831
12920
|
}
|
|
@@ -12835,16 +12924,16 @@ async function selectSkillRoot(source, tree, readBlob, sourceCommit) {
|
|
|
12835
12924
|
if (candidates.length === 0) {
|
|
12836
12925
|
const folders = skillFiles.filter((path) => path.split("/").at(-1) === source.skillSlug);
|
|
12837
12926
|
const exactPath = folders.length === 1 ? encodeGitHubPath(folders[0]) : "<exact-skill-folder-path>";
|
|
12838
|
-
throw new
|
|
12927
|
+
throw new HTTPException22(422, {
|
|
12839
12928
|
message: `No Skill frontmatter name matches skills.sh slug "${source.skillSlug}"; the link may be stale. Check the current Skill name, or explicitly select the intended Skill using its exact GitHub folder URL: https://github.com/${source.owner}/${source.repository}/tree/${sourceCommit}/${exactPath}`
|
|
12840
12929
|
});
|
|
12841
12930
|
}
|
|
12842
12931
|
}
|
|
12843
12932
|
if (candidates.length === 0) {
|
|
12844
|
-
throw new
|
|
12933
|
+
throw new HTTPException22(422, { message: "No Skill folder with SKILL.md was found" });
|
|
12845
12934
|
}
|
|
12846
12935
|
if (candidates.length > 1) {
|
|
12847
|
-
throw new
|
|
12936
|
+
throw new HTTPException22(422, {
|
|
12848
12937
|
message: "This source contains multiple Skills; paste the exact GitHub folder URL"
|
|
12849
12938
|
});
|
|
12850
12939
|
}
|
|
@@ -12857,13 +12946,13 @@ function skillFilesUnderRoot(tree, root) {
|
|
|
12857
12946
|
(entry) => entry.type === "commit" || entry.type === "blob" && entry.mode === "120000"
|
|
12858
12947
|
);
|
|
12859
12948
|
if (unsupported) {
|
|
12860
|
-
throw new
|
|
12949
|
+
throw new HTTPException22(422, {
|
|
12861
12950
|
message: `Skill folders may not contain symbolic links or submodules (${unsupported.path})`
|
|
12862
12951
|
});
|
|
12863
12952
|
}
|
|
12864
12953
|
const files = inside.filter((entry) => entry.type === "blob").sort((left, right) => left.path.localeCompare(right.path));
|
|
12865
12954
|
if (files.length === 0) {
|
|
12866
|
-
throw new
|
|
12955
|
+
throw new HTTPException22(422, { message: "The selected Skill folder is empty" });
|
|
12867
12956
|
}
|
|
12868
12957
|
return files;
|
|
12869
12958
|
}
|
|
@@ -12874,7 +12963,7 @@ function normalizeGitHubPath(segments) {
|
|
|
12874
12963
|
if (segments.length === 0 || segments.some(
|
|
12875
12964
|
(segment) => segment.length === 0 || segment === "." || segment === ".." || segment.includes("\\") || /[\u0000-\u001f\u007f]/u.test(segment)
|
|
12876
12965
|
)) {
|
|
12877
|
-
throw new
|
|
12966
|
+
throw new HTTPException22(422, { message: "The GitHub Skill path is invalid" });
|
|
12878
12967
|
}
|
|
12879
12968
|
return segments.join("/");
|
|
12880
12969
|
}
|
|
@@ -12886,7 +12975,7 @@ function stripGitSuffix(value) {
|
|
|
12886
12975
|
}
|
|
12887
12976
|
function assertGitHubRepository(owner, repository) {
|
|
12888
12977
|
if (!githubSegment.test(owner) || !githubSegment.test(stripGitSuffix(repository))) {
|
|
12889
|
-
throw new
|
|
12978
|
+
throw new HTTPException22(422, { message: "The GitHub owner or repository name is invalid" });
|
|
12890
12979
|
}
|
|
12891
12980
|
}
|
|
12892
12981
|
async function mapConcurrent(values, concurrency, map) {
|
|
@@ -13057,7 +13146,7 @@ function parseRetryAfter(value) {
|
|
|
13057
13146
|
}
|
|
13058
13147
|
|
|
13059
13148
|
// src/domain/github-skill-source.ts
|
|
13060
|
-
import { HTTPException as
|
|
13149
|
+
import { HTTPException as HTTPException23 } from "hono/http-exception";
|
|
13061
13150
|
import { pinnedFetch as pinnedFetch3, readResponseJsonBounded as readResponseJsonBounded2, readResponseTextBounded } from "@opengeni/network";
|
|
13062
13151
|
import {
|
|
13063
13152
|
PORTABLE_SKILL_MAX_FILES as PORTABLE_SKILL_MAX_FILES2,
|
|
@@ -13167,7 +13256,7 @@ async function downloadSkillSnapshot(settings, owner, repository, slug) {
|
|
|
13167
13256
|
);
|
|
13168
13257
|
if (!response.ok) {
|
|
13169
13258
|
await response.body?.cancel();
|
|
13170
|
-
throw new
|
|
13259
|
+
throw new HTTPException23(response.status === 429 ? 429 : 422, {
|
|
13171
13260
|
message: response.status === 429 ? "skills.sh is busy. Try again shortly." : response.status === 404 ? "This skill has no downloadable snapshot. Import its GitHub folder URL instead." : "Could not download this skill from skills.sh. Try again shortly."
|
|
13172
13261
|
});
|
|
13173
13262
|
}
|
|
@@ -13181,11 +13270,11 @@ async function downloadSkillSnapshot(settings, owner, repository, slug) {
|
|
|
13181
13270
|
"Skill download"
|
|
13182
13271
|
);
|
|
13183
13272
|
if (!Array.isArray(payload.files) || payload.files.length === 0 || payload.files.length > PORTABLE_SKILL_MAX_FILES2) {
|
|
13184
|
-
throw new
|
|
13273
|
+
throw new HTTPException23(422, { message: "The downloaded skill has an invalid file list" });
|
|
13185
13274
|
}
|
|
13186
13275
|
return payload.files.map((file) => {
|
|
13187
13276
|
if (!file || typeof file !== "object" || typeof file.path !== "string" || typeof file.contents !== "string") {
|
|
13188
|
-
throw new
|
|
13277
|
+
throw new HTTPException23(422, { message: "The downloaded skill contains an invalid file" });
|
|
13189
13278
|
}
|
|
13190
13279
|
return { path: file.path, content: file.contents };
|
|
13191
13280
|
});
|
|
@@ -13212,7 +13301,7 @@ async function githubJson(settings, path, maxBytes, label) {
|
|
|
13212
13301
|
await readResponseTextBounded(response, 8192, `${label} error`).catch(() => void 0);
|
|
13213
13302
|
if (response.status === 404) throw new Error(`${label} was not found or is not public`);
|
|
13214
13303
|
if (response.status === 403 || response.status === 429) {
|
|
13215
|
-
throw new
|
|
13304
|
+
throw new HTTPException23(429, {
|
|
13216
13305
|
message: "GitHub's public request limit has been reached. Skill search is still available; try previewing again after the limit resets."
|
|
13217
13306
|
});
|
|
13218
13307
|
}
|
|
@@ -13256,7 +13345,7 @@ import {
|
|
|
13256
13345
|
RigChangeTransitionError,
|
|
13257
13346
|
updateScopedRig
|
|
13258
13347
|
} from "@opengeni/db";
|
|
13259
|
-
import { HTTPException as
|
|
13348
|
+
import { HTTPException as HTTPException24 } from "hono/http-exception";
|
|
13260
13349
|
import { boundedParallelMap } from "@opengeni/runtime/mcp-network";
|
|
13261
13350
|
var MAX_RIGS_PER_WORKSPACE = 50;
|
|
13262
13351
|
var MAX_CHECKS_PER_RIG = 100;
|
|
@@ -13295,21 +13384,21 @@ function rigActorForGrant(grant) {
|
|
|
13295
13384
|
async function requireRigForApi(db, grant, rigId) {
|
|
13296
13385
|
const rig = await getRig3(db, grant, rigId);
|
|
13297
13386
|
if (!rig) {
|
|
13298
|
-
throw new
|
|
13387
|
+
throw new HTTPException24(404, { message: "sandbox environment not found" });
|
|
13299
13388
|
}
|
|
13300
13389
|
return rig;
|
|
13301
13390
|
}
|
|
13302
13391
|
async function requireRigChangeForApi(db, workspaceId, rigId, changeId) {
|
|
13303
13392
|
const change = await getRigChange(db, workspaceId, changeId);
|
|
13304
13393
|
if (!change || change.rigId !== rigId) {
|
|
13305
|
-
throw new
|
|
13394
|
+
throw new HTTPException24(404, { message: "sandbox environment change not found" });
|
|
13306
13395
|
}
|
|
13307
13396
|
return change;
|
|
13308
13397
|
}
|
|
13309
13398
|
function trimmedRigName(name) {
|
|
13310
13399
|
const trimmed = name.trim();
|
|
13311
13400
|
if (!trimmed) {
|
|
13312
|
-
throw new
|
|
13401
|
+
throw new HTTPException24(422, { message: "sandbox environment name is required" });
|
|
13313
13402
|
}
|
|
13314
13403
|
return trimmed;
|
|
13315
13404
|
}
|
|
@@ -13320,7 +13409,7 @@ function assertUniqueCheckNames(checks) {
|
|
|
13320
13409
|
const seen = /* @__PURE__ */ new Set();
|
|
13321
13410
|
for (const check of checks) {
|
|
13322
13411
|
if (seen.has(check.name)) {
|
|
13323
|
-
throw new
|
|
13412
|
+
throw new HTTPException24(422, {
|
|
13324
13413
|
message: `duplicate sandbox environment check name: ${check.name}`
|
|
13325
13414
|
});
|
|
13326
13415
|
}
|
|
@@ -13340,12 +13429,12 @@ async function assertVariableSetsExist(db, access, rigScope, ids) {
|
|
|
13340
13429
|
for (const id of unique) {
|
|
13341
13430
|
const variableSet = await getVariableSet2(db, access, id);
|
|
13342
13431
|
if (!variableSet) {
|
|
13343
|
-
throw new
|
|
13432
|
+
throw new HTTPException24(422, {
|
|
13344
13433
|
message: `unknown defaultVariableSetId: ${id}`
|
|
13345
13434
|
});
|
|
13346
13435
|
}
|
|
13347
13436
|
if (!variableSetScopeAllowedForRig(rigScope, variableSet.scope)) {
|
|
13348
|
-
throw new
|
|
13437
|
+
throw new HTTPException24(422, {
|
|
13349
13438
|
message: `${rigScope}-scoped sandbox environments cannot use ${variableSet.scope}-scoped variable sets`
|
|
13350
13439
|
});
|
|
13351
13440
|
}
|
|
@@ -13364,12 +13453,12 @@ async function createRigForApi(deps, grant, payload, options = {}) {
|
|
|
13364
13453
|
payload.defaultVariableSetIds.length > 0 ? payload.defaultVariableSetIds : void 0
|
|
13365
13454
|
);
|
|
13366
13455
|
if (await countRigs(deps.db, grant, payload.scope) >= MAX_RIGS_PER_WORKSPACE) {
|
|
13367
|
-
throw new
|
|
13456
|
+
throw new HTTPException24(422, {
|
|
13368
13457
|
message: `a workspace supports at most ${MAX_RIGS_PER_WORKSPACE} sandbox environments`
|
|
13369
13458
|
});
|
|
13370
13459
|
}
|
|
13371
13460
|
if (await getRigByName(deps.db, grant, name, payload.scope)) {
|
|
13372
|
-
throw new
|
|
13461
|
+
throw new HTTPException24(409, {
|
|
13373
13462
|
message: `sandbox environment name is already in use: ${name}`
|
|
13374
13463
|
});
|
|
13375
13464
|
}
|
|
@@ -13405,7 +13494,7 @@ async function updateRigForApi(deps, grant, rig, payload, options = {}) {
|
|
|
13405
13494
|
if (name !== void 0 && name !== rig.name) {
|
|
13406
13495
|
const existing = await getRigByName(deps.db, grant, name, rig.scope);
|
|
13407
13496
|
if (existing && existing.id !== rig.id) {
|
|
13408
|
-
throw new
|
|
13497
|
+
throw new HTTPException24(409, {
|
|
13409
13498
|
message: `sandbox environment name is already in use: ${name}`
|
|
13410
13499
|
});
|
|
13411
13500
|
}
|
|
@@ -13431,7 +13520,7 @@ async function deleteRigForApi(deps, grant, rig, options = {}) {
|
|
|
13431
13520
|
let current = error;
|
|
13432
13521
|
while (current instanceof Error) {
|
|
13433
13522
|
if (current.message.includes("active sessions")) {
|
|
13434
|
-
throw new
|
|
13523
|
+
throw new HTTPException24(409, { message: current.message });
|
|
13435
13524
|
}
|
|
13436
13525
|
current = current.cause;
|
|
13437
13526
|
}
|
|
@@ -13446,7 +13535,7 @@ async function deleteRigForApi(deps, grant, rig, options = {}) {
|
|
|
13446
13535
|
async function proposeRigChangeForApi(deps, grant, rig, request, options = {}) {
|
|
13447
13536
|
const workspaceId = rig.workspaceId;
|
|
13448
13537
|
if (!rig.activeVersion) {
|
|
13449
|
-
throw new
|
|
13538
|
+
throw new HTTPException24(422, {
|
|
13450
13539
|
message: "sandbox environment has no active version to base a change on"
|
|
13451
13540
|
});
|
|
13452
13541
|
}
|
|
@@ -13498,37 +13587,37 @@ async function promoteChangeWithActiveCas(deps, workspaceId, rigId, changeId, in
|
|
|
13498
13587
|
return await createRigVersionForChangePromotion(deps.db, workspaceId, rigId, changeId, input);
|
|
13499
13588
|
} catch (error) {
|
|
13500
13589
|
if (error instanceof RigActiveVersionChangedError) {
|
|
13501
|
-
throw new
|
|
13590
|
+
throw new HTTPException24(409, {
|
|
13502
13591
|
message: `sandbox environment moved since this change was verified (base ${error.expectedVersionId}, now ${error.actualVersionId ?? "none"}); re-verify before promoting`
|
|
13503
13592
|
});
|
|
13504
13593
|
}
|
|
13505
13594
|
if (error instanceof RigChangeTransitionError) {
|
|
13506
|
-
throw new
|
|
13595
|
+
throw new HTTPException24(409, { message: error.message });
|
|
13507
13596
|
}
|
|
13508
13597
|
throw error;
|
|
13509
13598
|
}
|
|
13510
13599
|
}
|
|
13511
13600
|
async function promoteSetupAppendChange(deps, grant, rig, change) {
|
|
13512
13601
|
if (change.kind !== "setup_append") {
|
|
13513
|
-
throw new
|
|
13602
|
+
throw new HTTPException24(422, {
|
|
13514
13603
|
message: "only setup_append changes auto-promote through this path"
|
|
13515
13604
|
});
|
|
13516
13605
|
}
|
|
13517
13606
|
if (change.status !== "proposed" && change.status !== "verifying") {
|
|
13518
|
-
throw new
|
|
13607
|
+
throw new HTTPException24(409, {
|
|
13519
13608
|
message: `sandbox environment change is ${change.status}; cannot promote`
|
|
13520
13609
|
});
|
|
13521
13610
|
}
|
|
13522
13611
|
if (!change.baseVersionId) {
|
|
13523
|
-
throw new
|
|
13612
|
+
throw new HTTPException24(422, { message: "sandbox environment change has no base version" });
|
|
13524
13613
|
}
|
|
13525
13614
|
const base = await getRigVersion2(deps.db, rig.workspaceId, rig.id, change.baseVersionId);
|
|
13526
13615
|
if (!base) {
|
|
13527
|
-
throw new
|
|
13616
|
+
throw new HTTPException24(404, { message: "base sandbox environment version not found" });
|
|
13528
13617
|
}
|
|
13529
13618
|
const payload = change.payload;
|
|
13530
13619
|
if (typeof payload.command !== "string" || !payload.command.trim()) {
|
|
13531
|
-
throw new
|
|
13620
|
+
throw new HTTPException24(422, {
|
|
13532
13621
|
message: "setup_append change is missing command"
|
|
13533
13622
|
});
|
|
13534
13623
|
}
|
|
@@ -13576,26 +13665,26 @@ async function promoteSetupAppendChange(deps, grant, rig, change) {
|
|
|
13576
13665
|
}
|
|
13577
13666
|
async function promoteVerifiedDefinitionEditChangeForApi(deps, grant, rig, change) {
|
|
13578
13667
|
if (change.kind !== "definition_edit") {
|
|
13579
|
-
throw new
|
|
13668
|
+
throw new HTTPException24(422, {
|
|
13580
13669
|
message: "only definition_edit changes use explicit promote"
|
|
13581
13670
|
});
|
|
13582
13671
|
}
|
|
13583
13672
|
if (change.status !== "proposed") {
|
|
13584
|
-
throw new
|
|
13673
|
+
throw new HTTPException24(409, {
|
|
13585
13674
|
message: `sandbox environment change is ${change.status}; cannot promote`
|
|
13586
13675
|
});
|
|
13587
13676
|
}
|
|
13588
13677
|
if (change.verification?.passed !== true) {
|
|
13589
|
-
throw new
|
|
13678
|
+
throw new HTTPException24(422, {
|
|
13590
13679
|
message: "definition_edit change must pass verification before promote"
|
|
13591
13680
|
});
|
|
13592
13681
|
}
|
|
13593
13682
|
if (!change.baseVersionId) {
|
|
13594
|
-
throw new
|
|
13683
|
+
throw new HTTPException24(422, { message: "sandbox environment change has no base version" });
|
|
13595
13684
|
}
|
|
13596
13685
|
const base = await getRigVersion2(deps.db, rig.workspaceId, rig.id, change.baseVersionId);
|
|
13597
13686
|
if (!base) {
|
|
13598
|
-
throw new
|
|
13687
|
+
throw new HTTPException24(404, { message: "base sandbox environment version not found" });
|
|
13599
13688
|
}
|
|
13600
13689
|
const payload = change.payload;
|
|
13601
13690
|
const nextDefinition = {
|
|
@@ -13642,7 +13731,7 @@ async function promoteVerifiedDefinitionEditChangeForApi(deps, grant, rig, chang
|
|
|
13642
13731
|
}
|
|
13643
13732
|
async function createRigVersionForApi(deps, grant, rig, payload) {
|
|
13644
13733
|
if (!rig.activeVersion) {
|
|
13645
|
-
throw new
|
|
13734
|
+
throw new HTTPException24(422, { message: "sandbox environment has no active version" });
|
|
13646
13735
|
}
|
|
13647
13736
|
assertUniqueCheckNames(payload.checks);
|
|
13648
13737
|
await assertVariableSetsExist(
|
|
@@ -13703,7 +13792,7 @@ import {
|
|
|
13703
13792
|
readTurnExecutionPolicyV1 as readTurnExecutionPolicyV13
|
|
13704
13793
|
} from "@opengeni/contracts";
|
|
13705
13794
|
import { getSession as getSession4, getSessionTurnForAttempt as getSessionTurnForAttempt4 } from "@opengeni/db";
|
|
13706
|
-
import { HTTPException as
|
|
13795
|
+
import { HTTPException as HTTPException25 } from "hono/http-exception";
|
|
13707
13796
|
|
|
13708
13797
|
// src/domain/pr-review.ts
|
|
13709
13798
|
import { createHash as createHash4, createHmac as createHmac2, timingSafeEqual } from "crypto";
|
|
@@ -14234,7 +14323,7 @@ async function automationCredentialRestrictionForGrant(db, grant) {
|
|
|
14234
14323
|
);
|
|
14235
14324
|
const session = await getSession4(db, grant.workspaceId, actor.sessionId);
|
|
14236
14325
|
if (!turn || turn.id !== actor.turnId || !session || session.accountId !== grant.accountId) {
|
|
14237
|
-
throw new
|
|
14326
|
+
throw new HTTPException25(403, {
|
|
14238
14327
|
message: "the creating automation agent attempt is not available"
|
|
14239
14328
|
});
|
|
14240
14329
|
}
|
|
@@ -14243,7 +14332,7 @@ async function automationCredentialRestrictionForGrant(db, grant) {
|
|
|
14243
14332
|
try {
|
|
14244
14333
|
policy = readTurnExecutionPolicyV13(metadata);
|
|
14245
14334
|
} catch {
|
|
14246
|
-
throw new
|
|
14335
|
+
throw new HTTPException25(409, {
|
|
14247
14336
|
message: "The frozen automation creator credential ceiling is invalid"
|
|
14248
14337
|
});
|
|
14249
14338
|
}
|
|
@@ -14259,7 +14348,7 @@ function requireAutomationCredentialRestrictionPermissions(restriction, permissi
|
|
|
14259
14348
|
(permission) => !isDeveloperSetupDelegatedPermissionAllowed(permission)
|
|
14260
14349
|
);
|
|
14261
14350
|
if (forbidden) {
|
|
14262
|
-
throw new
|
|
14351
|
+
throw new HTTPException25(403, {
|
|
14263
14352
|
message: `Developer setup cannot delegate automation permission: ${forbidden}`
|
|
14264
14353
|
});
|
|
14265
14354
|
}
|
|
@@ -14725,7 +14814,7 @@ import {
|
|
|
14725
14814
|
withWorkspaceSubjectRls as withWorkspaceSubjectRls5,
|
|
14726
14815
|
resolveXaiProviderAccountAuthoritySnapshotForAcceptance
|
|
14727
14816
|
} from "@opengeni/db";
|
|
14728
|
-
import { HTTPException as
|
|
14817
|
+
import { HTTPException as HTTPException26 } from "hono/http-exception";
|
|
14729
14818
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
14730
14819
|
function scheduledTaskToolsProvided(rawPayload) {
|
|
14731
14820
|
if (!rawPayload || typeof rawPayload !== "object") {
|
|
@@ -14746,9 +14835,7 @@ function workspaceCustomModelCommitGuard(input) {
|
|
|
14746
14835
|
reference
|
|
14747
14836
|
});
|
|
14748
14837
|
if (!active) {
|
|
14749
|
-
throw
|
|
14750
|
-
message: `model is not available: ${input.modelId}`
|
|
14751
|
-
});
|
|
14838
|
+
throw modelUnavailableHttpException(input.modelId);
|
|
14752
14839
|
}
|
|
14753
14840
|
};
|
|
14754
14841
|
}
|
|
@@ -14796,7 +14883,7 @@ async function createValidatedScheduledTask(input) {
|
|
|
14796
14883
|
"scheduled_tasks:manage"
|
|
14797
14884
|
) : null;
|
|
14798
14885
|
if (learning && learningContext?.actor.kind !== "human") {
|
|
14799
|
-
throw new
|
|
14886
|
+
throw new HTTPException26(403, {
|
|
14800
14887
|
message: "Only an authenticated person can configure Agent learning"
|
|
14801
14888
|
});
|
|
14802
14889
|
}
|
|
@@ -14826,7 +14913,7 @@ async function createValidatedScheduledTask(input) {
|
|
|
14826
14913
|
verifySlackChannel: input.verifySlackChannel
|
|
14827
14914
|
});
|
|
14828
14915
|
if (knowledgeAction && input.payload.overlapPolicy === "allow_concurrent")
|
|
14829
|
-
throw new
|
|
14916
|
+
throw new HTTPException26(422, { message: "Source tasks require skip or buffer_one overlap" });
|
|
14830
14917
|
const id = crypto.randomUUID();
|
|
14831
14918
|
validateScheduledTaskSchedule(input.payload.schedule);
|
|
14832
14919
|
const target = await validateScheduledTaskTarget({
|
|
@@ -14843,14 +14930,14 @@ async function createValidatedScheduledTask(input) {
|
|
|
14843
14930
|
agentConfig
|
|
14844
14931
|
});
|
|
14845
14932
|
if (knowledgeAction?.destination.kind === "personal" && input.payload.runMode !== "new_session_per_run")
|
|
14846
|
-
throw new
|
|
14933
|
+
throw new HTTPException26(422, {
|
|
14847
14934
|
message: "Personal source tasks create a private session for each run"
|
|
14848
14935
|
});
|
|
14849
14936
|
if (learning) {
|
|
14850
14937
|
const workspace = await requireWorkspace6(input.db, input.grant.workspaceId);
|
|
14851
14938
|
const destination = knowledgeAction?.destination.kind === "personal" || workspace.kind === "personal" || target?.tenancy?.visibility === "private" || target?.memoryScope === "user" ? "personal" : "workspace";
|
|
14852
14939
|
if (learning.scope !== destination)
|
|
14853
|
-
throw new
|
|
14940
|
+
throw new HTTPException26(422, {
|
|
14854
14941
|
message: `This task saves Knowledge in the ${destination} scope. Set its Agent learning override for that scope.`
|
|
14855
14942
|
});
|
|
14856
14943
|
}
|
|
@@ -15016,7 +15103,7 @@ async function frozenScheduledTaskCreatorPolicy(input) {
|
|
|
15016
15103
|
}
|
|
15017
15104
|
const session = await getSession5(input.db, input.grant.workspaceId, input.actor.sessionId);
|
|
15018
15105
|
if (!session) {
|
|
15019
|
-
throw new
|
|
15106
|
+
throw new HTTPException26(403, {
|
|
15020
15107
|
message: "the calling agent session is not available in this workspace"
|
|
15021
15108
|
});
|
|
15022
15109
|
}
|
|
@@ -15027,7 +15114,7 @@ async function frozenScheduledTaskCreatorPolicy(input) {
|
|
|
15027
15114
|
input.actor.attemptId
|
|
15028
15115
|
);
|
|
15029
15116
|
if (!turn || turn.id !== input.actor.turnId) {
|
|
15030
|
-
throw new
|
|
15117
|
+
throw new HTTPException26(403, { message: "the calling agent attempt is not available" });
|
|
15031
15118
|
}
|
|
15032
15119
|
const turnPolicy = readTurnExecutionPolicyV14(turn.metadata);
|
|
15033
15120
|
const sessionPolicy = readTurnExecutionPolicyV14(session.metadata);
|
|
@@ -15040,7 +15127,7 @@ async function frozenScheduledTaskCreatorPolicy(input) {
|
|
|
15040
15127
|
);
|
|
15041
15128
|
const firstPartyMcpPermissions = (session.firstPartyMcpPermissions ?? [...DEFAULT_FIRST_PARTY_MCP_PERMISSIONS3]).filter((permission) => hasPermission(input.grant.permissions, permission));
|
|
15042
15129
|
if (firstPartyMcpPermissions.length === 0) {
|
|
15043
|
-
throw new
|
|
15130
|
+
throw new HTTPException26(403, {
|
|
15044
15131
|
message: "the calling agent session holds no first-party MCP permission it could delegate to scheduled runs"
|
|
15045
15132
|
});
|
|
15046
15133
|
}
|
|
@@ -15083,16 +15170,16 @@ async function withScheduledTaskAuthorityWriteErrors(run) {
|
|
|
15083
15170
|
return await run();
|
|
15084
15171
|
} catch (error) {
|
|
15085
15172
|
if (error instanceof ScheduledTaskHeadChangedError)
|
|
15086
|
-
throw new
|
|
15173
|
+
throw new HTTPException26(409, {
|
|
15087
15174
|
message: "Scheduled task changed. Reload it before saving."
|
|
15088
15175
|
});
|
|
15089
15176
|
if (nestedPostgresSqlState6(error) === "40001")
|
|
15090
|
-
throw new
|
|
15177
|
+
throw new HTTPException26(409, {
|
|
15091
15178
|
message: "Scheduled task learning settings changed. Reload the task before saving."
|
|
15092
15179
|
});
|
|
15093
15180
|
if (nestedPostgresSqlState6(error) === "42501") {
|
|
15094
15181
|
const detail = nestedPostgresMessage(error);
|
|
15095
|
-
throw new
|
|
15182
|
+
throw new HTTPException26(409, {
|
|
15096
15183
|
message: detail ? `scheduled task authority denied: ${detail}` : "scheduled task authority denied"
|
|
15097
15184
|
});
|
|
15098
15185
|
}
|
|
@@ -15116,7 +15203,7 @@ async function updateScheduledTaskForApi(db, grant, taskId, update, learning) {
|
|
|
15116
15203
|
"scheduled_tasks:manage"
|
|
15117
15204
|
);
|
|
15118
15205
|
if (context.actor.kind !== "human")
|
|
15119
|
-
throw new
|
|
15206
|
+
throw new HTTPException26(403, {
|
|
15120
15207
|
message: "Only an authenticated person can configure Agent learning"
|
|
15121
15208
|
});
|
|
15122
15209
|
context.actor.settingsScopes = [learning.request.scope];
|
|
@@ -15138,7 +15225,7 @@ async function updateScheduledTaskForApi(db, grant, taskId, update, learning) {
|
|
|
15138
15225
|
);
|
|
15139
15226
|
const baseline = await getAgentLearningSettings(tx, context, baselineScope, source);
|
|
15140
15227
|
if (baseline.version !== learning.request.expectedVersion)
|
|
15141
|
-
throw new
|
|
15228
|
+
throw new HTTPException26(409, {
|
|
15142
15229
|
message: "Scheduled task learning settings changed. Reload the task before saving."
|
|
15143
15230
|
});
|
|
15144
15231
|
const task = await updateScheduledTask(tx, workspaceId, taskId, update);
|
|
@@ -15179,7 +15266,7 @@ async function assertScheduledTaskMutationOwner(db, grant, taskId) {
|
|
|
15179
15266
|
createdByActor: writer.actor ?? null
|
|
15180
15267
|
});
|
|
15181
15268
|
if (!matches)
|
|
15182
|
-
throw new
|
|
15269
|
+
throw new HTTPException26(403, {
|
|
15183
15270
|
message: "Only the schedule owner can change or run it. Copy it to create your own schedule."
|
|
15184
15271
|
});
|
|
15185
15272
|
}
|
|
@@ -15206,7 +15293,7 @@ async function scheduledTaskCredentialRestrictionForGrant(db, grant, actor) {
|
|
|
15206
15293
|
actor.attemptId
|
|
15207
15294
|
);
|
|
15208
15295
|
if (!session || !turn || turn.id !== actor.turnId) {
|
|
15209
|
-
throw new
|
|
15296
|
+
throw new HTTPException26(403, { message: "the calling agent attempt is not available" });
|
|
15210
15297
|
}
|
|
15211
15298
|
const policies = [
|
|
15212
15299
|
readTurnExecutionPolicyV14(turn.metadata),
|
|
@@ -15219,20 +15306,20 @@ async function scheduledTaskCredentialRestrictionForGrant(db, grant, actor) {
|
|
|
15219
15306
|
async function validateScheduledTaskTarget(input) {
|
|
15220
15307
|
if (input.runMode !== "existing_session") {
|
|
15221
15308
|
if (input.targetSessionId) {
|
|
15222
|
-
throw new
|
|
15309
|
+
throw new HTTPException26(422, {
|
|
15223
15310
|
message: "targetSessionId requires runMode=existing_session"
|
|
15224
15311
|
});
|
|
15225
15312
|
}
|
|
15226
15313
|
return null;
|
|
15227
15314
|
}
|
|
15228
15315
|
if (!input.targetSessionId) {
|
|
15229
|
-
throw new
|
|
15316
|
+
throw new HTTPException26(input.missingTargetStatus ?? 422, {
|
|
15230
15317
|
message: input.missingTargetStatus === 404 ? "target session not found" : "targetSessionId is required when runMode=existing_session"
|
|
15231
15318
|
});
|
|
15232
15319
|
}
|
|
15233
15320
|
requirePermission(input.grant, "sessions:control");
|
|
15234
15321
|
if (input.agentConfig.goal) {
|
|
15235
|
-
throw new
|
|
15322
|
+
throw new HTTPException26(422, {
|
|
15236
15323
|
message: "agentConfig.goal cannot be used with an existing-session target"
|
|
15237
15324
|
});
|
|
15238
15325
|
}
|
|
@@ -15251,44 +15338,44 @@ async function validateScheduledTaskTarget(input) {
|
|
|
15251
15338
|
);
|
|
15252
15339
|
} catch (error) {
|
|
15253
15340
|
if (error instanceof SessionAuthorizationDeniedError) {
|
|
15254
|
-
throw new
|
|
15341
|
+
throw new HTTPException26(404, { message: "target session not found" });
|
|
15255
15342
|
}
|
|
15256
15343
|
if (error instanceof SessionAuthorizationUnavailableError) {
|
|
15257
|
-
throw new
|
|
15344
|
+
throw new HTTPException26(503, { message: "session authorization is unavailable" });
|
|
15258
15345
|
}
|
|
15259
15346
|
throw error;
|
|
15260
15347
|
}
|
|
15261
15348
|
const session = await getSession5(input.db, input.grant.workspaceId, input.targetSessionId);
|
|
15262
15349
|
if (!session || session.accountId !== input.grant.accountId) {
|
|
15263
|
-
throw new
|
|
15350
|
+
throw new HTTPException26(404, { message: "target session not found" });
|
|
15264
15351
|
}
|
|
15265
15352
|
if (input.agentConfig.bundledSkillIds !== void 0 && !isDeepStrictEqual2(input.agentConfig.bundledSkillIds, session.bundledSkillIds)) {
|
|
15266
|
-
throw new
|
|
15353
|
+
throw new HTTPException26(422, {
|
|
15267
15354
|
message: "An existing-session schedule cannot change that session's bundled Skill selection"
|
|
15268
15355
|
});
|
|
15269
15356
|
}
|
|
15270
15357
|
if (session.status === "cancelled") {
|
|
15271
|
-
throw new
|
|
15358
|
+
throw new HTTPException26(409, {
|
|
15272
15359
|
message: "target session is cancelled; choose a revivable session"
|
|
15273
15360
|
});
|
|
15274
15361
|
}
|
|
15275
15362
|
if ((session.variableSetId ?? null) !== (input.variableSetId ?? null)) {
|
|
15276
|
-
throw new
|
|
15363
|
+
throw new HTTPException26(422, {
|
|
15277
15364
|
message: "target session variableSet attachment does not match the scheduled task"
|
|
15278
15365
|
});
|
|
15279
15366
|
}
|
|
15280
15367
|
if (!input.adoptTargetRig && (session.rigId ?? null) !== (input.rigId ?? null)) {
|
|
15281
|
-
throw new
|
|
15368
|
+
throw new HTTPException26(422, {
|
|
15282
15369
|
message: "target session sandbox environment does not match the scheduled task"
|
|
15283
15370
|
});
|
|
15284
15371
|
}
|
|
15285
15372
|
if (input.agentConfig.sandboxBackend !== void 0 && input.agentConfig.sandboxBackend !== session.sandboxBackend) {
|
|
15286
|
-
throw new
|
|
15373
|
+
throw new HTTPException26(422, {
|
|
15287
15374
|
message: "target session sandbox backend does not match the scheduled task"
|
|
15288
15375
|
});
|
|
15289
15376
|
}
|
|
15290
15377
|
if (scheduledSlackBotConnectionId(session.metadata) !== (input.agentConfig.slackBotConnectionId ?? null)) {
|
|
15291
|
-
throw new
|
|
15378
|
+
throw new HTTPException26(422, {
|
|
15292
15379
|
message: "target session OpenGeni Slack bot binding does not match the scheduled task"
|
|
15293
15380
|
});
|
|
15294
15381
|
}
|
|
@@ -15298,19 +15385,19 @@ async function validateScheduledTaskMachineTarget(input) {
|
|
|
15298
15385
|
const machineTarget = input.agentConfig.machineTarget;
|
|
15299
15386
|
if (!machineTarget) {
|
|
15300
15387
|
if (input.runMode !== "existing_session" && (input.agentConfig.sandboxBackend ?? input.settings.sandboxBackend) === "selfhosted") {
|
|
15301
|
-
throw new
|
|
15388
|
+
throw new HTTPException26(422, {
|
|
15302
15389
|
message: "self-hosted scheduled tasks require a Connected Machine; select a machine before saving"
|
|
15303
15390
|
});
|
|
15304
15391
|
}
|
|
15305
15392
|
return null;
|
|
15306
15393
|
}
|
|
15307
15394
|
if (input.runMode === "existing_session") {
|
|
15308
|
-
throw new
|
|
15395
|
+
throw new HTTPException26(422, {
|
|
15309
15396
|
message: "machineTarget cannot be used with an existing-session target"
|
|
15310
15397
|
});
|
|
15311
15398
|
}
|
|
15312
15399
|
if (!input.settings.sandboxOwnershipEnabled || !input.settings.sandboxSelfhostedEnabled) {
|
|
15313
|
-
throw new
|
|
15400
|
+
throw new HTTPException26(422, {
|
|
15314
15401
|
message: "Connected Machines are not enabled for scheduled tasks in this deployment"
|
|
15315
15402
|
});
|
|
15316
15403
|
}
|
|
@@ -15321,23 +15408,23 @@ async function validateScheduledTaskMachineTarget(input) {
|
|
|
15321
15408
|
};
|
|
15322
15409
|
const sandbox = await getSandbox5(input.db, access, machineTarget.targetSandboxId);
|
|
15323
15410
|
if (!sandbox || sandbox.kind !== "selfhosted" || !sandbox.enrollmentId) {
|
|
15324
|
-
throw new
|
|
15411
|
+
throw new HTTPException26(422, {
|
|
15325
15412
|
message: "the selected Connected Machine is unavailable"
|
|
15326
15413
|
});
|
|
15327
15414
|
}
|
|
15328
15415
|
if (sandbox.scope === "user") {
|
|
15329
|
-
throw new
|
|
15416
|
+
throw new HTTPException26(422, {
|
|
15330
15417
|
message: "personal Connected Machines cannot run unattended schedules; select a workspace or organization machine"
|
|
15331
15418
|
});
|
|
15332
15419
|
}
|
|
15333
15420
|
const enrollment = input.requireOnline ? await getLiveEnrollmentConnection3(input.db, access, sandbox.enrollmentId) : await getEnrollment3(input.db, access, sandbox.enrollmentId);
|
|
15334
15421
|
if (!enrollment || enrollment.status !== "active") {
|
|
15335
|
-
throw new
|
|
15422
|
+
throw new HTTPException26(422, {
|
|
15336
15423
|
message: input.requireOnline ? "the selected Connected Machine is offline" : "the selected Connected Machine is unavailable"
|
|
15337
15424
|
});
|
|
15338
15425
|
}
|
|
15339
15426
|
if (input.requireOnline && !enrollment.workspaceRoot) {
|
|
15340
|
-
throw new
|
|
15427
|
+
throw new HTTPException26(422, {
|
|
15341
15428
|
message: "the selected Connected Machine has not reported a workspace root; reconnect it with a current agent"
|
|
15342
15429
|
});
|
|
15343
15430
|
}
|
|
@@ -15406,7 +15493,7 @@ async function requireScheduledTaskRigVariableSetAttachments(input) {
|
|
|
15406
15493
|
async function requireScheduledTaskRig(db, access, rigId) {
|
|
15407
15494
|
const rig = await getRig4(db, access, rigId);
|
|
15408
15495
|
if (!rig) {
|
|
15409
|
-
throw new
|
|
15496
|
+
throw new HTTPException26(422, { message: `unknown rigId: ${rigId}` });
|
|
15410
15497
|
}
|
|
15411
15498
|
return rig;
|
|
15412
15499
|
}
|
|
@@ -15418,7 +15505,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
15418
15505
|
"scheduled_tasks:manage"
|
|
15419
15506
|
) : null;
|
|
15420
15507
|
if (context?.actor.kind !== "human")
|
|
15421
|
-
throw new
|
|
15508
|
+
throw new HTTPException26(403, {
|
|
15422
15509
|
message: "Only an authenticated person can configure Agent learning"
|
|
15423
15510
|
});
|
|
15424
15511
|
}
|
|
@@ -15438,7 +15525,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
15438
15525
|
};
|
|
15439
15526
|
}
|
|
15440
15527
|
if (input.payload.action && input.payload.action.kind !== "agent_turn") {
|
|
15441
|
-
throw new
|
|
15528
|
+
throw new HTTPException26(422, { message: "Source ingestion uses an ordinary agent task" });
|
|
15442
15529
|
}
|
|
15443
15530
|
const knowledgeSource = input.payload.agentConfig?.knowledgeSource ?? scheduledTaskKnowledgeSource(input.existing);
|
|
15444
15531
|
if (knowledgeSource && requestedKnowledgeSource && !isDeepStrictEqual2(requestedKnowledgeSource, existingKnowledgeSource)) {
|
|
@@ -15449,16 +15536,16 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
15449
15536
|
});
|
|
15450
15537
|
}
|
|
15451
15538
|
if (knowledgeSource && (input.payload.overlapPolicy ?? input.existing.overlapPolicy) === "allow_concurrent")
|
|
15452
|
-
throw new
|
|
15539
|
+
throw new HTTPException26(422, { message: "Source tasks require skip or buffer_one overlap" });
|
|
15453
15540
|
if (knowledgeSource?.destination.kind === "personal" && (input.payload.runMode ?? input.existing.runMode) !== "new_session_per_run")
|
|
15454
|
-
throw new
|
|
15541
|
+
throw new HTTPException26(422, {
|
|
15455
15542
|
message: "Personal source tasks create a private session for each run"
|
|
15456
15543
|
});
|
|
15457
15544
|
const existingTarget = input.existing.targetSessionId;
|
|
15458
15545
|
const nextRunMode = input.payload.runMode ?? input.existing.runMode;
|
|
15459
15546
|
const nextTargetSessionId = input.payload.targetSessionId !== void 0 ? input.payload.targetSessionId : nextRunMode === "existing_session" ? existingTarget : null;
|
|
15460
15547
|
if (input.existing.runMode === "reusable_session" && input.existing.reusableSessionId && nextRunMode === "existing_session") {
|
|
15461
|
-
throw new
|
|
15548
|
+
throw new HTTPException26(409, {
|
|
15462
15549
|
message: "cannot target an existing session after this task created its reusable session; create a new task"
|
|
15463
15550
|
});
|
|
15464
15551
|
}
|
|
@@ -15484,7 +15571,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
15484
15571
|
if (input.payload.variableSetId !== void 0) {
|
|
15485
15572
|
const nextVariableSetId = input.payload.variableSetId;
|
|
15486
15573
|
if ((input.existing.variableSetId ?? null) !== (nextVariableSetId ?? null) && input.existing.runMode === "reusable_session" && input.existing.reusableSessionId) {
|
|
15487
|
-
throw new
|
|
15574
|
+
throw new HTTPException26(409, {
|
|
15488
15575
|
message: "cannot change variableSet of a task with a live reusable session; recreate the task"
|
|
15489
15576
|
});
|
|
15490
15577
|
}
|
|
@@ -15505,7 +15592,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
15505
15592
|
}
|
|
15506
15593
|
if (input.payload.rigId !== void 0) {
|
|
15507
15594
|
if (input.existing.runMode === "reusable_session" && input.existing.reusableSessionId !== null && input.payload.rigId !== input.existing.rigId) {
|
|
15508
|
-
throw new
|
|
15595
|
+
throw new HTTPException26(409, {
|
|
15509
15596
|
message: "A reusable-session task cannot change rigId after materialization; recreate it"
|
|
15510
15597
|
});
|
|
15511
15598
|
}
|
|
@@ -15580,7 +15667,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
15580
15667
|
...input.toolsProvided !== void 0 ? { toolsProvided: input.toolsProvided } : {}
|
|
15581
15668
|
});
|
|
15582
15669
|
if (input.existing.reusableSessionId && input.existing.runMode === "reusable_session" && (input.existing.agentConfig.slackBotConnectionId ?? null) !== (nextAgentConfig2.slackBotConnectionId ?? null)) {
|
|
15583
|
-
throw new
|
|
15670
|
+
throw new HTTPException26(409, {
|
|
15584
15671
|
message: "cannot change the OpenGeni Slack bot connection of a task with a live reusable session; recreate the task"
|
|
15585
15672
|
});
|
|
15586
15673
|
}
|
|
@@ -15628,7 +15715,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
15628
15715
|
input.existing.id
|
|
15629
15716
|
);
|
|
15630
15717
|
if (existingXaiAuthority.scope === "user" && materialExecutionChange && (input.existing.createdBy.kind !== "subject" || input.existing.createdBy.subjectId !== input.grant.subjectId)) {
|
|
15631
|
-
throw new
|
|
15718
|
+
throw new HTTPException26(409, {
|
|
15632
15719
|
message: "changing a user-scoped xAI scheduled task requires the same causal human"
|
|
15633
15720
|
});
|
|
15634
15721
|
}
|
|
@@ -15753,7 +15840,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
15753
15840
|
async function requireScheduledTaskForApi(db, workspaceId, taskId) {
|
|
15754
15841
|
const task = await getScheduledTask(db, workspaceId, taskId);
|
|
15755
15842
|
if (!task) {
|
|
15756
|
-
throw new
|
|
15843
|
+
throw new HTTPException26(404, { message: "scheduled task not found" });
|
|
15757
15844
|
}
|
|
15758
15845
|
return task;
|
|
15759
15846
|
}
|
|
@@ -15779,12 +15866,12 @@ async function restoreScheduledTask(db, previous) {
|
|
|
15779
15866
|
}
|
|
15780
15867
|
async function validateKnowledgeSourceSyncAction(input) {
|
|
15781
15868
|
if (input.action.initiatingSubjectId !== input.grant.subjectId) {
|
|
15782
|
-
throw new
|
|
15869
|
+
throw new HTTPException26(403, {
|
|
15783
15870
|
message: "knowledge source sync must preserve the exact initiating subject"
|
|
15784
15871
|
});
|
|
15785
15872
|
}
|
|
15786
15873
|
if (input.action.connection.ownerSubjectId !== input.grant.subjectId) {
|
|
15787
|
-
throw new
|
|
15874
|
+
throw new HTTPException26(403, {
|
|
15788
15875
|
message: "knowledge source connection must belong to the initiating subject"
|
|
15789
15876
|
});
|
|
15790
15877
|
}
|
|
@@ -15795,10 +15882,10 @@ async function validateKnowledgeSourceSyncAction(input) {
|
|
|
15795
15882
|
initiatingSubjectId: input.grant.subjectId
|
|
15796
15883
|
});
|
|
15797
15884
|
if (!resolved || resolved.source.lifecycleState !== "active") {
|
|
15798
|
-
throw new
|
|
15885
|
+
throw new HTTPException26(404, { message: "knowledge source not found" });
|
|
15799
15886
|
}
|
|
15800
15887
|
if (resolved.source.syncGeneration !== input.action.sourceGeneration || resolved.source.lifecycleGeneration !== input.action.sourceLifecycleGeneration || scopedKnowledgeScopeKey(resolved.source.scope) !== scopedKnowledgeScopeKey(input.action.destination)) {
|
|
15801
|
-
throw new
|
|
15888
|
+
throw new HTTPException26(409, {
|
|
15802
15889
|
message: "knowledge source authority or generation changed"
|
|
15803
15890
|
});
|
|
15804
15891
|
}
|
|
@@ -15809,7 +15896,7 @@ async function validateKnowledgeSourceSyncAction(input) {
|
|
|
15809
15896
|
input.grant.subjectId
|
|
15810
15897
|
);
|
|
15811
15898
|
if (!connection || connection.accountId !== input.grant.accountId || connection.workspaceId !== input.grant.workspaceId || connection.subjectId !== input.action.connection.ownerSubjectId || connection.version !== input.action.connection.connectionVersion || connection.providerDomain.toLowerCase() !== input.action.connection.providerDomain.toLowerCase() || connection.kind !== input.action.connection.kind || connection.status !== "active") {
|
|
15812
|
-
throw new
|
|
15899
|
+
throw new HTTPException26(409, {
|
|
15813
15900
|
message: "knowledge source connection authority changed or requires reconnect"
|
|
15814
15901
|
});
|
|
15815
15902
|
}
|
|
@@ -15833,11 +15920,11 @@ async function deleteScheduledTaskLifecycle(input) {
|
|
|
15833
15920
|
input.workspaceId,
|
|
15834
15921
|
input.taskId
|
|
15835
15922
|
);
|
|
15836
|
-
if (!task2) throw new
|
|
15923
|
+
if (!task2) throw new HTTPException26(404, { message: "Scheduled task not found" });
|
|
15837
15924
|
await input.beforeDeleteCommit?.(tx);
|
|
15838
15925
|
const wasLive = task2.deletedAt === null;
|
|
15839
15926
|
if (scheduledTaskKnowledgeSource(task2) && (requireScheduledTaskKnowledgeSource(task2).initiatingSubjectId !== input.subjectId || requireScheduledTaskKnowledgeSource(task2).connection.ownerSubjectId !== input.subjectId)) {
|
|
15840
|
-
throw new
|
|
15927
|
+
throw new HTTPException26(403, {
|
|
15841
15928
|
message: "knowledge source schedule requires the exact initiating subject"
|
|
15842
15929
|
});
|
|
15843
15930
|
}
|
|
@@ -15926,7 +16013,7 @@ async function validateScheduledTaskAgentConfig(input) {
|
|
|
15926
16013
|
const actor = scheduledTaskInitiatorForGrant(input.grant).actor;
|
|
15927
16014
|
const parent = actor ? await getSession5(input.db, input.workspaceId, actor.sessionId) : null;
|
|
15928
16015
|
if (actor && (!parent || parent.accountId !== input.grant.accountId)) {
|
|
15929
|
-
throw new
|
|
16016
|
+
throw new HTTPException26(403, {
|
|
15930
16017
|
message: "Scheduled Skill selection requires the creating agent's session"
|
|
15931
16018
|
});
|
|
15932
16019
|
}
|
|
@@ -15937,7 +16024,7 @@ async function validateScheduledTaskAgentConfig(input) {
|
|
|
15937
16024
|
parent?.bundledSkillIds
|
|
15938
16025
|
);
|
|
15939
16026
|
} catch (error) {
|
|
15940
|
-
throw new
|
|
16027
|
+
throw new HTTPException26(422, {
|
|
15941
16028
|
message: error instanceof Error ? error.message : "Invalid bundled Skill selection"
|
|
15942
16029
|
});
|
|
15943
16030
|
}
|
|
@@ -15953,7 +16040,7 @@ async function validateScheduledTaskAgentConfig(input) {
|
|
|
15953
16040
|
const requestedTools = validateToolRefs(input.payload.agentConfig.tools ?? [], runtimeSettings);
|
|
15954
16041
|
const workspace = await requireWorkspace6(input.db, input.workspaceId);
|
|
15955
16042
|
if (parent?.tenancy?.visibility === "private" && workspace.kind !== "personal" && input.payload.runMode !== "existing_session" && !input.payload.targetSessionId) {
|
|
15956
|
-
throw new
|
|
16043
|
+
throw new HTTPException26(422, {
|
|
15957
16044
|
message: "Schedule this private chat as the existing-session target, or create the task in your Personal workspace."
|
|
15958
16045
|
});
|
|
15959
16046
|
}
|
|
@@ -15966,16 +16053,16 @@ async function validateScheduledTaskAgentConfig(input) {
|
|
|
15966
16053
|
);
|
|
15967
16054
|
const prompt = input.payload.agentConfig.prompt.trim();
|
|
15968
16055
|
if (!prompt) {
|
|
15969
|
-
throw new
|
|
16056
|
+
throw new HTTPException26(422, { message: "scheduled task prompt is required" });
|
|
15970
16057
|
}
|
|
15971
16058
|
if (hasReservedOpenGeniSlackBotSessionMetadata(input.payload.agentConfig.metadata)) {
|
|
15972
|
-
throw new
|
|
16059
|
+
throw new HTTPException26(422, {
|
|
15973
16060
|
message: `${OPENGENI_SLACK_BOT_SESSION_METADATA_KEY3} is reserved for scheduler routing`
|
|
15974
16061
|
});
|
|
15975
16062
|
}
|
|
15976
16063
|
await validateGitHubRepositorySelection(input.db, input.workspaceId, resources);
|
|
15977
16064
|
if (resources.some((resource) => resource.kind === "file") && !input.objectStorage) {
|
|
15978
|
-
throw new
|
|
16065
|
+
throw new HTTPException26(503, { message: "object storage is not configured" });
|
|
15979
16066
|
}
|
|
15980
16067
|
const fileActor = resources.some((resource) => resource.kind === "file") && hasPermission(input.grant.permissions, "files:read") ? input.authorization ? await fileOwnerContextForAccess({ db: input.db }, input.authorization, "files:read") : input.grant.principalKind === "agent_attempt" ? await fileOwnerContextForAgent({ db: input.db }, input.grant, "files:read") : { subjectId: input.grant.subjectId, privateFileOwnerSubjectId: null } : { subjectId: input.grant.subjectId, privateFileOwnerSubjectId: null };
|
|
15981
16068
|
await withSessionRlsActorContext5(fileActor, async () => {
|
|
@@ -16011,7 +16098,7 @@ async function validateScheduledTaskAgentConfig(input) {
|
|
|
16011
16098
|
const deploymentPolicy = await getNestedAgentDepthDeploymentPolicy(input.db);
|
|
16012
16099
|
const inheritedMaxDepth = typeof workspaceMaxDepth === "number" ? workspaceMaxDepth : deploymentPolicy.maxNestedAgentDepth;
|
|
16013
16100
|
if (requestedMaxDepth > inheritedMaxDepth && !hasPermission(input.grant.permissions, "workspace:admin")) {
|
|
16014
|
-
throw new
|
|
16101
|
+
throw new HTTPException26(403, {
|
|
16015
16102
|
message: `scheduled task maxNestedAgentDepth ${requestedMaxDepth} exceeds inherited limit ${inheritedMaxDepth}; workspace:admin is required to increase it`
|
|
16016
16103
|
});
|
|
16017
16104
|
}
|
|
@@ -16032,13 +16119,13 @@ function validateIncidentTelemetryPreflightSelection(settings, agentConfig) {
|
|
|
16032
16119
|
const preflight = agentConfig.incidentTelemetryPreflight;
|
|
16033
16120
|
if (executionClass === void 0 && preflight === void 0) return;
|
|
16034
16121
|
if (executionClass !== "incident_telemetry" || !preflight) {
|
|
16035
|
-
throw new
|
|
16122
|
+
throw new HTTPException26(422, {
|
|
16036
16123
|
message: "executionClass=incident_telemetry and incidentTelemetryPreflight must be configured together"
|
|
16037
16124
|
});
|
|
16038
16125
|
}
|
|
16039
16126
|
for (const required of preflight.requiredResources) {
|
|
16040
16127
|
if (!agentConfig.resources.some((selected) => isDeepStrictEqual2(selected, required))) {
|
|
16041
|
-
throw new
|
|
16128
|
+
throw new HTTPException26(422, {
|
|
16042
16129
|
message: "incidentTelemetryPreflight.requiredResources must be exact selected resources"
|
|
16043
16130
|
});
|
|
16044
16131
|
}
|
|
@@ -16046,13 +16133,13 @@ function validateIncidentTelemetryPreflightSelection(settings, agentConfig) {
|
|
|
16046
16133
|
const selectedMcpServerIds = new Set(agentConfig.tools.map((tool) => tool.id));
|
|
16047
16134
|
selectedMcpServerIds.add("opengeni");
|
|
16048
16135
|
if (preflight.requiredMcpServerIds.some((id) => !selectedMcpServerIds.has(id))) {
|
|
16049
|
-
throw new
|
|
16136
|
+
throw new HTTPException26(422, {
|
|
16050
16137
|
message: "incidentTelemetryPreflight.requiredMcpServerIds must be exact selected MCP servers"
|
|
16051
16138
|
});
|
|
16052
16139
|
}
|
|
16053
16140
|
const selectedFirstPartyTools = new Set(resolveFirstPartyMcpToolPolicy2(settings).default);
|
|
16054
16141
|
if (preflight.requiredFirstPartyMcpTools.some((tool) => !selectedFirstPartyTools.has(tool))) {
|
|
16055
|
-
throw new
|
|
16142
|
+
throw new HTTPException26(422, {
|
|
16056
16143
|
message: "incidentTelemetryPreflight.requiredFirstPartyMcpTools must be present in the selected first-party tool policy"
|
|
16057
16144
|
});
|
|
16058
16145
|
}
|
|
@@ -16060,18 +16147,18 @@ function validateIncidentTelemetryPreflightSelection(settings, agentConfig) {
|
|
|
16060
16147
|
if ((preflight.requiredFirstPartyMcpPermissions ?? []).some(
|
|
16061
16148
|
(permission) => !selectedFirstPartyPermissions.has(permission)
|
|
16062
16149
|
)) {
|
|
16063
|
-
throw new
|
|
16150
|
+
throw new HTTPException26(422, {
|
|
16064
16151
|
message: "incidentTelemetryPreflight.requiredFirstPartyMcpPermissions must be present in the scheduled responder permission policy"
|
|
16065
16152
|
});
|
|
16066
16153
|
}
|
|
16067
16154
|
const route = preflight.dataSource.route;
|
|
16068
16155
|
if (route.kind === "mcp" && !selectedMcpServerIds.has(route.serverId)) {
|
|
16069
|
-
throw new
|
|
16156
|
+
throw new HTTPException26(422, {
|
|
16070
16157
|
message: "incidentTelemetryPreflight.dataSource.route must use a selected MCP server"
|
|
16071
16158
|
});
|
|
16072
16159
|
}
|
|
16073
16160
|
if (route.kind === "first_party" && !selectedFirstPartyTools.has(route.tool)) {
|
|
16074
|
-
throw new
|
|
16161
|
+
throw new HTTPException26(422, {
|
|
16075
16162
|
message: "incidentTelemetryPreflight.dataSource.route must use a selected first-party tool"
|
|
16076
16163
|
});
|
|
16077
16164
|
}
|
|
@@ -16079,14 +16166,14 @@ function validateIncidentTelemetryPreflightSelection(settings, agentConfig) {
|
|
|
16079
16166
|
const declaredSets = new Set(preflight.requiredVariableSetNames);
|
|
16080
16167
|
const declaredVariables = new Set(preflight.requiredVariableNames);
|
|
16081
16168
|
if (!declaredSets.has(route.variableSetName) || route.variableNames.some((name) => !declaredVariables.has(name))) {
|
|
16082
|
-
throw new
|
|
16169
|
+
throw new HTTPException26(422, {
|
|
16083
16170
|
message: "incidentTelemetryPreflight.dataSource.route variable metadata must be declared as required"
|
|
16084
16171
|
});
|
|
16085
16172
|
}
|
|
16086
16173
|
}
|
|
16087
16174
|
if (route.kind === "rig_credential_hook") {
|
|
16088
16175
|
if (!preflight.requiredRig?.credentialHookIds.includes(route.credentialHookId)) {
|
|
16089
|
-
throw new
|
|
16176
|
+
throw new HTTPException26(422, {
|
|
16090
16177
|
message: "incidentTelemetryPreflight.dataSource.route rig hook must be declared as required"
|
|
16091
16178
|
});
|
|
16092
16179
|
}
|
|
@@ -16097,13 +16184,13 @@ function validateScheduledTaskSchedule(schedule) {
|
|
|
16097
16184
|
return;
|
|
16098
16185
|
}
|
|
16099
16186
|
if (new Date(schedule.startAt).getTime() >= new Date(schedule.endAt).getTime()) {
|
|
16100
|
-
throw new
|
|
16187
|
+
throw new HTTPException26(422, { message: "interval schedule endAt must be after startAt" });
|
|
16101
16188
|
}
|
|
16102
16189
|
}
|
|
16103
16190
|
function trimmedScheduledTaskName(name) {
|
|
16104
16191
|
const trimmed = name.trim();
|
|
16105
16192
|
if (!trimmed) {
|
|
16106
|
-
throw new
|
|
16193
|
+
throw new HTTPException26(422, { message: "scheduled task name is required" });
|
|
16107
16194
|
}
|
|
16108
16195
|
return trimmed;
|
|
16109
16196
|
}
|
|
@@ -16135,7 +16222,7 @@ import {
|
|
|
16135
16222
|
requireWorkspace as requireWorkspace7,
|
|
16136
16223
|
ScheduledTaskHeadChangedError as ScheduledTaskHeadChangedError2
|
|
16137
16224
|
} from "@opengeni/db";
|
|
16138
|
-
import { HTTPException as
|
|
16225
|
+
import { HTTPException as HTTPException27 } from "hono/http-exception";
|
|
16139
16226
|
import { z as z2 } from "zod";
|
|
16140
16227
|
var ALWAYS_ATTACHED_CONNECTOR = "opengeni";
|
|
16141
16228
|
var PRESERVED_ACCOUNT_SURFACES = /* @__PURE__ */ new Set([
|
|
@@ -16467,25 +16554,25 @@ async function refreshScheduledTaskAccess(input) {
|
|
|
16467
16554
|
const { grant } = input.authorization;
|
|
16468
16555
|
requirePermission(grant, "scheduled_tasks:manage");
|
|
16469
16556
|
if (!isScheduledTaskAccessRefreshHuman(input.authorization, grant.workspaceId)) {
|
|
16470
|
-
throw new
|
|
16557
|
+
throw new HTTPException27(403, {
|
|
16471
16558
|
message: "Only a signed-in person can refresh a schedule's access. API keys, services and agents cannot."
|
|
16472
16559
|
});
|
|
16473
16560
|
}
|
|
16474
16561
|
const existing = await requireScheduledTaskForApi(input.db, grant.workspaceId, input.taskId);
|
|
16475
16562
|
if (!hasAgentAccess(existing)) {
|
|
16476
|
-
throw new
|
|
16563
|
+
throw new HTTPException27(422, {
|
|
16477
16564
|
message: "This schedule has no connectors or tools to refresh."
|
|
16478
16565
|
});
|
|
16479
16566
|
}
|
|
16480
16567
|
if (existing.executionDigest !== input.request.executionDigest) {
|
|
16481
|
-
throw new
|
|
16568
|
+
throw new HTTPException27(409, {
|
|
16482
16569
|
message: "This schedule changed. Reload it and review the refresh again."
|
|
16483
16570
|
});
|
|
16484
16571
|
}
|
|
16485
16572
|
await assertScheduledTaskMutationOwner(input.db, grant, existing.id);
|
|
16486
16573
|
const source = scheduledTaskAccessSource(existing, grant);
|
|
16487
16574
|
if (!source) {
|
|
16488
|
-
throw new
|
|
16575
|
+
throw new HTTPException27(403, {
|
|
16489
16576
|
message: "Only the schedule owner can refresh its access."
|
|
16490
16577
|
});
|
|
16491
16578
|
}
|
|
@@ -16526,7 +16613,7 @@ async function refreshScheduledTaskAccess(input) {
|
|
|
16526
16613
|
update.expectedExecutionDigest = existing.executionDigest;
|
|
16527
16614
|
if (plan.creatorFirstPartyPolicy) {
|
|
16528
16615
|
if (plan.creatorFirstPartyPolicy.firstPartyMcpPermissions.length === 0) {
|
|
16529
|
-
throw new
|
|
16616
|
+
throw new HTTPException27(403, {
|
|
16530
16617
|
message: "You hold none of the permissions this schedule's OpenGeni tools need, so it cannot be refreshed with your access."
|
|
16531
16618
|
});
|
|
16532
16619
|
}
|
|
@@ -16536,7 +16623,7 @@ async function refreshScheduledTaskAccess(input) {
|
|
|
16536
16623
|
return await updateScheduledTaskForApi(input.db, grant, existing.id, update);
|
|
16537
16624
|
} catch (error) {
|
|
16538
16625
|
if (error instanceof ScheduledTaskHeadChangedError2) {
|
|
16539
|
-
throw new
|
|
16626
|
+
throw new HTTPException27(409, {
|
|
16540
16627
|
message: "This schedule changed. Reload it and review the refresh again."
|
|
16541
16628
|
});
|
|
16542
16629
|
}
|
|
@@ -16744,6 +16831,7 @@ import {
|
|
|
16744
16831
|
countSessionsAttachedToGroups,
|
|
16745
16832
|
enumerateUtcDays,
|
|
16746
16833
|
enumerateUtcHours,
|
|
16834
|
+
getSessionAccessProjection,
|
|
16747
16835
|
listFloorSessions,
|
|
16748
16836
|
listLiveWarmLeases,
|
|
16749
16837
|
listScheduledTasks,
|
|
@@ -16828,13 +16916,22 @@ var WORKSPACE_INSIGHTS_MODEL_FILTER_MAX_UTF8_BYTES = 512;
|
|
|
16828
16916
|
var WorkspaceInsightsFilterValidationError = class extends Error {
|
|
16829
16917
|
field;
|
|
16830
16918
|
maxUtf8Bytes;
|
|
16831
|
-
constructor(field, maxUtf8Bytes) {
|
|
16832
|
-
super(`${field} must be at most ${maxUtf8Bytes} UTF-8 bytes`);
|
|
16919
|
+
constructor(field, maxUtf8Bytes, message) {
|
|
16920
|
+
super(message ?? `${field} must be at most ${maxUtf8Bytes} UTF-8 bytes`);
|
|
16833
16921
|
this.name = "WorkspaceInsightsFilterValidationError";
|
|
16834
16922
|
this.field = field;
|
|
16835
16923
|
this.maxUtf8Bytes = maxUtf8Bytes;
|
|
16836
16924
|
}
|
|
16837
16925
|
};
|
|
16926
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16927
|
+
function normalizeWorkspaceInsightsSessionScope(value, field) {
|
|
16928
|
+
const normalized = value?.trim() || null;
|
|
16929
|
+
if (normalized === null || normalized === "all") return null;
|
|
16930
|
+
if (!UUID_PATTERN.test(normalized)) {
|
|
16931
|
+
throw new WorkspaceInsightsFilterValidationError(field, 36, `${field} must be a UUID`);
|
|
16932
|
+
}
|
|
16933
|
+
return normalized.toLowerCase();
|
|
16934
|
+
}
|
|
16838
16935
|
function normalizeWorkspaceInsightsFilter(value, field) {
|
|
16839
16936
|
const normalized = value?.trim() || null;
|
|
16840
16937
|
if (normalized === null || normalized === "all") return null;
|
|
@@ -16955,18 +17052,40 @@ function insightsSessionLabel(input) {
|
|
|
16955
17052
|
if (title) return title;
|
|
16956
17053
|
return `${(input.depth ?? 0) > 0 ? "Agent" : "Session"} ${input.id.slice(0, 8)}`;
|
|
16957
17054
|
}
|
|
17055
|
+
function insightsProjectLabel(input) {
|
|
17056
|
+
switch (input.kind) {
|
|
17057
|
+
case "project":
|
|
17058
|
+
return input.name?.trim() || "Untitled project";
|
|
17059
|
+
case "other":
|
|
17060
|
+
return `${input.projects.toLocaleString("en-US")} other ${input.projects === 1 ? "project" : "projects"}`;
|
|
17061
|
+
case "unfiled":
|
|
17062
|
+
return "No project";
|
|
17063
|
+
case "unavailable":
|
|
17064
|
+
return "Root session not visible";
|
|
17065
|
+
}
|
|
17066
|
+
}
|
|
16958
17067
|
async function getWorkspaceInsights(db, settings, input, observePhase) {
|
|
16959
17068
|
const provider = normalizeWorkspaceInsightsFilter(input.provider, "provider");
|
|
16960
17069
|
const model = normalizeWorkspaceInsightsFilter(input.model, "model");
|
|
17070
|
+
const rootSessionId = normalizeWorkspaceInsightsSessionScope(
|
|
17071
|
+
input.rootSessionId,
|
|
17072
|
+
"rootSessionId"
|
|
17073
|
+
);
|
|
17074
|
+
const sessionId = normalizeWorkspaceInsightsSessionScope(input.sessionId, "sessionId");
|
|
16961
17075
|
await measureInsightsPhase(
|
|
16962
17076
|
observePhase,
|
|
16963
17077
|
"require_workspace",
|
|
16964
17078
|
() => requireWorkspace8(db, input.workspaceId)
|
|
16965
17079
|
);
|
|
17080
|
+
const [visibleRootScope, visibleSessionScope] = await Promise.all([
|
|
17081
|
+
rootSessionId ? getSessionAccessProjection(db, input.workspaceId, rootSessionId) : null,
|
|
17082
|
+
sessionId ? getSessionAccessProjection(db, input.workspaceId, sessionId) : null
|
|
17083
|
+
]);
|
|
16966
17084
|
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
16967
17085
|
const window = resolveRangeWindow(input.range, now);
|
|
16968
17086
|
const modelFilterActive = Boolean(provider || model);
|
|
16969
|
-
const
|
|
17087
|
+
const scopeActive = Boolean(rootSessionId || sessionId);
|
|
17088
|
+
const filter = { provider, model, rootSessionId, sessionId };
|
|
16970
17089
|
const [modelBundle, usageBundle, liveWarm, tasks, depth, floorRows, machinesOnline] = await Promise.all([
|
|
16971
17090
|
measureInsightsPhase(
|
|
16972
17091
|
observePhase,
|
|
@@ -17027,10 +17146,18 @@ async function getWorkspaceInsights(db, settings, input, observePhase) {
|
|
|
17027
17146
|
factBuckets: factDays,
|
|
17028
17147
|
rootDrivers,
|
|
17029
17148
|
priorRootDrivers,
|
|
17149
|
+
projects,
|
|
17150
|
+
privateChats,
|
|
17151
|
+
privateChatsTruncated,
|
|
17030
17152
|
scheduleFacts,
|
|
17031
17153
|
facets,
|
|
17032
17154
|
recentCalls,
|
|
17033
|
-
promptContributions
|
|
17155
|
+
promptContributions,
|
|
17156
|
+
dataThrough,
|
|
17157
|
+
driverGroups,
|
|
17158
|
+
driversTruncated,
|
|
17159
|
+
facetsTruncated,
|
|
17160
|
+
recentCallsTruncated
|
|
17034
17161
|
} = modelBundle;
|
|
17035
17162
|
const {
|
|
17036
17163
|
workspaceCreditMicros,
|
|
@@ -17121,6 +17248,8 @@ async function getWorkspaceInsights(db, settings, input, observePhase) {
|
|
|
17121
17248
|
0
|
|
17122
17249
|
);
|
|
17123
17250
|
const modelCalls = modelRows.reduce((sum, row) => sum + row.calls, 0);
|
|
17251
|
+
const cachedTokens = modelRows.reduce((sum, row) => sum + row.cachedTokens, 0);
|
|
17252
|
+
const cacheInputTokens = modelRows.reduce((sum, row) => sum + row.cacheInputTokens, 0);
|
|
17124
17253
|
const priorInputTokens = priorModelRows.reduce((sum, row) => sum + row.inputTokens, 0);
|
|
17125
17254
|
const priorTotalTokens = priorModelRows.reduce((sum, row) => sum + row.totalTokens, 0);
|
|
17126
17255
|
const priorCachedTokens = priorModelRows.reduce((sum, row) => sum + row.cachedTokens, 0);
|
|
@@ -17145,7 +17274,7 @@ async function getWorkspaceInsights(db, settings, input, observePhase) {
|
|
|
17145
17274
|
cacheKnownCalls: 0,
|
|
17146
17275
|
calls: 0
|
|
17147
17276
|
};
|
|
17148
|
-
const modelCostMicros = modelFilterActive ? facts.costMicros : usageBuckets.get(bucket)?.costMicros ?? facts.costMicros;
|
|
17277
|
+
const modelCostMicros = modelFilterActive || scopeActive ? facts.costMicros : usageBuckets.get(bucket)?.costMicros ?? facts.costMicros;
|
|
17149
17278
|
return {
|
|
17150
17279
|
label: input.range === "today" ? bucket.slice(11) : bucket.slice(5),
|
|
17151
17280
|
modelCostUsd: microsToUsd(modelCostMicros),
|
|
@@ -17193,6 +17322,19 @@ async function getWorkspaceInsights(db, settings, input, observePhase) {
|
|
|
17193
17322
|
deltaUsdVsPrior: creditUsd - priorUsd
|
|
17194
17323
|
};
|
|
17195
17324
|
});
|
|
17325
|
+
const projectRows = projects.map((row) => ({
|
|
17326
|
+
id: row.kind === "project" && row.channelId ? `project:${row.channelId}` : row.kind,
|
|
17327
|
+
kind: row.kind,
|
|
17328
|
+
label: insightsProjectLabel(row),
|
|
17329
|
+
projects: row.projects,
|
|
17330
|
+
rootSessions: row.rootSessions,
|
|
17331
|
+
calls: row.calls,
|
|
17332
|
+
creditUsd: microsToUsd(row.pricedCostMicros),
|
|
17333
|
+
estimatedProviderUsd: microsToUsd(row.estimatedProviderCostMicros),
|
|
17334
|
+
estimatedProviderCostKnownCalls: row.estimatedProviderCostKnownCalls,
|
|
17335
|
+
tokens: row.totalTokens,
|
|
17336
|
+
cacheHitPct: cacheHitPct(row.cachedTokens, row.cacheInputTokens)
|
|
17337
|
+
}));
|
|
17196
17338
|
const scheduleFactById = new Map(scheduleFacts.map((row) => [row.scheduledTaskId, row]));
|
|
17197
17339
|
const schedules = tasks.map((task) => {
|
|
17198
17340
|
const fact = scheduleFactById.get(task.id);
|
|
@@ -17247,6 +17389,9 @@ async function getWorkspaceInsights(db, settings, input, observePhase) {
|
|
|
17247
17389
|
sessions: bucket.sessions
|
|
17248
17390
|
})),
|
|
17249
17391
|
drivers,
|
|
17392
|
+
projects: projectRows,
|
|
17393
|
+
privateChats,
|
|
17394
|
+
privateChatsTruncated,
|
|
17250
17395
|
schedules,
|
|
17251
17396
|
recentCalls: recentCalls.map((row) => ({
|
|
17252
17397
|
id: row.id,
|
|
@@ -17330,7 +17475,17 @@ async function getWorkspaceInsights(db, settings, input, observePhase) {
|
|
|
17330
17475
|
billableTokenCap: limits.maxMonthlyTokensPerWorkspace ?? null,
|
|
17331
17476
|
agentRunsUsed,
|
|
17332
17477
|
agentRunCap: limits.maxMonthlyAgentRunsPerWorkspace ?? null,
|
|
17333
|
-
modelFilterActive
|
|
17478
|
+
modelFilterActive: modelFilterActive || scopeActive,
|
|
17479
|
+
dataThrough: dataThrough?.toISOString() ?? null,
|
|
17480
|
+
cacheHitPct: cacheHitPct(cachedTokens, cacheInputTokens),
|
|
17481
|
+
scope: {
|
|
17482
|
+
rootSessionId: visibleRootScope ? rootSessionId : null,
|
|
17483
|
+
sessionId: visibleSessionScope ? sessionId : null
|
|
17484
|
+
},
|
|
17485
|
+
driverGroups,
|
|
17486
|
+
driversTruncated,
|
|
17487
|
+
facetsTruncated,
|
|
17488
|
+
recentCallsTruncated
|
|
17334
17489
|
});
|
|
17335
17490
|
return { snapshot };
|
|
17336
17491
|
}
|
|
@@ -17375,7 +17530,7 @@ function sanitizeSlackPublicationText(value) {
|
|
|
17375
17530
|
}
|
|
17376
17531
|
|
|
17377
17532
|
// src/domain/memory-slack-publication.ts
|
|
17378
|
-
var
|
|
17533
|
+
var UUID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
17379
17534
|
var SELECTOR_SEGMENT_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
|
|
17380
17535
|
var encoder = new TextEncoder();
|
|
17381
17536
|
var decoder = new TextDecoder();
|
|
@@ -17496,7 +17651,7 @@ function validIdentity(input) {
|
|
|
17496
17651
|
input.memory.supersededById,
|
|
17497
17652
|
input.change.relatedMemoryId
|
|
17498
17653
|
].filter((value) => value !== null);
|
|
17499
|
-
return uuids.every((value) =>
|
|
17654
|
+
return uuids.every((value) => UUID_PATTERN2.test(value)) && Number.isSafeInteger(input.memory.version) && input.memory.version > 0 && typeof input.memory.namespace === "string" && Array.isArray(input.memory.labels) && input.memory.labels.every((label) => typeof label === "string") && (input.change.ownerLabel === void 0 || input.change.ownerLabel === null || typeof input.change.ownerLabel === "string") && typeof input.distribution.shareSummary === "string" && IMPORTANCES.has(input.distribution.importance) && REQUESTED_MODES.has(input.distribution.slackMode) && CHANGE_KINDS.has(input.change.kind) && ORIGINS.has(input.change.origin) && AUDIENCES.has(input.distribution.audience) && SCOPE_TYPES.has(input.memory.scopeType) && MEMORY_STATUSES.has(input.memory.status) && MEMORY_KINDS.has(input.memory.kind);
|
|
17500
17655
|
}
|
|
17501
17656
|
function evaluateValidity(input) {
|
|
17502
17657
|
const now = Date.parse(input.context.now);
|
|
@@ -18782,7 +18937,7 @@ function providerReceiptWire(receipt2) {
|
|
|
18782
18937
|
}
|
|
18783
18938
|
|
|
18784
18939
|
// src/domain/workspace-members.ts
|
|
18785
|
-
import { HTTPException as
|
|
18940
|
+
import { HTTPException as HTTPException28 } from "hono/http-exception";
|
|
18786
18941
|
var MEMBER_ADMIN_PERMISSIONS = ["workspace:admin", "members:manage"];
|
|
18787
18942
|
function memberCanAdminister(member) {
|
|
18788
18943
|
return member.permissions.some((permission) => MEMBER_ADMIN_PERMISSIONS.includes(permission));
|
|
@@ -18792,27 +18947,27 @@ function isUserMember(member) {
|
|
|
18792
18947
|
}
|
|
18793
18948
|
function resolveMemberSubjectId(userId) {
|
|
18794
18949
|
if (!userId) {
|
|
18795
|
-
throw new
|
|
18950
|
+
throw new HTTPException28(404, { message: "user is not registered" });
|
|
18796
18951
|
}
|
|
18797
18952
|
return `user:${userId}`;
|
|
18798
18953
|
}
|
|
18799
18954
|
function assertWorkspaceMemberRemovable(input) {
|
|
18800
18955
|
const { members, subjectId, callerSubjectId } = input;
|
|
18801
18956
|
if (subjectId === callerSubjectId) {
|
|
18802
|
-
throw new
|
|
18957
|
+
throw new HTTPException28(409, {
|
|
18803
18958
|
message: "you cannot remove your own membership"
|
|
18804
18959
|
});
|
|
18805
18960
|
}
|
|
18806
18961
|
const target = members.find((member) => member.subjectId === subjectId);
|
|
18807
18962
|
if (!target) {
|
|
18808
|
-
throw new
|
|
18963
|
+
throw new HTTPException28(404, { message: "member not found" });
|
|
18809
18964
|
}
|
|
18810
18965
|
if (memberCanAdminister(target)) {
|
|
18811
18966
|
const remainingAdmins = members.filter(
|
|
18812
18967
|
(member) => member.subjectId !== subjectId && memberCanAdminister(member)
|
|
18813
18968
|
);
|
|
18814
18969
|
if (remainingAdmins.length === 0) {
|
|
18815
|
-
throw new
|
|
18970
|
+
throw new HTTPException28(409, {
|
|
18816
18971
|
message: "cannot remove the last member who can manage this workspace"
|
|
18817
18972
|
});
|
|
18818
18973
|
}
|
|
@@ -18821,28 +18976,28 @@ function assertWorkspaceMemberRemovable(input) {
|
|
|
18821
18976
|
function assertWorkspaceMemberUpdateAllowed(input) {
|
|
18822
18977
|
const { members, subjectId, callerSubjectId, nextPermissions } = input;
|
|
18823
18978
|
if (subjectId === callerSubjectId) {
|
|
18824
|
-
throw new
|
|
18979
|
+
throw new HTTPException28(409, {
|
|
18825
18980
|
message: "you cannot change your own workspace access"
|
|
18826
18981
|
});
|
|
18827
18982
|
}
|
|
18828
18983
|
const target = members.find((member) => member.subjectId === subjectId);
|
|
18829
18984
|
if (!target) {
|
|
18830
|
-
throw new
|
|
18985
|
+
throw new HTTPException28(404, { message: "member not found" });
|
|
18831
18986
|
}
|
|
18832
18987
|
if (memberCanAdminister(target) && !memberCanAdminister({ permissions: nextPermissions }) && !members.some((member) => member.subjectId !== subjectId && memberCanAdminister(member))) {
|
|
18833
|
-
throw new
|
|
18988
|
+
throw new HTTPException28(409, {
|
|
18834
18989
|
message: "the workspace must keep at least one administrator"
|
|
18835
18990
|
});
|
|
18836
18991
|
}
|
|
18837
18992
|
}
|
|
18838
18993
|
function assertWorkspaceDeletable(input) {
|
|
18839
18994
|
if (input.workspaceCountForAccount <= 1) {
|
|
18840
|
-
throw new
|
|
18995
|
+
throw new HTTPException28(409, {
|
|
18841
18996
|
message: "cannot delete the account's only workspace"
|
|
18842
18997
|
});
|
|
18843
18998
|
}
|
|
18844
18999
|
if (input.activeSessionCount > 0) {
|
|
18845
|
-
throw new
|
|
19000
|
+
throw new HTTPException28(409, {
|
|
18846
19001
|
message: "stop the workspace's running sessions before deleting it"
|
|
18847
19002
|
});
|
|
18848
19003
|
}
|
|
@@ -19102,7 +19257,7 @@ import {
|
|
|
19102
19257
|
saveNewSessionDraftInTransaction,
|
|
19103
19258
|
withWorkspaceSubjectRls as withWorkspaceSubjectRls6
|
|
19104
19259
|
} from "@opengeni/db";
|
|
19105
|
-
import { HTTPException as
|
|
19260
|
+
import { HTTPException as HTTPException29 } from "hono/http-exception";
|
|
19106
19261
|
import { canonicalizeConfiguredModelId as canonicalizeConfiguredModelId3 } from "@opengeni/config";
|
|
19107
19262
|
function hasOwn(value, key) {
|
|
19108
19263
|
return typeof value === "object" && value !== null && Object.hasOwn(value, key);
|
|
@@ -19282,7 +19437,7 @@ async function saveActorNewSessionDraftInFileScope(deps, grant, workspaceId, raw
|
|
|
19282
19437
|
const tools = toolsProvided ? validateToolRefs(input.tools, runtimeSettings) : [];
|
|
19283
19438
|
await validateGitHubRepositorySelection(deps.db, workspaceId, resources);
|
|
19284
19439
|
if (resources.some((resource) => resource.kind === "file") && !deps.objectStorage) {
|
|
19285
|
-
throw new
|
|
19440
|
+
throw new HTTPException29(503, {
|
|
19286
19441
|
message: "object storage is not configured"
|
|
19287
19442
|
});
|
|
19288
19443
|
}
|
|
@@ -19334,7 +19489,7 @@ async function saveActorNewSessionDraftInFileScope(deps, grant, workspaceId, raw
|
|
|
19334
19489
|
return { ...mapped, modelProvided: draftModelProvided(deps.settings, mapped) };
|
|
19335
19490
|
} catch (error) {
|
|
19336
19491
|
if (error instanceof NewSessionDraftAccessError) {
|
|
19337
|
-
throw new
|
|
19492
|
+
throw new HTTPException29(403, { message: error.message });
|
|
19338
19493
|
}
|
|
19339
19494
|
throw error;
|
|
19340
19495
|
}
|
|
@@ -19400,7 +19555,7 @@ async function submitComposerDraftForRequest(deps, grant, workspaceId, sessionId
|
|
|
19400
19555
|
|
|
19401
19556
|
// src/application/session-commands.ts
|
|
19402
19557
|
import { DraftTimelineAnnotations as DraftTimelineAnnotations3 } from "@opengeni/contracts";
|
|
19403
|
-
import { HTTPException as
|
|
19558
|
+
import { HTTPException as HTTPException30 } from "hono/http-exception";
|
|
19404
19559
|
import {
|
|
19405
19560
|
checkWorkspaceAllowance as checkWorkspaceAllowance3,
|
|
19406
19561
|
deleteSessionQueueItemInTransaction,
|
|
@@ -19481,7 +19636,7 @@ function agentActor(context) {
|
|
|
19481
19636
|
async function assertFreshAgentCommandAllowance(tx, deps, context, targetSessionId) {
|
|
19482
19637
|
const target = await getSession7(tx, context.workspaceId, targetSessionId);
|
|
19483
19638
|
if (!target || target.accountId !== context.accountId) {
|
|
19484
|
-
throw new
|
|
19639
|
+
throw new HTTPException30(404, { message: "Target session not found" });
|
|
19485
19640
|
}
|
|
19486
19641
|
if (deps.settings) {
|
|
19487
19642
|
const { settings } = await resolveWorkspaceCatalogSettings(tx, deps.settings, {
|
|
@@ -19506,7 +19661,7 @@ async function assertFreshAgentCommandAllowance(tx, deps, context, targetSession
|
|
|
19506
19661
|
subjectId: initiatingHumanForAllowance(frozen)
|
|
19507
19662
|
});
|
|
19508
19663
|
if (refusal) {
|
|
19509
|
-
throw new
|
|
19664
|
+
throw new HTTPException30(402, {
|
|
19510
19665
|
message: refusal.message,
|
|
19511
19666
|
cause: { allowed: false, ...refusal }
|
|
19512
19667
|
});
|
|
@@ -20223,13 +20378,13 @@ import {
|
|
|
20223
20378
|
withSessionRlsActorContext as withSessionRlsActorContext7
|
|
20224
20379
|
} from "@opengeni/db";
|
|
20225
20380
|
import { publishDurableSessionEvents as publishDurableSessionEvents4 } from "@opengeni/events";
|
|
20226
|
-
import { HTTPException as
|
|
20381
|
+
import { HTTPException as HTTPException31 } from "hono/http-exception";
|
|
20227
20382
|
import { ArchivedSessionImportError as ArchivedSessionImportError2 } from "@opengeni/db";
|
|
20228
20383
|
function requireImportAuthorization(authorization, workspaceId, permission) {
|
|
20229
20384
|
const grant = requireResolvedAccessGrantAuthorization(authorization, workspaceId);
|
|
20230
20385
|
requirePermission(grant, permission);
|
|
20231
20386
|
if (grantHasAgentAttemptAuthority(grant)) {
|
|
20232
|
-
throw new
|
|
20387
|
+
throw new HTTPException31(403, {
|
|
20233
20388
|
message: "Session history imports require a user or service request"
|
|
20234
20389
|
});
|
|
20235
20390
|
}
|
|
@@ -20266,7 +20421,7 @@ async function importArchivedSessionForRequest(deps, authorization, workspaceId,
|
|
|
20266
20421
|
}
|
|
20267
20422
|
const creator = creationInitiatorForGrant(grant);
|
|
20268
20423
|
if (!creator.initiator)
|
|
20269
|
-
throw new
|
|
20424
|
+
throw new HTTPException31(403, { message: "Import creator is unavailable" });
|
|
20270
20425
|
const actor = await fileOwnerContextForAccess(deps, authorization, "sessions:create");
|
|
20271
20426
|
const result = await withSessionRlsActorContext7(
|
|
20272
20427
|
actor,
|
|
@@ -20820,13 +20975,13 @@ import { createPinnedIntegrationTransport as createPinnedIntegrationTransport2 }
|
|
|
20820
20975
|
import {
|
|
20821
20976
|
buildConnectionTokenResolver,
|
|
20822
20977
|
getConnectionMetadata as getConnectionMetadata5,
|
|
20823
|
-
listConnectionsMetadata as
|
|
20978
|
+
listConnectionsMetadata as listConnectionsMetadata4,
|
|
20824
20979
|
listEnabledMcpCapabilityServers as listEnabledMcpCapabilityServers3,
|
|
20825
20980
|
listConnectorToolPermissionPolicies,
|
|
20826
20981
|
updateConnectorToolPermissionPolicies,
|
|
20827
20982
|
resolveConnectorActionPolicy as resolveConnectorActionPolicy2
|
|
20828
20983
|
} from "@opengeni/db";
|
|
20829
|
-
import { HTTPException as
|
|
20984
|
+
import { HTTPException as HTTPException32 } from "hono/http-exception";
|
|
20830
20985
|
import { GMAIL_REST_MCP_TOOLS, isOfficialGmailMcpConfig } from "@opengeni/runtime";
|
|
20831
20986
|
function connectorToolGroup(tool) {
|
|
20832
20987
|
if (tool.annotations?.destructiveHint === true || tool.annotations?.readOnlyHint === false)
|
|
@@ -20834,25 +20989,38 @@ function connectorToolGroup(tool) {
|
|
|
20834
20989
|
if (tool.annotations?.readOnlyHint === true) return "read";
|
|
20835
20990
|
return "other";
|
|
20836
20991
|
}
|
|
20992
|
+
function unpinnedConnectorToolPermissionAccount(ref, visible, subjectId) {
|
|
20993
|
+
const matches = visible.filter(
|
|
20994
|
+
(candidate) => candidate.subjectId === (ref.subjectScope === "subject" ? subjectId : null) && candidate.providerDomain === ref.providerDomain && (!ref.kind || candidate.kind === ref.kind) && candidate.status === "active"
|
|
20995
|
+
);
|
|
20996
|
+
if (matches.length > 1)
|
|
20997
|
+
throw new HTTPException32(409, {
|
|
20998
|
+
message: "Choose one account to manage its tool permissions"
|
|
20999
|
+
});
|
|
21000
|
+
return matches[0] ?? null;
|
|
21001
|
+
}
|
|
21002
|
+
function connectorToolPermissionReference(ref, connectionId) {
|
|
21003
|
+
return { ...ref, accountSelection: void 0, connectionId };
|
|
21004
|
+
}
|
|
20837
21005
|
async function resolveTarget2(input) {
|
|
20838
21006
|
if (input.capabilityId.startsWith("api:")) {
|
|
20839
|
-
throw new
|
|
21007
|
+
throw new HTTPException32(404, {
|
|
20840
21008
|
message: "Connected MCP connector not found; API Integration tool approval is set with installApiIntegration autoApprovedTools"
|
|
20841
21009
|
});
|
|
20842
21010
|
}
|
|
20843
21011
|
const catalog = await buildCapabilityCatalog(input);
|
|
20844
21012
|
const item = catalog.items.find((candidate) => candidate.id === input.capabilityId);
|
|
20845
21013
|
if (!item || !item.enabled || item.kind !== "mcp" || !item.runtime.mcpServerId || item.source === "built_in") {
|
|
20846
|
-
throw new
|
|
21014
|
+
throw new HTTPException32(404, { message: "Connected MCP connector not found" });
|
|
20847
21015
|
}
|
|
20848
21016
|
const enabled = await listEnabledMcpCapabilityServers3(input.db, input.workspaceId);
|
|
20849
21017
|
const settings = settingsWithMcpCapabilityServers(input.settings, enabled);
|
|
20850
21018
|
const server = settings.mcpServers.find((candidate) => candidate.id === item.runtime.mcpServerId);
|
|
20851
21019
|
if (!server || server.url !== item.endpointUrl)
|
|
20852
|
-
throw new
|
|
21020
|
+
throw new HTTPException32(409, { message: "Connector configuration is unavailable" });
|
|
20853
21021
|
const ref = server.connectionRef;
|
|
20854
21022
|
if (ref?.authoritySource === "host")
|
|
20855
|
-
throw new
|
|
21023
|
+
throw new HTTPException32(409, {
|
|
20856
21024
|
message: "Tool permissions for this connection are managed by its host"
|
|
20857
21025
|
});
|
|
20858
21026
|
let connection = ref?.connectionId ? await getConnectionMetadata5(
|
|
@@ -20861,21 +21029,19 @@ async function resolveTarget2(input) {
|
|
|
20861
21029
|
ref.connectionId,
|
|
20862
21030
|
input.grant.subjectId
|
|
20863
21031
|
) : null;
|
|
20864
|
-
if (ref
|
|
20865
|
-
const visible = await
|
|
21032
|
+
if (ref && !ref.connectionId && (ref.subjectScope === "subject" || ref.accountSelection === "all_eligible")) {
|
|
21033
|
+
const visible = await listConnectionsMetadata4(
|
|
20866
21034
|
input.db,
|
|
20867
21035
|
input.workspaceId,
|
|
20868
21036
|
input.grant.subjectId
|
|
20869
21037
|
);
|
|
20870
|
-
connection = visible.
|
|
20871
|
-
(candidate) => candidate.subjectId === input.grant.subjectId && candidate.providerDomain === ref.providerDomain && (!ref.kind || candidate.kind === ref.kind) && candidate.status === "active"
|
|
20872
|
-
) ?? null;
|
|
21038
|
+
connection = unpinnedConnectorToolPermissionAccount(ref, visible, input.grant.subjectId);
|
|
20873
21039
|
}
|
|
20874
21040
|
if (ref && (!connection || connection.providerDomain !== ref.providerDomain || ref.kind && connection.kind !== ref.kind || (ref.subjectScope === "subject" ? connection.subjectId !== input.grant.subjectId : connection.subjectId !== null))) {
|
|
20875
|
-
throw new
|
|
21041
|
+
throw new HTTPException32(409, { message: "Reconnect this connector to manage its tools" });
|
|
20876
21042
|
}
|
|
20877
21043
|
if (connection?.subjectId && !input.personalOwnerVerified)
|
|
20878
|
-
throw new
|
|
21044
|
+
throw new HTTPException32(403, {
|
|
20879
21045
|
message: "Only the authenticated connection owner may manage personal tool permissions"
|
|
20880
21046
|
});
|
|
20881
21047
|
const connectionId = connection?.id ?? `session-mcp:${server.id}:${createHash11("sha256").update(server.url, "utf8").digest("hex")}`;
|
|
@@ -20897,7 +21063,10 @@ async function listTools(input, target) {
|
|
|
20897
21063
|
...target.connection?.subjectId ? { subjectId: input.grant.subjectId } : {},
|
|
20898
21064
|
serverId: target.server.id,
|
|
20899
21065
|
toolName: "tools/list",
|
|
20900
|
-
connectionRef:
|
|
21066
|
+
connectionRef: connectorToolPermissionReference(
|
|
21067
|
+
target.server.connectionRef,
|
|
21068
|
+
target.connectionId
|
|
21069
|
+
),
|
|
20901
21070
|
destinationUrl: target.server.url
|
|
20902
21071
|
});
|
|
20903
21072
|
if (result.status !== "ok") throw new Error("Reconnect this connector to load its tools.");
|
|
@@ -21010,14 +21179,14 @@ function canManageConnectorPermissions(grant) {
|
|
|
21010
21179
|
}
|
|
21011
21180
|
async function updateConnectorToolPermissions(input) {
|
|
21012
21181
|
if (!canManageConnectorPermissions(input.grant))
|
|
21013
|
-
throw new
|
|
21182
|
+
throw new HTTPException32(403, { message: "Connector management permission required" });
|
|
21014
21183
|
if (input.payload.target === "tools" && input.payload.toolNames.some((name) => name === "*" || name !== name.trim()))
|
|
21015
|
-
throw new
|
|
21184
|
+
throw new HTTPException32(400, {
|
|
21016
21185
|
message: "Tool names must be exact and cannot use the connector-default wildcard."
|
|
21017
21186
|
});
|
|
21018
21187
|
const target = await resolveTarget2(input);
|
|
21019
21188
|
if (target.connectionId !== input.payload.connectionId)
|
|
21020
|
-
throw new
|
|
21189
|
+
throw new HTTPException32(409, {
|
|
21021
21190
|
message: "The connector account changed. Reload its permissions."
|
|
21022
21191
|
});
|
|
21023
21192
|
await updateConnectorToolPermissionPolicies(input.db, {
|
|
@@ -21230,6 +21399,7 @@ export {
|
|
|
21230
21399
|
computeScheduledTaskAccessPlan,
|
|
21231
21400
|
configureManagedUserAdmission,
|
|
21232
21401
|
connectorToolGroup,
|
|
21402
|
+
connectorToolPermissionReference,
|
|
21233
21403
|
consentManagedHumanSandboxRecovery,
|
|
21234
21404
|
controlAgentSessionWorkstream,
|
|
21235
21405
|
controlHumanSessionWorkstream,
|
|
@@ -21350,6 +21520,7 @@ export {
|
|
|
21350
21520
|
importArchivedSessionForRequest,
|
|
21351
21521
|
initialAutomaticTitleForSessionStart,
|
|
21352
21522
|
initiatingHumanForAllowance,
|
|
21523
|
+
insightsProjectLabel,
|
|
21353
21524
|
insightsSessionLabel,
|
|
21354
21525
|
inspectEditableArtifactLiveWireEnvelope,
|
|
21355
21526
|
installSkill,
|
|
@@ -21406,6 +21577,7 @@ export {
|
|
|
21406
21577
|
mergeRigDefaultVariableSetEnvironment,
|
|
21407
21578
|
mergeToolRefs,
|
|
21408
21579
|
modelFundingForAdmission,
|
|
21580
|
+
modelUnavailableHttpException,
|
|
21409
21581
|
moveHumanQueuePrompt,
|
|
21410
21582
|
nativeConnectionCapabilityRecommendations,
|
|
21411
21583
|
normalizeConversationActor,
|
|
@@ -21422,6 +21594,7 @@ export {
|
|
|
21422
21594
|
normalizeResources,
|
|
21423
21595
|
normalizeVideoGenerationRequest,
|
|
21424
21596
|
normalizeWorkspaceInsightsFilter,
|
|
21597
|
+
normalizeWorkspaceInsightsSessionScope,
|
|
21425
21598
|
normalizedSessionMcpCredentialHeaders,
|
|
21426
21599
|
officialMcpRegistryUrl,
|
|
21427
21600
|
ogatxEditableArtifactMutationIntentCodec,
|
|
@@ -21617,6 +21790,7 @@ export {
|
|
|
21617
21790
|
syncUpdatedScheduledTask,
|
|
21618
21791
|
triggerScheduledTaskForGrant,
|
|
21619
21792
|
unboundGitHubRepositoryResources,
|
|
21793
|
+
unpinnedConnectorToolPermissionAccount,
|
|
21620
21794
|
unsupportedEditableArtifactExport,
|
|
21621
21795
|
updateConnectorToolPermissions,
|
|
21622
21796
|
updateExternalIdentityMembershipForRequest,
|
|
@@ -21649,6 +21823,7 @@ export {
|
|
|
21649
21823
|
validateVariableSetAttachment,
|
|
21650
21824
|
validatedScheduledTaskUpdate,
|
|
21651
21825
|
variableSetScopeAllowedForRig,
|
|
21826
|
+
verifyDirectModelAccess,
|
|
21652
21827
|
verifyPrReviewWebhook,
|
|
21653
21828
|
videoGenerationAdmissionKey,
|
|
21654
21829
|
videoGenerationCapabilitiesForPolicy,
|
|
@@ -21672,6 +21847,7 @@ export {
|
|
|
21672
21847
|
workspaceCustomModelReference,
|
|
21673
21848
|
workspaceInsightsPhaseMetricObserver,
|
|
21674
21849
|
workspaceSessionEffectiveToolsContext,
|
|
21850
|
+
workspaceSessionToolPolicyContext,
|
|
21675
21851
|
workspaceSessionToolPolicyDefaultServerIds,
|
|
21676
21852
|
workspaceSessionToolPolicyDefaultServerIdsFor,
|
|
21677
21853
|
workspaceSessionToolPolicyServerIds,
|