@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/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 HTTPException {
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 HTTPException5 } from "hono/http-exception";
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 HTTPException3 } from "hono/http-exception";
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 HTTPException2 } from "hono/http-exception";
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 HTTPException2(422, {
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 HTTPException2(422, {
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 HTTPException2(422, {
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 HTTPException3(403, { message: "Developer setup keys cannot manage API keys" });
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 HTTPException3(403, {
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 HTTPException3(401, { message: "authentication required" });
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 HTTPException3(401, { message: "authentication required" });
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 HTTPException3(403, { message: "missing permission: account:admin" });
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 HTTPException3(403, { message: "workspace access authorization is invalid" });
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 HTTPException3(401, { message: "organization administrator session required" });
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 HTTPException3(403, {
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 HTTPException3(401, { message: "organization administrator session required" });
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 HTTPException3(403, {
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 HTTPException3(403, { message: "external workspace access denied" });
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 HTTPException3(403, {
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 HTTPException3(404, { message: "workspace not found" });
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 HTTPException3(403, { message: "workspace access denied" });
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 HTTPException3(403, {
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 HTTPException3(403, {
1165
+ throw new HTTPException4(403, {
1114
1166
  message: "missing permission: variable-sets:manage (deprecated alias: environments:manage)"
1115
1167
  });
1116
1168
  }
1117
- throw new HTTPException3(403, {
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 HTTPException3(403, {
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 HTTPException3(422, {
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 HTTPException3(422, {
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 HTTPException3(401, {
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 HTTPException3(422, {
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 HTTPException3(403, { message: "external actors require an organization key" });
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 HTTPException3(400, { message: "invalid external actor selection" });
1323
+ throw new HTTPException4(400, { message: "invalid external actor selection" });
1272
1324
  }
1273
1325
  let identity;
1274
1326
  try {
1275
- identity = await ensureExternalIdentity(deps.db, {
1276
- accountId: apiKey.accountId,
1277
- ...selection.identity
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 HTTPException3(403, { message: "external identity is unavailable" });
1336
+ throw new HTTPException4(403, { message: "external identity is unavailable" });
1282
1337
  }
1283
- throw new HTTPException3(503, { message: "external identity authority is unavailable" });
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 HTTPException3(403, { message: "Native identity link is unavailable or changed" });
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 HTTPException4 } from "hono/http-exception";
1541
+ import { HTTPException as HTTPException5 } from "hono/http-exception";
1487
1542
  function assertNativeMcpConnectionRef(connectionRef) {
1488
1543
  if (connectionRef?.authoritySource === "host") {
1489
- throw new HTTPException4(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(409, {
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 HTTPException5(409, {
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 HTTPException5(409, {
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 HTTPException5(422, {
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 HTTPException5(409, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(503, {
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 HTTPException5(422, {
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 HTTPException5(422, {
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 HTTPException5(409, {
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 HTTPException5(409, {
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 HTTPException5(409, {
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 HTTPException5(409, {
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 HTTPException5(409, {
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 HTTPException5(502, {
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 HTTPException5) {
2313
+ if (error instanceof HTTPException6) {
2259
2314
  throw error;
2260
2315
  }
2261
2316
  if (error instanceof Error && error.name === "AbortError") {
2262
- throw new HTTPException5(504, {
2317
+ throw new HTTPException6(504, {
2263
2318
  message: "MCP registry request timed out"
2264
2319
  });
2265
2320
  }
2266
- throw new HTTPException5(502, {
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 HTTPException5(404, { message: "capability not found" });
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 HTTPException6 } from "hono/http-exception";
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 HTTPException6(422, {
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 HTTPException6(422, { message: `unknown MCP server id: ${tool.id}` });
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 HTTPException6(403, { message: `${message}: ${widened.id}` });
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 HTTPException6(422, {
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 HTTPException6(422, {
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 HTTPException6(422, {
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 HTTPException6(422, {
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 HTTPException6(422, {
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 HTTPException6(422, { message: error.message });
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 HTTPException6(422, {
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 HTTPException6(422, {
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 HTTPException6(422, { message: "personal GitHub repository URI is invalid" });
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 HTTPException6(422, {
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 HTTPException6(422, {
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 HTTPException6(422, {
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 HTTPException6(422, {
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 HTTPException6(422, {
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 HTTPException6 && error.status === 422;
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 HTTPException6(422, { message: `duplicate file resource: ${resource.fileId}` });
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 HTTPException6(422, { message: `unknown file resource: ${resource.fileId}` });
3359
+ throw new HTTPException7(422, { message: `unknown file resource: ${resource.fileId}` });
3305
3360
  }
3306
3361
  if (file.status !== "ready") {
3307
- throw new HTTPException6(422, {
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 HTTPException6(422, { message: `invalid resource mount path: ${path}` });
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 HTTPException7 } from "hono/http-exception";
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 HTTPException7(403, { message: "Missing permission: documents:search" });
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 HTTPException7(403, { message: "Knowledge attempt unavailable" });
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 HTTPException7(403, { message: `Missing permission: ${permission}` });
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 HTTPException7(403, { message: "Knowledge attempt unavailable" });
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 HTTPException7(403, {
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 HTTPException8 } from "hono/http-exception";
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 HTTPException8(403, {
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 HTTPException8(409, { message: "The frozen session credential ceiling is invalid" });
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 HTTPException16 } from "hono/http-exception";
4503
+ import { HTTPException as HTTPException17 } from "hono/http-exception";
4448
4504
 
4449
4505
  // src/domain/external-creation-attribution.ts
4450
- import { HTTPException as HTTPException9 } from "hono/http-exception";
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 HTTPException9(422, {
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 HTTPException10 } from "hono/http-exception";
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 HTTPException10(denied2 ? 403 : 503, {
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 lockExternalWorkspaceMembershipLifecycle(tx, scope.accountId);
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 HTTPException11 } from "hono/http-exception";
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 HTTPException11(
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: withClaudeConnectionCatalog(
6497
+ settings: withDirectModelProviders(
6438
6498
  withClaudeConnectionCatalog(
6439
- withOrganizationOpenRouterCatalogProvider(
6440
- withOrganizationGatewayCatalogProvider(
6441
- withWorkspaceOpenRouterCatalogProvider(
6442
- withWorkspaceGatewayCatalogProvider(resolved.settings, gatewayCustomModels),
6443
- openRouterCustomModels
6499
+ withClaudeConnectionCatalog(
6500
+ withOrganizationOpenRouterCatalogProvider(
6501
+ withOrganizationGatewayCatalogProvider(
6502
+ withWorkspaceOpenRouterCatalogProvider(
6503
+ withWorkspaceGatewayCatalogProvider(resolved.settings, gatewayCustomModels),
6504
+ openRouterCustomModels
6505
+ ),
6506
+ organizationGatewayModels
6444
6507
  ),
6445
- organizationGatewayModels
6508
+ organizationOpenRouterModels
6446
6509
  ),
6447
- organizationOpenRouterModels
6510
+ claudeConnections
6448
6511
  ),
6449
- claudeConnections
6512
+ workspaceClaudeConnections,
6513
+ "workspace"
6450
6514
  ),
6451
- workspaceClaudeConnections,
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 HTTPException12 } from "hono/http-exception";
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 HTTPException12(422, { message: error.message, cause: error });
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 HTTPException12(422, {
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 HTTPException13 } from "hono/http-exception";
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 HTTPException13(422, { message: "Invalid timeline annotation source" });
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 HTTPException14 } from "hono/http-exception";
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 HTTPException14(422, {
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 HTTPException14(503, {
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 HTTPException14(404, { message: "variableSet not found" });
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 HTTPException14(422, { message: "unknown variableSetId" });
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 HTTPException15 } from "hono/http-exception";
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 HTTPException15(422, {
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 HTTPException15(422, {
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 HTTPException15(422, {
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 HTTPException15(422, {
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 HTTPException15(403, {
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 HTTPException15(409, {
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 HTTPException15(422, {
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 HTTPException16(422, {
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 HTTPException16(403, {
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 HTTPException16(403, {
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 HTTPException16(409, {
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 HTTPException16(422, {
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 HTTPException16(403, {
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 HTTPException16(403, {
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 HTTPException16(403, {
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 HTTPException16(403, {
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 HTTPException16(403, {
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 HTTPException16(403, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 new HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 new HTTPException16(422, { message: `model is not available: ${model}` });
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 new HTTPException16(422, { message: `model is not available: ${model}` });
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 HTTPException16(422, {
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 HTTPException16(404, { message: "session turn not found" });
9189
+ throw new HTTPException17(404, { message: "session turn not found" });
9098
9190
  }
9099
9191
  if (turn.status !== "queued") {
9100
- throw new HTTPException16(409, {
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 HTTPException16(422, { message: error.message, cause: error });
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 new HTTPException16(422, {
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 HTTPException16(
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 HTTPException16(409, { message: error.message });
9389
+ throw new HTTPException17(409, { message: error.message });
9300
9390
  }
9301
9391
  if (error instanceof Error && error.message.includes("cancelled")) {
9302
- throw new HTTPException16(409, { message: error.message });
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 HTTPException16(422, { message: error.message });
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 HTTPException16(403, {
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 HTTPException16(403, {
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 HTTPException16(403, {
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 HTTPException16(422, { message: 'memoryScope "user" requires an authenticated user' });
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 HTTPException16(422, { message: error.message, cause: error });
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 HTTPException16(422, {
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 HTTPException16(403, {
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 HTTPException16(422, {
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 HTTPException16(403, { message: error.message, cause: error });
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 HTTPException16(404, {
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 HTTPException16(403, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(403, {
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 HTTPException16(409, {
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 HTTPException16(409, { message: error.message, cause: error });
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(403, {
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 HTTPException16(503, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, { message: `model is not selectable: ${model}` });
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 ?? inheritedLatencyMode;
10132
+ const latencyMode = payload.latencyMode ?? "standard";
10044
10133
  if (payload.expectedNewSessionDraftRevision !== void 0 && payload.rigId === null) {
10045
- throw new HTTPException16(409, {
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 ? inheritedFromParent ? "continuation" : "deployment" : "explicit"
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 HTTPException16(403, {
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 HTTPException16(422, {
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 HTTPException16(403, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(404, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(422, {
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 HTTPException16(403, {
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 HTTPException16(
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 HTTPException16(403, { message: error.message });
10578
+ throw new HTTPException17(403, { message: error.message });
10490
10579
  }
10491
10580
  if (error instanceof SessionIdConflictError) {
10492
- throw new HTTPException16(409, {
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 HTTPException16(409, {
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 HTTPException16(409, { message: error.message, cause: error });
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 HTTPException16(422, { message: error.message, cause: error });
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 HTTPException16(422, {
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 HTTPException16(503, {
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 HTTPException16(422, {
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 HTTPException16(404, {
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 HTTPException16(422, {
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 HTTPException16(409, {
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 HTTPException16(403, {
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 HTTPException16(403, {
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 HTTPException16(409, {
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 HTTPException16(403, {
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 HTTPException16(403, {
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 HTTPException16(409, { message: "parent session is no longer available" });
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 HTTPException16(422, {
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 HTTPException16(404, { message: "session not found" });
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 HTTPException16(404, { message: "session not found" });
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 HTTPException17 } from "hono/http-exception";
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 lockExternalWorkspaceMembershipLifecycle2,
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 HTTPException17(403, {
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 HTTPException17(403, { message: "membership exceeds key authority" });
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 lockExternalWorkspaceMembershipLifecycle2(tx, grant.accountId);
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 HTTPException17(403, { message: "membership authority changed" });
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 HTTPException17(403, {
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 HTTPException17(409, {
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 HTTPException17(status, {
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 HTTPException17(403, {
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 HTTPException17(422, { message: "Invalid external identity reference" });
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 HTTPException17(422, { message: "Invalid external grant cancellation" });
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 HTTPException17(422, {
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 HTTPException18 } from "hono/http-exception";
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 HTTPException18(403, {
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 HTTPException18(422, { message: "invalid external identity transition" });
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 HTTPException19 } from "hono/http-exception";
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 HTTPException19(403, { message: "host authorization required" });
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 HTTPException19(422, { message: "invalid credential rotation request" });
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 HTTPException19(422, { message: "invalid credential rotation headers or server IDs" });
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 HTTPException19(403, { message: "credential rotation authority changed" });
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 HTTPException19(403, { message: "credential rotation authority changed" });
12545
+ throw new HTTPException20(403, { message: "credential rotation authority changed" });
12457
12546
  }
12458
12547
  if (!await subjectHasLiveWorkspaceAuthorityInScope(tx, scope)) {
12459
- throw new HTTPException19(403, { message: "credential rotation authority changed" });
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 HTTPException19)
12472
- throw new HTTPException19(error.status, { message: "credential rotation access denied" });
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 HTTPException19(403, { message: "credential rotation access denied" });
12563
+ throw new HTTPException20(403, { message: "credential rotation access denied" });
12475
12564
  if (error instanceof SessionAuthorizationUnavailableError)
12476
- throw new HTTPException19(503, { message: "credential rotation authorization unavailable" });
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 HTTPException19(status, { message: `credential_rotation_${error.code}` });
12568
+ throw new HTTPException20(status, { message: `credential_rotation_${error.code}` });
12480
12569
  }
12481
- throw new HTTPException19(503, { message: "credential rotation unavailable" });
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 lockExternalWorkspaceMembershipLifecycle3,
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 HTTPException20 } from "hono/http-exception";
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 HTTPException20(denied2 ? 403 : 503, {
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 HTTPException20(403, { message: "External Connect continuation required" });
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 HTTPException20(403, { message: "Connection API key authority changed" });
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 lockExternalWorkspaceMembershipLifecycle3(tx, state.accountId);
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 HTTPException20(403, { message: "Connection owner authority changed" });
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 HTTPException21 } from "hono/http-exception";
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 HTTPException21(422, { message: "GitHub returned an invalid source commit" });
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 HTTPException21(422, {
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 HTTPException21(422, {
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 HTTPException21(422, {
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 HTTPException21(422, {
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 HTTPException21(422, {
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 HTTPException21(422, { message: "Enter a valid GitHub or skills.sh URL" });
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 HTTPException21(422, {
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 HTTPException21(422, { message: "The Skill URL contains invalid encoding" });
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 HTTPException21(422, {
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 HTTPException21(422, { message: "The skills.sh Skill name is invalid" });
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 HTTPException21(422, {
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 HTTPException21(422, { message: "A GitHub URL must identify a repository" });
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 HTTPException21(422, {
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 HTTPException21(422, { message: "The GitHub URL is missing a revision" });
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 HTTPException21(422, { message: "The GitHub URL is missing a Skill folder" });
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 HTTPException21(422, {
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 HTTPException21(422, {
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 HTTPException21(422, {
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 HTTPException21(422, {
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 HTTPException21(422, {
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 HTTPException21(422, {
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 HTTPException21(422, { message: "No Skill folder with SKILL.md was found" });
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 HTTPException21(422, {
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 HTTPException21(422, {
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 HTTPException21(422, { message: "The selected Skill folder is empty" });
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 HTTPException21(422, { message: "The GitHub Skill path is invalid" });
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 HTTPException21(422, { message: "The GitHub owner or repository name is invalid" });
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 HTTPException22 } from "hono/http-exception";
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 HTTPException22(response.status === 429 ? 429 : 422, {
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 HTTPException22(422, { message: "The downloaded skill has an invalid file list" });
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 HTTPException22(422, { message: "The downloaded skill contains an invalid file" });
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 HTTPException22(429, {
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 HTTPException23 } from "hono/http-exception";
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 HTTPException23(404, { message: "sandbox environment not found" });
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 HTTPException23(404, { message: "sandbox environment change not found" });
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 HTTPException23(422, { message: "sandbox environment name is required" });
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 HTTPException23(422, {
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 HTTPException23(422, {
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 HTTPException23(422, {
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 HTTPException23(422, {
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 HTTPException23(409, {
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 HTTPException23(409, {
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 HTTPException23(409, { message: current.message });
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 HTTPException23(422, {
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 HTTPException23(409, {
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 HTTPException23(409, { message: error.message });
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 HTTPException23(422, {
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 HTTPException23(409, {
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 HTTPException23(422, { message: "sandbox environment change has no base version" });
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 HTTPException23(404, { message: "base sandbox environment version not found" });
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 HTTPException23(422, {
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 HTTPException23(422, {
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 HTTPException23(409, {
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 HTTPException23(422, {
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 HTTPException23(422, { message: "sandbox environment change has no base version" });
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 HTTPException23(404, { message: "base sandbox environment version not found" });
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 HTTPException23(422, { message: "sandbox environment has no active version" });
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 HTTPException24 } from "hono/http-exception";
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 HTTPException24(403, {
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 HTTPException24(409, {
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 HTTPException24(403, {
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 HTTPException25 } from "hono/http-exception";
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 new HTTPException25(422, {
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 HTTPException25(403, {
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 HTTPException25(422, { message: "Source tasks require skip or buffer_one overlap" });
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(403, {
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 HTTPException25(403, { message: "the calling agent attempt is not available" });
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 HTTPException25(403, {
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 HTTPException25(409, {
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 HTTPException25(409, {
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 HTTPException25(409, {
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 HTTPException25(403, {
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 HTTPException25(409, {
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 HTTPException25(403, {
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 HTTPException25(403, { message: "the calling agent attempt is not available" });
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 HTTPException25(422, {
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 HTTPException25(input.missingTargetStatus ?? 422, {
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 HTTPException25(422, {
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 HTTPException25(404, { message: "target session not found" });
15341
+ throw new HTTPException26(404, { message: "target session not found" });
15255
15342
  }
15256
15343
  if (error instanceof SessionAuthorizationUnavailableError) {
15257
- throw new HTTPException25(503, { message: "session authorization is unavailable" });
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 HTTPException25(404, { message: "target session not found" });
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 HTTPException25(422, {
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 HTTPException25(409, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, { message: `unknown rigId: ${rigId}` });
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 HTTPException25(403, {
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 HTTPException25(422, { message: "Source ingestion uses an ordinary agent task" });
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 HTTPException25(422, { message: "Source tasks require skip or buffer_one overlap" });
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 HTTPException25(422, {
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 HTTPException25(409, {
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 HTTPException25(409, {
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 HTTPException25(409, {
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 HTTPException25(409, {
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 HTTPException25(409, {
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 HTTPException25(404, { message: "scheduled task not found" });
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 HTTPException25(403, {
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 HTTPException25(403, {
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 HTTPException25(404, { message: "knowledge source not found" });
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 HTTPException25(409, {
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 HTTPException25(409, {
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 HTTPException25(404, { message: "Scheduled task not found" });
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 HTTPException25(403, {
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 HTTPException25(403, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, { message: "scheduled task prompt is required" });
16056
+ throw new HTTPException26(422, { message: "scheduled task prompt is required" });
15970
16057
  }
15971
16058
  if (hasReservedOpenGeniSlackBotSessionMetadata(input.payload.agentConfig.metadata)) {
15972
- throw new HTTPException25(422, {
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 HTTPException25(503, { message: "object storage is not configured" });
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 HTTPException25(403, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, {
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 HTTPException25(422, { message: "interval schedule endAt must be after startAt" });
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 HTTPException25(422, { message: "scheduled task name is required" });
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 HTTPException26 } from "hono/http-exception";
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 HTTPException26(403, {
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 HTTPException26(422, {
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 HTTPException26(409, {
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 HTTPException26(403, {
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 HTTPException26(403, {
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 HTTPException26(409, {
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 filter = { provider, model };
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 UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
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) => UUID_PATTERN.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);
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 HTTPException27 } from "hono/http-exception";
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 HTTPException27(404, { message: "user is not registered" });
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 HTTPException27(409, {
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 HTTPException27(404, { message: "member not found" });
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 HTTPException27(409, {
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 HTTPException27(409, {
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 HTTPException27(404, { message: "member not found" });
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 HTTPException27(409, {
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 HTTPException27(409, {
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 HTTPException27(409, {
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 HTTPException28 } from "hono/http-exception";
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 HTTPException28(503, {
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 HTTPException28(403, { message: error.message });
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 HTTPException29 } from "hono/http-exception";
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 HTTPException29(404, { message: "Target session not found" });
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 HTTPException29(402, {
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 HTTPException30 } from "hono/http-exception";
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 HTTPException30(403, {
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 HTTPException30(403, { message: "Import creator is unavailable" });
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 listConnectionsMetadata3,
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 HTTPException31 } from "hono/http-exception";
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 HTTPException31(404, {
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 HTTPException31(404, { message: "Connected MCP connector not found" });
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 HTTPException31(409, { message: "Connector configuration is unavailable" });
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 HTTPException31(409, {
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?.subjectScope === "subject" && !ref.connectionId) {
20865
- const visible = await listConnectionsMetadata3(
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.find(
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 HTTPException31(409, { message: "Reconnect this connector to manage its tools" });
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 HTTPException31(403, {
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: { ...target.server.connectionRef, connectionId: target.connectionId },
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 HTTPException31(403, { message: "Connector management permission required" });
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 HTTPException31(400, {
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 HTTPException31(409, {
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,