@opengeni/api-router 2.5.0 → 2.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +1 -1
- package/dist/auth/managed-auth-attempt-context.d.ts +5 -1
- package/dist/auth/managed-auth.d.ts +24 -1
- package/dist/{chunk-QESX7HDK.js → chunk-XTLI3CBH.js} +2236 -387
- package/dist/chunk-XTLI3CBH.js.map +1 -0
- package/dist/http/sse.d.ts +9 -0
- package/dist/index.js +104 -4
- package/dist/index.js.map +1 -1
- package/dist/interaction-metrics.d.ts +2 -0
- package/dist/mcp/company-brain-governed-writes.d.ts +1 -1
- package/dist/mcp/company-profile-agent-admin.d.ts +6 -6
- package/dist/mcp/remember.d.ts +2 -2
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/session-view.d.ts +1 -0
- package/dist/mcp/session-wait.d.ts +19 -0
- package/dist/routes/api-keys.d.ts +2 -0
- package/dist/routes/browser-sessions.d.ts +1 -0
- package/dist/routes/computer-sessions.d.ts +12 -0
- package/dist/routes/managed-auth-session-sets.d.ts +7 -0
- package/dist/routes/workspaces.d.ts +1 -1
- package/dist/sandbox/metrics-ingestion.d.ts +16 -0
- package/dist/workspace-delete-observability.d.ts +10 -0
- package/package.json +15 -15
- package/src/app.ts +172 -20
- package/src/auth/managed-auth-attempt-context.ts +40 -3
- package/src/auth/managed-auth-session-adapter.ts +1 -0
- package/src/auth/managed-auth.ts +164 -4
- package/src/http/sse.ts +279 -45
- package/src/integrations/oauth-client.ts +8 -1
- package/src/integrations/provider-oauth.ts +12 -2
- package/src/interaction-metrics.ts +30 -0
- package/src/mcp/company-brain-governed-writes.ts +38 -23
- package/src/mcp/company-profile-agent-admin.ts +7 -7
- package/src/mcp/remember.ts +19 -8
- package/src/mcp/server.ts +318 -26
- package/src/mcp/session-wait.ts +56 -8
- package/src/routes/api-integrations.ts +2 -2
- package/src/routes/api-keys.ts +149 -7
- package/src/routes/browser-sessions.ts +10 -2
- package/src/routes/capabilities.ts +3 -3
- package/src/routes/codex.ts +483 -74
- package/src/routes/company-profile.ts +64 -0
- package/src/routes/computer-sessions.ts +103 -1
- package/src/routes/integration-facets.ts +8 -5
- package/src/routes/interaction-resources.ts +7 -1
- package/src/routes/managed-auth-session-sets.ts +199 -2
- package/src/routes/organization-memberships.ts +28 -8
- package/src/routes/packs.ts +5 -5
- package/src/routes/plugins.ts +2 -2
- package/src/routes/scheduled-tasks.ts +9 -0
- package/src/routes/sessions.ts +3 -6
- package/src/routes/skills.ts +3 -3
- package/src/routes/workspaces.ts +293 -54
- package/src/sandbox/channel-a.ts +10 -4
- package/src/sandbox/machines.ts +13 -6
- package/src/sandbox/metrics-ingestion.ts +157 -3
- package/src/sandbox/viewer.ts +20 -1
- package/src/workspace-delete-observability.ts +75 -0
- package/dist/chunk-QESX7HDK.js.map +0 -1
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
withSessionRlsActorContext
|
|
43
43
|
} from "@opengeni/db";
|
|
44
44
|
import { requireSessionEventDurableFanoutCapability as requireSessionEventDurableFanoutCapability2 } from "@opengeni/events";
|
|
45
|
+
import { githubAppBotIdentityWarnings } from "@opengeni/github";
|
|
45
46
|
import { createObservability } from "@opengeni/observability";
|
|
46
47
|
import { createObjectStorage } from "@opengeni/storage";
|
|
47
48
|
import { WebStandardStreamableHTTPServerTransport as WebStandardStreamableHTTPServerTransport3 } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
@@ -122,6 +123,7 @@ import {
|
|
|
122
123
|
} from "@opengeni/core";
|
|
123
124
|
|
|
124
125
|
// src/auth/managed-auth.ts
|
|
126
|
+
import { canonicalPublicOrigin } from "@opengeni/config";
|
|
125
127
|
import { ensureManagedAccessForUser } from "@opengeni/db";
|
|
126
128
|
import {
|
|
127
129
|
ensureCanonicalHumanIdentityForAuthUser,
|
|
@@ -1298,8 +1300,14 @@ function denied(intent, code) {
|
|
|
1298
1300
|
// src/auth/managed-auth-attempt-context.ts
|
|
1299
1301
|
import { AsyncLocalStorage } from "async_hooks";
|
|
1300
1302
|
var managedAuthAttemptStorage = new AsyncLocalStorage();
|
|
1301
|
-
function runManagedAuthAttempt(transactionId, action) {
|
|
1302
|
-
return managedAuthAttemptStorage.run(
|
|
1303
|
+
function runManagedAuthAttempt(transactionId, providerId, action) {
|
|
1304
|
+
return managedAuthAttemptStorage.run(
|
|
1305
|
+
{ kind: "isolated_transaction", transactionId, providerId, createdAuthSessionId: null },
|
|
1306
|
+
action
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
function runManagedAuthProvider(providerId, action) {
|
|
1310
|
+
return managedAuthAttemptStorage.run({ kind: "provider", providerId }, action);
|
|
1303
1311
|
}
|
|
1304
1312
|
function runManagedAuthDiscardedProviderSession(action) {
|
|
1305
1313
|
return managedAuthAttemptStorage.run({ kind: "discard_provider_session" }, action);
|
|
@@ -1308,6 +1316,18 @@ function currentManagedAuthAttemptId() {
|
|
|
1308
1316
|
const context = managedAuthAttemptStorage.getStore();
|
|
1309
1317
|
return context?.kind === "isolated_transaction" ? context.transactionId : null;
|
|
1310
1318
|
}
|
|
1319
|
+
function currentManagedAuthProviderId() {
|
|
1320
|
+
const context = managedAuthAttemptStorage.getStore();
|
|
1321
|
+
return context?.kind === "isolated_transaction" || context?.kind === "provider" ? context.providerId : "credential";
|
|
1322
|
+
}
|
|
1323
|
+
function recordCurrentManagedAuthSession(authSessionId) {
|
|
1324
|
+
const context = managedAuthAttemptStorage.getStore();
|
|
1325
|
+
if (context?.kind === "isolated_transaction") context.createdAuthSessionId = authSessionId;
|
|
1326
|
+
}
|
|
1327
|
+
function currentManagedAuthCreatedSessionId() {
|
|
1328
|
+
const context = managedAuthAttemptStorage.getStore();
|
|
1329
|
+
return context?.kind === "isolated_transaction" ? context.createdAuthSessionId : null;
|
|
1330
|
+
}
|
|
1311
1331
|
function shouldDiscardCurrentManagedAuthProviderSession() {
|
|
1312
1332
|
return managedAuthAttemptStorage.getStore()?.kind === "discard_provider_session";
|
|
1313
1333
|
}
|
|
@@ -1320,6 +1340,12 @@ function managedAuthUserCreateOverride(settings, user) {
|
|
|
1320
1340
|
if (managedAuthRequiresEmailVerification(settings)) return void 0;
|
|
1321
1341
|
return { data: { ...user, emailVerified: true } };
|
|
1322
1342
|
}
|
|
1343
|
+
function managedAuthUserCreateAdmission(settings, user, providerId) {
|
|
1344
|
+
if (managedAuthRequiresEmailVerification(settings) && providerId !== "credential" && !user.emailVerified) {
|
|
1345
|
+
return false;
|
|
1346
|
+
}
|
|
1347
|
+
return managedAuthUserCreateOverride(settings, user);
|
|
1348
|
+
}
|
|
1323
1349
|
async function hashManagedAuthPassword(password) {
|
|
1324
1350
|
return await hashPassword(password);
|
|
1325
1351
|
}
|
|
@@ -1432,7 +1458,24 @@ function createManagedAuth(settings, db, managedEmailTransport) {
|
|
|
1432
1458
|
},
|
|
1433
1459
|
accountLinking: {
|
|
1434
1460
|
enabled: false
|
|
1435
|
-
}
|
|
1461
|
+
},
|
|
1462
|
+
encryptOAuthTokens: true,
|
|
1463
|
+
storeStateStrategy: "database"
|
|
1464
|
+
},
|
|
1465
|
+
socialProviders: {
|
|
1466
|
+
...settings.managedAuthGoogleClientId && settings.managedAuthGoogleClientSecret ? {
|
|
1467
|
+
google: {
|
|
1468
|
+
clientId: settings.managedAuthGoogleClientId,
|
|
1469
|
+
clientSecret: settings.managedAuthGoogleClientSecret,
|
|
1470
|
+
prompt: "select_account"
|
|
1471
|
+
}
|
|
1472
|
+
} : {},
|
|
1473
|
+
...settings.managedAuthGithubClientId && settings.managedAuthGithubClientSecret ? {
|
|
1474
|
+
github: {
|
|
1475
|
+
clientId: settings.managedAuthGithubClientId,
|
|
1476
|
+
clientSecret: settings.managedAuthGithubClientSecret
|
|
1477
|
+
}
|
|
1478
|
+
} : {}
|
|
1436
1479
|
},
|
|
1437
1480
|
verification: {
|
|
1438
1481
|
modelName: "auth_verifications",
|
|
@@ -1497,6 +1540,7 @@ function createManagedAuth(settings, db, managedEmailTransport) {
|
|
|
1497
1540
|
session: {
|
|
1498
1541
|
create: {
|
|
1499
1542
|
before: async (session) => {
|
|
1543
|
+
const providerId = currentManagedAuthProviderId();
|
|
1500
1544
|
await ensureCanonicalHumanIdentityForAuthUser(db, session.userId);
|
|
1501
1545
|
const preflightProjection = await getCanonicalHumanIdentityProjection(
|
|
1502
1546
|
db,
|
|
@@ -1510,7 +1554,7 @@ function createManagedAuth(settings, db, managedEmailTransport) {
|
|
|
1510
1554
|
if (!preflight.allowed) {
|
|
1511
1555
|
const exactRecoveryBinding = await getCanonicalHumanExactLoginBindingForAuthUser(db, {
|
|
1512
1556
|
authUserId: session.userId,
|
|
1513
|
-
providerId
|
|
1557
|
+
providerId
|
|
1514
1558
|
});
|
|
1515
1559
|
const recoveryBinding = preflightProjection.loginBindings.find(
|
|
1516
1560
|
(binding) => binding.id === exactRecoveryBinding.id && binding.status === "recovery_pending"
|
|
@@ -1544,7 +1588,7 @@ function createManagedAuth(settings, db, managedEmailTransport) {
|
|
|
1544
1588
|
const projection = await getCanonicalHumanIdentityProjection(db, session.userId);
|
|
1545
1589
|
const exactBinding = await getCanonicalHumanExactLoginBindingForAuthUser(db, {
|
|
1546
1590
|
authUserId: session.userId,
|
|
1547
|
-
providerId
|
|
1591
|
+
providerId
|
|
1548
1592
|
});
|
|
1549
1593
|
const activeBinding = projection.loginBindings.find(
|
|
1550
1594
|
(binding) => binding.id === exactBinding.id
|
|
@@ -1575,6 +1619,7 @@ function createManagedAuth(settings, db, managedEmailTransport) {
|
|
|
1575
1619
|
};
|
|
1576
1620
|
},
|
|
1577
1621
|
after: async (session) => {
|
|
1622
|
+
recordCurrentManagedAuthSession(session.id);
|
|
1578
1623
|
if (!shouldDiscardCurrentManagedAuthProviderSession()) return;
|
|
1579
1624
|
await db.execute(sql`delete from auth_sessions where id = ${session.id}`);
|
|
1580
1625
|
}
|
|
@@ -1582,7 +1627,7 @@ function createManagedAuth(settings, db, managedEmailTransport) {
|
|
|
1582
1627
|
},
|
|
1583
1628
|
user: {
|
|
1584
1629
|
create: {
|
|
1585
|
-
before: async (user) =>
|
|
1630
|
+
before: async (user) => managedAuthUserCreateAdmission(settings, user, currentManagedAuthProviderId()),
|
|
1586
1631
|
after: async (user) => {
|
|
1587
1632
|
if (!user.emailVerified) return;
|
|
1588
1633
|
await ensureManagedAccessForUser(db, {
|
|
@@ -1598,6 +1643,70 @@ function createManagedAuth(settings, db, managedEmailTransport) {
|
|
|
1598
1643
|
}
|
|
1599
1644
|
});
|
|
1600
1645
|
}
|
|
1646
|
+
async function resolveManagedAuthOAuthAttempt(auth, request, provider, publicBaseUrl) {
|
|
1647
|
+
const expectedOrigin = canonicalPublicOrigin(publicBaseUrl);
|
|
1648
|
+
if (!expectedOrigin) return null;
|
|
1649
|
+
const state = new URL(request.url).searchParams.get("state");
|
|
1650
|
+
if (!state) return null;
|
|
1651
|
+
const verification = await (await auth.$context).internalAdapter.findVerificationValue(state);
|
|
1652
|
+
if (!verification?.value) return null;
|
|
1653
|
+
let parsed;
|
|
1654
|
+
try {
|
|
1655
|
+
parsed = JSON.parse(verification.value);
|
|
1656
|
+
} catch {
|
|
1657
|
+
return null;
|
|
1658
|
+
}
|
|
1659
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
1660
|
+
const stateData = parsed;
|
|
1661
|
+
const proof = stateData.opengeniManagedAuth;
|
|
1662
|
+
if (!proof || typeof proof !== "object") return null;
|
|
1663
|
+
const value = proof;
|
|
1664
|
+
if (value.version !== 1 || value.provider !== provider || typeof value.transactionId !== "string" || typeof value.authorityHash !== "string" || typeof value.transactionSecretHash !== "string" || typeof value.expectedGeneration !== "string" || typeof value.expectedActorEpoch !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
|
|
1665
|
+
value.transactionId
|
|
1666
|
+
) || !/^[0-9a-f]{64}$/u.test(value.authorityHash) || !/^[0-9a-f]{64}$/u.test(value.transactionSecretHash) || !/^[1-9][0-9]*$/u.test(value.expectedGeneration) || !/^[1-9][0-9]*$/u.test(value.expectedActorEpoch)) {
|
|
1667
|
+
return null;
|
|
1668
|
+
}
|
|
1669
|
+
const callbackURL = typeof stateData.callbackURL === "string" ? stateData.callbackURL : null;
|
|
1670
|
+
const errorURL = typeof stateData.errorURL === "string" ? stateData.errorURL : null;
|
|
1671
|
+
if (!callbackURL || !errorURL || !managedAuthOAuthReturnMatches(callbackURL, expectedOrigin, value.transactionId, "complete") || !managedAuthOAuthReturnMatches(errorURL, expectedOrigin, value.transactionId, "error")) {
|
|
1672
|
+
return null;
|
|
1673
|
+
}
|
|
1674
|
+
return {
|
|
1675
|
+
transactionId: value.transactionId,
|
|
1676
|
+
provider,
|
|
1677
|
+
authorityHash: value.authorityHash,
|
|
1678
|
+
transactionSecretHash: value.transactionSecretHash,
|
|
1679
|
+
expectedGeneration: value.expectedGeneration,
|
|
1680
|
+
expectedActorEpoch: value.expectedActorEpoch
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
async function isolatedManagedAuthOAuthCallbackRequest(auth, request) {
|
|
1684
|
+
const context = await auth.$context;
|
|
1685
|
+
const stateCookieName = context.createAuthCookie("state").name;
|
|
1686
|
+
const headers = new Headers(request.headers);
|
|
1687
|
+
const stateCookie2 = cookiePair(headers.get("cookie"), stateCookieName);
|
|
1688
|
+
if (stateCookie2) headers.set("cookie", stateCookie2);
|
|
1689
|
+
else headers.delete("cookie");
|
|
1690
|
+
headers.delete("authorization");
|
|
1691
|
+
headers.delete("x-forwarded-user");
|
|
1692
|
+
return { request: new Request(request, { headers }), stateCookieName };
|
|
1693
|
+
}
|
|
1694
|
+
function managedAuthOAuthReturnMatches(raw, expectedOrigin, transactionId, outcome) {
|
|
1695
|
+
try {
|
|
1696
|
+
const url = new URL(raw);
|
|
1697
|
+
return url.origin === expectedOrigin && url.pathname === "/account-auth" && url.searchParams.size === 2 && url.searchParams.get("transaction") === transactionId && url.searchParams.get("social") === outcome && !url.hash;
|
|
1698
|
+
} catch {
|
|
1699
|
+
return false;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
function cookiePair(header, name) {
|
|
1703
|
+
for (const part of header?.split(";") ?? []) {
|
|
1704
|
+
const separator = part.indexOf("=");
|
|
1705
|
+
if (separator < 0 || part.slice(0, separator).trim() !== name) continue;
|
|
1706
|
+
return `${name}=${part.slice(separator + 1).trim()}`;
|
|
1707
|
+
}
|
|
1708
|
+
return null;
|
|
1709
|
+
}
|
|
1601
1710
|
function betterAuthBaseUrl(settings) {
|
|
1602
1711
|
const allowedHosts = splitCsv(settings.betterAuthAllowedHosts);
|
|
1603
1712
|
if (allowedHosts.length === 0) {
|
|
@@ -1647,8 +1756,12 @@ function escapeHtml(value) {
|
|
|
1647
1756
|
|
|
1648
1757
|
// src/app.ts
|
|
1649
1758
|
import {
|
|
1759
|
+
adoptManagedAuthSession,
|
|
1650
1760
|
MANAGED_AUTH_SESSION_SET_COOKIE as MANAGED_AUTH_SESSION_SET_COOKIE2,
|
|
1651
1761
|
ManagedAuthActorChangeError as ManagedAuthActorChangeError2,
|
|
1762
|
+
managedAuthCsrfHash as managedAuthCsrfHash2,
|
|
1763
|
+
managedAuthDerivedUuid as managedAuthDerivedUuid2,
|
|
1764
|
+
managedAuthSecretRequestDigest as managedAuthSecretRequestDigest2,
|
|
1652
1765
|
managedAuthSha256 as managedAuthSha2562
|
|
1653
1766
|
} from "@opengeni/core/managed-auth-session-sets";
|
|
1654
1767
|
|
|
@@ -1662,6 +1775,7 @@ function createBetterAuthSessionAdapter(auth, db) {
|
|
|
1662
1775
|
}
|
|
1663
1776
|
const result = await runManagedAuthAttempt(
|
|
1664
1777
|
input.transactionId,
|
|
1778
|
+
"credential",
|
|
1665
1779
|
async () => await auth.api.signInEmail({
|
|
1666
1780
|
body: {
|
|
1667
1781
|
email: input.credentials.email,
|
|
@@ -2145,6 +2259,7 @@ import {
|
|
|
2145
2259
|
stableJson,
|
|
2146
2260
|
compactSessionEventResult as compactSessionEventResult2,
|
|
2147
2261
|
sessionEventLatestClassToSemanticClass,
|
|
2262
|
+
MemorySlackPublicationDistribution,
|
|
2148
2263
|
SessionMcpCredentialUpdateInput,
|
|
2149
2264
|
ToolAuthNeededPayload,
|
|
2150
2265
|
VariableSetVariableName,
|
|
@@ -2157,6 +2272,7 @@ import {
|
|
|
2157
2272
|
SESSION_GOAL_SUCCESS_CRITERIA_MAX_BYTES,
|
|
2158
2273
|
SESSION_GOAL_TEXT_MAX_BYTES,
|
|
2159
2274
|
SESSION_INSTRUCTIONS_MAX_CHARACTERS,
|
|
2275
|
+
SESSION_TITLE_MAX_CHARACTERS,
|
|
2160
2276
|
MAX_SELECTED_VARIABLE_SETS,
|
|
2161
2277
|
sessionGoalUtf8Bytes,
|
|
2162
2278
|
TASK_NOTE_LIST_DEFAULT_LIMIT,
|
|
@@ -2205,6 +2321,8 @@ import {
|
|
|
2205
2321
|
readVariableSetSecretAtomically,
|
|
2206
2322
|
recordSyncedSocialPosts,
|
|
2207
2323
|
listVariableSets,
|
|
2324
|
+
MEMORY_CORRECT_TOOL_DESCRIPTION,
|
|
2325
|
+
MEMORY_SAVE_TOOL_DESCRIPTION,
|
|
2208
2326
|
MEMORY_SEARCH_TOOL_DESCRIPTION,
|
|
2209
2327
|
requireScheduledTask,
|
|
2210
2328
|
requireSession,
|
|
@@ -2234,7 +2352,11 @@ import {
|
|
|
2234
2352
|
acceptSessionHumanInputResponse,
|
|
2235
2353
|
HumanInputResponseValidationError
|
|
2236
2354
|
} from "@opengeni/db";
|
|
2237
|
-
import {
|
|
2355
|
+
import {
|
|
2356
|
+
appendAndPublishEvents as appendAndPublishEvents3,
|
|
2357
|
+
appendAndPublishTurnEventsFenced,
|
|
2358
|
+
publishDurableSessionEvents
|
|
2359
|
+
} from "@opengeni/events";
|
|
2238
2360
|
import { allowedFirstPartyMcpToolsForSession, codemodeWorkspaceUrl } from "@opengeni/config";
|
|
2239
2361
|
import {
|
|
2240
2362
|
createSignedState as createSignedState6,
|
|
@@ -2252,12 +2374,14 @@ import {
|
|
|
2252
2374
|
authorizedSocialConnectionsForGrant,
|
|
2253
2375
|
authorizedAtlassianConnectionsForGrant,
|
|
2254
2376
|
buildCapabilityCatalog,
|
|
2377
|
+
correctWorkspaceMemoryWithSlackPublication,
|
|
2255
2378
|
nativeConnectionCapabilityRecommendations,
|
|
2256
2379
|
requireLiveAgentAttemptAuthorization as requireLiveAgentAttemptAuthorization2,
|
|
2257
2380
|
requireSessionAuthorization as requireSessionAuthorization2,
|
|
2258
2381
|
requireSessionAuthorizationListScope,
|
|
2259
2382
|
SessionAuthorizationDeniedError as SessionAuthorizationDeniedError3,
|
|
2260
2383
|
SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError2,
|
|
2384
|
+
saveWorkspaceMemoryWithSlackPublication,
|
|
2261
2385
|
searchCapabilityCatalogItems
|
|
2262
2386
|
} from "@opengeni/core";
|
|
2263
2387
|
import { recordWorkspaceUsage as recordWorkspaceUsage3, requireLimit as requireLimit3 } from "@opengeni/core";
|
|
@@ -2502,6 +2626,8 @@ import {
|
|
|
2502
2626
|
withRunCredentialsSession
|
|
2503
2627
|
} from "@opengeni/runtime/sandbox";
|
|
2504
2628
|
import {
|
|
2629
|
+
managedSessionGroupBackend,
|
|
2630
|
+
managedSessionGroupOs,
|
|
2505
2631
|
providerSettingsForSessionSandboxRuntime,
|
|
2506
2632
|
relayConfigFromSettings,
|
|
2507
2633
|
resolveSessionSandboxRuntime,
|
|
@@ -3079,15 +3205,19 @@ async function withChannelAOperation(services, ctx, readOnly, fn) {
|
|
|
3079
3205
|
ctx.waitSignal?.throwIfAborted();
|
|
3080
3206
|
const pointer = await readActiveSandbox(db, workspaceId, session.id);
|
|
3081
3207
|
if (!pointer?.activeSandboxId) {
|
|
3082
|
-
|
|
3208
|
+
const groupBackend = managedSessionGroupBackend(
|
|
3209
|
+
settings.sandboxBackend,
|
|
3210
|
+
session.sandboxBackend
|
|
3211
|
+
);
|
|
3212
|
+
if (groupBackend) {
|
|
3083
3213
|
return await withChannelAOperation(
|
|
3084
3214
|
services,
|
|
3085
3215
|
{
|
|
3086
3216
|
...ctx,
|
|
3087
3217
|
session: {
|
|
3088
3218
|
...session,
|
|
3089
|
-
sandboxBackend:
|
|
3090
|
-
sandboxOs:
|
|
3219
|
+
sandboxBackend: groupBackend,
|
|
3220
|
+
sandboxOs: managedSessionGroupOs(session.sandboxBackend, session.sandboxOs)
|
|
3091
3221
|
}
|
|
3092
3222
|
},
|
|
3093
3223
|
readOnly,
|
|
@@ -3095,7 +3225,7 @@ async function withChannelAOperation(services, ctx, readOnly, fn) {
|
|
|
3095
3225
|
);
|
|
3096
3226
|
}
|
|
3097
3227
|
throw new HTTPException2(409, {
|
|
3098
|
-
message: "machine-home session has no active Connected Machine"
|
|
3228
|
+
message: "machine-home session has no active Connected Machine or managed sandbox"
|
|
3099
3229
|
});
|
|
3100
3230
|
}
|
|
3101
3231
|
const sandbox = await getSandbox(
|
|
@@ -4782,6 +4912,7 @@ import {
|
|
|
4782
4912
|
loadIntegrationOAuthClient,
|
|
4783
4913
|
normalizeBearerScheme,
|
|
4784
4914
|
replaceIntegrationOAuthClientIfCurrent,
|
|
4915
|
+
resolveNamedManagedPersonalWorkspaceGrant,
|
|
4785
4916
|
storeIntegrationOAuthClient,
|
|
4786
4917
|
updateConnection,
|
|
4787
4918
|
withDatabaseStatementTimeout
|
|
@@ -5502,7 +5633,8 @@ function requireIntegrationsStateSecret(settings) {
|
|
|
5502
5633
|
return secret;
|
|
5503
5634
|
}
|
|
5504
5635
|
async function requireOAuthCallbackGrant(db, state) {
|
|
5505
|
-
const
|
|
5636
|
+
const membershipGrant = await getWorkspaceGrant(db, state.subjectId, state.workspaceId);
|
|
5637
|
+
const grant = membershipGrant?.accountId === state.accountId ? membershipGrant : state.personalOwnerVerified ? await resolveNamedManagedPersonalWorkspaceGrant(db, state) : null;
|
|
5506
5638
|
if (!grant || grant.accountId !== state.accountId || !hasPermission3(grant.permissions, "connections:write")) {
|
|
5507
5639
|
throw new HTTPException8(403, {
|
|
5508
5640
|
message: "OAuth subject no longer has permission to write this workspace connection"
|
|
@@ -7528,6 +7660,7 @@ import {
|
|
|
7528
7660
|
ScheduledTaskSyncError,
|
|
7529
7661
|
syncCreatedScheduledTask as syncCreatedScheduledTask3,
|
|
7530
7662
|
syncUpdatedScheduledTask as syncUpdatedScheduledTask3,
|
|
7663
|
+
validateScheduledTaskMachineTarget,
|
|
7531
7664
|
validateScheduledTaskTarget,
|
|
7532
7665
|
updateScheduledTaskForApi,
|
|
7533
7666
|
validatedScheduledTaskUpdate
|
|
@@ -8211,7 +8344,32 @@ var SESSION_WAIT_EVENT_TYPES = [
|
|
|
8211
8344
|
"goal.cleared",
|
|
8212
8345
|
"goal.continuation"
|
|
8213
8346
|
];
|
|
8214
|
-
var
|
|
8347
|
+
var SESSION_WAIT_COMPLETION_EVENT_TYPES = [
|
|
8348
|
+
"turn.completed",
|
|
8349
|
+
"turn.failed",
|
|
8350
|
+
"turn.cancelled",
|
|
8351
|
+
"turn.superseded",
|
|
8352
|
+
"turn.capacity_waiting",
|
|
8353
|
+
"session.requiresAction",
|
|
8354
|
+
"session.humanInput.requested",
|
|
8355
|
+
"session.control.paused",
|
|
8356
|
+
"tool.auth_needed",
|
|
8357
|
+
"credential.auth_needed",
|
|
8358
|
+
"rig.setup.failed",
|
|
8359
|
+
"goal.paused"
|
|
8360
|
+
];
|
|
8361
|
+
var SESSION_WAIT_COMPLETION_EVENT_TYPE_SET = new Set(
|
|
8362
|
+
SESSION_WAIT_COMPLETION_EVENT_TYPES
|
|
8363
|
+
);
|
|
8364
|
+
function sessionWaitCompletionEventMatches(event) {
|
|
8365
|
+
if (!SESSION_WAIT_COMPLETION_EVENT_TYPE_SET.has(event.type)) return false;
|
|
8366
|
+
if (event.type !== "turn.completed") return true;
|
|
8367
|
+
if (event.payload === null || typeof event.payload !== "object" || Array.isArray(event.payload)) {
|
|
8368
|
+
return false;
|
|
8369
|
+
}
|
|
8370
|
+
const payload = event.payload;
|
|
8371
|
+
return Object.prototype.hasOwnProperty.call(payload, "output") && !Object.prototype.hasOwnProperty.call(payload, "segmentLimit") && !Object.prototype.hasOwnProperty.call(payload, "maintenance");
|
|
8372
|
+
}
|
|
8215
8373
|
var SESSION_WAIT_OWN_PENDING_EVENT_TYPE = "system.update.pending";
|
|
8216
8374
|
function ownPendingKindWakes(kind) {
|
|
8217
8375
|
const wakeClass = SESSION_SYSTEM_UPDATE_WAKE_CLASS[kind];
|
|
@@ -8239,6 +8397,10 @@ async function waitForSessionChanges(input) {
|
|
|
8239
8397
|
let liveFanout = true;
|
|
8240
8398
|
let waited = false;
|
|
8241
8399
|
const ownSessionId = input.source.readOwnPendingUpdateKinds ? input.ownSessionId : null;
|
|
8400
|
+
const targetEventTypeSet = new Set(
|
|
8401
|
+
input.targetEventTypes ?? SESSION_WAIT_EVENT_TYPES
|
|
8402
|
+
);
|
|
8403
|
+
const targetEventMatches = (event) => targetEventTypeSet.has(event.type) && (input.targetEventMatches?.(event) ?? true);
|
|
8242
8404
|
const targetAfter = /* @__PURE__ */ new Map();
|
|
8243
8405
|
for (const target of input.targets) {
|
|
8244
8406
|
const existing = targetAfter.get(target.sessionId);
|
|
@@ -8259,7 +8421,7 @@ async function waitForSessionChanges(input) {
|
|
|
8259
8421
|
const after = targetAfter.get(sessionId);
|
|
8260
8422
|
for (const event of events) {
|
|
8261
8423
|
if (event.sessionId !== sessionId) continue;
|
|
8262
|
-
if (after !== void 0 && event.sequence > after &&
|
|
8424
|
+
if (after !== void 0 && event.sequence > after && targetEventMatches(event)) {
|
|
8263
8425
|
return true;
|
|
8264
8426
|
}
|
|
8265
8427
|
if (sessionId === ownSessionId && event.type === SESSION_WAIT_OWN_PENDING_EVENT_TYPE) {
|
|
@@ -8290,7 +8452,7 @@ async function waitForSessionChanges(input) {
|
|
|
8290
8452
|
const changed = [];
|
|
8291
8453
|
for (const { target, read } of targetReads) {
|
|
8292
8454
|
const events = read.events.filter(
|
|
8293
|
-
(event) => event.sequence > target.afterSequence &&
|
|
8455
|
+
(event) => event.sequence > target.afterSequence && targetEventMatches(event)
|
|
8294
8456
|
);
|
|
8295
8457
|
if (events.length === 0) continue;
|
|
8296
8458
|
changed.push({
|
|
@@ -9119,6 +9281,8 @@ import {
|
|
|
9119
9281
|
renewSandboxProviderExpiration
|
|
9120
9282
|
} from "@opengeni/runtime/sandbox";
|
|
9121
9283
|
import {
|
|
9284
|
+
managedSessionGroupBackend as managedSessionGroupBackend2,
|
|
9285
|
+
managedSessionGroupOs as managedSessionGroupOs2,
|
|
9122
9286
|
providerSettingsForSessionSandboxRuntime as providerSettingsForSessionSandboxRuntime2,
|
|
9123
9287
|
relayConfigFromSettings as relayConfigFromSettings2,
|
|
9124
9288
|
resolveSessionSandboxRuntime as resolveSessionSandboxRuntime2
|
|
@@ -9178,7 +9342,21 @@ async function sessionAttachEnvironment(services, workspaceId, session, attachSu
|
|
|
9178
9342
|
}
|
|
9179
9343
|
async function attachViewer(services, input) {
|
|
9180
9344
|
const { db, settings } = services;
|
|
9181
|
-
const { accountId, workspaceId
|
|
9345
|
+
const { accountId, workspaceId } = input;
|
|
9346
|
+
const groupBackend = managedSessionGroupBackend2(
|
|
9347
|
+
settings.sandboxBackend,
|
|
9348
|
+
input.session.sandboxBackend
|
|
9349
|
+
);
|
|
9350
|
+
if (!groupBackend) {
|
|
9351
|
+
throw new HTTPException10(409, {
|
|
9352
|
+
message: "session has no managed sandbox group to attach"
|
|
9353
|
+
});
|
|
9354
|
+
}
|
|
9355
|
+
const session = groupBackend === input.session.sandboxBackend ? input.session : {
|
|
9356
|
+
...input.session,
|
|
9357
|
+
sandboxBackend: groupBackend,
|
|
9358
|
+
sandboxOs: managedSessionGroupOs2(input.session.sandboxBackend, input.session.sandboxOs)
|
|
9359
|
+
};
|
|
9182
9360
|
const viewerId = input.viewerId ?? crypto.randomUUID();
|
|
9183
9361
|
const attachSubjectId = claimableSubjectId(input.viewerSubjectId ?? null);
|
|
9184
9362
|
const attachAuthorityEpoch = attachSubjectId ? await getSessionAuthorityEpoch(db, { accountId, workspaceId, sessionId: session.id }) : null;
|
|
@@ -15872,17 +16050,19 @@ function mutationAnnotations(title, options) {
|
|
|
15872
16050
|
|
|
15873
16051
|
// src/mcp/company-brain-governed-writes.ts
|
|
15874
16052
|
import {
|
|
15875
|
-
AGENT_AUTHORED_DURABLE_TEXT_STYLE,
|
|
15876
16053
|
AGENT_AUTHORED_INSTRUCTION_POLICY_CONTENT_MAX_CHARS,
|
|
15877
16054
|
AGENT_AUTHORED_INSTRUCTION_POLICY_CONTENT_TOO_LONG_MESSAGE,
|
|
16055
|
+
AGENT_AUTHORED_INSTRUCTION_POLICY_STYLE,
|
|
15878
16056
|
AGENT_AUTHORED_PREFERENCE_CONTENT_MAX_CHARS,
|
|
15879
16057
|
AGENT_AUTHORED_PREFERENCE_CONTENT_TOO_LONG_MESSAGE,
|
|
16058
|
+
AGENT_AUTHORED_SKILL_STYLE,
|
|
15880
16059
|
PREFERENCE_REGISTRY_DESCRIPTOR_DESCRIPTION_MAX_CHARS,
|
|
15881
16060
|
PREFERENCE_REGISTRY_STABLE_KEY_MAX_CHARS,
|
|
15882
16061
|
PREFERENCE_REGISTRY_TITLE_MAX_CHARS,
|
|
15883
16062
|
WorkspaceInstructionPolicyTarget
|
|
15884
16063
|
} from "@opengeni/contracts";
|
|
15885
16064
|
import { createCompanyBrainLearningPolicyRouter } from "@opengeni/core";
|
|
16065
|
+
import { PreferenceRegistryStableKeyConflictError } from "@opengeni/db";
|
|
15886
16066
|
import * as z6 from "zod/v4";
|
|
15887
16067
|
var reason = z6.string().trim().min(1).max(4096);
|
|
15888
16068
|
var evidence = {
|
|
@@ -15903,6 +16083,20 @@ var taskNotePromotion = {
|
|
|
15903
16083
|
};
|
|
15904
16084
|
function registerCompanyBrainGovernedWriteTools(input) {
|
|
15905
16085
|
const router = input.router ?? createCompanyBrainLearningPolicyRouter({ db: input.db });
|
|
16086
|
+
const writeResult = async (write) => {
|
|
16087
|
+
try {
|
|
16088
|
+
return input.json(await write());
|
|
16089
|
+
} catch (error) {
|
|
16090
|
+
if (error instanceof PreferenceRegistryStableKeyConflictError) {
|
|
16091
|
+
return input.json({
|
|
16092
|
+
status: "not_proposed",
|
|
16093
|
+
code: "preference_stable_key_conflict",
|
|
16094
|
+
message: error.message
|
|
16095
|
+
});
|
|
16096
|
+
}
|
|
16097
|
+
throw error;
|
|
16098
|
+
}
|
|
16099
|
+
};
|
|
15906
16100
|
input.server.registerTool(
|
|
15907
16101
|
"knowledge_propose",
|
|
15908
16102
|
{
|
|
@@ -15911,8 +16105,8 @@ function registerCompanyBrainGovernedWriteTools(input) {
|
|
|
15911
16105
|
},
|
|
15912
16106
|
async (request) => {
|
|
15913
16107
|
await input.authorize();
|
|
15914
|
-
return
|
|
15915
|
-
|
|
16108
|
+
return writeResult(
|
|
16109
|
+
() => router.write({
|
|
15916
16110
|
attempt: input.attempt,
|
|
15917
16111
|
request: { kind: "propose_knowledge", ...request }
|
|
15918
16112
|
})
|
|
@@ -15927,8 +16121,8 @@ function registerCompanyBrainGovernedWriteTools(input) {
|
|
|
15927
16121
|
},
|
|
15928
16122
|
async (request) => {
|
|
15929
16123
|
await input.authorize();
|
|
15930
|
-
return
|
|
15931
|
-
|
|
16124
|
+
return writeResult(
|
|
16125
|
+
() => router.write({
|
|
15932
16126
|
attempt: input.attempt,
|
|
15933
16127
|
request: { kind: "correct_knowledge", ...request }
|
|
15934
16128
|
})
|
|
@@ -15943,8 +16137,8 @@ function registerCompanyBrainGovernedWriteTools(input) {
|
|
|
15943
16137
|
},
|
|
15944
16138
|
async (request) => {
|
|
15945
16139
|
await input.authorize();
|
|
15946
|
-
return
|
|
15947
|
-
|
|
16140
|
+
return writeResult(
|
|
16141
|
+
() => router.write({
|
|
15948
16142
|
attempt: input.attempt,
|
|
15949
16143
|
request: { kind: "promote_task_note_knowledge", ...request }
|
|
15950
16144
|
})
|
|
@@ -15954,7 +16148,7 @@ function registerCompanyBrainGovernedWriteTools(input) {
|
|
|
15954
16148
|
input.server.registerTool(
|
|
15955
16149
|
"task_note_promote_instruction_policy",
|
|
15956
16150
|
{
|
|
15957
|
-
description: `Atomically promote one still-active note from this exact root task tree into
|
|
16151
|
+
description: `Atomically promote one still-active note from this exact root task tree into a workspace instruction-policy proposal. The note bytes remain exact evidence and draft content. Use this only for a universal always-on rule, never for an incident, fact, decision, outcome, or conditional procedure. Once active, those bytes are composed verbatim into the prompt of every session the target applies to, so a note over ${AGENT_AUTHORED_INSTRUCTION_POLICY_CONTENT_MAX_CHARS} characters is rejected here rather than truncated: write a fresh minimal imperative note instead of promoting a long working note. Off creates nothing; Review first keeps the proposal inactive; Autonomous may activate an eligible proposal through the governed instruction lifecycle with an undoable receipt. This never widens scope.`,
|
|
15958
16152
|
inputSchema: {
|
|
15959
16153
|
...taskNotePromotion,
|
|
15960
16154
|
target: WorkspaceInstructionPolicyTarget,
|
|
@@ -15964,8 +16158,8 @@ function registerCompanyBrainGovernedWriteTools(input) {
|
|
|
15964
16158
|
},
|
|
15965
16159
|
async (request) => {
|
|
15966
16160
|
await input.authorize();
|
|
15967
|
-
return
|
|
15968
|
-
|
|
16161
|
+
return writeResult(
|
|
16162
|
+
() => router.write({
|
|
15969
16163
|
attempt: input.attempt,
|
|
15970
16164
|
request: { kind: "promote_task_note_instruction_policy", ...request }
|
|
15971
16165
|
})
|
|
@@ -15989,8 +16183,8 @@ function registerCompanyBrainGovernedWriteTools(input) {
|
|
|
15989
16183
|
},
|
|
15990
16184
|
async (request) => {
|
|
15991
16185
|
await input.authorize();
|
|
15992
|
-
return
|
|
15993
|
-
|
|
16186
|
+
return writeResult(
|
|
16187
|
+
() => router.write({
|
|
15994
16188
|
attempt: input.attempt,
|
|
15995
16189
|
request: { kind: "promote_task_note_preference", ...request }
|
|
15996
16190
|
})
|
|
@@ -16000,7 +16194,7 @@ function registerCompanyBrainGovernedWriteTools(input) {
|
|
|
16000
16194
|
input.server.registerTool(
|
|
16001
16195
|
"instruction_policy_propose",
|
|
16002
16196
|
{
|
|
16003
|
-
description: `Materialize an evidence-backed
|
|
16197
|
+
description: `Materialize an evidence-backed workspace instruction-policy proposal. Use this only for a minimal universal rule, never for an incident, fact, decision, outcome, or conditional procedure. Once active, this content is composed verbatim into the prompt of every session the target applies to (every session in this workspace for a global charter or policy, every session bound to the role for a role policy), so keep it under ${AGENT_AUTHORED_INSTRUCTION_POLICY_CONTENT_MAX_CHARS} characters. ${AGENT_AUTHORED_INSTRUCTION_POLICY_STYLE} Off creates nothing; Review first keeps the proposal inactive; Autonomous may activate an eligible proposal through the governed instruction lifecycle with an undoable receipt.`,
|
|
16004
16198
|
inputSchema: {
|
|
16005
16199
|
...evidence,
|
|
16006
16200
|
target: WorkspaceInstructionPolicyTarget,
|
|
@@ -16015,8 +16209,8 @@ function registerCompanyBrainGovernedWriteTools(input) {
|
|
|
16015
16209
|
},
|
|
16016
16210
|
async (request) => {
|
|
16017
16211
|
await input.authorize();
|
|
16018
|
-
return
|
|
16019
|
-
|
|
16212
|
+
return writeResult(
|
|
16213
|
+
() => router.write({
|
|
16020
16214
|
attempt: input.attempt,
|
|
16021
16215
|
request: { kind: "propose_instruction_policy", ...request }
|
|
16022
16216
|
})
|
|
@@ -16026,7 +16220,7 @@ function registerCompanyBrainGovernedWriteTools(input) {
|
|
|
16026
16220
|
input.server.registerTool(
|
|
16027
16221
|
"preference_propose",
|
|
16028
16222
|
{
|
|
16029
|
-
description: `Materialize an evidence-backed workspace Skill proposal in the structured preference authority. Use this only for reusable conditional how-to guidance, never for an incident, fact, decision, outcome, or universal always-on rule. Its short title and description are what get composed into every session prompt; the content is retrieved on demand, so its length is retrieval cost rather than standing prompt cost. Keep the content under ${AGENT_AUTHORED_PREFERENCE_CONTENT_MAX_CHARS} characters. ${
|
|
16223
|
+
description: `Materialize an evidence-backed workspace Skill proposal in the structured preference authority. Use this only for reusable conditional how-to guidance, never for an incident, fact, decision, outcome, or universal always-on rule. Its short title and description are what get composed into every session prompt; the content is retrieved on demand, so its length is retrieval cost rather than standing prompt cost. Keep the content under ${AGENT_AUTHORED_PREFERENCE_CONTENT_MAX_CHARS} characters. ${AGENT_AUTHORED_SKILL_STYLE} Under Suggest it stays inactive for human review; under Automatic an eligible decision is activated through the governed preference lifecycle with an undoable receipt. It never creates mandatory authority.`,
|
|
16030
16224
|
inputSchema: {
|
|
16031
16225
|
...evidence,
|
|
16032
16226
|
stableKey: z6.string().trim().min(1).max(PREFERENCE_REGISTRY_STABLE_KEY_MAX_CHARS),
|
|
@@ -16045,8 +16239,8 @@ function registerCompanyBrainGovernedWriteTools(input) {
|
|
|
16045
16239
|
},
|
|
16046
16240
|
async (request) => {
|
|
16047
16241
|
await input.authorize();
|
|
16048
|
-
return
|
|
16049
|
-
|
|
16242
|
+
return writeResult(
|
|
16243
|
+
() => router.write({
|
|
16050
16244
|
attempt: input.attempt,
|
|
16051
16245
|
request: { kind: "propose_preference", ...request }
|
|
16052
16246
|
})
|
|
@@ -16090,7 +16284,7 @@ function registerCompanyProfileAgentAdminTools(input) {
|
|
|
16090
16284
|
input.server.registerTool(
|
|
16091
16285
|
"company_profile_propose",
|
|
16092
16286
|
{
|
|
16093
|
-
description: `Prepare the organization's small, stable identity: identity says who the organization is, and mission says why it exists. Once activated, both fields are mandatory prompt context in every root session for the whole organization, so use one plain descriptive statement per field with no products, customers, goals, constraints, procedures, or marketing copy. Those details belong in organization-scoped Documents and are retrieved only when relevant. Each field is bounded to ${AGENT_AUTHORED_COMPANY_PROFILE_SCALAR_MAX_CHARS} characters for agent-authored proposals. This
|
|
16287
|
+
description: `Prepare the organization's small, stable identity: identity says who the organization is, and mission says why it exists. Once activated, both fields are mandatory prompt context in every root session for the whole organization, so use one plain descriptive statement per field with no products, customers, goals, constraints, procedures, or marketing copy. Those details belong in organization-scoped Documents and are retrieved only when relevant. Each field is bounded to ${AGENT_AUTHORED_COMPANY_PROFILE_SCALAR_MAX_CHARS} characters for agent-authored proposals. This is independent of workspace learning policy and follows the organization owner's Agent-managed identity mode. Off creates nothing. Review first returns status=confirmation_required with the exact \`humanInput\` payload; call \`request_human_input\` with it verbatim, then call \`company_profile_confirm\` with the returned requestId. Autonomous may return status=activated immediately after exact live-owner, stale-head, and compare-and-swap checks; do not ask for another confirmation in that case.`,
|
|
16094
16288
|
inputSchema: {
|
|
16095
16289
|
operationId: z7.string().uuid(),
|
|
16096
16290
|
identity: scalar,
|
|
@@ -16170,6 +16364,7 @@ import {
|
|
|
16170
16364
|
agentAuthoredDurableTextTooLongMessage
|
|
16171
16365
|
} from "@opengeni/contracts";
|
|
16172
16366
|
import { RememberError, createRememberRouter } from "@opengeni/core";
|
|
16367
|
+
import { PreferenceRegistryStableKeyConflictError as PreferenceRegistryStableKeyConflictError2 } from "@opengeni/db";
|
|
16173
16368
|
import * as z8 from "zod/v4";
|
|
16174
16369
|
var laneContentMaxChars = {
|
|
16175
16370
|
instruction_policy: AGENT_AUTHORED_INSTRUCTION_POLICY_CONTENT_MAX_CHARS2,
|
|
@@ -16188,7 +16383,7 @@ function registerRememberTools(input) {
|
|
|
16188
16383
|
input.server.registerTool(
|
|
16189
16384
|
"remember",
|
|
16190
16385
|
{
|
|
16191
|
-
description: `
|
|
16386
|
+
description: `Create governed durable knowledge, a Skill, or a mandatory workspace instruction. When the agent-only memory_save tool is available, use it instead for ordinary durable facts, decisions, incidents, bug fixes, and confirmed outcomes; those Memory writes are autonomous and independent of Learning mode. Use lane=knowledge only when memory_save is unavailable and the user explicitly requests reviewed workspace knowledge; lane=preference creates a Skill for reusable conditional how-to guidance; lane=instruction_policy is only for a universal always/never rule that should apply to nearly every task. Write the instruction lane as the shortest complete rule, at most ${AGENT_AUTHORED_INSTRUCTION_POLICY_CONTENT_MAX_CHARS2} characters and normally 1-3 imperative sentences, with no numbered steps, examples, rationale, or restated defaults. Keep a Skill under ${AGENT_AUTHORED_PREFERENCE_CONTENT_MAX_CHARS2} characters with one clear trigger and outcome, only the necessary steps, checks, and exceptions, and a one-sentence descriptor. Do not copy one item into multiple lanes. Under Autonomous learning, an eligible Skill or workspace instruction may activate immediately through its governed lifecycle. Under Review first, it remains inactive and the receipt returns status=confirmation_required with the exact \`humanInput\` payload: call \`request_human_input\` with it verbatim, then call \`remember_confirm\` with the returned requestId. Off creates no governed change. Reviewed Knowledge always needs confirmation. Do not use lane=knowledge for facts you merely inferred; use memory_save when available, otherwise knowledge_propose or task notes. Confirmed lane=knowledge content keeps its reviewed claim provenance and materializes its exact approved text into Memory for later \`memory_search\` retrieval.`,
|
|
16192
16387
|
inputSchema: {
|
|
16193
16388
|
lane: z8.enum(["preference", "instruction_policy", "knowledge"]),
|
|
16194
16389
|
...laneFields,
|
|
@@ -16220,9 +16415,20 @@ function registerRememberTools(input) {
|
|
|
16220
16415
|
title: request.title ?? content.slice(0, 80),
|
|
16221
16416
|
description: request.description ?? content.slice(0, 200)
|
|
16222
16417
|
} : lane === "instruction_policy" ? { ...base, lane, target: request.target } : { ...base, lane, subject: request.subject ?? content.slice(0, 80) };
|
|
16223
|
-
|
|
16224
|
-
|
|
16225
|
-
|
|
16418
|
+
try {
|
|
16419
|
+
return input.json(
|
|
16420
|
+
await router.remember({ attempt: input.attempt, request: rememberRequest })
|
|
16421
|
+
);
|
|
16422
|
+
} catch (error) {
|
|
16423
|
+
if (error instanceof PreferenceRegistryStableKeyConflictError2) {
|
|
16424
|
+
return input.json({
|
|
16425
|
+
status: "not_remembered",
|
|
16426
|
+
code: "preference_stable_key_conflict",
|
|
16427
|
+
message: error.message
|
|
16428
|
+
});
|
|
16429
|
+
}
|
|
16430
|
+
throw error;
|
|
16431
|
+
}
|
|
16226
16432
|
}
|
|
16227
16433
|
);
|
|
16228
16434
|
input.server.registerTool(
|
|
@@ -18450,6 +18656,7 @@ function observeWorkDiscovery(observability, observation) {
|
|
|
18450
18656
|
// src/mcp/server.ts
|
|
18451
18657
|
var ORCHESTRATION_FAILURE_CODE_MAX_LENGTH = 128;
|
|
18452
18658
|
var ORCHESTRATION_FAILURE_MESSAGE_MAX_UTF8_BYTES = 1024;
|
|
18659
|
+
var MCP_DISCOVERY_QUERY_MAX_UTF16_CODE_UNITS = WORK_DISCOVERY_QUERY_MAX_CHARS * 8;
|
|
18453
18660
|
function boundedOrchestrationFailureMessage(value) {
|
|
18454
18661
|
const normalized = value.replace(/[\u0000-\u001f\u007f]+/g, " ").trim();
|
|
18455
18662
|
if (!normalized) return "OpenGeni could not complete the request.";
|
|
@@ -18534,9 +18741,6 @@ var FIRST_PARTY_TOOL_AUTHORIZATION = {
|
|
|
18534
18741
|
goal_complete: { sessionRequired: true, allOf: ["goals:manage"] },
|
|
18535
18742
|
goal_pause: { sessionRequired: true, allOf: ["goals:manage"] },
|
|
18536
18743
|
memory_search: { sessionRequired: true, allOf: ["documents:search"] },
|
|
18537
|
-
// Retired: never registered, so these are never consulted. The map must stay
|
|
18538
|
-
// total over the tool-name union, which still carries both names so that
|
|
18539
|
-
// previously written scheduled-task snapshots keep parsing.
|
|
18540
18744
|
memory_save: { sessionRequired: true, allOf: ["documents:search"] },
|
|
18541
18745
|
memory_correct: { sessionRequired: true, allOf: ["documents:search"] },
|
|
18542
18746
|
preference_registry_summary: {
|
|
@@ -19699,6 +19903,14 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
|
|
|
19699
19903
|
agentConfig: task.agentConfig,
|
|
19700
19904
|
missingTargetStatus: 404
|
|
19701
19905
|
});
|
|
19906
|
+
await validateScheduledTaskMachineTarget({
|
|
19907
|
+
settings: deps.settings,
|
|
19908
|
+
db: deps.db,
|
|
19909
|
+
grant,
|
|
19910
|
+
runMode: task.runMode,
|
|
19911
|
+
agentConfig: task.agentConfig,
|
|
19912
|
+
requireOnline: true
|
|
19913
|
+
});
|
|
19702
19914
|
await requireLimit3(deps, {
|
|
19703
19915
|
accountId: grant.accountId,
|
|
19704
19916
|
workspaceId: grant.workspaceId,
|
|
@@ -21103,6 +21315,7 @@ function registerPreferenceRegistryTools(server, deps, grant, json) {
|
|
|
21103
21315
|
);
|
|
21104
21316
|
}
|
|
21105
21317
|
var MemoryKindSchema = z43.enum(["preference", "semantic", "procedural", "decision", "episodic"]);
|
|
21318
|
+
var MemoryWriteKindSchema = z43.enum(["semantic", "decision", "episodic"]);
|
|
21106
21319
|
function scheduledTaskReceipt(operation, task, outcome, changed, options = {}) {
|
|
21107
21320
|
return mcpMutationReceipt({
|
|
21108
21321
|
operation,
|
|
@@ -21150,11 +21363,34 @@ function scheduledTaskUpdateChangesState(task, update) {
|
|
|
21150
21363
|
if (update.personalConnectionDelegations !== void 0) return true;
|
|
21151
21364
|
return false;
|
|
21152
21365
|
}
|
|
21366
|
+
function memorySlackPublicationActor(actor, sessionId, fallbackOwnerLabel) {
|
|
21367
|
+
return {
|
|
21368
|
+
actor: {
|
|
21369
|
+
kind: actor.initiator.kind === "subject" ? "human" : "service",
|
|
21370
|
+
subjectId: actor.initiator.subjectId,
|
|
21371
|
+
initiatingHumanSubjectId: actor.initiatingHumanSubjectId,
|
|
21372
|
+
sessionId,
|
|
21373
|
+
turnId: actor.turnId,
|
|
21374
|
+
attemptId: actor.attemptId
|
|
21375
|
+
},
|
|
21376
|
+
ownerLabel: actor.initiator.label ?? fallbackOwnerLabel
|
|
21377
|
+
};
|
|
21378
|
+
}
|
|
21379
|
+
function memoryPreview(text) {
|
|
21380
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
21381
|
+
return normalized.length <= 120 ? normalized : `${normalized.slice(0, 119)}\u2026`;
|
|
21382
|
+
}
|
|
21153
21383
|
function registerMemoryTools(server, deps, grant, sessionId, json, promptMode) {
|
|
21384
|
+
const publicationInputSchema = z43.object({
|
|
21385
|
+
importance: z43.enum(["major", "normal", "minor"]),
|
|
21386
|
+
audience: z43.literal("workspace"),
|
|
21387
|
+
slackMode: z43.enum(["auto", "review", "never"]),
|
|
21388
|
+
shareSummary: z43.string().trim().min(1).max(4096)
|
|
21389
|
+
});
|
|
21154
21390
|
server.registerTool(
|
|
21155
21391
|
"memory_search",
|
|
21156
21392
|
{
|
|
21157
|
-
description: `${MEMORY_SEARCH_TOOL_DESCRIPTION} Legacy preference
|
|
21393
|
+
description: `${MEMORY_SEARCH_TOOL_DESCRIPTION} All existing Memory kinds are searchable. Legacy preference and procedure records are historical context, not active instructions; Skills and workspace instructions remain the behavioral authorities. When workspace Memory is enabled, use memory_save autonomously for durable facts, decisions, incidents, fixes, and outcomes, and memory_correct when an existing record is wrong or outdated.`,
|
|
21158
21394
|
inputSchema: {
|
|
21159
21395
|
query: z43.string().min(1),
|
|
21160
21396
|
kind: MemoryKindSchema.optional(),
|
|
@@ -21175,6 +21411,195 @@ function registerMemoryTools(server, deps, grant, sessionId, json, promptMode) {
|
|
|
21175
21411
|
)
|
|
21176
21412
|
})
|
|
21177
21413
|
);
|
|
21414
|
+
if (exactAgentAttemptClaims(grant) === null) return;
|
|
21415
|
+
server.registerTool(
|
|
21416
|
+
"memory_save",
|
|
21417
|
+
{
|
|
21418
|
+
description: MEMORY_SAVE_TOOL_DESCRIPTION,
|
|
21419
|
+
inputSchema: {
|
|
21420
|
+
text: z43.string().min(1),
|
|
21421
|
+
kind: MemoryWriteKindSchema,
|
|
21422
|
+
confidence: z43.number().min(0).max(1).optional(),
|
|
21423
|
+
replaces_id: z43.string().min(1).optional(),
|
|
21424
|
+
slack_publication: publicationInputSchema.optional()
|
|
21425
|
+
}
|
|
21426
|
+
},
|
|
21427
|
+
async ({ text, kind, confidence, replaces_id, slack_publication }) => {
|
|
21428
|
+
const actor = await requireLiveAgentAttemptAuthorization2(deps.db, grant, sessionId);
|
|
21429
|
+
const result = await saveWorkspaceMemoryWithSlackPublication(
|
|
21430
|
+
deps.db,
|
|
21431
|
+
{
|
|
21432
|
+
accountId: grant.accountId,
|
|
21433
|
+
workspaceId: grant.workspaceId,
|
|
21434
|
+
sessionId,
|
|
21435
|
+
text,
|
|
21436
|
+
kind,
|
|
21437
|
+
...confidence !== void 0 ? { confidence } : {},
|
|
21438
|
+
...replaces_id ? { replacesId: replaces_id } : {},
|
|
21439
|
+
origin: "agent"
|
|
21440
|
+
},
|
|
21441
|
+
slack_publication ? {
|
|
21442
|
+
distribution: MemorySlackPublicationDistribution.parse(slack_publication),
|
|
21443
|
+
actor: memorySlackPublicationActor(actor, sessionId, grant.subjectLabel ?? null).actor,
|
|
21444
|
+
ownerLabel: actor.initiator.label ?? grant.subjectLabel ?? null
|
|
21445
|
+
} : null,
|
|
21446
|
+
deps.getDocumentServices().embedder
|
|
21447
|
+
);
|
|
21448
|
+
let timelineWarning = null;
|
|
21449
|
+
try {
|
|
21450
|
+
await appendAndPublishEvents3(deps.db, deps.bus, grant.workspaceId, sessionId, [
|
|
21451
|
+
{
|
|
21452
|
+
type: "memory.saved",
|
|
21453
|
+
payload: {
|
|
21454
|
+
memoryId: result.memory.id,
|
|
21455
|
+
kind: result.memory.kind,
|
|
21456
|
+
preview: memoryPreview(result.memory.text),
|
|
21457
|
+
deduped: result.deduped,
|
|
21458
|
+
...result.superseded ? { supersededMemoryId: result.superseded.id } : {}
|
|
21459
|
+
}
|
|
21460
|
+
}
|
|
21461
|
+
]);
|
|
21462
|
+
} catch {
|
|
21463
|
+
timelineWarning = "Memory committed, but its session timeline event could not be recorded.";
|
|
21464
|
+
console.warn("workspace memory save: committed without session timeline event", {
|
|
21465
|
+
errorClass: "MemoryTimelineOperationError",
|
|
21466
|
+
errorCode: "memory_save_timeline_append_failed",
|
|
21467
|
+
origin: "api",
|
|
21468
|
+
workspaceId: grant.workspaceId,
|
|
21469
|
+
sessionId,
|
|
21470
|
+
memoryId: result.memory.id
|
|
21471
|
+
});
|
|
21472
|
+
}
|
|
21473
|
+
const changed = !result.deduped || result.updated || result.superseded !== null;
|
|
21474
|
+
const outcome = result.updated || result.superseded !== null ? "updated" : result.deduped ? "unchanged" : "created";
|
|
21475
|
+
return json(
|
|
21476
|
+
mcpMutationReceipt({
|
|
21477
|
+
operation: "memory_save",
|
|
21478
|
+
committed: true,
|
|
21479
|
+
outcome,
|
|
21480
|
+
changed,
|
|
21481
|
+
resource: {
|
|
21482
|
+
type: "knowledge_memory",
|
|
21483
|
+
id: result.memory.id,
|
|
21484
|
+
state: result.memory.status
|
|
21485
|
+
},
|
|
21486
|
+
relatedResources: result.superseded ? [
|
|
21487
|
+
{
|
|
21488
|
+
type: "knowledge_memory",
|
|
21489
|
+
id: result.superseded.id,
|
|
21490
|
+
state: result.superseded.status
|
|
21491
|
+
}
|
|
21492
|
+
] : void 0,
|
|
21493
|
+
timestamp: result.memory.updatedAt,
|
|
21494
|
+
idempotency: { status: "not_supported" },
|
|
21495
|
+
warnings: [
|
|
21496
|
+
...!result.embedded ? ["Memory committed without a vector embedding; keyword search remains available."] : [],
|
|
21497
|
+
...timelineWarning ? [timelineWarning] : []
|
|
21498
|
+
],
|
|
21499
|
+
facts: {
|
|
21500
|
+
deduped: result.deduped,
|
|
21501
|
+
dedupeReason: result.dedupeReason,
|
|
21502
|
+
updatedInPlace: result.updated,
|
|
21503
|
+
embedded: result.embedded,
|
|
21504
|
+
slackPublicationDecision: result.slackPublication.decision?.eligible ? "eligible" : result.slackPublication.decision?.reason ?? "not_requested",
|
|
21505
|
+
slackPublicationId: result.slackPublication.enqueue?.kind === "enqueued" || result.slackPublication.enqueue?.kind === "replayed" ? result.slackPublication.enqueue.publication.id : null,
|
|
21506
|
+
slackPublicationState: result.slackPublication.enqueue?.kind === "enqueued" || result.slackPublication.enqueue?.kind === "replayed" ? result.slackPublication.enqueue.publication.state : null
|
|
21507
|
+
}
|
|
21508
|
+
})
|
|
21509
|
+
);
|
|
21510
|
+
}
|
|
21511
|
+
);
|
|
21512
|
+
server.registerTool(
|
|
21513
|
+
"memory_correct",
|
|
21514
|
+
{
|
|
21515
|
+
description: MEMORY_CORRECT_TOOL_DESCRIPTION,
|
|
21516
|
+
inputSchema: {
|
|
21517
|
+
id: z43.string().min(1),
|
|
21518
|
+
reason: z43.string().min(1).optional(),
|
|
21519
|
+
replacement_text: z43.string().min(1).optional(),
|
|
21520
|
+
slack_publication: publicationInputSchema.optional()
|
|
21521
|
+
}
|
|
21522
|
+
},
|
|
21523
|
+
async ({ id, reason: reason2, replacement_text, slack_publication }) => {
|
|
21524
|
+
const actor = await requireLiveAgentAttemptAuthorization2(deps.db, grant, sessionId);
|
|
21525
|
+
const result = await correctWorkspaceMemoryWithSlackPublication(
|
|
21526
|
+
deps.db,
|
|
21527
|
+
{
|
|
21528
|
+
accountId: grant.accountId,
|
|
21529
|
+
workspaceId: grant.workspaceId,
|
|
21530
|
+
sessionId,
|
|
21531
|
+
id,
|
|
21532
|
+
...reason2 ? { reason: reason2 } : {},
|
|
21533
|
+
...replacement_text ? { replacementText: replacement_text } : {},
|
|
21534
|
+
origin: "agent"
|
|
21535
|
+
},
|
|
21536
|
+
slack_publication ? {
|
|
21537
|
+
distribution: MemorySlackPublicationDistribution.parse(slack_publication),
|
|
21538
|
+
actor: memorySlackPublicationActor(actor, sessionId, grant.subjectLabel ?? null).actor,
|
|
21539
|
+
ownerLabel: actor.initiator.label ?? grant.subjectLabel ?? null
|
|
21540
|
+
} : null,
|
|
21541
|
+
deps.getDocumentServices().embedder
|
|
21542
|
+
);
|
|
21543
|
+
let timelineWarning = null;
|
|
21544
|
+
try {
|
|
21545
|
+
await appendAndPublishEvents3(deps.db, deps.bus, grant.workspaceId, sessionId, [
|
|
21546
|
+
{
|
|
21547
|
+
type: "memory.corrected",
|
|
21548
|
+
payload: {
|
|
21549
|
+
memoryId: result.memory.id,
|
|
21550
|
+
kind: result.memory.kind,
|
|
21551
|
+
preview: memoryPreview(result.memory.text),
|
|
21552
|
+
action: result.action,
|
|
21553
|
+
...reason2 ? { reason: memoryPreview(reason2) } : {},
|
|
21554
|
+
...result.replacement ? {
|
|
21555
|
+
replacementMemoryId: result.replacement.id,
|
|
21556
|
+
replacementPreview: memoryPreview(result.replacement.text)
|
|
21557
|
+
} : {}
|
|
21558
|
+
}
|
|
21559
|
+
}
|
|
21560
|
+
]);
|
|
21561
|
+
} catch {
|
|
21562
|
+
timelineWarning = "Memory correction committed, but its session timeline event could not be recorded.";
|
|
21563
|
+
console.warn("workspace memory correction: committed without session timeline event", {
|
|
21564
|
+
errorClass: "MemoryTimelineOperationError",
|
|
21565
|
+
errorCode: "memory_correct_timeline_append_failed",
|
|
21566
|
+
origin: "api",
|
|
21567
|
+
workspaceId: grant.workspaceId,
|
|
21568
|
+
sessionId,
|
|
21569
|
+
memoryId: result.memory.id
|
|
21570
|
+
});
|
|
21571
|
+
}
|
|
21572
|
+
return json(
|
|
21573
|
+
mcpMutationReceipt({
|
|
21574
|
+
operation: "memory_correct",
|
|
21575
|
+
committed: true,
|
|
21576
|
+
outcome: "updated",
|
|
21577
|
+
changed: true,
|
|
21578
|
+
resource: {
|
|
21579
|
+
type: "knowledge_memory",
|
|
21580
|
+
id: result.memory.id,
|
|
21581
|
+
state: result.memory.status
|
|
21582
|
+
},
|
|
21583
|
+
relatedResources: result.replacement ? [
|
|
21584
|
+
{
|
|
21585
|
+
type: "knowledge_memory",
|
|
21586
|
+
id: result.replacement.id,
|
|
21587
|
+
state: result.replacement.status
|
|
21588
|
+
}
|
|
21589
|
+
] : void 0,
|
|
21590
|
+
timestamp: (result.replacement ?? result.memory).updatedAt,
|
|
21591
|
+
idempotency: { status: "not_supported" },
|
|
21592
|
+
warnings: timelineWarning ? [timelineWarning] : [],
|
|
21593
|
+
facts: {
|
|
21594
|
+
correctionAction: result.action,
|
|
21595
|
+
slackPublicationDecision: result.slackPublication.decision?.eligible ? "eligible" : result.slackPublication.decision?.reason ?? "not_requested",
|
|
21596
|
+
slackPublicationId: result.slackPublication.enqueue?.kind === "enqueued" || result.slackPublication.enqueue?.kind === "replayed" ? result.slackPublication.enqueue.publication.id : null,
|
|
21597
|
+
slackPublicationState: result.slackPublication.enqueue?.kind === "enqueued" || result.slackPublication.enqueue?.kind === "replayed" ? result.slackPublication.enqueue.publication.state : null
|
|
21598
|
+
}
|
|
21599
|
+
})
|
|
21600
|
+
);
|
|
21601
|
+
}
|
|
21602
|
+
);
|
|
21178
21603
|
}
|
|
21179
21604
|
function registerFleetTools(server, deps, grant, sessionId, json) {
|
|
21180
21605
|
const services = {
|
|
@@ -21668,7 +22093,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
21668
22093
|
includeLastMessage: z43.boolean().optional(),
|
|
21669
22094
|
orderBy: z43.enum(["createdAt", "updatedAt", "relevance"]).optional(),
|
|
21670
22095
|
updatedAfter: z43.string().max(64).optional(),
|
|
21671
|
-
query: z43.string().max(
|
|
22096
|
+
query: z43.string().max(MCP_DISCOVERY_QUERY_MAX_UTF16_CODE_UNITS).optional(),
|
|
21672
22097
|
statuses: z43.array(
|
|
21673
22098
|
z43.enum([
|
|
21674
22099
|
"queued",
|
|
@@ -21920,7 +22345,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
21920
22345
|
server.registerTool(
|
|
21921
22346
|
"session_wait",
|
|
21922
22347
|
{
|
|
21923
|
-
description: `Block until a watched session has new durable events after your cursor, until your own session has pending machine input (a child result, an agent message, a steer), or until maxWaitSeconds (default ${SESSION_WAIT_DEFAULT_SECONDS}, max ${SESSION_WAIT_MAX_SECONDS}) elapses. Use this for short waits inside the current turn instead of sleeping and polling session_events/session_get/sessions_list while a child or peer session works; for long waits end this turn with goal_wait rather than looping session_wait for hours while holding the turn and sandbox. Pass each target's sessionId and afterSequence (its last seen sequence, 0 for a new session)
|
|
22348
|
+
description: `Block until a watched session has new durable events after your cursor, until your own session has pending machine input (a child result, an agent message, a steer), or until maxWaitSeconds (default ${SESSION_WAIT_DEFAULT_SECONDS}, max ${SESSION_WAIT_MAX_SECONDS}) elapses. Use this for short waits inside the current turn instead of sleeping and polling session_events/session_get/sessions_list while a child or peer session works; for long waits end this turn with goal_wait rather than looping session_wait for hours while holding the turn and sandbox. Pass each target's sessionId and afterSequence (its last seen sequence, 0 for a new session). waitFor=change is the backward-compatible default and returns on turn lifecycle, agent.message.completed, blocking failures, goal facts, or session status/control changes. waitFor=completion is the child-result join: it ignores progress, completed commentary messages, goal facts, maintenance turns, and continuation segment settlements and returns only for a result-bearing final turn or a blocking state. A goal.completed event records goal state but is not a terminal child result. Raw deltas, tool receipts, sandbox diagnostics, and unrelated progress never wake either mode. Each changed target returns a bounded compact summary of up to ${SESSION_WAIT_EVENTS_PER_TARGET} exact durable events plus latestSequence (pass it back as the next afterSequence) and hasMore (drill down with session_events after=latestSequence). ownPendingUpdates > 0 means your own session has machine input that is delivered only when your next turn is claimed: finish this turn to receive it, or pass includeOwnPendingUpdates=false to keep waiting on the targets. timedOut=true means nothing changed; liveFanout=false means the live bus was unavailable and the wait relied on the deadline re-check. The whole result is byte-bounded: summaries are shortened first, then newest rows dropped, so a changed target may come back with events=[] and hasMore=true; read those rows with session_events after=latestSequence. The wait cannot exceed ${SESSION_WAIT_MAX_SECONDS} seconds because the MCP client request timeout is 60 seconds.`,
|
|
21924
22349
|
inputSchema: {
|
|
21925
22350
|
targets: z43.array(
|
|
21926
22351
|
z43.object({
|
|
@@ -21931,10 +22356,13 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
21931
22356
|
includeOwnPendingUpdates: z43.boolean().optional().describe(
|
|
21932
22357
|
"Also return when your own session has pending machine input (default true)."
|
|
21933
22358
|
),
|
|
22359
|
+
waitFor: z43.enum(["change", "completion"]).optional().describe(
|
|
22360
|
+
"change (default) returns on relevant activity; completion ignores messages, goal/progress, maintenance, and continuation segments until a result-bearing final turn or blocker."
|
|
22361
|
+
),
|
|
21934
22362
|
maxWaitSeconds: z43.number().int().min(1).max(SESSION_WAIT_MAX_SECONDS).optional()
|
|
21935
22363
|
}
|
|
21936
22364
|
},
|
|
21937
|
-
async ({ targets, includeOwnPendingUpdates, maxWaitSeconds }, extra) => {
|
|
22365
|
+
async ({ targets, includeOwnPendingUpdates, waitFor, maxWaitSeconds }, extra) => {
|
|
21938
22366
|
const distinct = new Set(targets.map((target) => target.sessionId));
|
|
21939
22367
|
if (distinct.size !== targets.length) {
|
|
21940
22368
|
throw new Error("session_wait targets must name distinct sessions");
|
|
@@ -21944,6 +22372,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
21944
22372
|
await requireSession(deps.db, grant.workspaceId, target.sessionId);
|
|
21945
22373
|
}
|
|
21946
22374
|
const ownSessionId = includeOwnPendingUpdates === false ? null : callerSessionId;
|
|
22375
|
+
const targetEventTypes = waitFor === "completion" ? SESSION_WAIT_COMPLETION_EVENT_TYPES : SESSION_WAIT_EVENT_TYPES;
|
|
21947
22376
|
const signal = extra?.signal;
|
|
21948
22377
|
const workspaceId = grant.workspaceId;
|
|
21949
22378
|
return json(
|
|
@@ -21951,6 +22380,8 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
21951
22380
|
targets,
|
|
21952
22381
|
ownSessionId,
|
|
21953
22382
|
maxWaitMs: (maxWaitSeconds ?? SESSION_WAIT_DEFAULT_SECONDS) * 1e3,
|
|
22383
|
+
targetEventTypes,
|
|
22384
|
+
targetEventMatches: waitFor === "completion" ? sessionWaitCompletionEventMatches : void 0,
|
|
21954
22385
|
signal,
|
|
21955
22386
|
source: {
|
|
21956
22387
|
reauthorizeTargets: async (sessionIds) => {
|
|
@@ -21969,7 +22400,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
21969
22400
|
direction: "after",
|
|
21970
22401
|
limit: SESSION_WAIT_EVENTS_PER_TARGET,
|
|
21971
22402
|
payloadMode: "full",
|
|
21972
|
-
includeTypes:
|
|
22403
|
+
includeTypes: targetEventTypes,
|
|
21973
22404
|
maxBytes: SESSION_EVENT_MCP_MAX_BYTES * 4
|
|
21974
22405
|
});
|
|
21975
22406
|
return { events: page.events, hasMore: page.hasMore };
|
|
@@ -21989,6 +22420,9 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
21989
22420
|
if (can("sessions:create") && sessionCreateVisible) {
|
|
21990
22421
|
const sessionCreateInput = z43.object({
|
|
21991
22422
|
initialMessage: z43.string().min(1),
|
|
22423
|
+
title: z43.string().min(1).max(SESSION_TITLE_MAX_CHARACTERS).optional().describe(
|
|
22424
|
+
"Concise semantic title for the child session. Omit only when the delegated goal or initial message already provides a suitable title; OpenGeni derives a sensitive-safe bounded fallback from that text."
|
|
22425
|
+
),
|
|
21992
22426
|
instructions: z43.string().min(1).max(SESSION_INSTRUCTIONS_MAX_CHARACTERS).optional(),
|
|
21993
22427
|
goal: z43.unknown().optional(),
|
|
21994
22428
|
resources: z43.array(z43.unknown()).optional(),
|
|
@@ -22047,7 +22481,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
22047
22481
|
server.registerTool(
|
|
22048
22482
|
"session_create",
|
|
22049
22483
|
{
|
|
22050
|
-
description: "Spawn a new agent session (a worker). The child inherits this session's visibility; a private session can only create a same-owner private child. Give a goal-bearing child its delegated objective. Its goal.rootConstraints may be an exact applicable subset of this accepted turn's frozen root constraints; omit that field to inherit all of them. Omit sandbox for the safe default: compatible children share the creator's box, while a different Variable Set, Rig, or machineTarget gets its own box. Use 'new' for deliberate isolation or {groupId} for a strict compatible sibling join. Put targetSandboxId and its optional workingDir together inside machineTarget; a machineTarget is always an own-box create even when the parent is backend none. To create a non-delegating leaf, pass a narrowed firstPartyMcpTools list that omits session_create; do not use a child-local depth override. Public REST/SDK callers retain advanced absolute depth and explicit shared-placement controls.",
|
|
22484
|
+
description: "Spawn a new agent session (a worker) only for a concrete, bounded subtask that can run independently and has a defined integration point in your current work. Do not delegate work you will also perform yourself; track the child and join its actual result before completing dependent work. Give the child a concise semantic title; if omitted, OpenGeni derives one from its delegated goal or initial message. The child inherits this session's visibility; a private session can only create a same-owner private child. Give a goal-bearing child its delegated objective. Its goal.rootConstraints may be an exact applicable subset of this accepted turn's frozen root constraints; omit that field to inherit all of them. Omit sandbox for the safe default: compatible children share the creator's box, while a different Variable Set, Rig, or machineTarget gets its own box. Use 'new' for deliberate isolation or {groupId} for a strict compatible sibling join. Put targetSandboxId and its optional workingDir together inside machineTarget; a machineTarget is always an own-box create even when the parent is backend none. To create a non-delegating leaf, pass a narrowed firstPartyMcpTools list that omits session_create; do not use a child-local depth override. Public REST/SDK callers retain advanced absolute depth and explicit shared-placement controls.",
|
|
22051
22485
|
inputSchema: sessionCreateInput
|
|
22052
22486
|
},
|
|
22053
22487
|
async (args) => {
|
|
@@ -22060,14 +22494,21 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
22060
22494
|
if (callerSessionId !== null) {
|
|
22061
22495
|
await authorizeFirstPartySession(deps, grant, callerSessionId, "session.child.create");
|
|
22062
22496
|
}
|
|
22063
|
-
const { machineTarget, ...request } = args;
|
|
22064
|
-
const result = await createSessionForRequestWithOutcome(
|
|
22065
|
-
|
|
22066
|
-
|
|
22067
|
-
|
|
22068
|
-
|
|
22069
|
-
|
|
22070
|
-
|
|
22497
|
+
const { machineTarget, title, ...request } = args;
|
|
22498
|
+
const result = await createSessionForRequestWithOutcome(
|
|
22499
|
+
deps,
|
|
22500
|
+
grant,
|
|
22501
|
+
grant.workspaceId,
|
|
22502
|
+
{
|
|
22503
|
+
...request,
|
|
22504
|
+
...machineTarget ? {
|
|
22505
|
+
targetSandboxId: machineTarget.targetSandboxId,
|
|
22506
|
+
...machineTarget.workingDir !== void 0 ? { workingDir: machineTarget.workingDir } : {}
|
|
22507
|
+
} : {}
|
|
22508
|
+
},
|
|
22509
|
+
void 0,
|
|
22510
|
+
title === void 0 ? {} : { automaticTitleCandidate: title }
|
|
22511
|
+
);
|
|
22071
22512
|
return json(sessionCreateMutationReceipt(result, Boolean(request.idempotencyKey)));
|
|
22072
22513
|
} catch (error) {
|
|
22073
22514
|
return orchestrationFailureResult("session_create", error);
|
|
@@ -23821,7 +24262,7 @@ function registerCapabilityRoutes(app, deps) {
|
|
|
23821
24262
|
});
|
|
23822
24263
|
app.post("/v1/workspaces/:workspaceId/capabilities", async (c) => {
|
|
23823
24264
|
const workspaceId = c.req.param("workspaceId");
|
|
23824
|
-
const grant = await requireAccessGrant3(c, deps, workspaceId, "
|
|
24265
|
+
const grant = await requireAccessGrant3(c, deps, workspaceId, "capabilities:manage");
|
|
23825
24266
|
const payload = CreateCapabilityCatalogItemRequest.parse(await c.req.json());
|
|
23826
24267
|
return c.json(
|
|
23827
24268
|
await createCatalogItem({ db, accountId: grant.accountId, workspaceId, payload }),
|
|
@@ -23849,7 +24290,7 @@ function registerCapabilityRoutes(app, deps) {
|
|
|
23849
24290
|
});
|
|
23850
24291
|
app.post("/v1/workspaces/:workspaceId/capabilities/:capabilityId/enable", async (c) => {
|
|
23851
24292
|
const workspaceId = c.req.param("workspaceId");
|
|
23852
|
-
const grant = await requireAccessGrant3(c, deps, workspaceId, "
|
|
24293
|
+
const grant = await requireAccessGrant3(c, deps, workspaceId, "capabilities:manage");
|
|
23853
24294
|
const payload = EnableCapabilityRequest.parse(await c.req.json());
|
|
23854
24295
|
const installation = await enableCapability({
|
|
23855
24296
|
db,
|
|
@@ -23864,7 +24305,7 @@ function registerCapabilityRoutes(app, deps) {
|
|
|
23864
24305
|
});
|
|
23865
24306
|
app.post("/v1/workspaces/:workspaceId/capabilities/:capabilityId/disable", async (c) => {
|
|
23866
24307
|
const workspaceId = c.req.param("workspaceId");
|
|
23867
|
-
const grant = await requireAccessGrant3(c, deps, workspaceId, "
|
|
24308
|
+
const grant = await requireAccessGrant3(c, deps, workspaceId, "capabilities:manage");
|
|
23868
24309
|
const installation = await disableCapability({
|
|
23869
24310
|
db,
|
|
23870
24311
|
accountId: grant.accountId,
|
|
@@ -23990,8 +24431,10 @@ import {
|
|
|
23990
24431
|
designateCodexAppsCredential,
|
|
23991
24432
|
disconnectAllCodexAccounts,
|
|
23992
24433
|
disconnectCodexAccount,
|
|
24434
|
+
disconnectOrganizationCodexAccount,
|
|
23993
24435
|
encryptEnvironmentValue as encryptEnvironmentValue6,
|
|
23994
24436
|
ensureCodexRotationSettings,
|
|
24437
|
+
ensureOrganizationCodexRotationSettings,
|
|
23995
24438
|
fetchCodexUsageForAccount,
|
|
23996
24439
|
fetchCodexRateLimitResetCreditsForAccount,
|
|
23997
24440
|
fenceCodexResetRedemptionSend,
|
|
@@ -23999,24 +24442,36 @@ import {
|
|
|
23999
24442
|
getCodexCredentialStatus,
|
|
24000
24443
|
getCodexAppsSettings,
|
|
24001
24444
|
getCodexRotationSettings,
|
|
24445
|
+
getOrganizationCodexRotationSettings,
|
|
24446
|
+
getWorkspaceCodexSubscriptionSource,
|
|
24002
24447
|
listPendingCodexCapacityWakeTargets,
|
|
24003
24448
|
listCodexAccountStatuses,
|
|
24449
|
+
listOrganizationCodexAccountStatuses,
|
|
24004
24450
|
listCodexResetRedemptionRecoveries,
|
|
24451
|
+
nestedPostgresSqlState,
|
|
24005
24452
|
releaseCodexResetRedemptionClaim,
|
|
24006
24453
|
updateCodexAllocatorEligibility,
|
|
24007
24454
|
loadCodexCredentialForRun,
|
|
24008
24455
|
renameCodexAccount,
|
|
24456
|
+
renameOrganizationCodexAccount,
|
|
24009
24457
|
setActiveCodexCredential,
|
|
24458
|
+
setActiveOrganizationCodexCredential,
|
|
24010
24459
|
setInitialActiveCodexCredential,
|
|
24460
|
+
setWorkspaceCodexSubscriptionMode,
|
|
24461
|
+
setWorkspaceCodexSubscriptionModeInTransaction,
|
|
24462
|
+
updateOrganizationCodexRotationSettings,
|
|
24011
24463
|
updateCodexRotationSettings,
|
|
24464
|
+
upsertOrganizationCodexSubscriptionCredential,
|
|
24012
24465
|
upsertCodexSubscriptionCredential,
|
|
24013
|
-
withCodexCapacityMutation
|
|
24466
|
+
withCodexCapacityMutation,
|
|
24467
|
+
withSessionCodexCapacityMutation
|
|
24014
24468
|
} from "@opengeni/db";
|
|
24015
24469
|
import { createSignedState as createSignedState7, readSignedState as readSignedState6 } from "@opengeni/github";
|
|
24016
24470
|
import {
|
|
24017
24471
|
getManagedSession,
|
|
24018
24472
|
hasPermission as hasPermission11,
|
|
24019
|
-
requireAccessGrant as requireAccessGrant4
|
|
24473
|
+
requireAccessGrant as requireAccessGrant4,
|
|
24474
|
+
requireCanonicalLocalAccountAdministrator
|
|
24020
24475
|
} from "@opengeni/core";
|
|
24021
24476
|
import { HTTPException as HTTPException21 } from "hono/http-exception";
|
|
24022
24477
|
import * as z9 from "zod/v4";
|
|
@@ -24085,6 +24540,7 @@ var CODEX_PROVIDER_LABEL = "Codex subscription \xB7 no credits";
|
|
|
24085
24540
|
function codexAccountJson(row, options = {}) {
|
|
24086
24541
|
return {
|
|
24087
24542
|
id: row.id,
|
|
24543
|
+
source: row.source,
|
|
24088
24544
|
chatgptAccountId: row.chatgptAccountId,
|
|
24089
24545
|
label: row.label,
|
|
24090
24546
|
email: row.accountEmail,
|
|
@@ -24113,7 +24569,7 @@ function codexAccountJson(row, options = {}) {
|
|
|
24113
24569
|
// P3 rotation cooldown: when set and in the future, this account is cooling-down.
|
|
24114
24570
|
exhaustedUntil: row.exhaustedUntil,
|
|
24115
24571
|
appsDesignated: options.appsCredentialId === row.id,
|
|
24116
|
-
canEnableApps: options.appsCredentialId === null && options.canManageApps === true && options.humanSubjectId !== null && options.humanSubjectId !== void 0 && row.connectedBySubjectId === options.humanSubjectId && row.status === "active"
|
|
24572
|
+
canEnableApps: row.source === "workspace" && options.appsCredentialId === null && options.canManageApps === true && options.humanSubjectId !== null && options.humanSubjectId !== void 0 && row.connectedBySubjectId === options.humanSubjectId && row.status === "active"
|
|
24117
24573
|
};
|
|
24118
24574
|
}
|
|
24119
24575
|
function codexUsageJson(payload) {
|
|
@@ -24159,6 +24615,48 @@ async function managedCookieHuman(c, deps) {
|
|
|
24159
24615
|
browserSessionHash: await hashCodexBrowserSession(session.session.id)
|
|
24160
24616
|
};
|
|
24161
24617
|
}
|
|
24618
|
+
async function requireOrganizationCodexHuman(c, deps, organizationId2) {
|
|
24619
|
+
const parsed = z9.string().uuid().safeParse(organizationId2);
|
|
24620
|
+
if (!parsed.success) throw new HTTPException21(422, { message: "invalid organization id" });
|
|
24621
|
+
let human = await managedCookieHuman(c, deps);
|
|
24622
|
+
if (!human && deps.settings.productAccessMode === "local") {
|
|
24623
|
+
const local = await requireCanonicalLocalAccountAdministrator(c, deps, organizationId2);
|
|
24624
|
+
human = {
|
|
24625
|
+
subjectId: local.subjectId,
|
|
24626
|
+
browserSessionHash: await hashCodexBrowserSession(`local:${local.subjectId}`)
|
|
24627
|
+
};
|
|
24628
|
+
}
|
|
24629
|
+
if (!human) {
|
|
24630
|
+
throw new HTTPException21(401, { message: "organization administrator session required" });
|
|
24631
|
+
}
|
|
24632
|
+
try {
|
|
24633
|
+
await getOrganizationCodexRotationSettings(deps.db, {
|
|
24634
|
+
organizationId: organizationId2,
|
|
24635
|
+
actorSubjectId: human.subjectId
|
|
24636
|
+
});
|
|
24637
|
+
} catch (error) {
|
|
24638
|
+
const state = nestedPostgresSqlState(error);
|
|
24639
|
+
if (state === "42501") {
|
|
24640
|
+
throw new HTTPException21(403, { message: "organization administration is not authorized" });
|
|
24641
|
+
}
|
|
24642
|
+
if (state === "P0002") {
|
|
24643
|
+
throw new HTTPException21(404, { message: "organization not found" });
|
|
24644
|
+
}
|
|
24645
|
+
throw error;
|
|
24646
|
+
}
|
|
24647
|
+
return human;
|
|
24648
|
+
}
|
|
24649
|
+
async function requireWorkspaceCodexManagementSource(deps, workspaceId) {
|
|
24650
|
+
const source = await getWorkspaceCodexSubscriptionSource(deps.db, workspaceId);
|
|
24651
|
+
if (source.effectiveSource === "organization") {
|
|
24652
|
+
throw new HTTPException21(409, {
|
|
24653
|
+
message: "this Codex subscription is managed in Organization settings"
|
|
24654
|
+
});
|
|
24655
|
+
}
|
|
24656
|
+
if (source.effectiveSource === "disabled") {
|
|
24657
|
+
throw new HTTPException21(409, { message: "Codex is disabled for this workspace" });
|
|
24658
|
+
}
|
|
24659
|
+
}
|
|
24162
24660
|
function requireSameOriginBrowserMutation(c, deps) {
|
|
24163
24661
|
const contentType = c.req.header("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
24164
24662
|
if (contentType !== "application/json") {
|
|
@@ -24166,13 +24664,13 @@ function requireSameOriginBrowserMutation(c, deps) {
|
|
|
24166
24664
|
message: "JSON browser request required"
|
|
24167
24665
|
});
|
|
24168
24666
|
}
|
|
24169
|
-
if (!deps.settings.publicBaseUrl) {
|
|
24667
|
+
if (deps.settings.productAccessMode !== "local" && !deps.settings.publicBaseUrl) {
|
|
24170
24668
|
throw new HTTPException21(503, {
|
|
24171
24669
|
message: "managed browser origin is not configured"
|
|
24172
24670
|
});
|
|
24173
24671
|
}
|
|
24174
|
-
const
|
|
24175
|
-
if (
|
|
24672
|
+
const origin = c.req.header("origin");
|
|
24673
|
+
if (deps.settings.productAccessMode === "local" ? !localBrowserOriginMatchesRequest(c, origin) : origin !== new URL(deps.settings.publicBaseUrl).origin) {
|
|
24176
24674
|
throw new HTTPException21(403, {
|
|
24177
24675
|
message: "same-origin browser request required"
|
|
24178
24676
|
});
|
|
@@ -24183,6 +24681,30 @@ function requireSameOriginBrowserMutation(c, deps) {
|
|
|
24183
24681
|
});
|
|
24184
24682
|
}
|
|
24185
24683
|
}
|
|
24684
|
+
function localBrowserOriginMatchesRequest(c, value) {
|
|
24685
|
+
if (!value) return false;
|
|
24686
|
+
let origin;
|
|
24687
|
+
try {
|
|
24688
|
+
origin = new URL(value);
|
|
24689
|
+
} catch {
|
|
24690
|
+
return false;
|
|
24691
|
+
}
|
|
24692
|
+
if (origin.origin !== value || origin.origin === "null" || origin.protocol !== "http:" && origin.protocol !== "https:") {
|
|
24693
|
+
return false;
|
|
24694
|
+
}
|
|
24695
|
+
const forwardedProtocol = c.req.header("x-forwarded-proto")?.trim().toLowerCase();
|
|
24696
|
+
const protocol = forwardedProtocol ? `${forwardedProtocol}:` : new URL(c.req.url).protocol;
|
|
24697
|
+
if (protocol !== "http:" && protocol !== "https:") return false;
|
|
24698
|
+
const forwardedHost = c.req.header("x-forwarded-host") ?? c.req.header("host");
|
|
24699
|
+
if (!forwardedHost || /[\s,/?#@\\]/u.test(forwardedHost)) return false;
|
|
24700
|
+
let request;
|
|
24701
|
+
try {
|
|
24702
|
+
request = new URL(`${protocol}//${forwardedHost}`);
|
|
24703
|
+
} catch {
|
|
24704
|
+
return false;
|
|
24705
|
+
}
|
|
24706
|
+
return origin.protocol === request.protocol && origin.hostname === request.hostname && (request.port === "" || origin.port === request.port);
|
|
24707
|
+
}
|
|
24186
24708
|
async function requireRedemptionHuman(c, deps, workspaceId) {
|
|
24187
24709
|
if (deps.settings.productAccessMode !== "managed") {
|
|
24188
24710
|
throw new HTTPException21(403, {
|
|
@@ -24201,7 +24723,7 @@ async function requireRedemptionHuman(c, deps, workspaceId) {
|
|
|
24201
24723
|
message: "managed browser session required"
|
|
24202
24724
|
});
|
|
24203
24725
|
}
|
|
24204
|
-
const grant = await requireAccessGrant4(c, deps, workspaceId, "
|
|
24726
|
+
const grant = await requireAccessGrant4(c, deps, workspaceId, "connections:write");
|
|
24205
24727
|
if (grant.subjectId !== human.subjectId) {
|
|
24206
24728
|
throw new HTTPException21(403, {
|
|
24207
24729
|
message: "managed browser identity mismatch"
|
|
@@ -24432,9 +24954,244 @@ async function signalPendingCodexCapacityTargets(deps, workspaceId) {
|
|
|
24432
24954
|
var CODEX_DEVICE_EXPIRY_SECONDS = 15 * 60;
|
|
24433
24955
|
function registerCodexRoutes(app, deps) {
|
|
24434
24956
|
const { db, settings, githubStateSecret } = deps;
|
|
24957
|
+
app.get("/v1/workspaces/:workspaceId/codex/source", async (c) => {
|
|
24958
|
+
const workspaceId = c.req.param("workspaceId");
|
|
24959
|
+
await requireAccessGrant4(c, deps, workspaceId, "workspace:read");
|
|
24960
|
+
return c.json(await getWorkspaceCodexSubscriptionSource(db, workspaceId));
|
|
24961
|
+
});
|
|
24962
|
+
app.patch("/v1/workspaces/:workspaceId/codex/source", async (c) => {
|
|
24963
|
+
const workspaceId = c.req.param("workspaceId");
|
|
24964
|
+
const grant = await requireAccessGrant4(c, deps, workspaceId, "connections:write");
|
|
24965
|
+
const parsed = z9.object({ mode: z9.enum(["automatic", "workspace", "organization", "disabled"]) }).safeParse(await c.req.json().catch(() => null));
|
|
24966
|
+
if (!parsed.success) {
|
|
24967
|
+
throw new HTTPException21(400, { message: "a valid Codex source mode is required" });
|
|
24968
|
+
}
|
|
24969
|
+
try {
|
|
24970
|
+
return c.json(
|
|
24971
|
+
await setWorkspaceCodexSubscriptionMode(db, {
|
|
24972
|
+
accountId: grant.accountId,
|
|
24973
|
+
workspaceId,
|
|
24974
|
+
subjectId: grant.subjectId,
|
|
24975
|
+
mode: parsed.data.mode
|
|
24976
|
+
})
|
|
24977
|
+
);
|
|
24978
|
+
} catch (error) {
|
|
24979
|
+
if (error instanceof Error && (error.message.includes("personal workspaces") || error.message.includes("active turns are using it"))) {
|
|
24980
|
+
throw new HTTPException21(409, { message: error.message });
|
|
24981
|
+
}
|
|
24982
|
+
throw error;
|
|
24983
|
+
}
|
|
24984
|
+
});
|
|
24985
|
+
app.get("/v1/organizations/:organizationId/codex/accounts", async (c) => {
|
|
24986
|
+
const organizationId2 = c.req.param("organizationId");
|
|
24987
|
+
const human = await requireOrganizationCodexHuman(c, deps, organizationId2);
|
|
24988
|
+
const [accounts, rotation] = await Promise.all([
|
|
24989
|
+
listOrganizationCodexAccountStatuses(db, {
|
|
24990
|
+
organizationId: organizationId2,
|
|
24991
|
+
actorSubjectId: human.subjectId
|
|
24992
|
+
}),
|
|
24993
|
+
getOrganizationCodexRotationSettings(db, {
|
|
24994
|
+
organizationId: organizationId2,
|
|
24995
|
+
actorSubjectId: human.subjectId
|
|
24996
|
+
})
|
|
24997
|
+
]);
|
|
24998
|
+
return c.json({
|
|
24999
|
+
accounts: accounts.map((account) => codexAccountJson(account)),
|
|
25000
|
+
activeAccountId: rotation?.activeCredentialId ?? null,
|
|
25001
|
+
settings: {
|
|
25002
|
+
rotationEnabled: rotation?.rotationEnabled ?? false,
|
|
25003
|
+
rotationStrategy: "sharded",
|
|
25004
|
+
activeCredentialId: rotation?.activeCredentialId ?? null
|
|
25005
|
+
}
|
|
25006
|
+
});
|
|
25007
|
+
});
|
|
25008
|
+
app.post("/v1/organizations/:organizationId/codex/connect/start", async (c) => {
|
|
25009
|
+
const organizationId2 = c.req.param("organizationId");
|
|
25010
|
+
requireSameOriginBrowserMutation(c, deps);
|
|
25011
|
+
const human = await requireOrganizationCodexHuman(c, deps, organizationId2);
|
|
25012
|
+
let start;
|
|
25013
|
+
try {
|
|
25014
|
+
start = await startDeviceCode();
|
|
25015
|
+
} catch (error) {
|
|
25016
|
+
throw new HTTPException21(502, {
|
|
25017
|
+
message: error instanceof CodexDeviceError ? error.message : "failed to start Codex device login"
|
|
25018
|
+
});
|
|
25019
|
+
}
|
|
25020
|
+
return c.json({
|
|
25021
|
+
userCode: start.userCode,
|
|
25022
|
+
verificationUri: start.verificationUri,
|
|
25023
|
+
intervalSeconds: start.intervalSeconds,
|
|
25024
|
+
state: createSignedState7(githubStateSecret, {
|
|
25025
|
+
organizationId: organizationId2,
|
|
25026
|
+
actorSubjectId: human.subjectId,
|
|
25027
|
+
deviceAuthId: start.deviceAuthId,
|
|
25028
|
+
userCode: start.userCode
|
|
25029
|
+
})
|
|
25030
|
+
});
|
|
25031
|
+
});
|
|
25032
|
+
app.post("/v1/organizations/:organizationId/codex/connect/poll", async (c) => {
|
|
25033
|
+
const organizationId2 = c.req.param("organizationId");
|
|
25034
|
+
requireSameOriginBrowserMutation(c, deps);
|
|
25035
|
+
const human = await requireOrganizationCodexHuman(c, deps, organizationId2);
|
|
25036
|
+
const { state } = await c.req.json().catch(() => null);
|
|
25037
|
+
const payload = state ? readSignedState6(state, githubStateSecret) : null;
|
|
25038
|
+
if (!payload || payload.organizationId !== organizationId2 || payload.actorSubjectId !== human.subjectId || !payload.deviceAuthId || !payload.userCode) {
|
|
25039
|
+
throw new HTTPException21(400, { message: "codex connect state is invalid or expired" });
|
|
25040
|
+
}
|
|
25041
|
+
if (typeof payload.iat === "number" && Date.now() / 1e3 - payload.iat > CODEX_DEVICE_EXPIRY_SECONDS) {
|
|
25042
|
+
return c.json({ status: "expired" });
|
|
25043
|
+
}
|
|
25044
|
+
let poll;
|
|
25045
|
+
try {
|
|
25046
|
+
poll = await pollDeviceCode({
|
|
25047
|
+
deviceAuthId: payload.deviceAuthId,
|
|
25048
|
+
userCode: payload.userCode
|
|
25049
|
+
});
|
|
25050
|
+
} catch (error) {
|
|
25051
|
+
throw new HTTPException21(502, {
|
|
25052
|
+
message: error instanceof CodexDeviceError ? error.message : "codex device poll failed"
|
|
25053
|
+
});
|
|
25054
|
+
}
|
|
25055
|
+
if (poll.status === "pending") return c.json({ status: "pending" });
|
|
25056
|
+
if (poll.status === "expired") return c.json({ status: "expired" });
|
|
25057
|
+
let tokens;
|
|
25058
|
+
try {
|
|
25059
|
+
tokens = await exchangeDeviceCode({
|
|
25060
|
+
authorizationCode: poll.authorizationCode,
|
|
25061
|
+
codeVerifier: poll.codeVerifier
|
|
25062
|
+
});
|
|
25063
|
+
} catch (error) {
|
|
25064
|
+
throw new HTTPException21(502, {
|
|
25065
|
+
message: error instanceof CodexDeviceError ? error.message : "codex token exchange failed"
|
|
25066
|
+
});
|
|
25067
|
+
}
|
|
25068
|
+
const key = environmentsEncryptionKeyBytes2(settings);
|
|
25069
|
+
if (!key) {
|
|
25070
|
+
throw new HTTPException21(500, {
|
|
25071
|
+
message: "OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY is not configured"
|
|
25072
|
+
});
|
|
25073
|
+
}
|
|
25074
|
+
const id = parseIdToken(tokens.idToken);
|
|
25075
|
+
await ensureOrganizationCodexRotationSettings(db, {
|
|
25076
|
+
organizationId: organizationId2,
|
|
25077
|
+
actorSubjectId: human.subjectId
|
|
25078
|
+
});
|
|
25079
|
+
const upserted = await upsertOrganizationCodexSubscriptionCredential(db, {
|
|
25080
|
+
organizationId: organizationId2,
|
|
25081
|
+
actorSubjectId: human.subjectId,
|
|
25082
|
+
credentialEncrypted: encryptEnvironmentValue6(
|
|
25083
|
+
key,
|
|
25084
|
+
JSON.stringify({
|
|
25085
|
+
access_token: tokens.accessToken,
|
|
25086
|
+
refresh_token: tokens.refreshToken,
|
|
25087
|
+
id_token: tokens.idToken
|
|
25088
|
+
})
|
|
25089
|
+
),
|
|
25090
|
+
chatgptAccountId: id.chatgptAccountId,
|
|
25091
|
+
scopes: null,
|
|
25092
|
+
planType: id.planType,
|
|
25093
|
+
isFedramp: id.isFedramp,
|
|
25094
|
+
expiresAt: accessTokenExpiry(tokens.accessToken),
|
|
25095
|
+
lastRefreshAt: /* @__PURE__ */ new Date(),
|
|
25096
|
+
accountEmail: id.email ?? null,
|
|
25097
|
+
label: id.email ?? id.chatgptAccountId ?? null
|
|
25098
|
+
});
|
|
25099
|
+
await signalCodexCapacityTargets(deps, upserted.wakeTargets);
|
|
25100
|
+
const rotation = await getOrganizationCodexRotationSettings(db, {
|
|
25101
|
+
organizationId: organizationId2,
|
|
25102
|
+
actorSubjectId: human.subjectId
|
|
25103
|
+
});
|
|
25104
|
+
return c.json({
|
|
25105
|
+
status: "connected",
|
|
25106
|
+
plan: id.planType,
|
|
25107
|
+
accountId: upserted.id,
|
|
25108
|
+
isActive: rotation?.activeCredentialId === upserted.id
|
|
25109
|
+
});
|
|
25110
|
+
});
|
|
25111
|
+
app.post("/v1/organizations/:organizationId/codex/accounts/:accountId/activate", async (c) => {
|
|
25112
|
+
const organizationId2 = c.req.param("organizationId");
|
|
25113
|
+
requireSameOriginBrowserMutation(c, deps);
|
|
25114
|
+
const human = await requireOrganizationCodexHuman(c, deps, organizationId2);
|
|
25115
|
+
const credentialId = c.req.param("accountId");
|
|
25116
|
+
const activation = await setActiveOrganizationCodexCredential(db, {
|
|
25117
|
+
organizationId: organizationId2,
|
|
25118
|
+
actorSubjectId: human.subjectId,
|
|
25119
|
+
credentialId
|
|
25120
|
+
});
|
|
25121
|
+
if (!activation.activated) {
|
|
25122
|
+
throw new HTTPException21(404, { message: "codex account not found" });
|
|
25123
|
+
}
|
|
25124
|
+
await signalCodexCapacityTargets(deps, activation.wakeTargets);
|
|
25125
|
+
return c.json({ activated: true, accountId: credentialId });
|
|
25126
|
+
});
|
|
25127
|
+
app.patch("/v1/organizations/:organizationId/codex/settings", async (c) => {
|
|
25128
|
+
const organizationId2 = c.req.param("organizationId");
|
|
25129
|
+
requireSameOriginBrowserMutation(c, deps);
|
|
25130
|
+
const human = await requireOrganizationCodexHuman(c, deps, organizationId2);
|
|
25131
|
+
const parsed = z9.object({ rotationEnabled: z9.boolean() }).safeParse(await c.req.json().catch(() => null));
|
|
25132
|
+
if (!parsed.success) {
|
|
25133
|
+
throw new HTTPException21(400, { message: "rotationEnabled is required" });
|
|
25134
|
+
}
|
|
25135
|
+
const updated = await updateOrganizationCodexRotationSettings(db, {
|
|
25136
|
+
organizationId: organizationId2,
|
|
25137
|
+
actorSubjectId: human.subjectId,
|
|
25138
|
+
rotationEnabled: parsed.data.rotationEnabled
|
|
25139
|
+
});
|
|
25140
|
+
if (!updated) throw new HTTPException21(404, { message: "Codex settings not found" });
|
|
25141
|
+
await signalCodexCapacityTargets(deps, updated.wakeTargets);
|
|
25142
|
+
return c.json({
|
|
25143
|
+
rotationEnabled: updated.rotationEnabled,
|
|
25144
|
+
rotationStrategy: "sharded",
|
|
25145
|
+
activeCredentialId: updated.activeCredentialId
|
|
25146
|
+
});
|
|
25147
|
+
});
|
|
25148
|
+
app.patch("/v1/organizations/:organizationId/codex/accounts/:accountId", async (c) => {
|
|
25149
|
+
const organizationId2 = c.req.param("organizationId");
|
|
25150
|
+
requireSameOriginBrowserMutation(c, deps);
|
|
25151
|
+
const human = await requireOrganizationCodexHuman(c, deps, organizationId2);
|
|
25152
|
+
const body4 = await c.req.json().catch(() => null);
|
|
25153
|
+
const renamed = await renameOrganizationCodexAccount(db, {
|
|
25154
|
+
organizationId: organizationId2,
|
|
25155
|
+
actorSubjectId: human.subjectId,
|
|
25156
|
+
credentialId: c.req.param("accountId"),
|
|
25157
|
+
label: typeof body4?.label === "string" ? body4.label : null
|
|
25158
|
+
});
|
|
25159
|
+
if (!renamed) throw new HTTPException21(404, { message: "codex account not found" });
|
|
25160
|
+
const accounts = await listOrganizationCodexAccountStatuses(db, {
|
|
25161
|
+
organizationId: organizationId2,
|
|
25162
|
+
actorSubjectId: human.subjectId
|
|
25163
|
+
});
|
|
25164
|
+
const row = accounts.find((account) => account.id === c.req.param("accountId"));
|
|
25165
|
+
if (!row) throw new HTTPException21(404, { message: "codex account not found" });
|
|
25166
|
+
return c.json(codexAccountJson(row));
|
|
25167
|
+
});
|
|
25168
|
+
app.delete("/v1/organizations/:organizationId/codex/accounts/:accountId", async (c) => {
|
|
25169
|
+
const organizationId2 = c.req.param("organizationId");
|
|
25170
|
+
requireSameOriginBrowserMutation(c, deps);
|
|
25171
|
+
const human = await requireOrganizationCodexHuman(c, deps, organizationId2);
|
|
25172
|
+
let result;
|
|
25173
|
+
try {
|
|
25174
|
+
result = await disconnectOrganizationCodexAccount(db, {
|
|
25175
|
+
organizationId: organizationId2,
|
|
25176
|
+
actorSubjectId: human.subjectId,
|
|
25177
|
+
credentialId: c.req.param("accountId")
|
|
25178
|
+
});
|
|
25179
|
+
} catch (error) {
|
|
25180
|
+
const cause = error?.cause;
|
|
25181
|
+
const message = cause instanceof Error ? cause.message : error instanceof Error ? error.message : "";
|
|
25182
|
+
if (message.includes("active turns are using it")) {
|
|
25183
|
+
throw new HTTPException21(409, {
|
|
25184
|
+
message: "Codex subscription cannot disconnect while active turns are using it"
|
|
25185
|
+
});
|
|
25186
|
+
}
|
|
25187
|
+
throw error;
|
|
25188
|
+
}
|
|
25189
|
+
await signalCodexCapacityTargets(deps, result.wakeTargets);
|
|
25190
|
+
return c.json({ disconnected: result.removed, newActiveId: result.newActiveCredentialId });
|
|
25191
|
+
});
|
|
24435
25192
|
app.post("/v1/workspaces/:workspaceId/codex/connect/start", async (c) => {
|
|
24436
25193
|
const workspaceId = c.req.param("workspaceId");
|
|
24437
|
-
await requireAccessGrant4(c, deps, workspaceId, "
|
|
25194
|
+
await requireAccessGrant4(c, deps, workspaceId, "connections:write");
|
|
24438
25195
|
let start;
|
|
24439
25196
|
try {
|
|
24440
25197
|
start = await startDeviceCode();
|
|
@@ -24457,7 +25214,7 @@ function registerCodexRoutes(app, deps) {
|
|
|
24457
25214
|
});
|
|
24458
25215
|
app.post("/v1/workspaces/:workspaceId/codex/connect/poll", async (c) => {
|
|
24459
25216
|
const workspaceId = c.req.param("workspaceId");
|
|
24460
|
-
const grant = await requireAccessGrant4(c, deps, workspaceId, "
|
|
25217
|
+
const grant = await requireAccessGrant4(c, deps, workspaceId, "connections:write");
|
|
24461
25218
|
const { state } = await c.req.json();
|
|
24462
25219
|
const payload = state ? readSignedState6(state, githubStateSecret) : null;
|
|
24463
25220
|
if (!payload || payload.workspaceId !== workspaceId || !payload.deviceAuthId || !payload.userCode) {
|
|
@@ -24504,47 +25261,56 @@ function registerCodexRoutes(app, deps) {
|
|
|
24504
25261
|
message: "OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY is not configured"
|
|
24505
25262
|
});
|
|
24506
25263
|
}
|
|
24507
|
-
await
|
|
24508
|
-
|
|
24509
|
-
|
|
24510
|
-
|
|
24511
|
-
|
|
24512
|
-
|
|
24513
|
-
|
|
24514
|
-
|
|
24515
|
-
|
|
24516
|
-
|
|
24517
|
-
|
|
24518
|
-
|
|
24519
|
-
|
|
24520
|
-
|
|
24521
|
-
|
|
24522
|
-
|
|
24523
|
-
|
|
24524
|
-
|
|
24525
|
-
|
|
24526
|
-
|
|
24527
|
-
|
|
24528
|
-
|
|
24529
|
-
|
|
24530
|
-
|
|
24531
|
-
|
|
24532
|
-
|
|
24533
|
-
|
|
24534
|
-
|
|
24535
|
-
|
|
24536
|
-
|
|
24537
|
-
|
|
25264
|
+
const mutation = await withSessionCodexCapacityMutation(db, { workspaceId, reason: "codex_credential_connected" }, async (tx) => {
|
|
25265
|
+
const upserted2 = await upsertCodexSubscriptionCredential(tx, {
|
|
25266
|
+
accountId: grant.accountId,
|
|
25267
|
+
workspaceId,
|
|
25268
|
+
credentialEncrypted: encryptEnvironmentValue6(
|
|
25269
|
+
key,
|
|
25270
|
+
JSON.stringify({
|
|
25271
|
+
access_token: tokens.accessToken,
|
|
25272
|
+
refresh_token: tokens.refreshToken,
|
|
25273
|
+
id_token: tokens.idToken
|
|
25274
|
+
})
|
|
25275
|
+
),
|
|
25276
|
+
chatgptAccountId: id.chatgptAccountId,
|
|
25277
|
+
scopes: null,
|
|
25278
|
+
// device grant scopes are discovered at runtime, not asserted here
|
|
25279
|
+
planType: id.planType,
|
|
25280
|
+
isFedramp: id.isFedramp,
|
|
25281
|
+
expiresAt: accessTokenExpiry(tokens.accessToken),
|
|
25282
|
+
lastRefreshAt: /* @__PURE__ */ new Date(),
|
|
25283
|
+
accountEmail: id.email ?? null,
|
|
25284
|
+
label: id.email ?? id.chatgptAccountId ?? null,
|
|
25285
|
+
connectedBySubjectId: connectingHuman?.subjectId === grant.subjectId ? connectingHuman.subjectId : null
|
|
25286
|
+
});
|
|
25287
|
+
if (upserted2.kind === "unresolved_redemption") {
|
|
25288
|
+
return { result: { upserted: upserted2, isActive: false }, changed: false };
|
|
25289
|
+
}
|
|
25290
|
+
await ensureCodexRotationSettings(tx, grant.accountId, workspaceId);
|
|
25291
|
+
await setInitialActiveCodexCredential(tx, workspaceId, upserted2.id);
|
|
25292
|
+
const source = await getWorkspaceCodexSubscriptionSource(tx, workspaceId);
|
|
25293
|
+
await setWorkspaceCodexSubscriptionModeInTransaction(tx, {
|
|
25294
|
+
accountId: grant.accountId,
|
|
25295
|
+
workspaceId,
|
|
25296
|
+
subjectId: grant.subjectId,
|
|
25297
|
+
mode: source.workspaceKind === "personal" ? "automatic" : "workspace"
|
|
25298
|
+
});
|
|
25299
|
+
const rotation = await getCodexRotationSettings(tx, workspaceId);
|
|
25300
|
+
return {
|
|
25301
|
+
result: {
|
|
25302
|
+
upserted: upserted2,
|
|
25303
|
+
isActive: rotation?.activeCredentialId === upserted2.id
|
|
25304
|
+
},
|
|
25305
|
+
changed: true
|
|
25306
|
+
};
|
|
25307
|
+
});
|
|
25308
|
+
const { upserted, isActive } = mutation.result;
|
|
24538
25309
|
if (upserted.kind === "unresolved_redemption") {
|
|
24539
25310
|
throw new HTTPException21(409, {
|
|
24540
25311
|
message: "this subscription has an unresolved reset redemption; recover it before changing ownership"
|
|
24541
25312
|
});
|
|
24542
25313
|
}
|
|
24543
|
-
const rotation = await getCodexRotationSettings(db, workspaceId);
|
|
24544
|
-
let isActive = rotation?.activeCredentialId === upserted.id;
|
|
24545
|
-
if (!isActive && rotation?.activeCredentialId == null) {
|
|
24546
|
-
isActive = await setInitialActiveCodexCredential(db, workspaceId, upserted.id);
|
|
24547
|
-
}
|
|
24548
25314
|
await signalCodexCapacityTargets(deps, mutation.wakeTargets);
|
|
24549
25315
|
return c.json({ status: "connected", plan: id.planType, accountId: upserted.id, isActive });
|
|
24550
25316
|
});
|
|
@@ -24555,7 +25321,10 @@ function registerCodexRoutes(app, deps) {
|
|
|
24555
25321
|
if (!status) {
|
|
24556
25322
|
return c.json({ connected: false });
|
|
24557
25323
|
}
|
|
24558
|
-
const accounts = await
|
|
25324
|
+
const [accounts, source] = await Promise.all([
|
|
25325
|
+
listCodexAccountStatuses(db, workspaceId),
|
|
25326
|
+
getWorkspaceCodexSubscriptionSource(db, workspaceId)
|
|
25327
|
+
]);
|
|
24559
25328
|
const activeRow = accounts.find((account) => account.id === status.credentialId) ?? null;
|
|
24560
25329
|
const activeAccount = activeRow ? {
|
|
24561
25330
|
id: activeRow.id,
|
|
@@ -24595,17 +25364,19 @@ function registerCodexRoutes(app, deps) {
|
|
|
24595
25364
|
// ClientModel[] the picker surfaces under the "no credits" group
|
|
24596
25365
|
activeAccount,
|
|
24597
25366
|
// the account a session runs on when unpinned (label for the indicator)
|
|
24598
|
-
accountCount: accounts.length
|
|
25367
|
+
accountCount: accounts.length,
|
|
25368
|
+
source
|
|
24599
25369
|
});
|
|
24600
25370
|
});
|
|
24601
25371
|
app.get("/v1/workspaces/:workspaceId/codex/accounts", async (c) => {
|
|
24602
25372
|
const workspaceId = c.req.param("workspaceId");
|
|
24603
25373
|
const grant = await requireAccessGrant4(c, deps, workspaceId, "workspace:read");
|
|
24604
|
-
const [accounts, rotation, apps, human] = await Promise.all([
|
|
25374
|
+
const [accounts, rotation, apps, human, source] = await Promise.all([
|
|
24605
25375
|
listCodexAccountStatuses(db, workspaceId),
|
|
24606
25376
|
getCodexRotationSettings(db, workspaceId),
|
|
24607
25377
|
getCodexAppsSettings(db, workspaceId),
|
|
24608
|
-
managedCookieHuman(c, deps)
|
|
25378
|
+
managedCookieHuman(c, deps),
|
|
25379
|
+
getWorkspaceCodexSubscriptionSource(db, workspaceId)
|
|
24609
25380
|
]);
|
|
24610
25381
|
const activeAccountId = rotation?.activeCredentialId ?? null;
|
|
24611
25382
|
const humanSubjectId = human?.subjectId === grant.subjectId ? human.subjectId : null;
|
|
@@ -24619,6 +25390,7 @@ function registerCodexRoutes(app, deps) {
|
|
|
24619
25390
|
})
|
|
24620
25391
|
),
|
|
24621
25392
|
activeAccountId,
|
|
25393
|
+
source,
|
|
24622
25394
|
apps: {
|
|
24623
25395
|
available: settings.codexConnectedAppsEnabled,
|
|
24624
25396
|
credentialId: apps.credentialId,
|
|
@@ -24637,6 +25409,7 @@ function registerCodexRoutes(app, deps) {
|
|
|
24637
25409
|
});
|
|
24638
25410
|
app.post("/v1/workspaces/:workspaceId/codex/apps", async (c) => {
|
|
24639
25411
|
const workspaceId = c.req.param("workspaceId");
|
|
25412
|
+
await requireWorkspaceCodexManagementSource(deps, workspaceId);
|
|
24640
25413
|
if (!settings.codexConnectedAppsEnabled) {
|
|
24641
25414
|
throw new HTTPException21(409, { message: "Codex Apps is disabled for this deployment" });
|
|
24642
25415
|
}
|
|
@@ -24679,6 +25452,7 @@ function registerCodexRoutes(app, deps) {
|
|
|
24679
25452
|
});
|
|
24680
25453
|
app.delete("/v1/workspaces/:workspaceId/codex/apps", async (c) => {
|
|
24681
25454
|
const workspaceId = c.req.param("workspaceId");
|
|
25455
|
+
await requireWorkspaceCodexManagementSource(deps, workspaceId);
|
|
24682
25456
|
const { human, accountId } = await requireCodexAppsHuman(c, deps, workspaceId);
|
|
24683
25457
|
const parsed = z9.object({ expectedVersion: z9.number().int().nonnegative() }).safeParse(await c.req.json().catch(() => null));
|
|
24684
25458
|
if (!parsed.success) {
|
|
@@ -24703,7 +25477,8 @@ function registerCodexRoutes(app, deps) {
|
|
|
24703
25477
|
});
|
|
24704
25478
|
app.post("/v1/workspaces/:workspaceId/codex/accounts/:accountId/activate", async (c) => {
|
|
24705
25479
|
const workspaceId = c.req.param("workspaceId");
|
|
24706
|
-
await requireAccessGrant4(c, deps, workspaceId, "
|
|
25480
|
+
await requireAccessGrant4(c, deps, workspaceId, "connections:write");
|
|
25481
|
+
await requireWorkspaceCodexManagementSource(deps, workspaceId);
|
|
24707
25482
|
const accountId = c.req.param("accountId");
|
|
24708
25483
|
const mutation = await withCodexCapacityMutation(
|
|
24709
25484
|
db,
|
|
@@ -24722,7 +25497,8 @@ function registerCodexRoutes(app, deps) {
|
|
|
24722
25497
|
});
|
|
24723
25498
|
app.patch("/v1/workspaces/:workspaceId/codex/settings", async (c) => {
|
|
24724
25499
|
const workspaceId = c.req.param("workspaceId");
|
|
24725
|
-
const grant = await requireAccessGrant4(c, deps, workspaceId, "
|
|
25500
|
+
const grant = await requireAccessGrant4(c, deps, workspaceId, "connections:write");
|
|
25501
|
+
await requireWorkspaceCodexManagementSource(deps, workspaceId);
|
|
24726
25502
|
const body4 = await c.req.json().catch(() => ({}));
|
|
24727
25503
|
const patch = {};
|
|
24728
25504
|
if (typeof body4.rotationEnabled === "boolean") {
|
|
@@ -24759,7 +25535,8 @@ function registerCodexRoutes(app, deps) {
|
|
|
24759
25535
|
});
|
|
24760
25536
|
app.patch("/v1/workspaces/:workspaceId/codex/accounts/:accountId", async (c) => {
|
|
24761
25537
|
const workspaceId = c.req.param("workspaceId");
|
|
24762
|
-
await requireAccessGrant4(c, deps, workspaceId, "
|
|
25538
|
+
await requireAccessGrant4(c, deps, workspaceId, "connections:write");
|
|
25539
|
+
await requireWorkspaceCodexManagementSource(deps, workspaceId);
|
|
24763
25540
|
const accountId = c.req.param("accountId");
|
|
24764
25541
|
const body4 = await c.req.json();
|
|
24765
25542
|
const label = typeof body4.label === "string" ? body4.label : null;
|
|
@@ -24776,7 +25553,8 @@ function registerCodexRoutes(app, deps) {
|
|
|
24776
25553
|
});
|
|
24777
25554
|
app.patch("/v1/workspaces/:workspaceId/codex/accounts/:accountId/allocator", async (c) => {
|
|
24778
25555
|
const workspaceId = c.req.param("workspaceId");
|
|
24779
|
-
const grant = await requireAccessGrant4(c, deps, workspaceId, "
|
|
25556
|
+
const grant = await requireAccessGrant4(c, deps, workspaceId, "connections:write");
|
|
25557
|
+
await requireWorkspaceCodexManagementSource(deps, workspaceId);
|
|
24780
25558
|
const parsed = z9.object({
|
|
24781
25559
|
enabled: z9.boolean(),
|
|
24782
25560
|
expectedVersion: z9.number().int().positive()
|
|
@@ -24809,7 +25587,8 @@ function registerCodexRoutes(app, deps) {
|
|
|
24809
25587
|
});
|
|
24810
25588
|
app.delete("/v1/workspaces/:workspaceId/codex/accounts/:accountId", async (c) => {
|
|
24811
25589
|
const workspaceId = c.req.param("workspaceId");
|
|
24812
|
-
const grant = await requireAccessGrant4(c, deps, workspaceId, "
|
|
25590
|
+
const grant = await requireAccessGrant4(c, deps, workspaceId, "connections:write");
|
|
25591
|
+
await requireWorkspaceCodexManagementSource(deps, workspaceId);
|
|
24813
25592
|
const accountId = c.req.param("accountId");
|
|
24814
25593
|
const mutation = await withCodexCapacityMutation(
|
|
24815
25594
|
db,
|
|
@@ -24830,7 +25609,8 @@ function registerCodexRoutes(app, deps) {
|
|
|
24830
25609
|
});
|
|
24831
25610
|
app.delete("/v1/workspaces/:workspaceId/codex", async (c) => {
|
|
24832
25611
|
const workspaceId = c.req.param("workspaceId");
|
|
24833
|
-
const grant = await requireAccessGrant4(c, deps, workspaceId, "
|
|
25612
|
+
const grant = await requireAccessGrant4(c, deps, workspaceId, "connections:write");
|
|
25613
|
+
await requireWorkspaceCodexManagementSource(deps, workspaceId);
|
|
24834
25614
|
const mutation = await withCodexCapacityMutation(
|
|
24835
25615
|
db,
|
|
24836
25616
|
{ workspaceId, reason: "codex_credentials_disconnected" },
|
|
@@ -24913,7 +25693,7 @@ function registerCodexRoutes(app, deps) {
|
|
|
24913
25693
|
const grant = await requireAccessGrant4(c, deps, workspaceId, "workspace:read");
|
|
24914
25694
|
const human = await managedCookieHuman(c, deps);
|
|
24915
25695
|
const accounts = await listCodexAccountStatuses(db, workspaceId);
|
|
24916
|
-
const ownerRecoveries = human && human.subjectId === grant.subjectId && hasPermission11(grant.permissions, "
|
|
25696
|
+
const ownerRecoveries = human && human.subjectId === grant.subjectId && hasPermission11(grant.permissions, "connections:write") ? await listCodexResetRedemptionRecoveries(db, {
|
|
24917
25697
|
accountId: grant.accountId,
|
|
24918
25698
|
workspaceId,
|
|
24919
25699
|
subjectId: human.subjectId
|
|
@@ -24928,14 +25708,17 @@ function registerCodexRoutes(app, deps) {
|
|
|
24928
25708
|
const account = queue.shift();
|
|
24929
25709
|
if (!account) return;
|
|
24930
25710
|
const canResumeRedemption = Boolean(
|
|
24931
|
-
human && human.subjectId === grant.subjectId && human.subjectId === account.connectedBySubjectId && hasPermission11(grant.permissions, "
|
|
25711
|
+
account.source === "workspace" && human && human.subjectId === grant.subjectId && human.subjectId === account.connectedBySubjectId && hasPermission11(grant.permissions, "connections:write")
|
|
24932
25712
|
);
|
|
24933
25713
|
const canRedeem = canResumeRedemption && account.status === "active";
|
|
24934
|
-
const redemptionAccess =
|
|
25714
|
+
const redemptionAccess = account.source === "organization" ? {
|
|
25715
|
+
ownership: "managed_human_unavailable",
|
|
25716
|
+
canClaimUnownedViaReconnect: false
|
|
25717
|
+
} : codexRedemptionAccess({
|
|
24935
25718
|
connectedBySubjectId: account.connectedBySubjectId,
|
|
24936
25719
|
grantSubjectId: grant.subjectId,
|
|
24937
25720
|
managedHumanSubjectId: human?.subjectId ?? null,
|
|
24938
|
-
canManage: hasPermission11(grant.permissions, "
|
|
25721
|
+
canManage: hasPermission11(grant.permissions, "connections:write")
|
|
24939
25722
|
});
|
|
24940
25723
|
overview[account.id] = await fetchCodexAccountOverview(
|
|
24941
25724
|
deps,
|
|
@@ -24971,17 +25754,20 @@ function registerCodexRoutes(app, deps) {
|
|
|
24971
25754
|
await Promise.all(
|
|
24972
25755
|
accounts.filter((account) => overview[account.id] == null).map(async (account) => {
|
|
24973
25756
|
const canResumeRedemption = Boolean(
|
|
24974
|
-
human && human.subjectId === grant.subjectId && human.subjectId === account.connectedBySubjectId && hasPermission11(grant.permissions, "
|
|
25757
|
+
account.source === "workspace" && human && human.subjectId === grant.subjectId && human.subjectId === account.connectedBySubjectId && hasPermission11(grant.permissions, "connections:write")
|
|
24975
25758
|
);
|
|
24976
25759
|
const fallback = await fetchCodexAccountOverview(
|
|
24977
25760
|
deps,
|
|
24978
25761
|
workspaceId,
|
|
24979
25762
|
account,
|
|
24980
|
-
|
|
25763
|
+
account.source === "organization" ? {
|
|
25764
|
+
ownership: "managed_human_unavailable",
|
|
25765
|
+
canClaimUnownedViaReconnect: false
|
|
25766
|
+
} : codexRedemptionAccess({
|
|
24981
25767
|
connectedBySubjectId: account.connectedBySubjectId,
|
|
24982
25768
|
grantSubjectId: grant.subjectId,
|
|
24983
25769
|
managedHumanSubjectId: human?.subjectId ?? null,
|
|
24984
|
-
canManage: hasPermission11(grant.permissions, "
|
|
25770
|
+
canManage: hasPermission11(grant.permissions, "connections:write")
|
|
24985
25771
|
}),
|
|
24986
25772
|
false,
|
|
24987
25773
|
canResumeRedemption,
|
|
@@ -25012,6 +25798,11 @@ function registerCodexRoutes(app, deps) {
|
|
|
25012
25798
|
const accounts = await listCodexAccountStatuses(db, workspaceId);
|
|
25013
25799
|
const account = accounts.find((candidate) => candidate.id === credentialId);
|
|
25014
25800
|
if (!account) throw new HTTPException21(404, { message: "codex account not found" });
|
|
25801
|
+
if (account.source === "organization") {
|
|
25802
|
+
throw new HTTPException21(409, {
|
|
25803
|
+
message: "organization Codex subscriptions are managed in Organization settings"
|
|
25804
|
+
});
|
|
25805
|
+
}
|
|
25015
25806
|
let existing = await getCodexResetRedemptionAttempt(db, workspaceId, parsed.data.attemptId);
|
|
25016
25807
|
if (account.connectedBySubjectId !== human.subjectId) {
|
|
25017
25808
|
throw new HTTPException21(403, {
|
|
@@ -26259,7 +27050,8 @@ import {
|
|
|
26259
27050
|
getConnectionMetadata as getConnectionMetadata5,
|
|
26260
27051
|
getWorkspaceGrant as getWorkspaceGrant6,
|
|
26261
27052
|
loadConnectionCredentialForBroker as loadConnectionCredentialForBroker4,
|
|
26262
|
-
persistProviderOAuthConnection
|
|
27053
|
+
persistProviderOAuthConnection,
|
|
27054
|
+
resolveNamedManagedPersonalWorkspaceGrant as resolveNamedManagedPersonalWorkspaceGrant2
|
|
26263
27055
|
} from "@opengeni/db";
|
|
26264
27056
|
import { createSignedState as createSignedState9, readSignedState as readSignedState8 } from "@opengeni/github";
|
|
26265
27057
|
import {
|
|
@@ -26742,7 +27534,8 @@ async function providerFetch3(deps, url, init) {
|
|
|
26742
27534
|
}
|
|
26743
27535
|
}
|
|
26744
27536
|
async function requireProviderOAuthGrant(deps, state) {
|
|
26745
|
-
const
|
|
27537
|
+
const membershipGrant = await getWorkspaceGrant6(deps.db, state.subjectId, state.workspaceId);
|
|
27538
|
+
const grant = membershipGrant?.accountId === state.accountId ? membershipGrant : state.personalOwnerVerified ? await resolveNamedManagedPersonalWorkspaceGrant2(deps.db, state) : null;
|
|
26746
27539
|
if (!grant || grant.accountId !== state.accountId || !hasPermission12(grant.permissions, "connections:write")) {
|
|
26747
27540
|
throw new ProviderOAuthCallbackError("connection_conflict");
|
|
26748
27541
|
}
|
|
@@ -28477,7 +29270,7 @@ import {
|
|
|
28477
29270
|
requireAccessGrant as requireAccessGrant7,
|
|
28478
29271
|
requireAccountAdminAuthorizationStamp,
|
|
28479
29272
|
requireAccessGrantAuthorization as requireAccessGrantAuthorization2,
|
|
28480
|
-
saveWorkspaceMemoryWithSlackPublication
|
|
29273
|
+
saveWorkspaceMemoryWithSlackPublication as saveWorkspaceMemoryWithSlackPublication2
|
|
28481
29274
|
} from "@opengeni/core";
|
|
28482
29275
|
import { recordWorkspaceUsage as recordWorkspaceUsage4, requireLimit as requireLimit4 } from "@opengeni/core";
|
|
28483
29276
|
|
|
@@ -29605,7 +30398,7 @@ function registerDocumentRoutes(app, deps) {
|
|
|
29605
30398
|
}
|
|
29606
30399
|
if (payload.status === "active") {
|
|
29607
30400
|
try {
|
|
29608
|
-
const result = await
|
|
30401
|
+
const result = await saveWorkspaceMemoryWithSlackPublication2(
|
|
29609
30402
|
db,
|
|
29610
30403
|
{
|
|
29611
30404
|
accountId: grant.accountId,
|
|
@@ -30472,6 +31265,7 @@ import {
|
|
|
30472
31265
|
readMachineMetricsLatestForWorkspace
|
|
30473
31266
|
} from "@opengeni/db";
|
|
30474
31267
|
import { MachineView, MetricSample } from "@opengeni/contracts";
|
|
31268
|
+
import { managedSessionGroupBackend as managedSessionGroupBackend3 } from "@opengeni/core";
|
|
30475
31269
|
import { selfhostedHeartbeatLiveness } from "@opengeni/runtime/sandbox";
|
|
30476
31270
|
var ACTIVE_UPDATE_STATUSES = /* @__PURE__ */ new Set([
|
|
30477
31271
|
"requested",
|
|
@@ -30603,7 +31397,8 @@ async function listMachines(services, input) {
|
|
|
30603
31397
|
}
|
|
30604
31398
|
}
|
|
30605
31399
|
const machines = [];
|
|
30606
|
-
|
|
31400
|
+
const groupBackend = session ? managedSessionGroupBackend3(services.settings.sandboxBackend, session.sandboxBackend) : null;
|
|
31401
|
+
if (session && groupBackend) {
|
|
30607
31402
|
const groupActive = activeSandboxId === null;
|
|
30608
31403
|
const groupLease = await readLease3(db, workspaceId, session.sandboxGroupId);
|
|
30609
31404
|
machines.push(
|
|
@@ -30611,7 +31406,7 @@ async function listMachines(services, input) {
|
|
|
30611
31406
|
sandboxId: session.sandboxGroupId,
|
|
30612
31407
|
enrollmentId: null,
|
|
30613
31408
|
name: "session sandbox",
|
|
30614
|
-
kind:
|
|
31409
|
+
kind: groupBackend === "opensandbox" ? "opensandbox" : "modal",
|
|
30615
31410
|
state: "online",
|
|
30616
31411
|
active: groupActive,
|
|
30617
31412
|
isSessionGroup: true,
|
|
@@ -31568,12 +32363,37 @@ function trimmedVariableSetName(name) {
|
|
|
31568
32363
|
}
|
|
31569
32364
|
|
|
31570
32365
|
// src/routes/api-keys.ts
|
|
31571
|
-
import {
|
|
31572
|
-
|
|
32366
|
+
import {
|
|
32367
|
+
CreateApiKeyRequest,
|
|
32368
|
+
CreateApiKeyResponse,
|
|
32369
|
+
CreateOrganizationApiKeyRequest
|
|
32370
|
+
} from "@opengeni/contracts";
|
|
32371
|
+
import {
|
|
32372
|
+
createApiKey,
|
|
32373
|
+
createOrganizationApiKey as createOrganizationApiKeyRecord,
|
|
32374
|
+
listApiKeys,
|
|
32375
|
+
listOrganizationApiKeys,
|
|
32376
|
+
OrganizationApiKeyLimitExceededError,
|
|
32377
|
+
revokeApiKey,
|
|
32378
|
+
revokeOrganizationApiKey
|
|
32379
|
+
} from "@opengeni/db";
|
|
32380
|
+
import { configuredStaticUsageLimits } from "@opengeni/config";
|
|
31573
32381
|
import { zValidator } from "@hono/zod-validator";
|
|
31574
32382
|
import { HTTPException as HTTPException31 } from "hono/http-exception";
|
|
31575
|
-
import {
|
|
32383
|
+
import {
|
|
32384
|
+
accountScopedApiKeyWorkspaceAuthority,
|
|
32385
|
+
requireAccessContext,
|
|
32386
|
+
requireAccessGrant as requireAccessGrant12,
|
|
32387
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization6
|
|
32388
|
+
} from "@opengeni/core";
|
|
31576
32389
|
import { requireLimit as requireLimit5 } from "@opengeni/core";
|
|
32390
|
+
var organizationApiKeyPermissions = [
|
|
32391
|
+
"account:read",
|
|
32392
|
+
"workspace:create",
|
|
32393
|
+
"workspace:read",
|
|
32394
|
+
"workspace:admin",
|
|
32395
|
+
"api_keys:manage"
|
|
32396
|
+
];
|
|
31577
32397
|
function registerApiKeyRoutes(app, deps) {
|
|
31578
32398
|
app.get("/v1/workspaces/:workspaceId/api-keys", async (c) => {
|
|
31579
32399
|
const workspaceId = c.req.param("workspaceId");
|
|
@@ -31585,10 +32405,16 @@ function registerApiKeyRoutes(app, deps) {
|
|
|
31585
32405
|
zValidator("json", CreateApiKeyRequest.omit({ workspaceId: true })),
|
|
31586
32406
|
async (c) => {
|
|
31587
32407
|
const workspaceId = c.req.param("workspaceId");
|
|
31588
|
-
const
|
|
32408
|
+
const authorization = await requireAccessGrantAuthorization6(
|
|
32409
|
+
c,
|
|
32410
|
+
deps,
|
|
32411
|
+
workspaceId,
|
|
32412
|
+
"api_keys:manage"
|
|
32413
|
+
);
|
|
32414
|
+
const grant = authorization.grant;
|
|
31589
32415
|
const body4 = c.req.valid("json");
|
|
31590
32416
|
const permissions = body4.permissions.length > 0 ? body4.permissions : ["workspace:read"];
|
|
31591
|
-
ensureDelegablePermissions(
|
|
32417
|
+
ensureDelegablePermissions(authorization, permissions);
|
|
31592
32418
|
await requireLimit5(deps, {
|
|
31593
32419
|
accountId: grant.accountId,
|
|
31594
32420
|
workspaceId,
|
|
@@ -31615,11 +32441,72 @@ function registerApiKeyRoutes(app, deps) {
|
|
|
31615
32441
|
await requireAccessGrant12(c, deps, workspaceId, "api_keys:manage");
|
|
31616
32442
|
return c.json(await revokeApiKey(deps.db, workspaceId, c.req.param("apiKeyId")));
|
|
31617
32443
|
});
|
|
32444
|
+
app.get("/v1/organizations/:organizationId/api-keys", async (c) => {
|
|
32445
|
+
const organizationId2 = c.req.param("organizationId");
|
|
32446
|
+
const context = await requireAccessContext(c, deps);
|
|
32447
|
+
requireOrganizationApiKeyControlPermission(context, organizationId2);
|
|
32448
|
+
return c.json({ apiKeys: await listOrganizationApiKeys(deps.db, organizationId2) });
|
|
32449
|
+
});
|
|
32450
|
+
app.post(
|
|
32451
|
+
"/v1/organizations/:organizationId/api-keys",
|
|
32452
|
+
zValidator("json", CreateOrganizationApiKeyRequest),
|
|
32453
|
+
async (c) => {
|
|
32454
|
+
const organizationId2 = c.req.param("organizationId");
|
|
32455
|
+
const context = await requireAccessContext(c, deps);
|
|
32456
|
+
requireOrganizationApiKeyControlPermission(context, organizationId2);
|
|
32457
|
+
const body4 = c.req.valid("json");
|
|
32458
|
+
const token = generateApiKeyToken();
|
|
32459
|
+
try {
|
|
32460
|
+
const apiKey = await createOrganizationApiKeyRecord(deps.db, {
|
|
32461
|
+
accountId: organizationId2,
|
|
32462
|
+
name: body4.name,
|
|
32463
|
+
description: body4.description ?? null,
|
|
32464
|
+
prefix: token.slice(0, 14),
|
|
32465
|
+
keyHash: await sha256Hex2(token),
|
|
32466
|
+
permissions: organizationApiKeyPermissions,
|
|
32467
|
+
expiresAt: body4.expiresAt ? new Date(body4.expiresAt) : null,
|
|
32468
|
+
maxActiveKeys: organizationApiKeyLimit(deps),
|
|
32469
|
+
rotationSourceApiKeyId: authenticatedApiKeyId(context)
|
|
32470
|
+
});
|
|
32471
|
+
return c.json(CreateApiKeyResponse.parse({ apiKey, token }), 201);
|
|
32472
|
+
} catch (error) {
|
|
32473
|
+
if (error instanceof OrganizationApiKeyLimitExceededError) {
|
|
32474
|
+
throw new HTTPException31(429, { message: error.message });
|
|
32475
|
+
}
|
|
32476
|
+
throw error;
|
|
32477
|
+
}
|
|
32478
|
+
}
|
|
32479
|
+
);
|
|
32480
|
+
app.delete("/v1/organizations/:organizationId/api-keys/:apiKeyId", async (c) => {
|
|
32481
|
+
const organizationId2 = c.req.param("organizationId");
|
|
32482
|
+
const context = await requireAccessContext(c, deps);
|
|
32483
|
+
requireOrganizationApiKeyControlPermission(context, organizationId2);
|
|
32484
|
+
const apiKey = await revokeOrganizationApiKey(deps.db, organizationId2, c.req.param("apiKeyId"));
|
|
32485
|
+
if (!apiKey) {
|
|
32486
|
+
throw new HTTPException31(404, { message: "API key not found" });
|
|
32487
|
+
}
|
|
32488
|
+
return c.json(apiKey);
|
|
32489
|
+
});
|
|
31618
32490
|
}
|
|
31619
|
-
function
|
|
32491
|
+
function requireAccountPermission(context, accountId, permission) {
|
|
32492
|
+
const grant = context.accountGrants.find((candidate) => candidate.accountId === accountId);
|
|
32493
|
+
if (!grant || !grant.permissions.includes(permission) && !grant.permissions.includes("account:admin")) {
|
|
32494
|
+
throw new HTTPException31(403, { message: `missing permission: ${permission}` });
|
|
32495
|
+
}
|
|
32496
|
+
}
|
|
32497
|
+
function ensureDelegablePermissions(authorization, requested) {
|
|
32498
|
+
const grantPermissions = authorization.grant.permissions;
|
|
31620
32499
|
if (grantPermissions.includes("workspace:admin")) {
|
|
32500
|
+
const accountLiteralPermissions = /* @__PURE__ */ new Set([
|
|
32501
|
+
"account:read",
|
|
32502
|
+
"account:admin",
|
|
32503
|
+
"workspace:create",
|
|
32504
|
+
"billing:read",
|
|
32505
|
+
"billing:manage"
|
|
32506
|
+
]);
|
|
32507
|
+
const workspaceLiteralPermissions = /* @__PURE__ */ new Set(["members:manage", "secrets:read"]);
|
|
31621
32508
|
const highTrustMissing = requested.filter(
|
|
31622
|
-
(permission) => permission
|
|
32509
|
+
(permission) => accountLiteralPermissions.has(permission) && !authorization.accountGrant?.permissions.includes(permission) || workspaceLiteralPermissions.has(permission) && !grantPermissions.includes(permission)
|
|
31623
32510
|
);
|
|
31624
32511
|
if (highTrustMissing.length === 0) return;
|
|
31625
32512
|
throw new HTTPException31(403, {
|
|
@@ -31633,6 +32520,23 @@ function ensureDelegablePermissions(grantPermissions, requested) {
|
|
|
31633
32520
|
});
|
|
31634
32521
|
}
|
|
31635
32522
|
}
|
|
32523
|
+
function requireOrganizationApiKeyControlPermission(context, organizationId2) {
|
|
32524
|
+
requireAccountPermission(context, organizationId2, "api_keys:manage");
|
|
32525
|
+
if (!context.subjectId.startsWith("api_key:")) return;
|
|
32526
|
+
const authority = accountScopedApiKeyWorkspaceAuthority(context);
|
|
32527
|
+
if (!authority || authority.accountId !== organizationId2 || !authority.permissions.includes("api_keys:manage")) {
|
|
32528
|
+
throw new HTTPException31(403, { message: "organization API key authority required" });
|
|
32529
|
+
}
|
|
32530
|
+
}
|
|
32531
|
+
function authenticatedApiKeyId(context) {
|
|
32532
|
+
return context.subjectId.startsWith("api_key:") ? context.subjectId.slice("api_key:".length) : null;
|
|
32533
|
+
}
|
|
32534
|
+
function organizationApiKeyLimit(deps) {
|
|
32535
|
+
if (deps.settings.usageLimitsMode !== "static" && deps.settings.usageLimitsMode !== "managed") {
|
|
32536
|
+
return null;
|
|
32537
|
+
}
|
|
32538
|
+
return configuredStaticUsageLimits(deps.settings).maxApiKeysPerWorkspace ?? null;
|
|
32539
|
+
}
|
|
31636
32540
|
function generateApiKeyToken() {
|
|
31637
32541
|
const bytes = new Uint8Array(32);
|
|
31638
32542
|
crypto.getRandomValues(bytes);
|
|
@@ -31666,10 +32570,10 @@ import {
|
|
|
31666
32570
|
} from "@opengeni/db";
|
|
31667
32571
|
import { HTTPException as HTTPException32 } from "hono/http-exception";
|
|
31668
32572
|
import Stripe from "stripe";
|
|
31669
|
-
import { requireAccessContext } from "@opengeni/core";
|
|
32573
|
+
import { requireAccessContext as requireAccessContext2 } from "@opengeni/core";
|
|
31670
32574
|
function registerBillingRoutes(app, deps) {
|
|
31671
32575
|
app.get("/v1/billing", async (c) => {
|
|
31672
|
-
const context = await
|
|
32576
|
+
const context = await requireAccessContext2(c, deps);
|
|
31673
32577
|
const accountId = requireSelectedAccount(context, c.req.query("accountId"), "billing:read");
|
|
31674
32578
|
return c.json({
|
|
31675
32579
|
mode: deps.settings.billingMode,
|
|
@@ -31677,7 +32581,7 @@ function registerBillingRoutes(app, deps) {
|
|
|
31677
32581
|
});
|
|
31678
32582
|
});
|
|
31679
32583
|
app.get("/v1/billing/usage", async (c) => {
|
|
31680
|
-
const context = await
|
|
32584
|
+
const context = await requireAccessContext2(c, deps);
|
|
31681
32585
|
const accountId = requireSelectedAccount(context, c.req.query("accountId"), "billing:read");
|
|
31682
32586
|
const workspaceId = c.req.query("workspaceId");
|
|
31683
32587
|
if (workspaceId && !context.workspaceGrants.some(
|
|
@@ -31695,7 +32599,7 @@ function registerBillingRoutes(app, deps) {
|
|
|
31695
32599
|
});
|
|
31696
32600
|
});
|
|
31697
32601
|
app.get("/v1/billing/entitlements", async (c) => {
|
|
31698
|
-
const context = await
|
|
32602
|
+
const context = await requireAccessContext2(c, deps);
|
|
31699
32603
|
const accountId = requireSelectedAccount(context, c.req.query("accountId"), "billing:read");
|
|
31700
32604
|
return c.json({
|
|
31701
32605
|
accountId,
|
|
@@ -31707,7 +32611,7 @@ function registerBillingRoutes(app, deps) {
|
|
|
31707
32611
|
if (deps.settings.billingMode !== "stripe") {
|
|
31708
32612
|
throw new HTTPException32(404, { message: "stripe billing is not enabled" });
|
|
31709
32613
|
}
|
|
31710
|
-
const context = await
|
|
32614
|
+
const context = await requireAccessContext2(c, deps);
|
|
31711
32615
|
const parsed = CreateCheckoutRequest.safeParse(await c.req.json());
|
|
31712
32616
|
if (!parsed.success) {
|
|
31713
32617
|
throw new HTTPException32(400, {
|
|
@@ -31749,7 +32653,7 @@ function registerBillingRoutes(app, deps) {
|
|
|
31749
32653
|
if (deps.settings.billingMode !== "stripe") {
|
|
31750
32654
|
throw new HTTPException32(404, { message: "stripe billing is not enabled" });
|
|
31751
32655
|
}
|
|
31752
|
-
const context = await
|
|
32656
|
+
const context = await requireAccessContext2(c, deps);
|
|
31753
32657
|
const parsed = CreateBillingPortalRequest.safeParse(await c.req.json());
|
|
31754
32658
|
if (!parsed.success) {
|
|
31755
32659
|
throw new HTTPException32(400, {
|
|
@@ -33176,7 +34080,7 @@ function safeControlRequestId(value) {
|
|
|
33176
34080
|
|
|
33177
34081
|
// src/interaction-frame-proxy.ts
|
|
33178
34082
|
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, createHmac as createHmac7, randomBytes as randomBytes8 } from "crypto";
|
|
33179
|
-
import { canonicalPublicOrigin } from "@opengeni/config";
|
|
34083
|
+
import { canonicalPublicOrigin as canonicalPublicOrigin2 } from "@opengeni/config";
|
|
33180
34084
|
import { WebSocket as UpstreamWebSocket } from "ws";
|
|
33181
34085
|
var INTERACTION_FRAME_PROXY_PATH = "/v1/interaction/frame-proxy";
|
|
33182
34086
|
var INTERACTION_FRAME_PROXY_PROTOCOL_PREFIX = "opengeni-frame-proxy.";
|
|
@@ -33196,7 +34100,7 @@ function placementUsesInteractionFrameProxy(backend, options) {
|
|
|
33196
34100
|
return options?.openSandboxSignedEndpoints !== true;
|
|
33197
34101
|
}
|
|
33198
34102
|
function resolveInteractionFrameProxyRequestUrl(input) {
|
|
33199
|
-
const publicOrigin =
|
|
34103
|
+
const publicOrigin = canonicalPublicOrigin2(input.publicBaseUrl) ?? (input.webBaseUrl?.startsWith("https://") ? canonicalPublicOrigin2(input.webBaseUrl) : null);
|
|
33200
34104
|
if (publicOrigin) return `${publicOrigin}/`;
|
|
33201
34105
|
const request = new URL(input.requestUrl);
|
|
33202
34106
|
const forwardedProto = firstForwardedValue(input.forwardedProto)?.toLowerCase();
|
|
@@ -33497,6 +34401,13 @@ var STALE_INTERACTION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
33497
34401
|
"frame_stale",
|
|
33498
34402
|
"attempt_stale"
|
|
33499
34403
|
]);
|
|
34404
|
+
var COMPUTER_FRAME_EVIDENCE_MISMATCH_REASONS = /* @__PURE__ */ new Set([
|
|
34405
|
+
"frame_session_mismatch",
|
|
34406
|
+
"frame_target_mismatch",
|
|
34407
|
+
"frame_controller_mismatch",
|
|
34408
|
+
"frame_media_mismatch",
|
|
34409
|
+
"frame_digest_mismatch"
|
|
34410
|
+
]);
|
|
33500
34411
|
function observeBrowserActionResult(observability, startedAtMs, request, receipt) {
|
|
33501
34412
|
interactionOperationMetricObserver(observability)({
|
|
33502
34413
|
resource: "browser",
|
|
@@ -33515,6 +34426,21 @@ function observeComputerActionResult(observability, startedAtMs, request, receip
|
|
|
33515
34426
|
durationMs: elapsedMs(startedAtMs)
|
|
33516
34427
|
});
|
|
33517
34428
|
}
|
|
34429
|
+
function observeComputerFrameEvidenceMismatch(observability, reason2) {
|
|
34430
|
+
if (!observability || !COMPUTER_FRAME_EVIDENCE_MISMATCH_REASONS.has(reason2)) return;
|
|
34431
|
+
try {
|
|
34432
|
+
observability.incrementCounter({
|
|
34433
|
+
name: "opengeni_computer_frame_evidence_mismatches_total",
|
|
34434
|
+
help: "Computer frame evidence rejected at the controller-to-API boundary by bounded reason.",
|
|
34435
|
+
labels: { reason: reason2 }
|
|
34436
|
+
});
|
|
34437
|
+
} catch {
|
|
34438
|
+
}
|
|
34439
|
+
try {
|
|
34440
|
+
observability.warn("Computer frame evidence mismatch", { reason: reason2 });
|
|
34441
|
+
} catch {
|
|
34442
|
+
}
|
|
34443
|
+
}
|
|
33518
34444
|
function observeLifecycleResult(observability, startedAtMs, response) {
|
|
33519
34445
|
interactionOperationMetricObserver(observability)({
|
|
33520
34446
|
resource: response.operation.resourceKind === "browser_session" ? "browser" : "computer",
|
|
@@ -36476,14 +37402,19 @@ function assertCreateReplay(request, session) {
|
|
|
36476
37402
|
}
|
|
36477
37403
|
}
|
|
36478
37404
|
async function ensureHeadedBrowserDisplayStack(session, headless, linkedComputer) {
|
|
36479
|
-
if (!browserNeedsStandaloneDisplayStack({
|
|
37405
|
+
if (!browserNeedsStandaloneDisplayStack({
|
|
37406
|
+
headless,
|
|
37407
|
+
linkedComputer,
|
|
37408
|
+
nativeBrowserControl: typeof session.ensureBrowserControl === "function"
|
|
37409
|
+
}))
|
|
37410
|
+
return;
|
|
36480
37411
|
if (typeof session.exec !== "function" && typeof session.execCommand !== "function") return;
|
|
36481
37412
|
await ensureDisplayStack2(session, {
|
|
36482
37413
|
telemetryContext: { callerKind: "viewer" }
|
|
36483
37414
|
});
|
|
36484
37415
|
}
|
|
36485
37416
|
function browserNeedsStandaloneDisplayStack(input) {
|
|
36486
|
-
return !input.headless && !input.linkedComputer;
|
|
37417
|
+
return !input.headless && !input.linkedComputer && !input.nativeBrowserControl;
|
|
36487
37418
|
}
|
|
36488
37419
|
async function ensureLinkedComputerController(deps, grant, browser, placement, client) {
|
|
36489
37420
|
if (!browser.linkedComputerSessionId) return null;
|
|
@@ -37477,6 +38408,7 @@ import {
|
|
|
37477
38408
|
BrowserControlServerUnsupportedError as BrowserControlServerUnsupportedError2,
|
|
37478
38409
|
BrowserControlTransportError as BrowserControlTransportError4,
|
|
37479
38410
|
BrowserControlUnsupportedError as BrowserControlUnsupportedError2,
|
|
38411
|
+
ComputerFrameEvidenceMismatchError,
|
|
37480
38412
|
buildSelfhostedBackendSession as buildSelfhostedBackendSession3,
|
|
37481
38413
|
buildStreamUrl as buildStreamUrl2,
|
|
37482
38414
|
exposedPortEndpointFromUrl as exposedPortEndpointFromUrl3,
|
|
@@ -37484,6 +38416,7 @@ import {
|
|
|
37484
38416
|
NatsControlRpc as NatsControlRpc5,
|
|
37485
38417
|
NatsOpStreamTransport as NatsOpStreamTransport3,
|
|
37486
38418
|
provisionBrowserControlClient as provisionBrowserControlClient2,
|
|
38419
|
+
validateComputerControlFrameEvidence,
|
|
37487
38420
|
renewSandboxProviderExpiration as renewSandboxProviderExpiration3
|
|
37488
38421
|
} from "@opengeni/runtime/sandbox";
|
|
37489
38422
|
import { HTTPException as HTTPException36 } from "hono/http-exception";
|
|
@@ -37507,6 +38440,38 @@ function connectedMachineComputerAccessError(state, requiresControl) {
|
|
|
37507
38440
|
}
|
|
37508
38441
|
|
|
37509
38442
|
// src/routes/computer-sessions.ts
|
|
38443
|
+
var MODEL_COMPUTER_FRAME_MAX_BYTES = 256 * 1024;
|
|
38444
|
+
function validateComputerFrameForApi(frame, expected) {
|
|
38445
|
+
return validateComputerControlFrameEvidence(frame, expected);
|
|
38446
|
+
}
|
|
38447
|
+
async function captureModelComputerFrame(sessionClient, expected) {
|
|
38448
|
+
let captured = validateComputerFrameForApi(
|
|
38449
|
+
await sessionClient.capture(expected.targetId, {
|
|
38450
|
+
format: "jpeg",
|
|
38451
|
+
quality: 55,
|
|
38452
|
+
maxWidth: 1024,
|
|
38453
|
+
maxHeight: 768
|
|
38454
|
+
}),
|
|
38455
|
+
expected
|
|
38456
|
+
);
|
|
38457
|
+
if (captured.data.byteLength > MODEL_COMPUTER_FRAME_MAX_BYTES) {
|
|
38458
|
+
captured = validateComputerFrameForApi(
|
|
38459
|
+
await sessionClient.capture(expected.targetId, {
|
|
38460
|
+
format: "jpeg",
|
|
38461
|
+
quality: 30,
|
|
38462
|
+
maxWidth: 640,
|
|
38463
|
+
maxHeight: 480
|
|
38464
|
+
}),
|
|
38465
|
+
expected
|
|
38466
|
+
);
|
|
38467
|
+
}
|
|
38468
|
+
if (captured.data.byteLength > MODEL_COMPUTER_FRAME_MAX_BYTES) {
|
|
38469
|
+
throw new BrowserControlProtocolError2(
|
|
38470
|
+
"computer screenshot could not honor the model image byte bound"
|
|
38471
|
+
);
|
|
38472
|
+
}
|
|
38473
|
+
return captured;
|
|
38474
|
+
}
|
|
37510
38475
|
function registerComputerSessionRoutes(app, deps) {
|
|
37511
38476
|
const channelServices = {
|
|
37512
38477
|
db: deps.db,
|
|
@@ -37754,6 +38719,46 @@ function registerComputerSessionRoutes(app, deps) {
|
|
|
37754
38719
|
return context.json(result);
|
|
37755
38720
|
}
|
|
37756
38721
|
);
|
|
38722
|
+
app.get(
|
|
38723
|
+
"/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/targets/:targetId/screenshot",
|
|
38724
|
+
async (context) => {
|
|
38725
|
+
const { workspaceId, grant, computerSessionId } = await routePreamble(
|
|
38726
|
+
context,
|
|
38727
|
+
"sessions:read"
|
|
38728
|
+
);
|
|
38729
|
+
const targetId = requireOpaqueParam2(context, "targetId");
|
|
38730
|
+
const frame = await withActiveComputerController(
|
|
38731
|
+
context,
|
|
38732
|
+
grant,
|
|
38733
|
+
workspaceId,
|
|
38734
|
+
computerSessionId,
|
|
38735
|
+
"session.read",
|
|
38736
|
+
"computer.read",
|
|
38737
|
+
async ({ sessionClient, binding }) => {
|
|
38738
|
+
try {
|
|
38739
|
+
return await captureModelComputerFrame(sessionClient, {
|
|
38740
|
+
computerSessionId,
|
|
38741
|
+
controllerGeneration: binding.controllerGeneration,
|
|
38742
|
+
targetId
|
|
38743
|
+
});
|
|
38744
|
+
} catch (error) {
|
|
38745
|
+
if (error instanceof ComputerFrameEvidenceMismatchError) {
|
|
38746
|
+
observeComputerFrameEvidenceMismatch(deps.observability, error.reason);
|
|
38747
|
+
}
|
|
38748
|
+
throw error;
|
|
38749
|
+
}
|
|
38750
|
+
}
|
|
38751
|
+
);
|
|
38752
|
+
return new Response(frame.data.slice().buffer, {
|
|
38753
|
+
status: 200,
|
|
38754
|
+
headers: {
|
|
38755
|
+
"cache-control": "no-store",
|
|
38756
|
+
"content-type": frame.mediaType,
|
|
38757
|
+
"x-opengeni-computer-frame": frame.metadataHeader
|
|
38758
|
+
}
|
|
38759
|
+
});
|
|
38760
|
+
}
|
|
38761
|
+
);
|
|
37757
38762
|
app.get(
|
|
37758
38763
|
"/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/clipboard",
|
|
37759
38764
|
async (context) => {
|
|
@@ -39614,7 +40619,7 @@ import {
|
|
|
39614
40619
|
} from "@opengeni/contracts/personal-github";
|
|
39615
40620
|
import {
|
|
39616
40621
|
requireAccessGrant as requireAccessGrant17,
|
|
39617
|
-
requireAccessGrantAuthorization as
|
|
40622
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization7
|
|
39618
40623
|
} from "@opengeni/core";
|
|
39619
40624
|
import {
|
|
39620
40625
|
getPersonalGitHubRepositorySelectionState as getPersonalGitHubRepositorySelectionState2,
|
|
@@ -39982,7 +40987,7 @@ function registerPersonalGitHubRoutes(app, deps) {
|
|
|
39982
40987
|
});
|
|
39983
40988
|
app.post("/v1/workspaces/:workspaceId/connections/github/oauth/start", async (c) => {
|
|
39984
40989
|
const workspaceId = c.req.param("workspaceId");
|
|
39985
|
-
const access = await
|
|
40990
|
+
const access = await requireAccessGrantAuthorization7(c, deps, workspaceId, "connections:write");
|
|
39986
40991
|
assertPersonalConnectionOwnerPrincipal(access, "My GitHub account");
|
|
39987
40992
|
const payload = PersonalGitHubOAuthStartRequest.parse(await c.req.json().catch(() => ({})));
|
|
39988
40993
|
return c.json(
|
|
@@ -39996,7 +41001,7 @@ function registerPersonalGitHubRoutes(app, deps) {
|
|
|
39996
41001
|
});
|
|
39997
41002
|
app.post("/v1/workspaces/:workspaceId/connections/:connectionId/github/reconnect", async (c) => {
|
|
39998
41003
|
const workspaceId = c.req.param("workspaceId");
|
|
39999
|
-
const access = await
|
|
41004
|
+
const access = await requireAccessGrantAuthorization7(c, deps, workspaceId, "connections:write");
|
|
40000
41005
|
assertPersonalConnectionOwnerPrincipal(access, "My GitHub account");
|
|
40001
41006
|
const payload = PersonalGitHubOAuthStartRequest.omit({ connectionId: true }).parse(
|
|
40002
41007
|
await c.req.json().catch(() => ({}))
|
|
@@ -40015,7 +41020,7 @@ function registerPersonalGitHubRoutes(app, deps) {
|
|
|
40015
41020
|
async (c) => {
|
|
40016
41021
|
const workspaceId = c.req.param("workspaceId");
|
|
40017
41022
|
const connectionId = c.req.param("connectionId");
|
|
40018
|
-
const access = await
|
|
41023
|
+
const access = await requireAccessGrantAuthorization7(
|
|
40019
41024
|
c,
|
|
40020
41025
|
deps,
|
|
40021
41026
|
workspaceId,
|
|
@@ -40066,7 +41071,7 @@ function registerPersonalGitHubRoutes(app, deps) {
|
|
|
40066
41071
|
async (c) => {
|
|
40067
41072
|
const workspaceId = c.req.param("workspaceId");
|
|
40068
41073
|
const connectionId = c.req.param("connectionId");
|
|
40069
|
-
const access = await
|
|
41074
|
+
const access = await requireAccessGrantAuthorization7(
|
|
40070
41075
|
c,
|
|
40071
41076
|
deps,
|
|
40072
41077
|
workspaceId,
|
|
@@ -40113,7 +41118,7 @@ function registerPersonalGitHubRoutes(app, deps) {
|
|
|
40113
41118
|
async (c) => {
|
|
40114
41119
|
const workspaceId = c.req.param("workspaceId");
|
|
40115
41120
|
const connectionId = c.req.param("connectionId");
|
|
40116
|
-
const access = await
|
|
41121
|
+
const access = await requireAccessGrantAuthorization7(
|
|
40117
41122
|
c,
|
|
40118
41123
|
deps,
|
|
40119
41124
|
workspaceId,
|
|
@@ -41132,7 +42137,7 @@ function registerApiIntegrationRoutes(app, deps, overrides = {}) {
|
|
|
41132
42137
|
});
|
|
41133
42138
|
app.post("/v1/workspaces/:workspaceId/integrations/install", async (c) => {
|
|
41134
42139
|
const workspaceId = c.req.param("workspaceId");
|
|
41135
|
-
const grant = await requireAccessGrant18(c, deps, workspaceId, "
|
|
42140
|
+
const grant = await requireAccessGrant18(c, deps, workspaceId, "capabilities:manage");
|
|
41136
42141
|
const payload = InstallApiIntegrationRequest.parse(await c.req.json());
|
|
41137
42142
|
const resolved = await resolveForRoute({
|
|
41138
42143
|
deps,
|
|
@@ -41227,7 +42232,7 @@ function registerApiIntegrationRoutes(app, deps, overrides = {}) {
|
|
|
41227
42232
|
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey",
|
|
41228
42233
|
async (c) => {
|
|
41229
42234
|
const workspaceId = c.req.param("workspaceId");
|
|
41230
|
-
const grant = await requireAccessGrant18(c, deps, workspaceId, "
|
|
42235
|
+
const grant = await requireAccessGrant18(c, deps, workspaceId, "capabilities:manage");
|
|
41231
42236
|
const capabilityId = decodeURIComponent(c.req.param("capabilityId"));
|
|
41232
42237
|
const instanceKey = decodeURIComponent(c.req.param("instanceKey"));
|
|
41233
42238
|
const payload = UninstallApiIntegrationRequest.parse(await c.req.json());
|
|
@@ -41353,7 +42358,7 @@ import {
|
|
|
41353
42358
|
import {
|
|
41354
42359
|
hasPermission as hasPermission16,
|
|
41355
42360
|
requireAccessGrant as requireAccessGrant19,
|
|
41356
|
-
requireAccessGrantAuthorization as
|
|
42361
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization8
|
|
41357
42362
|
} from "@opengeni/core";
|
|
41358
42363
|
import {
|
|
41359
42364
|
configureIntegrationFacet as configureIntegrationFacet2,
|
|
@@ -41398,11 +42403,11 @@ function registerIntegrationFacetRoutes(app, deps) {
|
|
|
41398
42403
|
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets/:facetKey/source",
|
|
41399
42404
|
async (c) => {
|
|
41400
42405
|
const workspaceId = c.req.param("workspaceId");
|
|
41401
|
-
const authorization = await
|
|
42406
|
+
const authorization = await requireAccessGrantAuthorization8(
|
|
41402
42407
|
c,
|
|
41403
42408
|
deps,
|
|
41404
42409
|
workspaceId,
|
|
41405
|
-
"
|
|
42410
|
+
"capabilities:manage"
|
|
41406
42411
|
);
|
|
41407
42412
|
const { grant } = authorization;
|
|
41408
42413
|
try {
|
|
@@ -41417,7 +42422,10 @@ function registerIntegrationFacetRoutes(app, deps) {
|
|
|
41417
42422
|
facetKey: decoded(c.req.param("facetKey")),
|
|
41418
42423
|
payload: await c.req.json(),
|
|
41419
42424
|
canManageOrganizationDestination: authorization.accountGrant?.permissions.includes("account:admin") === true,
|
|
41420
|
-
canManageWorkspaceDestination: hasPermission16(
|
|
42425
|
+
canManageWorkspaceDestination: hasPermission16(
|
|
42426
|
+
grant.permissions,
|
|
42427
|
+
"capabilities:manage"
|
|
42428
|
+
),
|
|
41421
42429
|
canManagePersonalDestination: authorization.contextIntegrity && authorization.authenticatedSubjectId === grant.subjectId
|
|
41422
42430
|
})
|
|
41423
42431
|
)
|
|
@@ -41453,7 +42461,7 @@ function registerIntegrationFacetRoutes(app, deps) {
|
|
|
41453
42461
|
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets/:facetKey",
|
|
41454
42462
|
async (c) => {
|
|
41455
42463
|
const workspaceId = c.req.param("workspaceId");
|
|
41456
|
-
const grant = await requireAccessGrant19(c, deps, workspaceId, "
|
|
42464
|
+
const grant = await requireAccessGrant19(c, deps, workspaceId, "capabilities:manage");
|
|
41457
42465
|
const payload = UpsertIntegrationFacetRequest.parse(await c.req.json());
|
|
41458
42466
|
const capabilityId = decoded(c.req.param("capabilityId"));
|
|
41459
42467
|
const instanceKey = decoded(c.req.param("instanceKey"));
|
|
@@ -41525,7 +42533,7 @@ function registerIntegrationFacetRoutes(app, deps) {
|
|
|
41525
42533
|
`/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets/:facetKey/${action}`,
|
|
41526
42534
|
async (c) => {
|
|
41527
42535
|
const workspaceId = c.req.param("workspaceId");
|
|
41528
|
-
const grant = await requireAccessGrant19(c, deps, workspaceId, "
|
|
42536
|
+
const grant = await requireAccessGrant19(c, deps, workspaceId, "capabilities:manage");
|
|
41529
42537
|
const payload = MutateIntegrationFacetRequest.parse(await c.req.json());
|
|
41530
42538
|
try {
|
|
41531
42539
|
return c.json(
|
|
@@ -41553,7 +42561,7 @@ function registerIntegrationFacetRoutes(app, deps) {
|
|
|
41553
42561
|
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets/:facetKey",
|
|
41554
42562
|
async (c) => {
|
|
41555
42563
|
const workspaceId = c.req.param("workspaceId");
|
|
41556
|
-
const grant = await requireAccessGrant19(c, deps, workspaceId, "
|
|
42564
|
+
const grant = await requireAccessGrant19(c, deps, workspaceId, "capabilities:manage");
|
|
41557
42565
|
const payload = MutateIntegrationFacetRequest.parse(await c.req.json());
|
|
41558
42566
|
try {
|
|
41559
42567
|
return c.json(
|
|
@@ -41676,6 +42684,8 @@ var WORKSPACE_CONTROL_REPLAY_PAGE_SIZE = 100;
|
|
|
41676
42684
|
var SSE_QUEUED_FRAME_MAX_COUNT = 1;
|
|
41677
42685
|
var SSE_WRITE_STALL_TIMEOUT_MS = 3e4;
|
|
41678
42686
|
var SSE_HEARTBEAT_INTERVAL_MS = 15e3;
|
|
42687
|
+
var HTTP1_BROWSER_SSE_BATCH_MAX_BYTES = 512 * 1024;
|
|
42688
|
+
var HTTP1_BROWSER_SSE_BATCH_CONTENT_TYPE = "application/vnd.opengeni.sse-batch";
|
|
41679
42689
|
var activeSseStreams = {
|
|
41680
42690
|
session: 0,
|
|
41681
42691
|
workspace_control: 0,
|
|
@@ -41684,16 +42694,21 @@ var activeSseStreams = {
|
|
|
41684
42694
|
function createByteBoundedSseStream(options = {}) {
|
|
41685
42695
|
const maxQueuedBytes = options.maxQueuedBytes ?? SESSION_EVENT_SSE_FRAME_MAX_BYTES;
|
|
41686
42696
|
const stallTimeoutMs = options.stallTimeoutMs ?? SSE_WRITE_STALL_TIMEOUT_MS;
|
|
42697
|
+
const connectionLifetimeMs = options.connectionLifetimeMs;
|
|
41687
42698
|
if (!Number.isSafeInteger(maxQueuedBytes) || maxQueuedBytes <= 0) {
|
|
41688
42699
|
throw new RangeError("SSE byte high-water mark must be a positive safe integer");
|
|
41689
42700
|
}
|
|
41690
42701
|
if (!Number.isSafeInteger(stallTimeoutMs) || stallTimeoutMs <= 0) {
|
|
41691
42702
|
throw new RangeError("SSE write stall timeout must be a positive safe integer");
|
|
41692
42703
|
}
|
|
42704
|
+
if (connectionLifetimeMs !== void 0 && (!Number.isSafeInteger(connectionLifetimeMs) || connectionLifetimeMs <= 0)) {
|
|
42705
|
+
throw new RangeError("SSE connection lifetime must be a positive safe integer");
|
|
42706
|
+
}
|
|
41693
42707
|
const encoder4 = new TextEncoder();
|
|
41694
42708
|
let controller;
|
|
41695
42709
|
let stopped = false;
|
|
41696
42710
|
let capacityWake = null;
|
|
42711
|
+
let lifetimeTimer = null;
|
|
41697
42712
|
let queuedFrames = 0;
|
|
41698
42713
|
let queuedBytes = 0;
|
|
41699
42714
|
const wakeWriter = () => {
|
|
@@ -41704,6 +42719,10 @@ function createByteBoundedSseStream(options = {}) {
|
|
|
41704
42719
|
const stop = (settle) => {
|
|
41705
42720
|
if (stopped) return;
|
|
41706
42721
|
stopped = true;
|
|
42722
|
+
if (lifetimeTimer !== null) {
|
|
42723
|
+
clearTimeout(lifetimeTimer);
|
|
42724
|
+
lifetimeTimer = null;
|
|
42725
|
+
}
|
|
41707
42726
|
wakeWriter();
|
|
41708
42727
|
options.onStop?.();
|
|
41709
42728
|
try {
|
|
@@ -41733,6 +42752,9 @@ function createByteBoundedSseStream(options = {}) {
|
|
|
41733
42752
|
size: () => 1
|
|
41734
42753
|
}
|
|
41735
42754
|
);
|
|
42755
|
+
if (connectionLifetimeMs !== void 0) {
|
|
42756
|
+
lifetimeTimer = setTimeout(() => stop(() => controller.close()), connectionLifetimeMs);
|
|
42757
|
+
}
|
|
41736
42758
|
return {
|
|
41737
42759
|
stream,
|
|
41738
42760
|
write: async (frame) => {
|
|
@@ -41845,6 +42867,17 @@ function createLatestWinsDelivery(send, onError) {
|
|
|
41845
42867
|
};
|
|
41846
42868
|
}
|
|
41847
42869
|
async function sseSessionStream(db, bus, workspaceId, sessionId, after, signal, options = {}) {
|
|
42870
|
+
if (isHttp1BrowserBatch(options)) {
|
|
42871
|
+
const events = await listSessionEvents(db, workspaceId, sessionId, {
|
|
42872
|
+
after,
|
|
42873
|
+
limit: SESSION_REPLAY_PAGE_SIZE
|
|
42874
|
+
});
|
|
42875
|
+
await options.reauthorize?.();
|
|
42876
|
+
return finiteSseBatchResponse(
|
|
42877
|
+
coalesceSessionEventDeltas(events).map(formatSessionEventSse),
|
|
42878
|
+
options
|
|
42879
|
+
);
|
|
42880
|
+
}
|
|
41848
42881
|
const durableFanout = requireSessionEventDurableFanoutCapability(bus);
|
|
41849
42882
|
const heartbeatIntervalMs = resolveHeartbeatInterval(options.heartbeatIntervalMs);
|
|
41850
42883
|
let lastSent = after;
|
|
@@ -41878,6 +42911,7 @@ async function sseSessionStream(db, bus, workspaceId, sessionId, after, signal,
|
|
|
41878
42911
|
release?.();
|
|
41879
42912
|
};
|
|
41880
42913
|
const channel = createByteBoundedSseStream({
|
|
42914
|
+
connectionLifetimeMs: options.connectionLifetimeMs,
|
|
41881
42915
|
maxQueuedBytes: options.maxQueuedBytes ?? SESSION_EVENT_SSE_FRAME_MAX_BYTES,
|
|
41882
42916
|
...options.stallTimeoutMs === void 0 ? {} : { stallTimeoutMs: options.stallTimeoutMs },
|
|
41883
42917
|
onObservation: sseObservationReporter("session", options),
|
|
@@ -41887,11 +42921,11 @@ async function sseSessionStream(db, bus, workspaceId, sessionId, after, signal,
|
|
|
41887
42921
|
const fail = (error) => {
|
|
41888
42922
|
channel.fail(retryableSseFailure("session event stream delivery failed", error));
|
|
41889
42923
|
};
|
|
41890
|
-
stopReauthorization = startSseReauthorization(options, channel.stopped,
|
|
42924
|
+
stopReauthorization = startSseReauthorization(options, channel.stopped, channel.close);
|
|
41891
42925
|
let writeTail = Promise.resolve();
|
|
41892
42926
|
const writeFrame = (frame) => {
|
|
41893
42927
|
const write = writeTail.then(async () => {
|
|
41894
|
-
await options
|
|
42928
|
+
await reauthorizeSseOrClose(options, channel);
|
|
41895
42929
|
if (!await channel.write(frame)) throw new SseStreamStoppedError();
|
|
41896
42930
|
});
|
|
41897
42931
|
writeTail = write.catch(() => {
|
|
@@ -42010,14 +43044,7 @@ async function sseSessionStream(db, bus, workspaceId, sessionId, after, signal,
|
|
|
42010
43044
|
signal.addEventListener("abort", abort, { once: true });
|
|
42011
43045
|
detachAbortListener = () => signal.removeEventListener("abort", abort);
|
|
42012
43046
|
}
|
|
42013
|
-
return
|
|
42014
|
-
headers: {
|
|
42015
|
-
"Content-Type": "text/event-stream; charset=utf-8",
|
|
42016
|
-
"Cache-Control": "no-cache, no-transform",
|
|
42017
|
-
Connection: "keep-alive",
|
|
42018
|
-
...options.actorEpoch ? { [MANAGED_AUTH_ACTOR_EPOCH_HEADER]: options.actorEpoch } : {}
|
|
42019
|
-
}
|
|
42020
|
-
});
|
|
43047
|
+
return await sseHttpResponse(channel.stream, options);
|
|
42021
43048
|
}
|
|
42022
43049
|
async function replaySessionEvents(loadPage, send, after, pageSize = SESSION_REPLAY_PAGE_SIZE) {
|
|
42023
43050
|
let cursor = after;
|
|
@@ -42043,6 +43070,19 @@ async function replaySessionEvents(loadPage, send, after, pageSize = SESSION_REP
|
|
|
42043
43070
|
}
|
|
42044
43071
|
}
|
|
42045
43072
|
async function sseWorkspaceControlStream(db, bus, workspaceId, after, signal, options = {}) {
|
|
43073
|
+
if (isHttp1BrowserBatch(options)) {
|
|
43074
|
+
const events = await listWorkspaceControlEvents(
|
|
43075
|
+
db,
|
|
43076
|
+
workspaceId,
|
|
43077
|
+
after,
|
|
43078
|
+
WORKSPACE_CONTROL_REPLAY_PAGE_SIZE
|
|
43079
|
+
);
|
|
43080
|
+
await options.reauthorize?.();
|
|
43081
|
+
return finiteSseBatchResponse(
|
|
43082
|
+
events.sort((left, right) => left.sequence - right.sequence).map(formatWorkspaceControlEventSse),
|
|
43083
|
+
options
|
|
43084
|
+
);
|
|
43085
|
+
}
|
|
42046
43086
|
const heartbeatIntervalMs = resolveHeartbeatInterval(options.heartbeatIntervalMs);
|
|
42047
43087
|
let lastSent = after;
|
|
42048
43088
|
let bootstrapping = true;
|
|
@@ -42070,6 +43110,7 @@ async function sseWorkspaceControlStream(db, bus, workspaceId, after, signal, op
|
|
|
42070
43110
|
release?.();
|
|
42071
43111
|
};
|
|
42072
43112
|
const channel = createByteBoundedSseStream({
|
|
43113
|
+
connectionLifetimeMs: options.connectionLifetimeMs,
|
|
42073
43114
|
maxQueuedBytes: options.maxQueuedBytes ?? SESSION_EVENT_SSE_FRAME_MAX_BYTES,
|
|
42074
43115
|
...options.stallTimeoutMs === void 0 ? {} : { stallTimeoutMs: options.stallTimeoutMs },
|
|
42075
43116
|
onObservation: sseObservationReporter("workspace_control", options),
|
|
@@ -42079,11 +43120,11 @@ async function sseWorkspaceControlStream(db, bus, workspaceId, after, signal, op
|
|
|
42079
43120
|
const fail = (error) => {
|
|
42080
43121
|
channel.fail(retryableSseFailure("workspace control stream delivery failed", error));
|
|
42081
43122
|
};
|
|
42082
|
-
stopReauthorization = startSseReauthorization(options, channel.stopped,
|
|
43123
|
+
stopReauthorization = startSseReauthorization(options, channel.stopped, channel.close);
|
|
42083
43124
|
let writeTail = Promise.resolve();
|
|
42084
43125
|
const writeFrame = (frame) => {
|
|
42085
43126
|
const write = writeTail.then(async () => {
|
|
42086
|
-
await options
|
|
43127
|
+
await reauthorizeSseOrClose(options, channel);
|
|
42087
43128
|
if (!await channel.write(frame)) throw new SseStreamStoppedError();
|
|
42088
43129
|
});
|
|
42089
43130
|
writeTail = write.catch(() => {
|
|
@@ -42168,20 +43209,39 @@ async function sseWorkspaceControlStream(db, bus, workspaceId, after, signal, op
|
|
|
42168
43209
|
signal.addEventListener("abort", abort, { once: true });
|
|
42169
43210
|
detachAbortListener = () => signal.removeEventListener("abort", abort);
|
|
42170
43211
|
}
|
|
42171
|
-
return
|
|
42172
|
-
headers: {
|
|
42173
|
-
"Content-Type": "text/event-stream; charset=utf-8",
|
|
42174
|
-
"Cache-Control": "no-cache, no-transform",
|
|
42175
|
-
Connection: "keep-alive",
|
|
42176
|
-
...options.actorEpoch ? { [MANAGED_AUTH_ACTOR_EPOCH_HEADER]: options.actorEpoch } : {}
|
|
42177
|
-
}
|
|
42178
|
-
});
|
|
43212
|
+
return await sseHttpResponse(channel.stream, options);
|
|
42179
43213
|
}
|
|
42180
43214
|
async function sseWorkspaceLiveStream(db, bus, accountId, workspaceId, controlAfter, interactionAfter, signal, options = {}) {
|
|
43215
|
+
if (isHttp1BrowserBatch(options)) {
|
|
43216
|
+
const [controlEvents, interactionState] = await Promise.all([
|
|
43217
|
+
listWorkspaceControlEvents(db, workspaceId, controlAfter, WORKSPACE_CONTROL_REPLAY_PAGE_SIZE),
|
|
43218
|
+
getWorkspaceInteractionRevisionState(db, { accountId, workspaceId })
|
|
43219
|
+
]);
|
|
43220
|
+
await options.reauthorize?.();
|
|
43221
|
+
const frames = [];
|
|
43222
|
+
if (interactionState.revision > interactionAfter) {
|
|
43223
|
+
frames.push(
|
|
43224
|
+
formatWorkspaceInteractionRevisionSse(
|
|
43225
|
+
WorkspaceInteractionRevisionEvent.parse({
|
|
43226
|
+
workspaceId,
|
|
43227
|
+
sequence: interactionState.revision,
|
|
43228
|
+
revision: interactionState.revision,
|
|
43229
|
+
type: "workspace.interaction.changed",
|
|
43230
|
+
occurredAt: (interactionState.updatedAt ?? /* @__PURE__ */ new Date()).toISOString()
|
|
43231
|
+
})
|
|
43232
|
+
)
|
|
43233
|
+
);
|
|
43234
|
+
}
|
|
43235
|
+
frames.push(
|
|
43236
|
+
...controlEvents.sort((left, right) => left.sequence - right.sequence).map(formatWorkspaceControlEventSse)
|
|
43237
|
+
);
|
|
43238
|
+
return finiteSseBatchResponse(frames, options);
|
|
43239
|
+
}
|
|
42181
43240
|
const upstream = new AbortController();
|
|
42182
43241
|
let stopReauthorization = () => {
|
|
42183
43242
|
};
|
|
42184
43243
|
const channel = createByteBoundedSseStream({
|
|
43244
|
+
connectionLifetimeMs: options.connectionLifetimeMs,
|
|
42185
43245
|
maxQueuedBytes: options.maxQueuedBytes ?? SESSION_EVENT_SSE_FRAME_MAX_BYTES,
|
|
42186
43246
|
...options.stallTimeoutMs === void 0 ? {} : { stallTimeoutMs: options.stallTimeoutMs },
|
|
42187
43247
|
onObservation: sseObservationReporter("workspace_interaction", options),
|
|
@@ -42197,18 +43257,19 @@ async function sseWorkspaceLiveStream(db, bus, accountId, workspaceId, controlAf
|
|
|
42197
43257
|
};
|
|
42198
43258
|
if (signal.aborted) abort();
|
|
42199
43259
|
else signal.addEventListener("abort", abort, { once: true });
|
|
42200
|
-
stopReauthorization = startSseReauthorization(options, channel.stopped,
|
|
42201
|
-
channel.fail(retryableSseFailure("workspace live stream authorization failed", error));
|
|
42202
|
-
});
|
|
43260
|
+
stopReauthorization = startSseReauthorization(options, channel.stopped, channel.close);
|
|
42203
43261
|
const upstreamOptions = {
|
|
42204
43262
|
...options,
|
|
43263
|
+
connectionLifetimeMs: void 0,
|
|
43264
|
+
finiteResponseMaxBytes: void 0,
|
|
43265
|
+
finiteResponseMediaType: void 0,
|
|
42205
43266
|
reauthorize: void 0,
|
|
42206
43267
|
reauthorizeAfterMs: void 0
|
|
42207
43268
|
};
|
|
42208
43269
|
let writeTail = Promise.resolve(true);
|
|
42209
43270
|
const write = (frame) => {
|
|
42210
43271
|
const pending = writeTail.then(async () => {
|
|
42211
|
-
await options
|
|
43272
|
+
await reauthorizeSseOrClose(options, channel);
|
|
42212
43273
|
return await channel.write(frame);
|
|
42213
43274
|
});
|
|
42214
43275
|
writeTail = pending.catch(() => false);
|
|
@@ -42269,16 +43330,28 @@ async function sseWorkspaceLiveStream(db, bus, accountId, workspaceId, controlAf
|
|
|
42269
43330
|
if (!upstream.signal.aborted && !channel.stopped()) channel.fail(error);
|
|
42270
43331
|
}
|
|
42271
43332
|
})();
|
|
42272
|
-
return
|
|
42273
|
-
headers: {
|
|
42274
|
-
"Content-Type": "text/event-stream; charset=utf-8",
|
|
42275
|
-
"Cache-Control": "no-cache, no-transform",
|
|
42276
|
-
Connection: "keep-alive",
|
|
42277
|
-
...options.actorEpoch ? { [MANAGED_AUTH_ACTOR_EPOCH_HEADER]: options.actorEpoch } : {}
|
|
42278
|
-
}
|
|
42279
|
-
});
|
|
43333
|
+
return await sseHttpResponse(channel.stream, options);
|
|
42280
43334
|
}
|
|
42281
43335
|
async function sseWorkspaceInteractionRevisionStream(db, accountId, workspaceId, after, signal, options = {}) {
|
|
43336
|
+
if (isHttp1BrowserBatch(options)) {
|
|
43337
|
+
const state = await getWorkspaceInteractionRevisionState(db, {
|
|
43338
|
+
accountId,
|
|
43339
|
+
workspaceId
|
|
43340
|
+
});
|
|
43341
|
+
await options.reauthorize?.();
|
|
43342
|
+
const frames = state.revision > after ? [
|
|
43343
|
+
formatWorkspaceInteractionRevisionSse(
|
|
43344
|
+
WorkspaceInteractionRevisionEvent.parse({
|
|
43345
|
+
workspaceId,
|
|
43346
|
+
sequence: state.revision,
|
|
43347
|
+
revision: state.revision,
|
|
43348
|
+
type: "workspace.interaction.changed",
|
|
43349
|
+
occurredAt: (state.updatedAt ?? /* @__PURE__ */ new Date()).toISOString()
|
|
43350
|
+
})
|
|
43351
|
+
)
|
|
43352
|
+
] : [];
|
|
43353
|
+
return finiteSseBatchResponse(frames, options);
|
|
43354
|
+
}
|
|
42282
43355
|
const heartbeatIntervalMs = resolveHeartbeatInterval(options.heartbeatIntervalMs);
|
|
42283
43356
|
const pollIntervalMs = resolveInteractionPollInterval(options.pollIntervalMs);
|
|
42284
43357
|
let lastSent = after;
|
|
@@ -42291,6 +43364,7 @@ async function sseWorkspaceInteractionRevisionStream(db, accountId, workspaceId,
|
|
|
42291
43364
|
let closeMetrics = () => {
|
|
42292
43365
|
};
|
|
42293
43366
|
const channel = createByteBoundedSseStream({
|
|
43367
|
+
connectionLifetimeMs: options.connectionLifetimeMs,
|
|
42294
43368
|
maxQueuedBytes: options.maxQueuedBytes ?? SESSION_EVENT_SSE_FRAME_MAX_BYTES,
|
|
42295
43369
|
...options.stallTimeoutMs === void 0 ? {} : { stallTimeoutMs: options.stallTimeoutMs },
|
|
42296
43370
|
onObservation: sseObservationReporter("workspace_interaction", options),
|
|
@@ -42302,11 +43376,9 @@ async function sseWorkspaceInteractionRevisionStream(db, accountId, workspaceId,
|
|
|
42302
43376
|
}
|
|
42303
43377
|
});
|
|
42304
43378
|
closeMetrics = observeSseConnection("workspace_interaction", after, options.observability);
|
|
42305
|
-
stopReauthorization = startSseReauthorization(options, channel.stopped,
|
|
42306
|
-
channel.fail(retryableSseFailure("workspace interaction stream authorization failed", error));
|
|
42307
|
-
});
|
|
43379
|
+
stopReauthorization = startSseReauthorization(options, channel.stopped, channel.close);
|
|
42308
43380
|
const write = async (frame) => {
|
|
42309
|
-
await options
|
|
43381
|
+
await reauthorizeSseOrClose(options, channel);
|
|
42310
43382
|
const accepted = await channel.write(frame);
|
|
42311
43383
|
if (accepted) lastWriteAt = Date.now();
|
|
42312
43384
|
return accepted;
|
|
@@ -42316,7 +43388,10 @@ async function sseWorkspaceInteractionRevisionStream(db, accountId, workspaceId,
|
|
|
42316
43388
|
if (!await write(": connected\n\n")) return;
|
|
42317
43389
|
for (; ; ) {
|
|
42318
43390
|
if (stopRequested || signal.aborted || channel.stopped()) return;
|
|
42319
|
-
const state = await getWorkspaceInteractionRevisionState(db, {
|
|
43391
|
+
const state = await getWorkspaceInteractionRevisionState(db, {
|
|
43392
|
+
accountId,
|
|
43393
|
+
workspaceId
|
|
43394
|
+
});
|
|
42320
43395
|
if (state.revision > lastSent) {
|
|
42321
43396
|
const event = WorkspaceInteractionRevisionEvent.parse({
|
|
42322
43397
|
workspaceId,
|
|
@@ -42344,14 +43419,7 @@ async function sseWorkspaceInteractionRevisionStream(db, accountId, workspaceId,
|
|
|
42344
43419
|
signal.addEventListener("abort", abort, { once: true });
|
|
42345
43420
|
detachAbortListener = () => signal.removeEventListener("abort", abort);
|
|
42346
43421
|
}
|
|
42347
|
-
return
|
|
42348
|
-
headers: {
|
|
42349
|
-
"Content-Type": "text/event-stream; charset=utf-8",
|
|
42350
|
-
"Cache-Control": "no-cache, no-transform",
|
|
42351
|
-
Connection: "keep-alive",
|
|
42352
|
-
...options.actorEpoch ? { [MANAGED_AUTH_ACTOR_EPOCH_HEADER]: options.actorEpoch } : {}
|
|
42353
|
-
}
|
|
42354
|
-
});
|
|
43422
|
+
return await sseHttpResponse(channel.stream, options);
|
|
42355
43423
|
}
|
|
42356
43424
|
function observeSseConnection(stream, after, observability) {
|
|
42357
43425
|
activeSseStreams[stream] += 1;
|
|
@@ -42400,6 +43468,117 @@ async function replayWorkspaceControlEvents(loadPage, send, after, pageSize) {
|
|
|
42400
43468
|
}
|
|
42401
43469
|
var SseStreamStoppedError = class extends Error {
|
|
42402
43470
|
};
|
|
43471
|
+
async function reauthorizeSseOrClose(options, channel) {
|
|
43472
|
+
try {
|
|
43473
|
+
await options.reauthorize?.();
|
|
43474
|
+
} catch {
|
|
43475
|
+
channel.close();
|
|
43476
|
+
throw new SseStreamStoppedError();
|
|
43477
|
+
}
|
|
43478
|
+
}
|
|
43479
|
+
function browserSseDeliveryOptions(transport) {
|
|
43480
|
+
return transport === "http1-bounded" ? {
|
|
43481
|
+
finiteResponseMaxBytes: HTTP1_BROWSER_SSE_BATCH_MAX_BYTES,
|
|
43482
|
+
finiteResponseMediaType: "http1-browser-batch"
|
|
43483
|
+
} : {};
|
|
43484
|
+
}
|
|
43485
|
+
async function sseHttpResponse(stream, options) {
|
|
43486
|
+
const maxBytes = options.finiteResponseMaxBytes;
|
|
43487
|
+
let body4 = stream;
|
|
43488
|
+
let contentLength = null;
|
|
43489
|
+
if (maxBytes !== void 0) {
|
|
43490
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < SESSION_EVENT_SSE_FRAME_MAX_BYTES || maxBytes > HTTP1_BROWSER_SSE_BATCH_MAX_BYTES) {
|
|
43491
|
+
throw new RangeError(
|
|
43492
|
+
`finite SSE batch limit must be between ${SESSION_EVENT_SSE_FRAME_MAX_BYTES} and ${HTTP1_BROWSER_SSE_BATCH_MAX_BYTES} bytes`
|
|
43493
|
+
);
|
|
43494
|
+
}
|
|
43495
|
+
body4 = await collectFiniteSseBatch(stream, maxBytes);
|
|
43496
|
+
contentLength = body4.byteLength;
|
|
43497
|
+
}
|
|
43498
|
+
return new Response(body4, {
|
|
43499
|
+
headers: {
|
|
43500
|
+
// A bounded HTTP/1 poll carries the same SSE-framed bytes the SDK
|
|
43501
|
+
// already parses, but it is an ordinary finite response at the browser
|
|
43502
|
+
// transport boundary. Keeping `text/event-stream` here lets Chromium
|
|
43503
|
+
// retain an orphaned fetch in its shared per-origin SSE pool after the
|
|
43504
|
+
// initiating document is replaced, starving unrelated finite reads in
|
|
43505
|
+
// every tab. HTTP/2 and other unbounded streams keep the standard media
|
|
43506
|
+
// type; only the explicit `http1-bounded` fallback uses this vendor type.
|
|
43507
|
+
"Content-Type": contentLength !== null && options.finiteResponseMediaType === "http1-browser-batch" ? `${HTTP1_BROWSER_SSE_BATCH_CONTENT_TYPE}; charset=utf-8` : "text/event-stream; charset=utf-8",
|
|
43508
|
+
"Cache-Control": "no-cache, no-transform",
|
|
43509
|
+
// A known-length response is already terminal and leaves the HTTP/1
|
|
43510
|
+
// socket reusable. Only a genuinely live SSE response needs an explicit
|
|
43511
|
+
// close when its stream ends.
|
|
43512
|
+
...contentLength === null ? { Connection: "close" } : { "Content-Length": String(contentLength) },
|
|
43513
|
+
...options.actorEpoch ? { [MANAGED_AUTH_ACTOR_EPOCH_HEADER]: options.actorEpoch } : {}
|
|
43514
|
+
}
|
|
43515
|
+
});
|
|
43516
|
+
}
|
|
43517
|
+
function isHttp1BrowserBatch(options) {
|
|
43518
|
+
return options.finiteResponseMediaType === "http1-browser-batch" && options.finiteResponseMaxBytes !== void 0;
|
|
43519
|
+
}
|
|
43520
|
+
function finiteSseBatchResponse(frames, options) {
|
|
43521
|
+
const maxBytes = options.finiteResponseMaxBytes;
|
|
43522
|
+
if (maxBytes === void 0 || !Number.isSafeInteger(maxBytes) || maxBytes < SESSION_EVENT_SSE_FRAME_MAX_BYTES || maxBytes > HTTP1_BROWSER_SSE_BATCH_MAX_BYTES) {
|
|
43523
|
+
throw new RangeError(
|
|
43524
|
+
`finite SSE batch limit must be between ${SESSION_EVENT_SSE_FRAME_MAX_BYTES} and ${HTTP1_BROWSER_SSE_BATCH_MAX_BYTES} bytes`
|
|
43525
|
+
);
|
|
43526
|
+
}
|
|
43527
|
+
const encoder4 = new TextEncoder();
|
|
43528
|
+
const chunks = [];
|
|
43529
|
+
let length = 0;
|
|
43530
|
+
for (const frame of frames) {
|
|
43531
|
+
const chunk = encoder4.encode(frame);
|
|
43532
|
+
if (chunk.byteLength > maxBytes) {
|
|
43533
|
+
throw new RangeError(
|
|
43534
|
+
`SSE frame cannot fit in the finite browser batch (${chunk.byteLength} > ${maxBytes} bytes)`
|
|
43535
|
+
);
|
|
43536
|
+
}
|
|
43537
|
+
if (length + chunk.byteLength > maxBytes) break;
|
|
43538
|
+
chunks.push(chunk);
|
|
43539
|
+
length += chunk.byteLength;
|
|
43540
|
+
}
|
|
43541
|
+
const body4 = new Uint8Array(length);
|
|
43542
|
+
let offset = 0;
|
|
43543
|
+
for (const chunk of chunks) {
|
|
43544
|
+
body4.set(chunk, offset);
|
|
43545
|
+
offset += chunk.byteLength;
|
|
43546
|
+
}
|
|
43547
|
+
return new Response(body4, {
|
|
43548
|
+
headers: {
|
|
43549
|
+
"Content-Type": `${HTTP1_BROWSER_SSE_BATCH_CONTENT_TYPE}; charset=utf-8`,
|
|
43550
|
+
"Cache-Control": "no-cache, no-transform",
|
|
43551
|
+
"Content-Length": String(body4.byteLength),
|
|
43552
|
+
...options.actorEpoch ? { [MANAGED_AUTH_ACTOR_EPOCH_HEADER]: options.actorEpoch } : {}
|
|
43553
|
+
}
|
|
43554
|
+
});
|
|
43555
|
+
}
|
|
43556
|
+
async function collectFiniteSseBatch(stream, maxBytes) {
|
|
43557
|
+
const reader = stream.getReader();
|
|
43558
|
+
const chunks = [];
|
|
43559
|
+
let length = 0;
|
|
43560
|
+
try {
|
|
43561
|
+
for (; ; ) {
|
|
43562
|
+
const next = await reader.read();
|
|
43563
|
+
if (next.done) break;
|
|
43564
|
+
if (length + next.value.byteLength > maxBytes) {
|
|
43565
|
+
await reader.cancel("finite SSE batch reached its byte limit");
|
|
43566
|
+
break;
|
|
43567
|
+
}
|
|
43568
|
+
chunks.push(next.value);
|
|
43569
|
+
length += next.value.byteLength;
|
|
43570
|
+
}
|
|
43571
|
+
} finally {
|
|
43572
|
+
reader.releaseLock();
|
|
43573
|
+
}
|
|
43574
|
+
const bytes = new Uint8Array(length);
|
|
43575
|
+
let offset = 0;
|
|
43576
|
+
for (const chunk of chunks) {
|
|
43577
|
+
bytes.set(chunk, offset);
|
|
43578
|
+
offset += chunk.byteLength;
|
|
43579
|
+
}
|
|
43580
|
+
return bytes.buffer;
|
|
43581
|
+
}
|
|
42403
43582
|
function sseObservationReporter(stream, options) {
|
|
42404
43583
|
return (observation) => {
|
|
42405
43584
|
options.onObservation?.(observation);
|
|
@@ -42492,6 +43671,7 @@ function registerInteractionResourceRoutes(app, deps) {
|
|
|
42492
43671
|
nonnegativeSafeIntegerQuery(context, "interactionAfter", 0),
|
|
42493
43672
|
context.req.raw.signal,
|
|
42494
43673
|
{
|
|
43674
|
+
...browserSseDeliveryOptions(context.req.query("transport")),
|
|
42495
43675
|
observability: deps.observability,
|
|
42496
43676
|
actorEpoch: getManagedAuthRequestActorEpoch(context.req.raw) ?? void 0,
|
|
42497
43677
|
reauthorize: async () => {
|
|
@@ -42516,6 +43696,7 @@ function registerInteractionResourceRoutes(app, deps) {
|
|
|
42516
43696
|
after,
|
|
42517
43697
|
context.req.raw.signal,
|
|
42518
43698
|
{
|
|
43699
|
+
...browserSseDeliveryOptions(context.req.query("transport")),
|
|
42519
43700
|
observability: deps.observability,
|
|
42520
43701
|
actorEpoch: getManagedAuthRequestActorEpoch(context.req.raw) ?? void 0,
|
|
42521
43702
|
reauthorize: async () => {
|
|
@@ -43002,7 +44183,7 @@ import {
|
|
|
43002
44183
|
getPackInstallation as getPackInstallation2,
|
|
43003
44184
|
listPrReviewAppRegistrations as listPrReviewAppRegistrations2,
|
|
43004
44185
|
listPrReviewRepositoryBindings as listPrReviewRepositoryBindings2,
|
|
43005
|
-
nestedPostgresSqlState as
|
|
44186
|
+
nestedPostgresSqlState as nestedPostgresSqlState3,
|
|
43006
44187
|
recordAuditEvent as recordAuditEvent9,
|
|
43007
44188
|
updatePrReviewAppRegistration,
|
|
43008
44189
|
updatePrReviewRepositoryBinding
|
|
@@ -43220,7 +44401,7 @@ import {
|
|
|
43220
44401
|
getPackInstallation,
|
|
43221
44402
|
listPrReviewAppRegistrations,
|
|
43222
44403
|
listPrReviewRepositoryBindings,
|
|
43223
|
-
nestedPostgresSqlState,
|
|
44404
|
+
nestedPostgresSqlState as nestedPostgresSqlState2,
|
|
43224
44405
|
PrReviewDispatchAuthorityError,
|
|
43225
44406
|
recordAuditEvent as recordAuditEvent8,
|
|
43226
44407
|
resolveManagedGitHubPrReviewRoute,
|
|
@@ -44071,7 +45252,7 @@ function registerPrReviewGitHubRoutes(app, deps) {
|
|
|
44071
45252
|
if (error instanceof PrReviewDispatchAuthorityError) {
|
|
44072
45253
|
throw new HTTPException44(409, { message: error.message });
|
|
44073
45254
|
}
|
|
44074
|
-
if (
|
|
45255
|
+
if (nestedPostgresSqlState2(error) === "23505") {
|
|
44075
45256
|
throw new HTTPException44(409, {
|
|
44076
45257
|
message: "One of these repositories is already connected to OpenGeni Lens in another workspace"
|
|
44077
45258
|
});
|
|
@@ -44777,7 +45958,7 @@ async function mapPrReviewUniqueConflict(operation, message) {
|
|
|
44777
45958
|
try {
|
|
44778
45959
|
return await operation;
|
|
44779
45960
|
} catch (error) {
|
|
44780
|
-
if (
|
|
45961
|
+
if (nestedPostgresSqlState3(error) === "23505") {
|
|
44781
45962
|
throw new HTTPException45(409, { message });
|
|
44782
45963
|
}
|
|
44783
45964
|
throw error;
|
|
@@ -44869,7 +46050,7 @@ function registerPackRoutes(app, deps) {
|
|
|
44869
46050
|
});
|
|
44870
46051
|
app.post("/v1/workspaces/:workspaceId/packs", async (c) => {
|
|
44871
46052
|
const workspaceId = c.req.param("workspaceId");
|
|
44872
|
-
const grant = await requireAccessGrant24(c, deps, workspaceId, "
|
|
46053
|
+
const grant = await requireAccessGrant24(c, deps, workspaceId, "capabilities:manage");
|
|
44873
46054
|
const manifest = RegisterCapabilityPackRequest.parse(await c.req.json());
|
|
44874
46055
|
if (isBuiltInCapabilityPack(manifest.id)) {
|
|
44875
46056
|
throw new HTTPException46(409, {
|
|
@@ -44885,7 +46066,7 @@ function registerPackRoutes(app, deps) {
|
|
|
44885
46066
|
});
|
|
44886
46067
|
app.delete("/v1/workspaces/:workspaceId/packs/:packId", async (c) => {
|
|
44887
46068
|
const workspaceId = c.req.param("workspaceId");
|
|
44888
|
-
await requireAccessGrant24(c, deps, workspaceId, "
|
|
46069
|
+
await requireAccessGrant24(c, deps, workspaceId, "capabilities:manage");
|
|
44889
46070
|
const packId = c.req.param("packId");
|
|
44890
46071
|
if (isBuiltInCapabilityPack(packId)) {
|
|
44891
46072
|
throw new HTTPException46(409, {
|
|
@@ -44945,7 +46126,7 @@ function registerPackRoutes(app, deps) {
|
|
|
44945
46126
|
});
|
|
44946
46127
|
app.post("/v1/workspaces/:workspaceId/packs/:packId/install", async (c) => {
|
|
44947
46128
|
const workspaceId = c.req.param("workspaceId");
|
|
44948
|
-
const grant = await requireAccessGrant24(c, deps, workspaceId, "
|
|
46129
|
+
const grant = await requireAccessGrant24(c, deps, workspaceId, "capabilities:manage");
|
|
44949
46130
|
const pack = await requirePack(db, workspaceId, c.req.param("packId"));
|
|
44950
46131
|
const payload = InstallPackRequest.parse(await c.req.json());
|
|
44951
46132
|
const preview = PackInstallationPreview.parse(
|
|
@@ -45154,7 +46335,7 @@ function registerPackRoutes(app, deps) {
|
|
|
45154
46335
|
});
|
|
45155
46336
|
app.delete("/v1/workspaces/:workspaceId/packs/:packId/installation", async (c) => {
|
|
45156
46337
|
const workspaceId = c.req.param("workspaceId");
|
|
45157
|
-
const grant = await requireAccessGrant24(c, deps, workspaceId, "
|
|
46338
|
+
const grant = await requireAccessGrant24(c, deps, workspaceId, "capabilities:manage");
|
|
45158
46339
|
const pack = await requirePack(db, workspaceId, c.req.param("packId"));
|
|
45159
46340
|
const payload = UninstallPackRequest.parse(await c.req.json());
|
|
45160
46341
|
const requestDigest = sha2562(
|
|
@@ -45240,7 +46421,7 @@ function registerPackRoutes(app, deps) {
|
|
|
45240
46421
|
});
|
|
45241
46422
|
app.post("/v1/workspaces/:workspaceId/packs/:packId/enable", async (c) => {
|
|
45242
46423
|
const workspaceId = c.req.param("workspaceId");
|
|
45243
|
-
const grant = await requireAccessGrant24(c, deps, workspaceId, "
|
|
46424
|
+
const grant = await requireAccessGrant24(c, deps, workspaceId, "capabilities:manage");
|
|
45244
46425
|
const pack = await requirePack(db, workspaceId, c.req.param("packId"));
|
|
45245
46426
|
if (capabilityPackRequiresInstallationPlan(pack)) {
|
|
45246
46427
|
throw new HTTPException46(409, {
|
|
@@ -45640,7 +46821,7 @@ function registerPluginRoutes(app, deps, overrides = {}) {
|
|
|
45640
46821
|
});
|
|
45641
46822
|
app.post("/v1/workspaces/:workspaceId/plugins/install", async (c) => {
|
|
45642
46823
|
const workspaceId = c.req.param("workspaceId");
|
|
45643
|
-
const grant = await requireAccessGrant25(c, deps, workspaceId, "
|
|
46824
|
+
const grant = await requireAccessGrant25(c, deps, workspaceId, "capabilities:manage");
|
|
45644
46825
|
const payload = InstallPluginRequest.parse(await c.req.json());
|
|
45645
46826
|
const resolved = await resolvePluginPackage({
|
|
45646
46827
|
deps,
|
|
@@ -45766,7 +46947,7 @@ function registerPluginRoutes(app, deps, overrides = {}) {
|
|
|
45766
46947
|
});
|
|
45767
46948
|
app.delete("/v1/workspaces/:workspaceId/plugins/:pluginKey", async (c) => {
|
|
45768
46949
|
const workspaceId = c.req.param("workspaceId");
|
|
45769
|
-
const grant = await requireAccessGrant25(c, deps, workspaceId, "
|
|
46950
|
+
const grant = await requireAccessGrant25(c, deps, workspaceId, "capabilities:manage");
|
|
45770
46951
|
const pluginKey = decodeURIComponent(c.req.param("pluginKey"));
|
|
45771
46952
|
const payload = UninstallPluginRequest.parse(await c.req.json());
|
|
45772
46953
|
try {
|
|
@@ -46222,7 +47403,7 @@ function registerSkillRoutes(app, deps, overrides = {}) {
|
|
|
46222
47403
|
});
|
|
46223
47404
|
app.post("/v1/workspaces/:workspaceId/skills/library/:libraryId/install", async (c) => {
|
|
46224
47405
|
const workspaceId = c.req.param("workspaceId");
|
|
46225
|
-
const grant = await requireAccessGrant26(c, deps, workspaceId, "
|
|
47406
|
+
const grant = await requireAccessGrant26(c, deps, workspaceId, "capabilities:manage");
|
|
46226
47407
|
const libraryId = decodeURIComponent(c.req.param("libraryId"));
|
|
46227
47408
|
const payload = InstallLibrarySkillRequest.parse(await c.req.json());
|
|
46228
47409
|
let loaded;
|
|
@@ -46298,7 +47479,7 @@ function registerSkillRoutes(app, deps, overrides = {}) {
|
|
|
46298
47479
|
});
|
|
46299
47480
|
app.post("/v1/workspaces/:workspaceId/skills/install", async (c) => {
|
|
46300
47481
|
const workspaceId = c.req.param("workspaceId");
|
|
46301
|
-
const grant = await requireAccessGrant26(c, deps, workspaceId, "
|
|
47482
|
+
const grant = await requireAccessGrant26(c, deps, workspaceId, "capabilities:manage");
|
|
46302
47483
|
const payload = InstallSkillRequest.parse(await c.req.json());
|
|
46303
47484
|
const resolved = await resolveForRoute2(payload.url, github);
|
|
46304
47485
|
if (resolved.preview.sourceCommit !== payload.expectedSourceCommit || resolved.preview.contentSha256 !== payload.expectedContentSha256) {
|
|
@@ -46358,7 +47539,7 @@ function registerSkillRoutes(app, deps, overrides = {}) {
|
|
|
46358
47539
|
});
|
|
46359
47540
|
app.delete("/v1/workspaces/:workspaceId/skills/:capabilityId", async (c) => {
|
|
46360
47541
|
const workspaceId = c.req.param("workspaceId");
|
|
46361
|
-
const grant = await requireAccessGrant26(c, deps, workspaceId, "
|
|
47542
|
+
const grant = await requireAccessGrant26(c, deps, workspaceId, "capabilities:manage");
|
|
46362
47543
|
const capabilityId = decodeURIComponent(c.req.param("capabilityId"));
|
|
46363
47544
|
const payload = UninstallSkillRequest.parse(await c.req.json());
|
|
46364
47545
|
try {
|
|
@@ -46512,7 +47693,7 @@ import {
|
|
|
46512
47693
|
import { HTTPException as HTTPException50 } from "hono/http-exception";
|
|
46513
47694
|
import {
|
|
46514
47695
|
requireAccessGrant as requireAccessGrant28,
|
|
46515
|
-
requireAccessGrantAuthorization as
|
|
47696
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization9,
|
|
46516
47697
|
requirePermission as requirePermission7
|
|
46517
47698
|
} from "@opengeni/core";
|
|
46518
47699
|
import {
|
|
@@ -46553,7 +47734,7 @@ async function parseRigRequest(c, schema, label) {
|
|
|
46553
47734
|
function registerRigRoutes(app, deps) {
|
|
46554
47735
|
const { db, workflowClient } = deps;
|
|
46555
47736
|
async function requireRigMutation(c, workspaceId, permission) {
|
|
46556
|
-
const authorization = await
|
|
47737
|
+
const authorization = await requireAccessGrantAuthorization9(c, deps, workspaceId, permission);
|
|
46557
47738
|
const rigId = c.req.param("rigId");
|
|
46558
47739
|
if (!rigId) {
|
|
46559
47740
|
throw new HTTPException50(400, { message: "rig id is required" });
|
|
@@ -46631,7 +47812,7 @@ function registerRigRoutes(app, deps) {
|
|
|
46631
47812
|
});
|
|
46632
47813
|
app.post("/v1/workspaces/:workspaceId/rigs", async (c) => {
|
|
46633
47814
|
const workspaceId = c.req.param("workspaceId");
|
|
46634
|
-
const authorization = await
|
|
47815
|
+
const authorization = await requireAccessGrantAuthorization9(c, deps, workspaceId);
|
|
46635
47816
|
const grant = authorization.grant;
|
|
46636
47817
|
requirePermission7(grant, "rigs:manage");
|
|
46637
47818
|
const payload = await parseRigRequest(c, CreateRigRequest, "Rig create request");
|
|
@@ -46657,7 +47838,7 @@ function registerRigRoutes(app, deps) {
|
|
|
46657
47838
|
});
|
|
46658
47839
|
app.patch("/v1/workspaces/:workspaceId/rigs/:rigId", async (c) => {
|
|
46659
47840
|
const workspaceId = c.req.param("workspaceId");
|
|
46660
|
-
const authorization = await
|
|
47841
|
+
const authorization = await requireAccessGrantAuthorization9(c, deps, workspaceId);
|
|
46661
47842
|
const grant = authorization.grant;
|
|
46662
47843
|
requirePermission7(grant, "rigs:manage");
|
|
46663
47844
|
const rig = await requireRigForApi2(db, grant, c.req.param("rigId"));
|
|
@@ -46672,7 +47853,7 @@ function registerRigRoutes(app, deps) {
|
|
|
46672
47853
|
});
|
|
46673
47854
|
app.delete("/v1/workspaces/:workspaceId/rigs/:rigId", async (c) => {
|
|
46674
47855
|
const workspaceId = c.req.param("workspaceId");
|
|
46675
|
-
const authorization = await
|
|
47856
|
+
const authorization = await requireAccessGrantAuthorization9(c, deps, workspaceId);
|
|
46676
47857
|
const grant = authorization.grant;
|
|
46677
47858
|
requirePermission7(grant, "rigs:manage");
|
|
46678
47859
|
const rig = await requireRigForApi2(db, grant, c.req.param("rigId"));
|
|
@@ -46795,6 +47976,7 @@ import {
|
|
|
46795
47976
|
syncCreatedScheduledTask as syncCreatedScheduledTask5,
|
|
46796
47977
|
syncUpdatedScheduledTask as syncUpdatedScheduledTask4,
|
|
46797
47978
|
updateScheduledTaskForApi as updateScheduledTaskForApi2,
|
|
47979
|
+
validateScheduledTaskMachineTarget as validateScheduledTaskMachineTarget2,
|
|
46798
47980
|
validateScheduledTaskTarget as validateScheduledTaskTarget2,
|
|
46799
47981
|
validatedScheduledTaskUpdate as validatedScheduledTaskUpdate2
|
|
46800
47982
|
} from "@opengeni/core";
|
|
@@ -46918,6 +48100,14 @@ function registerScheduledTaskRoutes(app, deps) {
|
|
|
46918
48100
|
agentConfig: task.agentConfig,
|
|
46919
48101
|
missingTargetStatus: 404
|
|
46920
48102
|
});
|
|
48103
|
+
await validateScheduledTaskMachineTarget2({
|
|
48104
|
+
settings,
|
|
48105
|
+
db,
|
|
48106
|
+
grant,
|
|
48107
|
+
runMode: task.runMode,
|
|
48108
|
+
agentConfig: task.agentConfig,
|
|
48109
|
+
requireOnline: true
|
|
48110
|
+
});
|
|
46921
48111
|
await requireLimit8(deps, {
|
|
46922
48112
|
accountId: grant.accountId,
|
|
46923
48113
|
workspaceId,
|
|
@@ -47095,7 +48285,7 @@ import {
|
|
|
47095
48285
|
recordStreamAcknowledgment,
|
|
47096
48286
|
requestSessionCompaction,
|
|
47097
48287
|
setSessionCodexPinInTransaction,
|
|
47098
|
-
withSessionCodexCapacityMutation,
|
|
48288
|
+
withSessionCodexCapacityMutation as withSessionCodexCapacityMutation2,
|
|
47099
48289
|
setSessionChannel,
|
|
47100
48290
|
updateSessionVariableSets,
|
|
47101
48291
|
ChannelNotFoundError,
|
|
@@ -47148,7 +48338,7 @@ import {
|
|
|
47148
48338
|
requestSessionBackgroundCommandCancellation
|
|
47149
48339
|
} from "@opengeni/db/session-background-commands";
|
|
47150
48340
|
import {
|
|
47151
|
-
appendAndPublishEvents as
|
|
48341
|
+
appendAndPublishEvents as appendAndPublishEvents4,
|
|
47152
48342
|
boundSessionEventHttpPage,
|
|
47153
48343
|
coalesceSessionEventDeltas as coalesceSessionEventDeltas2,
|
|
47154
48344
|
publishDurableSessionEvents as publishDurableSessionEvents3
|
|
@@ -47901,7 +49091,7 @@ import {
|
|
|
47901
49091
|
getManagedAuthRequestActorEpoch as getManagedAuthRequestActorEpoch2,
|
|
47902
49092
|
hasPermission as hasPermission18,
|
|
47903
49093
|
requireAccessGrant as requireAccessGrant30,
|
|
47904
|
-
requireAccessGrantAuthorization as
|
|
49094
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization10,
|
|
47905
49095
|
requireFreshAccessGrant as requireFreshAccessGrant2,
|
|
47906
49096
|
requirePermission as requirePermission8,
|
|
47907
49097
|
requireSessionAuthorization as requireSessionAuthorization7,
|
|
@@ -48250,7 +49440,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
48250
49440
|
exitCode: process.exitCode,
|
|
48251
49441
|
reason: process.state === "exited" ? "exit" : "lost"
|
|
48252
49442
|
};
|
|
48253
|
-
await
|
|
49443
|
+
await appendAndPublishEvents4(db, bus, ctx.workspaceId, ctx.session.id, [
|
|
48254
49444
|
{ type: "terminal.pty.exited", payload: exited }
|
|
48255
49445
|
]);
|
|
48256
49446
|
};
|
|
@@ -48331,7 +49521,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
48331
49521
|
};
|
|
48332
49522
|
app.get("/v1/workspaces/:workspaceId/session-tenancy/capabilities", async (c) => {
|
|
48333
49523
|
const workspaceId = c.req.param("workspaceId");
|
|
48334
|
-
const authorization = await
|
|
49524
|
+
const authorization = await requireAccessGrantAuthorization10(
|
|
48335
49525
|
c,
|
|
48336
49526
|
deps,
|
|
48337
49527
|
workspaceId,
|
|
@@ -48341,7 +49531,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
48341
49531
|
});
|
|
48342
49532
|
app.post("/v1/workspaces/:workspaceId/sessions", async (c) => {
|
|
48343
49533
|
const workspaceId = c.req.param("workspaceId");
|
|
48344
|
-
const authorization = await
|
|
49534
|
+
const authorization = await requireAccessGrantAuthorization10(
|
|
48345
49535
|
c,
|
|
48346
49536
|
deps,
|
|
48347
49537
|
workspaceId,
|
|
@@ -48375,7 +49565,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
48375
49565
|
});
|
|
48376
49566
|
app.put("/v1/workspaces/:workspaceId/new-session-draft", async (c) => {
|
|
48377
49567
|
const workspaceId = c.req.param("workspaceId");
|
|
48378
|
-
const authorization = await
|
|
49568
|
+
const authorization = await requireAccessGrantAuthorization10(
|
|
48379
49569
|
c,
|
|
48380
49570
|
deps,
|
|
48381
49571
|
workspaceId,
|
|
@@ -48429,7 +49619,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
48429
49619
|
});
|
|
48430
49620
|
app.get("/v1/workspaces/:workspaceId/sessions", async (c) => {
|
|
48431
49621
|
const workspaceId = c.req.param("workspaceId");
|
|
48432
|
-
const authorization = await
|
|
49622
|
+
const authorization = await requireAccessGrantAuthorization10(
|
|
48433
49623
|
c,
|
|
48434
49624
|
deps,
|
|
48435
49625
|
workspaceId,
|
|
@@ -48685,7 +49875,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
48685
49875
|
app.put("/v1/workspaces/:workspaceId/sessions/:sessionId/visibility", async (c) => {
|
|
48686
49876
|
const workspaceId = c.req.param("workspaceId");
|
|
48687
49877
|
const sessionId = c.req.param("sessionId");
|
|
48688
|
-
const authorization = await
|
|
49878
|
+
const authorization = await requireAccessGrantAuthorization10(c, deps, workspaceId);
|
|
48689
49879
|
const parsed = UpdateSessionVisibilityRequest.safeParse(await c.req.json().catch(() => null));
|
|
48690
49880
|
if (!parsed.success) {
|
|
48691
49881
|
throw new ApiHttpError(422, {
|
|
@@ -48713,7 +49903,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
48713
49903
|
app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/forks", async (c) => {
|
|
48714
49904
|
const workspaceId = c.req.param("workspaceId");
|
|
48715
49905
|
const sessionId = c.req.param("sessionId");
|
|
48716
|
-
const authorization = await
|
|
49906
|
+
const authorization = await requireAccessGrantAuthorization10(c, deps, workspaceId);
|
|
48717
49907
|
const parsed = ForkSessionRequest.safeParse(await c.req.json().catch(() => null));
|
|
48718
49908
|
if (!parsed.success) {
|
|
48719
49909
|
throw new ApiHttpError(422, {
|
|
@@ -49356,7 +50546,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
49356
50546
|
);
|
|
49357
50547
|
app.put("/v1/workspaces/:workspaceId/sessions/:sessionId/pin", async (c) => {
|
|
49358
50548
|
const workspaceId = c.req.param("workspaceId");
|
|
49359
|
-
const authorization = await
|
|
50549
|
+
const authorization = await requireAccessGrantAuthorization10(
|
|
49360
50550
|
c,
|
|
49361
50551
|
deps,
|
|
49362
50552
|
workspaceId,
|
|
@@ -49409,7 +50599,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
49409
50599
|
});
|
|
49410
50600
|
app.put("/v1/workspaces/:workspaceId/sessions/:sessionId/attention", async (c) => {
|
|
49411
50601
|
const workspaceId = c.req.param("workspaceId");
|
|
49412
|
-
const authorization = await
|
|
50602
|
+
const authorization = await requireAccessGrantAuthorization10(
|
|
49413
50603
|
c,
|
|
49414
50604
|
deps,
|
|
49415
50605
|
workspaceId,
|
|
@@ -49459,7 +50649,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
49459
50649
|
});
|
|
49460
50650
|
app.put("/v1/workspaces/:workspaceId/sessions/:sessionId/archive", async (c) => {
|
|
49461
50651
|
const workspaceId = c.req.param("workspaceId");
|
|
49462
|
-
const authorization = await
|
|
50652
|
+
const authorization = await requireAccessGrantAuthorization10(
|
|
49463
50653
|
c,
|
|
49464
50654
|
deps,
|
|
49465
50655
|
workspaceId,
|
|
@@ -49541,7 +50731,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
49541
50731
|
}
|
|
49542
50732
|
}
|
|
49543
50733
|
const pinned = target === "auto" ? null : target;
|
|
49544
|
-
const mutation = await
|
|
50734
|
+
const mutation = await withSessionCodexCapacityMutation2(
|
|
49545
50735
|
db,
|
|
49546
50736
|
{ workspaceId, reason: "codex_manual_session_pin_changed" },
|
|
49547
50737
|
async (tx) => {
|
|
@@ -50121,7 +51311,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
50121
51311
|
}
|
|
50122
51312
|
throw error;
|
|
50123
51313
|
});
|
|
50124
|
-
await
|
|
51314
|
+
await appendAndPublishEvents4(db, bus, workspaceId, sessionId, [
|
|
50125
51315
|
{
|
|
50126
51316
|
type: "session.context.cleared",
|
|
50127
51317
|
payload: {
|
|
@@ -50319,6 +51509,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
50319
51509
|
Number.isFinite(after) ? after : 0,
|
|
50320
51510
|
c.req.raw.signal,
|
|
50321
51511
|
{
|
|
51512
|
+
...browserSseDeliveryOptions(c.req.query("transport")),
|
|
50322
51513
|
observability: deps.observability,
|
|
50323
51514
|
actorEpoch: getManagedAuthRequestActorEpoch2(c.req.raw) ?? void 0,
|
|
50324
51515
|
reauthorizeAfterMs: authorization?.reauthorizeAfterMs ?? SESSION_AUTHORIZATION_DEFAULT_REAUTHORIZE_MS,
|
|
@@ -50533,7 +51724,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
50533
51724
|
});
|
|
50534
51725
|
app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/steer", async (c) => {
|
|
50535
51726
|
const workspaceId = c.req.param("workspaceId");
|
|
50536
|
-
const authorization = await
|
|
51727
|
+
const authorization = await requireAccessGrantAuthorization10(
|
|
50537
51728
|
c,
|
|
50538
51729
|
deps,
|
|
50539
51730
|
workspaceId,
|
|
@@ -50565,7 +51756,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
50565
51756
|
});
|
|
50566
51757
|
app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/composer-draft/submit", async (c) => {
|
|
50567
51758
|
const workspaceId = c.req.param("workspaceId");
|
|
50568
|
-
const authorization = await
|
|
51759
|
+
const authorization = await requireAccessGrantAuthorization10(
|
|
50569
51760
|
c,
|
|
50570
51761
|
deps,
|
|
50571
51762
|
workspaceId,
|
|
@@ -50587,7 +51778,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
50587
51778
|
});
|
|
50588
51779
|
app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/events", async (c) => {
|
|
50589
51780
|
const workspaceId = c.req.param("workspaceId");
|
|
50590
|
-
const authorization = await
|
|
51781
|
+
const authorization = await requireAccessGrantAuthorization10(
|
|
50591
51782
|
c,
|
|
50592
51783
|
deps,
|
|
50593
51784
|
workspaceId,
|
|
@@ -50780,12 +51971,8 @@ function registerSessionRoutes(app, deps) {
|
|
|
50780
51971
|
// Human take-control: when the desktop is available + this policy is on
|
|
50781
51972
|
// (default), the cell is mode "interactive" — the noVNC viewer drives :0
|
|
50782
51973
|
// (x11vnc runs without -viewonly). Off → mode "read-only" (client disables
|
|
50783
|
-
// take-control).
|
|
51974
|
+
// take-control). Agent interaction uses managed ComputerSession tools.
|
|
50784
51975
|
desktopInteractive: settings.sandboxDesktopInteractive,
|
|
50785
|
-
// P4.3 computer-use: the agent drives the active display; availability
|
|
50786
|
-
// tracks the desktop tier + a desktop-capable backend.
|
|
50787
|
-
computerUseEnabled: settings.computerUseEnabled,
|
|
50788
|
-
computerUseReadOnly: settings.computerUseReadOnly,
|
|
50789
51976
|
// Graceful degrade when scoped stream credentials cannot be minted.
|
|
50790
51977
|
streamTokenSecretAvailable: !streamTokenDegraded(settings),
|
|
50791
51978
|
desktopAcknowledged: acknowledged,
|
|
@@ -51465,7 +52652,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
51465
52652
|
};
|
|
51466
52653
|
events.push({ type: "terminal.pty.output.delta", payload: delta });
|
|
51467
52654
|
}
|
|
51468
|
-
await
|
|
52655
|
+
await appendAndPublishEvents4(db, bus, ctx.workspaceId, ctx.session.id, events);
|
|
51469
52656
|
return opened.response;
|
|
51470
52657
|
});
|
|
51471
52658
|
return c.json(out, 201);
|
|
@@ -51526,7 +52713,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
51526
52713
|
chunk: output,
|
|
51527
52714
|
seq: seq++
|
|
51528
52715
|
};
|
|
51529
|
-
await
|
|
52716
|
+
await appendAndPublishEvents4(db, bus, ctx.workspaceId, ctx.session.id, [
|
|
51530
52717
|
{ type: "terminal.pty.output.delta", payload: delta }
|
|
51531
52718
|
]);
|
|
51532
52719
|
}
|
|
@@ -52302,7 +53489,7 @@ import {
|
|
|
52302
53489
|
} from "@opengeni/db";
|
|
52303
53490
|
import { HTTPException as HTTPException54 } from "hono/http-exception";
|
|
52304
53491
|
import { z as z14 } from "zod";
|
|
52305
|
-
import { requireAccessGrant as requireAccessGrant31, requireAccessGrantAuthorization as
|
|
53492
|
+
import { requireAccessGrant as requireAccessGrant31, requireAccessGrantAuthorization as requireAccessGrantAuthorization11 } from "@opengeni/core";
|
|
52306
53493
|
function registerSocialRoutes(app, deps) {
|
|
52307
53494
|
const { db, settings, observability } = deps;
|
|
52308
53495
|
app.get("/v1/workspaces/:workspaceId/social/connections", async (c) => {
|
|
@@ -52372,7 +53559,7 @@ function registerSocialRoutes(app, deps) {
|
|
|
52372
53559
|
});
|
|
52373
53560
|
}
|
|
52374
53561
|
const payload = parsed.data;
|
|
52375
|
-
const access = await
|
|
53562
|
+
const access = await requireAccessGrantAuthorization11(
|
|
52376
53563
|
c,
|
|
52377
53564
|
deps,
|
|
52378
53565
|
workspaceId,
|
|
@@ -52490,6 +53677,9 @@ function socialHttpException(error) {
|
|
|
52490
53677
|
import {
|
|
52491
53678
|
AddWorkspaceMemberRequest,
|
|
52492
53679
|
CreateWorkspaceRequest,
|
|
53680
|
+
EnsureWorkspaceRequest,
|
|
53681
|
+
EnsureWorkspaceResponse,
|
|
53682
|
+
ListWorkspaceMemberCandidatesResponse,
|
|
52493
53683
|
ListWorkspaceMembersResponse,
|
|
52494
53684
|
SetWorkspaceDefaultRigRequest,
|
|
52495
53685
|
UpdateWorkspaceMemberRequest,
|
|
@@ -52508,45 +53698,134 @@ import {
|
|
|
52508
53698
|
allWorkspacePermissions,
|
|
52509
53699
|
createWorkspace,
|
|
52510
53700
|
deleteWorkspaceIfQuiescent,
|
|
52511
|
-
|
|
53701
|
+
ensureWorkspaceByExternalIdentity,
|
|
53702
|
+
findWorkspaceByExternalIdentity,
|
|
53703
|
+
getManagedUserProfilesByIds,
|
|
52512
53704
|
getWorkspaceModelPolicy,
|
|
52513
53705
|
grantWorkspaceAccess,
|
|
52514
53706
|
listWorkspaceMembers,
|
|
53707
|
+
listWorkspaceMemberManagementCandidates,
|
|
52515
53708
|
normalizeWorkspaceMembershipPermissions,
|
|
52516
53709
|
listWorkspaceControlEvents as listWorkspaceControlEvents2,
|
|
53710
|
+
listSharedWorkspacesForAccount,
|
|
52517
53711
|
listWorkspacesForSubject,
|
|
53712
|
+
nestedPostgresSqlState as nestedPostgresSqlState4,
|
|
52518
53713
|
removeWorkspaceMember,
|
|
52519
53714
|
requireWorkspace,
|
|
52520
53715
|
getRig,
|
|
52521
53716
|
setWorkspaceDefaultRig,
|
|
52522
53717
|
updateWorkspace,
|
|
52523
53718
|
updateWorkspaceSettings,
|
|
53719
|
+
upsertWorkspaceMemberAsWorkspaceManager,
|
|
52524
53720
|
upsertWorkspaceModelPolicy,
|
|
52525
53721
|
workspaceCodexSubscriptionActive,
|
|
52526
53722
|
workspaceControlRequestLockTimeoutMs as workspaceControlRequestLockTimeoutMs2,
|
|
52527
53723
|
workspaceXaiSubscriptionActive,
|
|
52528
|
-
workspaceVercelAiGatewayConnectionActive
|
|
53724
|
+
workspaceVercelAiGatewayConnectionActive,
|
|
53725
|
+
WorkspaceExternalIdentityConflictError,
|
|
53726
|
+
WorkspaceLimitExceededError
|
|
52529
53727
|
} from "@opengeni/db";
|
|
52530
53728
|
import { boundWorkspaceControlHttpPage } from "@opengeni/events";
|
|
52531
53729
|
import { HTTPException as HTTPException55 } from "hono/http-exception";
|
|
52532
53730
|
import {
|
|
52533
53731
|
getManagedAuthRequestActorEpoch as getManagedAuthRequestActorEpoch3,
|
|
53732
|
+
accountScopedApiKeyWorkspaceAuthority as accountScopedApiKeyWorkspaceAuthority2,
|
|
52534
53733
|
hasPermission as hasPermission19,
|
|
52535
|
-
requireAccessContext as
|
|
53734
|
+
requireAccessContext as requireAccessContext3,
|
|
52536
53735
|
requireAccessGrant as requireAccessGrant32,
|
|
52537
53736
|
requireFreshAccessGrant as requireFreshAccessGrant3
|
|
52538
53737
|
} from "@opengeni/core";
|
|
52539
53738
|
import { requireLimit as requireLimit9 } from "@opengeni/core";
|
|
52540
53739
|
import {
|
|
52541
53740
|
assertWorkspaceMemberRemovable,
|
|
52542
|
-
|
|
52543
|
-
|
|
53741
|
+
assertWorkspaceMemberUpdateAllowed,
|
|
53742
|
+
controlHumanWorkspace
|
|
52544
53743
|
} from "@opengeni/core";
|
|
53744
|
+
|
|
53745
|
+
// src/workspace-delete-observability.ts
|
|
53746
|
+
var DURATION_BUCKETS2 = [
|
|
53747
|
+
5e-3,
|
|
53748
|
+
0.01,
|
|
53749
|
+
0.025,
|
|
53750
|
+
0.05,
|
|
53751
|
+
0.1,
|
|
53752
|
+
0.25,
|
|
53753
|
+
0.5,
|
|
53754
|
+
1,
|
|
53755
|
+
2.5,
|
|
53756
|
+
5,
|
|
53757
|
+
10,
|
|
53758
|
+
30,
|
|
53759
|
+
60,
|
|
53760
|
+
120,
|
|
53761
|
+
300,
|
|
53762
|
+
600,
|
|
53763
|
+
1200,
|
|
53764
|
+
2400,
|
|
53765
|
+
3600
|
|
53766
|
+
];
|
|
53767
|
+
var INVENTORY_BUCKETS = [0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1e3, 5e3, 1e4];
|
|
53768
|
+
function recordWorkspaceDeleteObservation(observability, identity, observation) {
|
|
53769
|
+
const labels = { phase: observation.phase, outcome: observation.outcome };
|
|
53770
|
+
observability.observeHistogram({
|
|
53771
|
+
name: "opengeni_workspace_delete_phase_seconds",
|
|
53772
|
+
help: "Workspace deletion transaction and bounded internal phase duration in seconds.",
|
|
53773
|
+
labels,
|
|
53774
|
+
buckets: DURATION_BUCKETS2,
|
|
53775
|
+
value: Math.max(0, observation.durationSeconds)
|
|
53776
|
+
});
|
|
53777
|
+
if (observation.phase === "transaction") {
|
|
53778
|
+
observability.incrementCounter({
|
|
53779
|
+
name: "opengeni_workspace_delete_attempts_total",
|
|
53780
|
+
help: "Workspace deletion attempts by terminal database outcome.",
|
|
53781
|
+
labels: { outcome: observation.outcome }
|
|
53782
|
+
});
|
|
53783
|
+
}
|
|
53784
|
+
for (const [kind, count] of Object.entries(observation.inventory ?? {})) {
|
|
53785
|
+
observability.observeHistogram({
|
|
53786
|
+
name: "opengeni_workspace_delete_inventory_rows",
|
|
53787
|
+
help: "Rows or live owners observed by workspace deletion preflight inventory class.",
|
|
53788
|
+
labels: { kind },
|
|
53789
|
+
buckets: INVENTORY_BUCKETS,
|
|
53790
|
+
value: Math.max(0, Math.floor(count ?? 0))
|
|
53791
|
+
});
|
|
53792
|
+
}
|
|
53793
|
+
observability.info("Workspace deletion phase", {
|
|
53794
|
+
accountId: identity.accountId,
|
|
53795
|
+
workspaceId: identity.workspaceId,
|
|
53796
|
+
phase: observation.phase,
|
|
53797
|
+
outcome: observation.outcome,
|
|
53798
|
+
durationSeconds: observation.durationSeconds,
|
|
53799
|
+
inventoryJson: JSON.stringify(observation.inventory ?? {})
|
|
53800
|
+
});
|
|
53801
|
+
}
|
|
53802
|
+
function workspaceDeleteObserver(observability, identity) {
|
|
53803
|
+
if (!observability) return void 0;
|
|
53804
|
+
return {
|
|
53805
|
+
onPhase: (observation) => {
|
|
53806
|
+
try {
|
|
53807
|
+
recordWorkspaceDeleteObservation(observability, identity, observation);
|
|
53808
|
+
} catch {
|
|
53809
|
+
try {
|
|
53810
|
+
observability.incrementCounter({
|
|
53811
|
+
name: "opengeni_observability_observer_errors_total",
|
|
53812
|
+
help: "Observability observer failures isolated from product execution.",
|
|
53813
|
+
labels: { observer: "workspace_delete" }
|
|
53814
|
+
});
|
|
53815
|
+
} catch {
|
|
53816
|
+
}
|
|
53817
|
+
}
|
|
53818
|
+
}
|
|
53819
|
+
};
|
|
53820
|
+
}
|
|
53821
|
+
|
|
53822
|
+
// src/routes/workspaces.ts
|
|
52545
53823
|
import {
|
|
52546
53824
|
AI_GATEWAY_REALTIME_MODELS,
|
|
52547
53825
|
CODEX_REALTIME_MODEL_ID,
|
|
52548
53826
|
SUPERGROK_REALTIME_MODEL_ID as SUPERGROK_REALTIME_MODEL_ID2,
|
|
52549
|
-
canonicalizeConfiguredModelId
|
|
53827
|
+
canonicalizeConfiguredModelId,
|
|
53828
|
+
configuredStaticUsageLimits as configuredStaticUsageLimits2
|
|
52550
53829
|
} from "@opengeni/config";
|
|
52551
53830
|
function canonicalWorkspacePolicyModelIds(settings, modelIds) {
|
|
52552
53831
|
if (modelIds === null || modelIds === void 0) {
|
|
@@ -52567,10 +53846,18 @@ function workspaceUpdateRequestsAccountTransfer(value) {
|
|
|
52567
53846
|
}
|
|
52568
53847
|
function registerWorkspaceRoutes(app, deps) {
|
|
52569
53848
|
app.get("/v1/access/me", async (c) => {
|
|
52570
|
-
return c.json(await
|
|
53849
|
+
return c.json(await requireAccessContext3(c, deps));
|
|
52571
53850
|
});
|
|
52572
53851
|
app.get("/v1/workspaces", async (c) => {
|
|
52573
|
-
const context = await
|
|
53852
|
+
const context = await requireAccessContext3(c, deps);
|
|
53853
|
+
const accountScopedAuthority = accountScopedApiKeyWorkspaceAuthority2(context);
|
|
53854
|
+
if (accountScopedAuthority && hasPermission19(accountScopedAuthority.permissions, "workspace:read")) {
|
|
53855
|
+
return c.json(
|
|
53856
|
+
(await listSharedWorkspacesForAccount(deps.db, accountScopedAuthority.accountId)).map(
|
|
53857
|
+
(workspace) => Workspace.parse(workspace)
|
|
53858
|
+
)
|
|
53859
|
+
);
|
|
53860
|
+
}
|
|
52574
53861
|
const readableWorkspaceIds = [
|
|
52575
53862
|
...new Set(
|
|
52576
53863
|
context.workspaceGrants.filter((grant) => hasPermission19(grant.permissions, "workspace:read")).map((grant) => grant.workspaceId)
|
|
@@ -52588,32 +53875,96 @@ function registerWorkspaceRoutes(app, deps) {
|
|
|
52588
53875
|
)
|
|
52589
53876
|
);
|
|
52590
53877
|
});
|
|
53878
|
+
app.put("/v1/workspaces/external", async (c) => {
|
|
53879
|
+
const context = await requireAccessContext3(c, deps);
|
|
53880
|
+
const payload = EnsureWorkspaceRequest.parse(await c.req.json());
|
|
53881
|
+
requireAccountPermission2(context, payload.accountId, "workspace:create");
|
|
53882
|
+
try {
|
|
53883
|
+
const existing = await findWorkspaceByExternalIdentity(deps.db, {
|
|
53884
|
+
externalSource: payload.externalSource,
|
|
53885
|
+
externalId: payload.externalId
|
|
53886
|
+
});
|
|
53887
|
+
if (existing) {
|
|
53888
|
+
if (existing.accountId !== payload.accountId || existing.kind !== "shared") {
|
|
53889
|
+
throw new WorkspaceExternalIdentityConflictError();
|
|
53890
|
+
}
|
|
53891
|
+
return c.json(
|
|
53892
|
+
EnsureWorkspaceResponse.parse({
|
|
53893
|
+
workspace: existing,
|
|
53894
|
+
created: false
|
|
53895
|
+
})
|
|
53896
|
+
);
|
|
53897
|
+
}
|
|
53898
|
+
await requireLimit9(deps, {
|
|
53899
|
+
accountId: payload.accountId,
|
|
53900
|
+
action: "workspace:create",
|
|
53901
|
+
quantity: 1
|
|
53902
|
+
});
|
|
53903
|
+
const result = await ensureWorkspaceByExternalIdentity(deps.db, {
|
|
53904
|
+
accountId: payload.accountId,
|
|
53905
|
+
externalSource: payload.externalSource,
|
|
53906
|
+
externalId: payload.externalId,
|
|
53907
|
+
name: payload.name,
|
|
53908
|
+
slug: payload.slug ?? null,
|
|
53909
|
+
...payload.agentInstructions !== void 0 ? { agentInstructions: normalizeAgentInstructions(payload.agentInstructions) } : {},
|
|
53910
|
+
maxWorkspacesPerAccount: workspaceLimit(deps)
|
|
53911
|
+
});
|
|
53912
|
+
const response = EnsureWorkspaceResponse.parse(result);
|
|
53913
|
+
return result.created ? c.json(response, 201) : c.json(response);
|
|
53914
|
+
} catch (error) {
|
|
53915
|
+
if (error instanceof WorkspaceExternalIdentityConflictError) {
|
|
53916
|
+
throw new HTTPException55(409, {
|
|
53917
|
+
message: "external workspace identity is already in use"
|
|
53918
|
+
});
|
|
53919
|
+
}
|
|
53920
|
+
if (error instanceof WorkspaceLimitExceededError) {
|
|
53921
|
+
throw new HTTPException55(429, { message: error.message });
|
|
53922
|
+
}
|
|
53923
|
+
throw error;
|
|
53924
|
+
}
|
|
53925
|
+
});
|
|
52591
53926
|
app.post("/v1/workspaces", async (c) => {
|
|
52592
|
-
const context = await
|
|
53927
|
+
const context = await requireAccessContext3(c, deps);
|
|
52593
53928
|
const payload = CreateWorkspaceRequest.parse(await c.req.json());
|
|
52594
53929
|
const accountId = payload.accountId ?? context.defaultAccountId;
|
|
52595
53930
|
if (!accountId) {
|
|
52596
|
-
throw new HTTPException55(409, {
|
|
53931
|
+
throw new HTTPException55(409, {
|
|
53932
|
+
message: "account selection is required"
|
|
53933
|
+
});
|
|
52597
53934
|
}
|
|
52598
|
-
|
|
52599
|
-
await requireLimit9(deps, {
|
|
52600
|
-
const workspace = await createWorkspace(deps.db, {
|
|
52601
|
-
accountId,
|
|
52602
|
-
name: payload.name.trim(),
|
|
52603
|
-
slug: payload.slug?.trim() || null,
|
|
52604
|
-
externalSource: payload.externalSource ?? null,
|
|
52605
|
-
externalId: payload.externalId ?? null,
|
|
52606
|
-
...payload.agentInstructions !== void 0 ? { agentInstructions: normalizeAgentInstructions(payload.agentInstructions) } : {}
|
|
52607
|
-
});
|
|
52608
|
-
await grantWorkspaceAccess(deps.db, {
|
|
53935
|
+
requireAccountPermission2(context, accountId, "workspace:create");
|
|
53936
|
+
await requireLimit9(deps, {
|
|
52609
53937
|
accountId,
|
|
52610
|
-
|
|
52611
|
-
|
|
52612
|
-
role: "owner",
|
|
52613
|
-
permissions: allWorkspacePermissions,
|
|
52614
|
-
...context.subjectLabel ? { subjectLabel: context.subjectLabel } : {}
|
|
53938
|
+
action: "workspace:create",
|
|
53939
|
+
quantity: 1
|
|
52615
53940
|
});
|
|
52616
|
-
|
|
53941
|
+
try {
|
|
53942
|
+
const workspace = await createWorkspace(deps.db, {
|
|
53943
|
+
accountId,
|
|
53944
|
+
name: payload.name.trim(),
|
|
53945
|
+
slug: payload.slug?.trim() || null,
|
|
53946
|
+
externalSource: payload.externalSource ?? null,
|
|
53947
|
+
externalId: payload.externalId ?? null,
|
|
53948
|
+
...payload.agentInstructions !== void 0 ? {
|
|
53949
|
+
agentInstructions: normalizeAgentInstructions(payload.agentInstructions)
|
|
53950
|
+
} : {},
|
|
53951
|
+
maxWorkspacesPerAccount: workspaceLimit(deps)
|
|
53952
|
+
});
|
|
53953
|
+
await grantWorkspaceAccess(deps.db, {
|
|
53954
|
+
accountId,
|
|
53955
|
+
workspaceId: workspace.id,
|
|
53956
|
+
subjectId: context.subjectId,
|
|
53957
|
+
role: "owner",
|
|
53958
|
+
permissions: allWorkspacePermissions,
|
|
53959
|
+
...context.subjectLabel ? { subjectLabel: context.subjectLabel } : {}
|
|
53960
|
+
});
|
|
53961
|
+
return c.json(Workspace.parse(workspace), 201);
|
|
53962
|
+
} catch (error) {
|
|
53963
|
+
if (error instanceof WorkspaceLimitExceededError) {
|
|
53964
|
+
throw new HTTPException55(429, { message: error.message });
|
|
53965
|
+
}
|
|
53966
|
+
throw error;
|
|
53967
|
+
}
|
|
52617
53968
|
});
|
|
52618
53969
|
app.get("/v1/workspaces/:workspaceId", async (c) => {
|
|
52619
53970
|
const workspaceId = c.req.param("workspaceId");
|
|
@@ -52637,7 +53988,9 @@ function registerWorkspaceRoutes(app, deps) {
|
|
|
52637
53988
|
const workspace = await updateWorkspace(deps.db, workspaceId, {
|
|
52638
53989
|
...payload.name !== void 0 ? { name: payload.name.trim() } : {},
|
|
52639
53990
|
...payload.slug !== void 0 ? { slug: payload.slug?.trim() || null } : {},
|
|
52640
|
-
...payload.agentInstructions !== void 0 ? {
|
|
53991
|
+
...payload.agentInstructions !== void 0 ? {
|
|
53992
|
+
agentInstructions: normalizeAgentInstructions(payload.agentInstructions)
|
|
53993
|
+
} : {}
|
|
52641
53994
|
});
|
|
52642
53995
|
return c.json(Workspace.parse(workspace));
|
|
52643
53996
|
});
|
|
@@ -52646,7 +53999,9 @@ function registerWorkspaceRoutes(app, deps) {
|
|
|
52646
53999
|
await requireAccessGrant32(c, deps, workspaceId, "workspace:admin");
|
|
52647
54000
|
const parsed = UpdateWorkspaceSettingsRequest.safeParse(await c.req.json());
|
|
52648
54001
|
if (!parsed.success) {
|
|
52649
|
-
throw new HTTPException55(400, {
|
|
54002
|
+
throw new HTTPException55(400, {
|
|
54003
|
+
message: "invalid workspace settings patch"
|
|
54004
|
+
});
|
|
52650
54005
|
}
|
|
52651
54006
|
const workspace = await updateWorkspaceSettings(deps.db, workspaceId, parsed.data, {
|
|
52652
54007
|
controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs2()
|
|
@@ -52757,11 +54112,15 @@ function registerWorkspaceRoutes(app, deps) {
|
|
|
52757
54112
|
const workspaceId = c.req.param("workspaceId");
|
|
52758
54113
|
const grant = await requireAccessGrant32(c, deps, workspaceId, "workspace:admin");
|
|
52759
54114
|
if (workspaceControlUtf8Bytes2(grant.subjectId) > WORKSPACE_CONTROL_ACTOR_MAX_BYTES2) {
|
|
52760
|
-
throw new HTTPException55(400, {
|
|
54115
|
+
throw new HTTPException55(400, {
|
|
54116
|
+
message: "workspace-control actor is too large"
|
|
54117
|
+
});
|
|
52761
54118
|
}
|
|
52762
54119
|
const parsed = WorkspaceInferenceControlRequest.safeParse(await c.req.json().catch(() => null));
|
|
52763
54120
|
if (!parsed.success) {
|
|
52764
|
-
throw new HTTPException55(400, {
|
|
54121
|
+
throw new HTTPException55(400, {
|
|
54122
|
+
message: "invalid workspace inference-control request"
|
|
54123
|
+
});
|
|
52765
54124
|
}
|
|
52766
54125
|
return c.json(
|
|
52767
54126
|
await controlHumanWorkspace(
|
|
@@ -52803,6 +54162,7 @@ function registerWorkspaceRoutes(app, deps) {
|
|
|
52803
54162
|
after,
|
|
52804
54163
|
c.req.raw.signal,
|
|
52805
54164
|
{
|
|
54165
|
+
...browserSseDeliveryOptions(c.req.query("transport")),
|
|
52806
54166
|
observability: deps.observability,
|
|
52807
54167
|
actorEpoch: getManagedAuthRequestActorEpoch3(c.req.raw) ?? void 0,
|
|
52808
54168
|
reauthorize: async () => {
|
|
@@ -52818,7 +54178,9 @@ function registerWorkspaceRoutes(app, deps) {
|
|
|
52818
54178
|
if (payload.rigId) {
|
|
52819
54179
|
const rig = await getRig(deps.db, workspaceId, payload.rigId);
|
|
52820
54180
|
if (!rig) {
|
|
52821
|
-
throw new HTTPException55(422, {
|
|
54181
|
+
throw new HTTPException55(422, {
|
|
54182
|
+
message: `unknown rigId: ${payload.rigId}`
|
|
54183
|
+
});
|
|
52822
54184
|
}
|
|
52823
54185
|
}
|
|
52824
54186
|
const workspace = await setWorkspaceDefaultRig(deps.db, workspaceId, payload.rigId);
|
|
@@ -52827,15 +54189,22 @@ function registerWorkspaceRoutes(app, deps) {
|
|
|
52827
54189
|
app.delete("/v1/workspaces/:workspaceId", async (c) => {
|
|
52828
54190
|
const workspaceId = c.req.param("workspaceId");
|
|
52829
54191
|
const grant = await requireAccessGrant32(c, deps, workspaceId, "workspace:admin");
|
|
52830
|
-
const
|
|
54192
|
+
const deleteObserver = workspaceDeleteObserver(deps.observability, {
|
|
52831
54193
|
accountId: grant.accountId,
|
|
52832
54194
|
workspaceId
|
|
52833
54195
|
});
|
|
54196
|
+
const deleted = await deleteWorkspaceIfQuiescent(deps.db, {
|
|
54197
|
+
accountId: grant.accountId,
|
|
54198
|
+
workspaceId,
|
|
54199
|
+
...deleteObserver ? { observer: deleteObserver } : {}
|
|
54200
|
+
});
|
|
52834
54201
|
if (deleted.status === "not_found") {
|
|
52835
54202
|
throw new HTTPException55(404, { message: "workspace not found" });
|
|
52836
54203
|
}
|
|
52837
54204
|
if (deleted.status === "only_workspace") {
|
|
52838
|
-
throw new HTTPException55(409, {
|
|
54205
|
+
throw new HTTPException55(409, {
|
|
54206
|
+
message: "cannot delete the account's only workspace"
|
|
54207
|
+
});
|
|
52839
54208
|
}
|
|
52840
54209
|
if (deleted.status === "active_sessions") {
|
|
52841
54210
|
throw new HTTPException55(409, {
|
|
@@ -52864,7 +54233,9 @@ function registerWorkspaceRoutes(app, deps) {
|
|
|
52864
54233
|
{
|
|
52865
54234
|
db: deps.db,
|
|
52866
54235
|
deleteSchedule: async (temporalScheduleId) => {
|
|
52867
|
-
await deps.workflowClient.deleteScheduledTaskSchedule({
|
|
54236
|
+
await deps.workflowClient.deleteScheduledTaskSchedule({
|
|
54237
|
+
temporalScheduleId
|
|
54238
|
+
});
|
|
52868
54239
|
},
|
|
52869
54240
|
...deps.observability ? { observability: deps.observability } : {}
|
|
52870
54241
|
},
|
|
@@ -52875,25 +54246,71 @@ function registerWorkspaceRoutes(app, deps) {
|
|
|
52875
54246
|
app.get("/v1/workspaces/:workspaceId/members", async (c) => {
|
|
52876
54247
|
const workspaceId = c.req.param("workspaceId");
|
|
52877
54248
|
await requireAccessGrant32(c, deps, workspaceId, "workspace:read");
|
|
52878
|
-
const members = await
|
|
54249
|
+
const members = await listWorkspacePeople(deps, workspaceId);
|
|
52879
54250
|
return c.json(workspaceMembersResponse(members));
|
|
52880
54251
|
});
|
|
54252
|
+
app.get("/v1/workspaces/:workspaceId/member-candidates", async (c) => {
|
|
54253
|
+
const workspaceId = c.req.param("workspaceId");
|
|
54254
|
+
const grant = await requireAccessGrant32(c, deps, workspaceId, "members:manage");
|
|
54255
|
+
try {
|
|
54256
|
+
return c.json(
|
|
54257
|
+
ListWorkspaceMemberCandidatesResponse.parse({
|
|
54258
|
+
members: await listWorkspaceMemberManagementCandidates(deps.db, {
|
|
54259
|
+
accountId: grant.accountId,
|
|
54260
|
+
workspaceId,
|
|
54261
|
+
actorSubjectId: grant.subjectId
|
|
54262
|
+
})
|
|
54263
|
+
})
|
|
54264
|
+
);
|
|
54265
|
+
} catch (error) {
|
|
54266
|
+
rethrowWorkspaceMemberCandidateError(error);
|
|
54267
|
+
}
|
|
54268
|
+
});
|
|
52881
54269
|
app.post("/v1/workspaces/:workspaceId/members", async (c) => {
|
|
52882
54270
|
const workspaceId = c.req.param("workspaceId");
|
|
52883
54271
|
const grant = await requireAccessGrant32(c, deps, workspaceId, "members:manage");
|
|
52884
54272
|
const payload = AddWorkspaceMemberRequest.parse(await c.req.json());
|
|
52885
|
-
|
|
52886
|
-
|
|
52887
|
-
|
|
52888
|
-
|
|
52889
|
-
|
|
52890
|
-
|
|
52891
|
-
|
|
52892
|
-
|
|
52893
|
-
|
|
52894
|
-
}
|
|
52895
|
-
const
|
|
52896
|
-
|
|
54273
|
+
let candidates;
|
|
54274
|
+
try {
|
|
54275
|
+
candidates = await listWorkspaceMemberManagementCandidates(deps.db, {
|
|
54276
|
+
accountId: grant.accountId,
|
|
54277
|
+
workspaceId,
|
|
54278
|
+
actorSubjectId: grant.subjectId
|
|
54279
|
+
});
|
|
54280
|
+
} catch (error) {
|
|
54281
|
+
rethrowWorkspaceMemberCandidateError(error);
|
|
54282
|
+
}
|
|
54283
|
+
const candidate = candidates.find(
|
|
54284
|
+
(entry2) => entry2.organizationMembershipId === payload.organizationMembershipId
|
|
54285
|
+
);
|
|
54286
|
+
if (!candidate) {
|
|
54287
|
+
throw new HTTPException55(404, {
|
|
54288
|
+
message: "this organization member is not available to add"
|
|
54289
|
+
});
|
|
54290
|
+
}
|
|
54291
|
+
const subjectId = candidate.subjectId;
|
|
54292
|
+
const existing = await listWorkspaceMembers(deps.db, workspaceId);
|
|
54293
|
+
if (existing.some((member2) => member2.subjectId === subjectId)) {
|
|
54294
|
+
throw new HTTPException55(409, {
|
|
54295
|
+
message: "this person already has access to the workspace"
|
|
54296
|
+
});
|
|
54297
|
+
}
|
|
54298
|
+
try {
|
|
54299
|
+
await upsertWorkspaceMemberAsWorkspaceManager(deps.db, {
|
|
54300
|
+
accountId: grant.accountId,
|
|
54301
|
+
workspaceId,
|
|
54302
|
+
actorSubjectId: grant.subjectId,
|
|
54303
|
+
targetSubjectId: subjectId,
|
|
54304
|
+
mode: "add",
|
|
54305
|
+
subjectLabel: candidate.name?.trim() || candidate.email || subjectId,
|
|
54306
|
+
role: payload.role ?? "member",
|
|
54307
|
+
permissions: payload.permissions
|
|
54308
|
+
});
|
|
54309
|
+
} catch (error) {
|
|
54310
|
+
rethrowWorkspaceMemberCandidateError(error);
|
|
54311
|
+
}
|
|
54312
|
+
const members = await listWorkspacePeople(deps, workspaceId);
|
|
54313
|
+
const member = members.find((addedMember) => addedMember.subjectId === subjectId);
|
|
52897
54314
|
if (!member) {
|
|
52898
54315
|
throw new HTTPException55(500, { message: "failed to add member" });
|
|
52899
54316
|
}
|
|
@@ -52904,20 +54321,32 @@ function registerWorkspaceRoutes(app, deps) {
|
|
|
52904
54321
|
const grant = await requireAccessGrant32(c, deps, workspaceId, "members:manage");
|
|
52905
54322
|
const subjectId = decodeURIComponent(c.req.param("subjectId"));
|
|
52906
54323
|
const payload = UpdateWorkspaceMemberRequest.parse(await c.req.json());
|
|
52907
|
-
const existing = await
|
|
54324
|
+
const existing = await listWorkspacePeople(deps, workspaceId);
|
|
52908
54325
|
const current = existing.find((member2) => member2.subjectId === subjectId);
|
|
52909
54326
|
if (!current) {
|
|
52910
54327
|
throw new HTTPException55(404, { message: "member not found" });
|
|
52911
54328
|
}
|
|
52912
|
-
|
|
52913
|
-
|
|
52914
|
-
workspaceId,
|
|
54329
|
+
assertWorkspaceMemberUpdateAllowed({
|
|
54330
|
+
members: existing,
|
|
52915
54331
|
subjectId,
|
|
52916
|
-
|
|
52917
|
-
|
|
52918
|
-
permissions: payload.permissions
|
|
54332
|
+
callerSubjectId: grant.subjectId,
|
|
54333
|
+
nextPermissions: payload.permissions
|
|
52919
54334
|
});
|
|
52920
|
-
|
|
54335
|
+
try {
|
|
54336
|
+
await upsertWorkspaceMemberAsWorkspaceManager(deps.db, {
|
|
54337
|
+
accountId: grant.accountId,
|
|
54338
|
+
workspaceId,
|
|
54339
|
+
actorSubjectId: grant.subjectId,
|
|
54340
|
+
targetSubjectId: subjectId,
|
|
54341
|
+
mode: "update",
|
|
54342
|
+
...current.subjectLabel ? { subjectLabel: current.subjectLabel } : {},
|
|
54343
|
+
role: payload.role ?? current.role,
|
|
54344
|
+
permissions: payload.permissions
|
|
54345
|
+
});
|
|
54346
|
+
} catch (error) {
|
|
54347
|
+
rethrowWorkspaceMemberCandidateError(error);
|
|
54348
|
+
}
|
|
54349
|
+
const members = await listWorkspacePeople(deps, workspaceId);
|
|
52921
54350
|
const member = members.find((candidate) => candidate.subjectId === subjectId);
|
|
52922
54351
|
if (!member) {
|
|
52923
54352
|
throw new HTTPException55(500, { message: "failed to update member" });
|
|
@@ -52929,7 +54358,11 @@ function registerWorkspaceRoutes(app, deps) {
|
|
|
52929
54358
|
const grant = await requireAccessGrant32(c, deps, workspaceId, "members:manage");
|
|
52930
54359
|
const subjectId = decodeURIComponent(c.req.param("subjectId"));
|
|
52931
54360
|
const members = await listWorkspaceMembers(deps.db, workspaceId);
|
|
52932
|
-
assertWorkspaceMemberRemovable({
|
|
54361
|
+
assertWorkspaceMemberRemovable({
|
|
54362
|
+
members,
|
|
54363
|
+
subjectId,
|
|
54364
|
+
callerSubjectId: grant.subjectId
|
|
54365
|
+
});
|
|
52933
54366
|
await removeWorkspaceMember(deps.db, {
|
|
52934
54367
|
accountId: grant.accountId,
|
|
52935
54368
|
workspaceId,
|
|
@@ -52939,6 +54372,62 @@ function registerWorkspaceRoutes(app, deps) {
|
|
|
52939
54372
|
return c.body(null, 204);
|
|
52940
54373
|
});
|
|
52941
54374
|
}
|
|
54375
|
+
function workspaceLimit(deps) {
|
|
54376
|
+
if (deps.settings.usageLimitsMode !== "static" && deps.settings.usageLimitsMode !== "managed") {
|
|
54377
|
+
return null;
|
|
54378
|
+
}
|
|
54379
|
+
return configuredStaticUsageLimits2(deps.settings).maxWorkspacesPerAccount ?? null;
|
|
54380
|
+
}
|
|
54381
|
+
async function listWorkspacePeople(deps, workspaceId) {
|
|
54382
|
+
const members = await listWorkspaceMembers(deps.db, workspaceId);
|
|
54383
|
+
const userIds = members.flatMap(
|
|
54384
|
+
(member) => member.subjectId.startsWith("user:") ? [member.subjectId.slice("user:".length)] : []
|
|
54385
|
+
);
|
|
54386
|
+
const profiles = await getManagedUserProfilesByIds(deps.db, userIds);
|
|
54387
|
+
const profileBySubject = new Map(
|
|
54388
|
+
profiles.map((profile) => [`user:${profile.id}`, profile.name?.trim() || profile.email])
|
|
54389
|
+
);
|
|
54390
|
+
return members.map((member) => ({
|
|
54391
|
+
...member,
|
|
54392
|
+
subjectLabel: profileBySubject.get(member.subjectId) ?? member.subjectLabel
|
|
54393
|
+
}));
|
|
54394
|
+
}
|
|
54395
|
+
function rethrowWorkspaceMemberCandidateError(error) {
|
|
54396
|
+
const managementCode = error && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
54397
|
+
if (managementCode === "WORKSPACE_MEMBER_ALREADY_EXISTS") {
|
|
54398
|
+
throw new HTTPException55(409, {
|
|
54399
|
+
message: "this person already has access to the workspace"
|
|
54400
|
+
});
|
|
54401
|
+
}
|
|
54402
|
+
if (managementCode === "WORKSPACE_MEMBER_NOT_FOUND") {
|
|
54403
|
+
throw new HTTPException55(404, { message: "member not found" });
|
|
54404
|
+
}
|
|
54405
|
+
if (managementCode === "WORKSPACE_MEMBER_SELF_UPDATE") {
|
|
54406
|
+
throw new HTTPException55(409, {
|
|
54407
|
+
message: "you cannot change your own workspace access"
|
|
54408
|
+
});
|
|
54409
|
+
}
|
|
54410
|
+
if (managementCode === "WORKSPACE_MEMBER_LAST_ADMIN") {
|
|
54411
|
+
throw new HTTPException55(409, {
|
|
54412
|
+
message: "the workspace must keep at least one administrator"
|
|
54413
|
+
});
|
|
54414
|
+
}
|
|
54415
|
+
const sqlState = nestedPostgresSqlState4(error);
|
|
54416
|
+
if (sqlState === "P0002") {
|
|
54417
|
+
throw new HTTPException55(404, { message: "workspace not found" });
|
|
54418
|
+
}
|
|
54419
|
+
if (sqlState === "42501") {
|
|
54420
|
+
throw new HTTPException55(403, {
|
|
54421
|
+
message: "workspace member management is not allowed"
|
|
54422
|
+
});
|
|
54423
|
+
}
|
|
54424
|
+
if (sqlState === "54000") {
|
|
54425
|
+
throw new HTTPException55(409, {
|
|
54426
|
+
message: "this organization has too many members to show here"
|
|
54427
|
+
});
|
|
54428
|
+
}
|
|
54429
|
+
throw error;
|
|
54430
|
+
}
|
|
52942
54431
|
function normalizeAgentInstructions(value) {
|
|
52943
54432
|
if (value === null) {
|
|
52944
54433
|
return null;
|
|
@@ -52946,10 +54435,12 @@ function normalizeAgentInstructions(value) {
|
|
|
52946
54435
|
const trimmed = value.trim();
|
|
52947
54436
|
return trimmed.length > 0 ? trimmed : null;
|
|
52948
54437
|
}
|
|
52949
|
-
function
|
|
54438
|
+
function requireAccountPermission2(context, accountId, permission) {
|
|
52950
54439
|
const grant = context.accountGrants.find((candidate) => candidate.accountId === accountId);
|
|
52951
54440
|
if (!grant || !grant.permissions.includes(permission) && !grant.permissions.includes("account:admin")) {
|
|
52952
|
-
throw new HTTPException55(403, {
|
|
54441
|
+
throw new HTTPException55(403, {
|
|
54442
|
+
message: `missing permission: ${permission}`
|
|
54443
|
+
});
|
|
52953
54444
|
}
|
|
52954
54445
|
}
|
|
52955
54446
|
|
|
@@ -53505,6 +54996,7 @@ function registerWorkspaceLearningRoutes(app, deps) {
|
|
|
53505
54996
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
53506
54997
|
import {
|
|
53507
54998
|
ActivateCompanyProfileRevisionRequest,
|
|
54999
|
+
CompanyProfileAgentPolicy,
|
|
53508
55000
|
CompanyProfileConflictResponse,
|
|
53509
55001
|
CompanyProfileDiffRequest,
|
|
53510
55002
|
CompanyProfileDiffResponse,
|
|
@@ -53514,23 +55006,27 @@ import {
|
|
|
53514
55006
|
CompanyProfileOperationReuseResponse,
|
|
53515
55007
|
CompanyProfileRevision,
|
|
53516
55008
|
RollbackCompanyProfileRequest,
|
|
53517
|
-
UpdateCompanyProfileRequest
|
|
55009
|
+
UpdateCompanyProfileRequest,
|
|
55010
|
+
UpdateCompanyProfileAgentPolicyRequest
|
|
53518
55011
|
} from "@opengeni/contracts";
|
|
53519
55012
|
import {
|
|
53520
55013
|
requireAccessGrant as requireAccessGrant35,
|
|
53521
|
-
requireAccessGrantAuthorization as
|
|
55014
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization12
|
|
53522
55015
|
} from "@opengeni/core";
|
|
53523
55016
|
import {
|
|
53524
55017
|
activateCompanyProfileRevision,
|
|
55018
|
+
CompanyProfileAgentPolicyError,
|
|
53525
55019
|
CompanyProfileConflictError,
|
|
53526
55020
|
CompanyProfileInvalidOperationError,
|
|
53527
55021
|
CompanyProfileNotFoundError,
|
|
53528
55022
|
CompanyProfileOperationReuseError,
|
|
53529
55023
|
diffCompanyProfileRevisions,
|
|
53530
55024
|
getCompanyProfileRevision,
|
|
55025
|
+
getCompanyProfileAgentPolicy,
|
|
53531
55026
|
listCompanyProfile,
|
|
53532
55027
|
rollbackCompanyProfileRevision,
|
|
53533
|
-
updateCompanyProfile
|
|
55028
|
+
updateCompanyProfile,
|
|
55029
|
+
updateCompanyProfileAgentPolicy
|
|
53534
55030
|
} from "@opengeni/db";
|
|
53535
55031
|
import { HTTPException as HTTPException58 } from "hono/http-exception";
|
|
53536
55032
|
import { z as z17 } from "zod";
|
|
@@ -53574,6 +55070,15 @@ function profileError(context, error) {
|
|
|
53574
55070
|
if (error instanceof CompanyProfileInvalidOperationError) {
|
|
53575
55071
|
return context.json({ code: "INVALID_COMPANY_PROFILE_OPERATION", message: error.message }, 422);
|
|
53576
55072
|
}
|
|
55073
|
+
if (error instanceof CompanyProfileAgentPolicyError) {
|
|
55074
|
+
if (error.code === "authority_unavailable") {
|
|
55075
|
+
throw new HTTPException58(403, { message: error.message });
|
|
55076
|
+
}
|
|
55077
|
+
if (error.code === "policy_conflict" || error.code === "operation_reused") {
|
|
55078
|
+
throw new HTTPException58(409, { message: error.message });
|
|
55079
|
+
}
|
|
55080
|
+
throw new HTTPException58(422, { message: error.message });
|
|
55081
|
+
}
|
|
53577
55082
|
throw error;
|
|
53578
55083
|
}
|
|
53579
55084
|
function revisionId(context) {
|
|
@@ -53584,6 +55089,54 @@ function revisionId(context) {
|
|
|
53584
55089
|
}
|
|
53585
55090
|
function registerCompanyProfileRoutes(app, deps) {
|
|
53586
55091
|
const base = "/v1/workspaces/:workspaceId/company-profile";
|
|
55092
|
+
app.get(`${base}/agent-policy`, async (context) => {
|
|
55093
|
+
const workspaceId = context.req.param("workspaceId");
|
|
55094
|
+
const access = await requireAccessGrantAuthorization12(
|
|
55095
|
+
context,
|
|
55096
|
+
deps,
|
|
55097
|
+
workspaceId,
|
|
55098
|
+
"workspace:read"
|
|
55099
|
+
);
|
|
55100
|
+
requireDirectAccountAdmin(access);
|
|
55101
|
+
try {
|
|
55102
|
+
return context.json(
|
|
55103
|
+
CompanyProfileAgentPolicy.parse(
|
|
55104
|
+
await getCompanyProfileAgentPolicy(deps.db, {
|
|
55105
|
+
accountId: access.grant.accountId,
|
|
55106
|
+
workspaceId,
|
|
55107
|
+
actorSubjectId: access.grant.subjectId
|
|
55108
|
+
})
|
|
55109
|
+
)
|
|
55110
|
+
);
|
|
55111
|
+
} catch (error) {
|
|
55112
|
+
return profileError(context, error);
|
|
55113
|
+
}
|
|
55114
|
+
});
|
|
55115
|
+
app.patch(`${base}/agent-policy`, async (context) => {
|
|
55116
|
+
const workspaceId = context.req.param("workspaceId");
|
|
55117
|
+
const access = await requireAccessGrantAuthorization12(
|
|
55118
|
+
context,
|
|
55119
|
+
deps,
|
|
55120
|
+
workspaceId,
|
|
55121
|
+
"workspace:read"
|
|
55122
|
+
);
|
|
55123
|
+
requireDirectAccountAdmin(access);
|
|
55124
|
+
const request = await parseBody3(context, UpdateCompanyProfileAgentPolicyRequest);
|
|
55125
|
+
try {
|
|
55126
|
+
return context.json(
|
|
55127
|
+
CompanyProfileAgentPolicy.parse(
|
|
55128
|
+
await updateCompanyProfileAgentPolicy(deps.db, {
|
|
55129
|
+
accountId: access.grant.accountId,
|
|
55130
|
+
workspaceId,
|
|
55131
|
+
actorSubjectId: access.grant.subjectId,
|
|
55132
|
+
...request
|
|
55133
|
+
})
|
|
55134
|
+
)
|
|
55135
|
+
);
|
|
55136
|
+
} catch (error) {
|
|
55137
|
+
return profileError(context, error);
|
|
55138
|
+
}
|
|
55139
|
+
});
|
|
53587
55140
|
app.get(base, async (context) => {
|
|
53588
55141
|
const workspaceId = context.req.param("workspaceId");
|
|
53589
55142
|
const grant = await requireAccessGrant35(context, deps, workspaceId, "workspace:read");
|
|
@@ -53605,7 +55158,7 @@ function registerCompanyProfileRoutes(app, deps) {
|
|
|
53605
55158
|
});
|
|
53606
55159
|
app.put(base, async (context) => {
|
|
53607
55160
|
const workspaceId = context.req.param("workspaceId");
|
|
53608
|
-
const access = await
|
|
55161
|
+
const access = await requireAccessGrantAuthorization12(
|
|
53609
55162
|
context,
|
|
53610
55163
|
deps,
|
|
53611
55164
|
workspaceId,
|
|
@@ -53657,7 +55210,7 @@ function registerCompanyProfileRoutes(app, deps) {
|
|
|
53657
55210
|
});
|
|
53658
55211
|
app.post(`${base}/rollback`, async (context) => {
|
|
53659
55212
|
const workspaceId = context.req.param("workspaceId");
|
|
53660
|
-
const access = await
|
|
55213
|
+
const access = await requireAccessGrantAuthorization12(
|
|
53661
55214
|
context,
|
|
53662
55215
|
deps,
|
|
53663
55216
|
workspaceId,
|
|
@@ -53704,7 +55257,7 @@ function registerCompanyProfileRoutes(app, deps) {
|
|
|
53704
55257
|
});
|
|
53705
55258
|
app.post(`${base}/revisions/:revisionId/activate`, async (context) => {
|
|
53706
55259
|
const workspaceId = context.req.param("workspaceId");
|
|
53707
|
-
const access = await
|
|
55260
|
+
const access = await requireAccessGrantAuthorization12(
|
|
53708
55261
|
context,
|
|
53709
55262
|
deps,
|
|
53710
55263
|
workspaceId,
|
|
@@ -54628,7 +56181,7 @@ import {
|
|
|
54628
56181
|
} from "@opengeni/contracts";
|
|
54629
56182
|
import {
|
|
54630
56183
|
requireAccessGrant as requireAccessGrant37,
|
|
54631
|
-
requireAccessGrantAuthorization as
|
|
56184
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization13
|
|
54632
56185
|
} from "@opengeni/core";
|
|
54633
56186
|
import {
|
|
54634
56187
|
listSlackTaskPolicy,
|
|
@@ -54687,7 +56240,7 @@ function registerSlackTaskPolicyRoutes(app, deps) {
|
|
|
54687
56240
|
});
|
|
54688
56241
|
app.put(base, async (context) => {
|
|
54689
56242
|
const workspaceId = context.req.param("workspaceId");
|
|
54690
|
-
const access = await
|
|
56243
|
+
const access = await requireAccessGrantAuthorization13(
|
|
54691
56244
|
context,
|
|
54692
56245
|
deps,
|
|
54693
56246
|
workspaceId,
|
|
@@ -55155,7 +56708,7 @@ import {
|
|
|
55155
56708
|
import {
|
|
55156
56709
|
hasPermission as hasPermission22,
|
|
55157
56710
|
requireAccessGrant as requireAccessGrant40,
|
|
55158
|
-
requireAccessGrantAuthorization as
|
|
56711
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization14
|
|
55159
56712
|
} from "@opengeni/core";
|
|
55160
56713
|
import {
|
|
55161
56714
|
activatePreferenceRegistryRevision,
|
|
@@ -55173,7 +56726,7 @@ import {
|
|
|
55173
56726
|
PreferenceRegistryInitiatorError,
|
|
55174
56727
|
PreferenceRegistryInvalidOperationError,
|
|
55175
56728
|
PreferenceRegistryNotFoundError,
|
|
55176
|
-
PreferenceRegistryStableKeyConflictError,
|
|
56729
|
+
PreferenceRegistryStableKeyConflictError as PreferenceRegistryStableKeyConflictError3,
|
|
55177
56730
|
rejectPreferenceRegistryProposal,
|
|
55178
56731
|
supersedePreferenceRegistry
|
|
55179
56732
|
} from "@opengeni/db";
|
|
@@ -55250,7 +56803,7 @@ function preferenceError(context, error) {
|
|
|
55250
56803
|
if (error instanceof PreferenceRegistryNotFoundError) {
|
|
55251
56804
|
return context.json({ code: "PREFERENCE_REGISTRY_NOT_FOUND", message: error.message }, 404);
|
|
55252
56805
|
}
|
|
55253
|
-
if (error instanceof
|
|
56806
|
+
if (error instanceof PreferenceRegistryStableKeyConflictError3) {
|
|
55254
56807
|
return context.json({ code: error.code, message: error.message }, 409);
|
|
55255
56808
|
}
|
|
55256
56809
|
if (error instanceof PreferenceRegistryInvalidOperationError) {
|
|
@@ -55303,7 +56856,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
|
|
|
55303
56856
|
});
|
|
55304
56857
|
app.post(`${base}/proposals`, async (context) => {
|
|
55305
56858
|
const workspaceId = context.req.param("workspaceId");
|
|
55306
|
-
const access = await
|
|
56859
|
+
const access = await requireAccessGrantAuthorization14(
|
|
55307
56860
|
context,
|
|
55308
56861
|
deps,
|
|
55309
56862
|
workspaceId,
|
|
@@ -55383,7 +56936,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
|
|
|
55383
56936
|
});
|
|
55384
56937
|
app.post(`${base}/:preferenceId/activate`, async (context) => {
|
|
55385
56938
|
const workspaceId = context.req.param("workspaceId");
|
|
55386
|
-
const access = await
|
|
56939
|
+
const access = await requireAccessGrantAuthorization14(
|
|
55387
56940
|
context,
|
|
55388
56941
|
deps,
|
|
55389
56942
|
workspaceId,
|
|
@@ -55412,7 +56965,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
|
|
|
55412
56965
|
});
|
|
55413
56966
|
app.post(`${base}/:preferenceId/correct`, async (context) => {
|
|
55414
56967
|
const workspaceId = context.req.param("workspaceId");
|
|
55415
|
-
const access = await
|
|
56968
|
+
const access = await requireAccessGrantAuthorization14(
|
|
55416
56969
|
context,
|
|
55417
56970
|
deps,
|
|
55418
56971
|
workspaceId,
|
|
@@ -55441,7 +56994,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
|
|
|
55441
56994
|
});
|
|
55442
56995
|
app.post(`${base}/:preferenceId/scope`, async (context) => {
|
|
55443
56996
|
const workspaceId = context.req.param("workspaceId");
|
|
55444
|
-
const access = await
|
|
56997
|
+
const access = await requireAccessGrantAuthorization14(
|
|
55445
56998
|
context,
|
|
55446
56999
|
deps,
|
|
55447
57000
|
workspaceId,
|
|
@@ -55470,7 +57023,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
|
|
|
55470
57023
|
});
|
|
55471
57024
|
app.post(`${base}/:preferenceId/deactivate`, async (context) => {
|
|
55472
57025
|
const workspaceId = context.req.param("workspaceId");
|
|
55473
|
-
const access = await
|
|
57026
|
+
const access = await requireAccessGrantAuthorization14(
|
|
55474
57027
|
context,
|
|
55475
57028
|
deps,
|
|
55476
57029
|
workspaceId,
|
|
@@ -55499,7 +57052,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
|
|
|
55499
57052
|
});
|
|
55500
57053
|
app.post(`${base}/:preferenceId/supersede`, async (context) => {
|
|
55501
57054
|
const workspaceId = context.req.param("workspaceId");
|
|
55502
|
-
const access = await
|
|
57055
|
+
const access = await requireAccessGrantAuthorization14(
|
|
55503
57056
|
context,
|
|
55504
57057
|
deps,
|
|
55505
57058
|
workspaceId,
|
|
@@ -55528,7 +57081,7 @@ function registerPreferenceRegistryRoutes(app, deps) {
|
|
|
55528
57081
|
});
|
|
55529
57082
|
app.post(`${base}/:preferenceId/reject`, async (context) => {
|
|
55530
57083
|
const workspaceId = context.req.param("workspaceId");
|
|
55531
|
-
const access = await
|
|
57084
|
+
const access = await requireAccessGrantAuthorization14(
|
|
55532
57085
|
context,
|
|
55533
57086
|
deps,
|
|
55534
57087
|
workspaceId,
|
|
@@ -56980,7 +58533,8 @@ import {
|
|
|
56980
58533
|
} from "@opengeni/contracts";
|
|
56981
58534
|
import {
|
|
56982
58535
|
getManagedSession as getManagedSession3,
|
|
56983
|
-
organizationMembershipHttpStatus
|
|
58536
|
+
organizationMembershipHttpStatus,
|
|
58537
|
+
requireCanonicalLocalAccountAdministrator as requireCanonicalLocalAccountAdministrator2
|
|
56984
58538
|
} from "@opengeni/core";
|
|
56985
58539
|
import {
|
|
56986
58540
|
acceptOrganizationInvitation,
|
|
@@ -56998,7 +58552,7 @@ import {
|
|
|
56998
58552
|
listOrganizationAdministrationMembers,
|
|
56999
58553
|
listOrganizationInvitations,
|
|
57000
58554
|
listSelfOrganizationInvitations,
|
|
57001
|
-
nestedPostgresSqlState as
|
|
58555
|
+
nestedPostgresSqlState as nestedPostgresSqlState5,
|
|
57002
58556
|
prepareOrganizationUserSetupDelivery,
|
|
57003
58557
|
revokeOrganizationInvitation,
|
|
57004
58558
|
settleOrganizationUserSetupDelivery,
|
|
@@ -57031,6 +58585,20 @@ async function requireManagedHuman(context, deps) {
|
|
|
57031
58585
|
}
|
|
57032
58586
|
return { session, subjectId: `user:${session.user.id}` };
|
|
57033
58587
|
}
|
|
58588
|
+
async function requireOrganizationAdministrator(context, deps, organizationId2) {
|
|
58589
|
+
if (deps.settings.productAccessMode === "managed") {
|
|
58590
|
+
return await requireManagedHuman(context, deps);
|
|
58591
|
+
}
|
|
58592
|
+
if (deps.settings.productAccessMode === "local") {
|
|
58593
|
+
const { subjectId } = await requireCanonicalLocalAccountAdministrator2(
|
|
58594
|
+
context,
|
|
58595
|
+
deps,
|
|
58596
|
+
organizationId2
|
|
58597
|
+
);
|
|
58598
|
+
return { subjectId };
|
|
58599
|
+
}
|
|
58600
|
+
throw new HTTPException67(401, { message: "organization administrator session required" });
|
|
58601
|
+
}
|
|
57034
58602
|
async function parseBody6(context, schema) {
|
|
57035
58603
|
const parsed = schema.safeParse(await context.req.json().catch(() => null));
|
|
57036
58604
|
if (!parsed.success) {
|
|
@@ -57046,7 +58614,7 @@ function parseId(schema, value, label) {
|
|
|
57046
58614
|
return parsed.data;
|
|
57047
58615
|
}
|
|
57048
58616
|
function rethrowMembershipError(error) {
|
|
57049
|
-
const status = organizationMembershipHttpStatus(
|
|
58617
|
+
const status = organizationMembershipHttpStatus(nestedPostgresSqlState5(error));
|
|
57050
58618
|
if (status !== null) {
|
|
57051
58619
|
throw new HTTPException67(status, {
|
|
57052
58620
|
message: status === 403 ? "organization administration is not authorized" : status === 404 ? "organization resource not found" : status === 409 ? "organization membership state changed; refresh and retry" : "invalid organization membership operation"
|
|
@@ -57089,7 +58657,7 @@ function registerOrganizationMembershipRoutes(app, deps) {
|
|
|
57089
58657
|
})
|
|
57090
58658
|
);
|
|
57091
58659
|
} catch (error) {
|
|
57092
|
-
if (
|
|
58660
|
+
if (nestedPostgresSqlState5(error) === "42501") {
|
|
57093
58661
|
throw new HTTPException67(403, {
|
|
57094
58662
|
message: "organization membership is not active"
|
|
57095
58663
|
});
|
|
@@ -57126,12 +58694,12 @@ function registerOrganizationMembershipRoutes(app, deps) {
|
|
|
57126
58694
|
}
|
|
57127
58695
|
});
|
|
57128
58696
|
app.get("/v1/organizations/:organizationId/overview", async (context) => {
|
|
57129
|
-
const { subjectId } = await requireManagedHuman(context, deps);
|
|
57130
58697
|
const organizationId2 = parseId(
|
|
57131
58698
|
OrganizationId,
|
|
57132
58699
|
context.req.param("organizationId"),
|
|
57133
58700
|
"organization id"
|
|
57134
58701
|
);
|
|
58702
|
+
const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId2);
|
|
57135
58703
|
try {
|
|
57136
58704
|
return context.json(
|
|
57137
58705
|
OrganizationAdministrationOverview.parse(
|
|
@@ -57146,12 +58714,12 @@ function registerOrganizationMembershipRoutes(app, deps) {
|
|
|
57146
58714
|
}
|
|
57147
58715
|
});
|
|
57148
58716
|
app.patch("/v1/organizations/:organizationId", async (context) => {
|
|
57149
|
-
const { subjectId } = await requireManagedHuman(context, deps);
|
|
57150
58717
|
const organizationId2 = parseId(
|
|
57151
58718
|
OrganizationId,
|
|
57152
58719
|
context.req.param("organizationId"),
|
|
57153
58720
|
"organization id"
|
|
57154
58721
|
);
|
|
58722
|
+
const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId2);
|
|
57155
58723
|
const payload = await parseBody6(context, UpdateOrganizationNameRequest);
|
|
57156
58724
|
try {
|
|
57157
58725
|
return context.json(
|
|
@@ -57168,12 +58736,12 @@ function registerOrganizationMembershipRoutes(app, deps) {
|
|
|
57168
58736
|
}
|
|
57169
58737
|
});
|
|
57170
58738
|
app.patch("/v1/organizations/:organizationId/workspaces/:workspaceId", async (context) => {
|
|
57171
|
-
const { subjectId } = await requireManagedHuman(context, deps);
|
|
57172
58739
|
const organizationId2 = parseId(
|
|
57173
58740
|
OrganizationId,
|
|
57174
58741
|
context.req.param("organizationId"),
|
|
57175
58742
|
"organization id"
|
|
57176
58743
|
);
|
|
58744
|
+
const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId2);
|
|
57177
58745
|
const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
|
|
57178
58746
|
const payload = await parseBody6(context, UpdateOrganizationWorkspaceRequest);
|
|
57179
58747
|
try {
|
|
@@ -57194,12 +58762,12 @@ function registerOrganizationMembershipRoutes(app, deps) {
|
|
|
57194
58762
|
}
|
|
57195
58763
|
});
|
|
57196
58764
|
app.post("/v1/organizations/:organizationId/workspaces", async (context) => {
|
|
57197
|
-
const { subjectId } = await requireManagedHuman(context, deps);
|
|
57198
58765
|
const organizationId2 = parseId(
|
|
57199
58766
|
OrganizationId,
|
|
57200
58767
|
context.req.param("organizationId"),
|
|
57201
58768
|
"organization id"
|
|
57202
58769
|
);
|
|
58770
|
+
const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId2);
|
|
57203
58771
|
const payload = await parseBody6(context, CreateOrganizationWorkspaceRequest);
|
|
57204
58772
|
try {
|
|
57205
58773
|
return context.json(
|
|
@@ -57220,12 +58788,12 @@ function registerOrganizationMembershipRoutes(app, deps) {
|
|
|
57220
58788
|
app.patch(
|
|
57221
58789
|
"/v1/organizations/:organizationId/workspaces/:workspaceId/settings",
|
|
57222
58790
|
async (context) => {
|
|
57223
|
-
const { subjectId } = await requireManagedHuman(context, deps);
|
|
57224
58791
|
const organizationId2 = parseId(
|
|
57225
58792
|
OrganizationId,
|
|
57226
58793
|
context.req.param("organizationId"),
|
|
57227
58794
|
"organization id"
|
|
57228
58795
|
);
|
|
58796
|
+
const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId2);
|
|
57229
58797
|
const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
|
|
57230
58798
|
const payload = await parseBody6(context, UpdateWorkspaceSettingsRequest2);
|
|
57231
58799
|
try {
|
|
@@ -57350,7 +58918,7 @@ function registerOrganizationMembershipRoutes(app, deps) {
|
|
|
57350
58918
|
)
|
|
57351
58919
|
);
|
|
57352
58920
|
} catch (error) {
|
|
57353
|
-
if (
|
|
58921
|
+
if (nestedPostgresSqlState5(error) === "55000") {
|
|
57354
58922
|
throw new HTTPException67(409, {
|
|
57355
58923
|
message: "private-session readiness is not activated for this organization"
|
|
57356
58924
|
});
|
|
@@ -57539,12 +59107,12 @@ function registerOrganizationMembershipRoutes(app, deps) {
|
|
|
57539
59107
|
}
|
|
57540
59108
|
);
|
|
57541
59109
|
app.get("/v1/organizations/:organizationId/members", async (context) => {
|
|
57542
|
-
const { subjectId } = await requireManagedHuman(context, deps);
|
|
57543
59110
|
const organizationId2 = parseId(
|
|
57544
59111
|
OrganizationId,
|
|
57545
59112
|
context.req.param("organizationId"),
|
|
57546
59113
|
"organization id"
|
|
57547
59114
|
);
|
|
59115
|
+
const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId2);
|
|
57548
59116
|
try {
|
|
57549
59117
|
return context.json(
|
|
57550
59118
|
ListOrganizationAdministrationMembersResponse.parse({
|
|
@@ -57584,12 +59152,12 @@ function registerOrganizationMembershipRoutes(app, deps) {
|
|
|
57584
59152
|
}
|
|
57585
59153
|
});
|
|
57586
59154
|
app.get("/v1/organizations/:organizationId/retention-policy", async (context) => {
|
|
57587
|
-
const { subjectId } = await requireManagedHuman(context, deps);
|
|
57588
59155
|
const organizationId2 = parseId(
|
|
57589
59156
|
OrganizationId,
|
|
57590
59157
|
context.req.param("organizationId"),
|
|
57591
59158
|
"organization id"
|
|
57592
59159
|
);
|
|
59160
|
+
const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId2);
|
|
57593
59161
|
try {
|
|
57594
59162
|
return context.json(
|
|
57595
59163
|
OrganizationRetentionPolicy.parse(
|
|
@@ -57604,12 +59172,12 @@ function registerOrganizationMembershipRoutes(app, deps) {
|
|
|
57604
59172
|
}
|
|
57605
59173
|
});
|
|
57606
59174
|
app.patch("/v1/organizations/:organizationId/retention-policy", async (context) => {
|
|
57607
|
-
const { subjectId } = await requireManagedHuman(context, deps);
|
|
57608
59175
|
const organizationId2 = parseId(
|
|
57609
59176
|
OrganizationId,
|
|
57610
59177
|
context.req.param("organizationId"),
|
|
57611
59178
|
"organization id"
|
|
57612
59179
|
);
|
|
59180
|
+
const { subjectId } = await requireOrganizationAdministrator(context, deps, organizationId2);
|
|
57613
59181
|
const payload = await parseBody6(context, UpdateOrganizationRetentionPolicyRequest);
|
|
57614
59182
|
try {
|
|
57615
59183
|
return context.json(
|
|
@@ -57963,7 +59531,7 @@ import {
|
|
|
57963
59531
|
completeSelfServiceOrganizationSetup,
|
|
57964
59532
|
completeOrganizationUserSetup,
|
|
57965
59533
|
getSelfServiceOrganizationOnboardingState,
|
|
57966
|
-
nestedPostgresSqlState as
|
|
59534
|
+
nestedPostgresSqlState as nestedPostgresSqlState6,
|
|
57967
59535
|
preflightOrganizationUserSetup,
|
|
57968
59536
|
previewOrganizationUserSetup
|
|
57969
59537
|
} from "@opengeni/db";
|
|
@@ -58012,7 +59580,7 @@ function registerManagedOnboardingRoutes(app, deps, options = {}) {
|
|
|
58012
59580
|
)
|
|
58013
59581
|
);
|
|
58014
59582
|
} catch (error) {
|
|
58015
|
-
const sqlState =
|
|
59583
|
+
const sqlState = nestedPostgresSqlState6(error);
|
|
58016
59584
|
if (sqlState === "22023") {
|
|
58017
59585
|
throw new HTTPException69(422, {
|
|
58018
59586
|
message: "invalid organization setup request"
|
|
@@ -58092,7 +59660,7 @@ function registerManagedOnboardingRoutes(app, deps, options = {}) {
|
|
|
58092
59660
|
)
|
|
58093
59661
|
);
|
|
58094
59662
|
} catch (error) {
|
|
58095
|
-
const sqlState =
|
|
59663
|
+
const sqlState = nestedPostgresSqlState6(error);
|
|
58096
59664
|
if (sqlState === "22023") {
|
|
58097
59665
|
throw new HTTPException69(422, {
|
|
58098
59666
|
message: "invalid account setup request"
|
|
@@ -58210,6 +59778,7 @@ import {
|
|
|
58210
59778
|
CompleteManagedAuthLoginTransactionResponse,
|
|
58211
59779
|
LogoutManagedAuthLoginSlotRequest,
|
|
58212
59780
|
LogoutManagedAuthSessionSetRequest,
|
|
59781
|
+
MANAGED_AUTH_TRANSACTION_TTL_SECONDS,
|
|
58213
59782
|
MANAGED_AUTH_SESSION_SET_API_CONTRACT_HEADER,
|
|
58214
59783
|
MANAGED_AUTH_SESSION_SET_API_CONTRACT_REVISION,
|
|
58215
59784
|
ManagedAuthDeepLinkResolution,
|
|
@@ -58217,7 +59786,9 @@ import {
|
|
|
58217
59786
|
ManagedAuthSessionSetProjection,
|
|
58218
59787
|
ManagedAuthSessionSetErrorCode,
|
|
58219
59788
|
ResolveManagedAuthDeepLinkRequest,
|
|
58220
|
-
SelectManagedAuthLoginSlotRequest
|
|
59789
|
+
SelectManagedAuthLoginSlotRequest,
|
|
59790
|
+
StartManagedAuthSocialTransactionRequest,
|
|
59791
|
+
StartManagedAuthSocialTransactionResponse
|
|
58221
59792
|
} from "@opengeni/contracts/managed-auth-session-sets";
|
|
58222
59793
|
import { hasPermission as hasPermission23, requireSessionAuthorization as requireSessionAuthorization8 } from "@opengeni/core";
|
|
58223
59794
|
import {
|
|
@@ -58260,6 +59831,13 @@ import {
|
|
|
58260
59831
|
import { ensureManagedAccessForUser as ensureManagedAccessForUser2, getSession as getSession8 } from "@opengeni/db";
|
|
58261
59832
|
import { deleteCookie as deleteCookie3, getCookie, setCookie as setCookie3 } from "hono/cookie";
|
|
58262
59833
|
import { HTTPException as HTTPException70 } from "hono/http-exception";
|
|
59834
|
+
import { z as z24 } from "zod";
|
|
59835
|
+
var ManagedAuthSocialStartReceipt = z24.object({
|
|
59836
|
+
version: z24.literal(1),
|
|
59837
|
+
requestDigest: z24.string().regex(/^[0-9a-f]{64}$/u),
|
|
59838
|
+
url: z24.string().url().max(4096),
|
|
59839
|
+
stateCookie: z24.string().min(1).max(4096)
|
|
59840
|
+
}).strict();
|
|
58263
59841
|
function registerManagedAuthSessionSetRoutes(app, deps) {
|
|
58264
59842
|
const noStore = async (context, next) => {
|
|
58265
59843
|
context.header("cache-control", "no-store");
|
|
@@ -58482,6 +60060,125 @@ function registerManagedAuthSessionSetRoutes(app, deps) {
|
|
|
58482
60060
|
throwHttp(error);
|
|
58483
60061
|
}
|
|
58484
60062
|
});
|
|
60063
|
+
app.post("/v1/auth/session-set/transactions/social", async (context) => {
|
|
60064
|
+
const body4 = await bodyAs(context, StartManagedAuthSocialTransactionRequest);
|
|
60065
|
+
const { authority, actorEpoch } = await requireMutation(context, deps, body4.expectedGeneration);
|
|
60066
|
+
const available = requireAvailable(deps);
|
|
60067
|
+
if (!managedAuthSocialProviderConfigured(deps, body4.provider)) {
|
|
60068
|
+
throw managedAuthApiError(404, "managed_authentication_unavailable");
|
|
60069
|
+
}
|
|
60070
|
+
const transactionSecret = requireTransactionSecret(context, body4.transactionId);
|
|
60071
|
+
const publicBaseUrl = deps.settings.publicBaseUrl;
|
|
60072
|
+
if (!publicBaseUrl) {
|
|
60073
|
+
throw managedAuthApiError(503, "managed_authentication_unavailable", {
|
|
60074
|
+
retryable: true
|
|
60075
|
+
});
|
|
60076
|
+
}
|
|
60077
|
+
const callbackURL = new URL("/account-auth", publicBaseUrl);
|
|
60078
|
+
callbackURL.searchParams.set("transaction", body4.transactionId);
|
|
60079
|
+
callbackURL.searchParams.set("social", "complete");
|
|
60080
|
+
const errorCallbackURL = new URL(callbackURL);
|
|
60081
|
+
errorCallbackURL.searchParams.set("social", "error");
|
|
60082
|
+
try {
|
|
60083
|
+
const authorityHash = managedAuthSha256(authority);
|
|
60084
|
+
const transactionSecretHash = managedAuthSha256(transactionSecret);
|
|
60085
|
+
const requestDigest = digest(deps, {
|
|
60086
|
+
operation: "social_start",
|
|
60087
|
+
authorityHash,
|
|
60088
|
+
actorEpoch,
|
|
60089
|
+
transactionSecretHash,
|
|
60090
|
+
request: body4
|
|
60091
|
+
});
|
|
60092
|
+
const receiptIdentifier = `opengeni-managed-social-start:${authorityHash}:${body4.operationId}`;
|
|
60093
|
+
const receipt = await deps.db.transaction(async (transaction) => {
|
|
60094
|
+
await transaction.execute(
|
|
60095
|
+
sql`select pg_advisory_xact_lock(hashtextextended(${receiptIdentifier}, 0))`
|
|
60096
|
+
);
|
|
60097
|
+
const authContext = await available.managedAuth.$context;
|
|
60098
|
+
const stored = await authContext.internalAdapter.findVerificationValue(receiptIdentifier);
|
|
60099
|
+
if (stored && new Date(stored.expiresAt).getTime() > Date.now()) {
|
|
60100
|
+
let parsed2;
|
|
60101
|
+
try {
|
|
60102
|
+
parsed2 = ManagedAuthSocialStartReceipt.safeParse(JSON.parse(stored.value));
|
|
60103
|
+
} catch (error) {
|
|
60104
|
+
throw new ManagedAuthCompletionOutcomeUnknownError({
|
|
60105
|
+
cause: error
|
|
60106
|
+
});
|
|
60107
|
+
}
|
|
60108
|
+
if (!parsed2.success) {
|
|
60109
|
+
throw new ManagedAuthCompletionOutcomeUnknownError({
|
|
60110
|
+
cause: parsed2.error
|
|
60111
|
+
});
|
|
60112
|
+
}
|
|
60113
|
+
if (parsed2.data.requestDigest !== requestDigest) {
|
|
60114
|
+
throw new ManagedAuthSessionSetOperationReuseError();
|
|
60115
|
+
}
|
|
60116
|
+
return parsed2.data;
|
|
60117
|
+
}
|
|
60118
|
+
if (stored) {
|
|
60119
|
+
await authContext.internalAdapter.deleteVerificationByIdentifier(receiptIdentifier);
|
|
60120
|
+
}
|
|
60121
|
+
const result = await available.managedAuth.api.signInSocial({
|
|
60122
|
+
body: {
|
|
60123
|
+
provider: body4.provider,
|
|
60124
|
+
disableRedirect: true,
|
|
60125
|
+
callbackURL: callbackURL.toString(),
|
|
60126
|
+
errorCallbackURL: errorCallbackURL.toString(),
|
|
60127
|
+
additionalData: {
|
|
60128
|
+
opengeniManagedAuth: {
|
|
60129
|
+
version: 1,
|
|
60130
|
+
operationId: body4.operationId,
|
|
60131
|
+
provider: body4.provider,
|
|
60132
|
+
transactionId: body4.transactionId,
|
|
60133
|
+
authorityHash,
|
|
60134
|
+
transactionSecretHash,
|
|
60135
|
+
expectedGeneration: body4.expectedGeneration,
|
|
60136
|
+
expectedActorEpoch: actorEpoch
|
|
60137
|
+
}
|
|
60138
|
+
}
|
|
60139
|
+
},
|
|
60140
|
+
headers: isolatedManagedAuthHeaders(context.req.raw),
|
|
60141
|
+
returnHeaders: true
|
|
60142
|
+
});
|
|
60143
|
+
const url = validatedManagedAuthSocialAuthorizationUrl({
|
|
60144
|
+
provider: body4.provider,
|
|
60145
|
+
rawUrl: result.response?.url,
|
|
60146
|
+
expectedClientId: body4.provider === "google" ? deps.settings.managedAuthGoogleClientId : deps.settings.managedAuthGithubClientId,
|
|
60147
|
+
expectedCallbackUrl: new URL(
|
|
60148
|
+
`/v1/auth/callback/${body4.provider}`,
|
|
60149
|
+
publicBaseUrl
|
|
60150
|
+
).toString()
|
|
60151
|
+
});
|
|
60152
|
+
const stateCookieName = authContext.createAuthCookie("state").name;
|
|
60153
|
+
const stateCookie2 = setCookieHeaders(result.headers).find(
|
|
60154
|
+
(cookie) => cookie.startsWith(`${stateCookieName}=`)
|
|
60155
|
+
);
|
|
60156
|
+
const parsed = ManagedAuthSocialStartReceipt.parse({
|
|
60157
|
+
version: 1,
|
|
60158
|
+
requestDigest,
|
|
60159
|
+
url,
|
|
60160
|
+
stateCookie: stateCookie2
|
|
60161
|
+
});
|
|
60162
|
+
const created = await authContext.internalAdapter.createVerificationValue({
|
|
60163
|
+
identifier: receiptIdentifier,
|
|
60164
|
+
value: JSON.stringify(parsed),
|
|
60165
|
+
expiresAt: new Date(Date.now() + MANAGED_AUTH_TRANSACTION_TTL_SECONDS * 1e3)
|
|
60166
|
+
});
|
|
60167
|
+
if (!created) {
|
|
60168
|
+
throw new ManagedAuthCompletionOutcomeUnknownError();
|
|
60169
|
+
}
|
|
60170
|
+
return parsed;
|
|
60171
|
+
});
|
|
60172
|
+
context.header("set-cookie", receipt.stateCookie, { append: true });
|
|
60173
|
+
return jsonWithActorEpoch(
|
|
60174
|
+
context,
|
|
60175
|
+
actorEpoch,
|
|
60176
|
+
StartManagedAuthSocialTransactionResponse.parse({ url: receipt.url })
|
|
60177
|
+
);
|
|
60178
|
+
} catch (error) {
|
|
60179
|
+
throwHttp(error);
|
|
60180
|
+
}
|
|
60181
|
+
});
|
|
58485
60182
|
app.delete("/v1/auth/session-set/transactions/:transactionId", async (context) => {
|
|
58486
60183
|
const body4 = await bodyAs(context, CancelManagedAuthLoginTransactionRequest);
|
|
58487
60184
|
if (body4.transactionId !== context.req.param("transactionId")) invalid();
|
|
@@ -58618,6 +60315,26 @@ function registerManagedAuthSessionSetRoutes(app, deps) {
|
|
|
58618
60315
|
);
|
|
58619
60316
|
});
|
|
58620
60317
|
}
|
|
60318
|
+
function validatedManagedAuthSocialAuthorizationUrl(input) {
|
|
60319
|
+
if (typeof input.rawUrl !== "string") {
|
|
60320
|
+
throw new ManagedAuthCompletionOutcomeUnknownError();
|
|
60321
|
+
}
|
|
60322
|
+
let url;
|
|
60323
|
+
try {
|
|
60324
|
+
url = new URL(input.rawUrl);
|
|
60325
|
+
} catch (error) {
|
|
60326
|
+
throw new ManagedAuthCompletionOutcomeUnknownError({ cause: error });
|
|
60327
|
+
}
|
|
60328
|
+
const expectedEndpoint = input.provider === "google" ? { origin: "https://accounts.google.com", pathname: "/o/oauth2/v2/auth" } : { origin: "https://github.com", pathname: "/login/oauth/authorize" };
|
|
60329
|
+
const exactlyOne = (name, expected) => {
|
|
60330
|
+
const values = url.searchParams.getAll(name);
|
|
60331
|
+
return values.length === 1 && values[0] !== "" && (expected === void 0 || values[0] === expected);
|
|
60332
|
+
};
|
|
60333
|
+
if (url.origin !== expectedEndpoint.origin || url.pathname !== expectedEndpoint.pathname || url.username || url.password || url.hash || !exactlyOne("client_id", input.expectedClientId) || !exactlyOne("redirect_uri", input.expectedCallbackUrl) || !exactlyOne("state")) {
|
|
60334
|
+
throw new ManagedAuthCompletionOutcomeUnknownError();
|
|
60335
|
+
}
|
|
60336
|
+
return input.rawUrl;
|
|
60337
|
+
}
|
|
58621
60338
|
function requireManagedAuthProviderRouteAllowed(method, pathname) {
|
|
58622
60339
|
const normalizedMethod = method.toUpperCase();
|
|
58623
60340
|
const allowed2 = normalizedMethod === "POST" && (/* @__PURE__ */ new Set([
|
|
@@ -58626,16 +60343,27 @@ function requireManagedAuthProviderRouteAllowed(method, pathname) {
|
|
|
58626
60343
|
"/v1/auth/send-verification-email",
|
|
58627
60344
|
"/v1/auth/request-password-reset",
|
|
58628
60345
|
"/v1/auth/reset-password"
|
|
58629
|
-
])).has(pathname) || normalizedMethod === "GET" && (pathname === "/v1/auth/verify-email" || pathname === "/v1/auth/error" || pathname === "/v1/auth/ok" || /^\/v1\/auth\/reset-password\/[^/]+$/.test(pathname));
|
|
60346
|
+
])).has(pathname) || normalizedMethod === "GET" && (pathname === "/v1/auth/verify-email" || pathname === "/v1/auth/error" || pathname === "/v1/auth/ok" || /^\/v1\/auth\/callback\/(?:google|github)$/.test(pathname) || /^\/v1\/auth\/reset-password\/[^/]+$/.test(pathname));
|
|
58630
60347
|
if (!allowed2) throw managedAuthApiError(409, "provider_route_blocked");
|
|
58631
60348
|
}
|
|
60349
|
+
function managedAuthSocialProviderConfigured(deps, provider) {
|
|
60350
|
+
return provider === "google" ? Boolean(
|
|
60351
|
+
deps.settings.managedAuthGoogleClientId && deps.settings.managedAuthGoogleClientSecret
|
|
60352
|
+
) : Boolean(
|
|
60353
|
+
deps.settings.managedAuthGithubClientId && deps.settings.managedAuthGithubClientSecret
|
|
60354
|
+
);
|
|
60355
|
+
}
|
|
58632
60356
|
async function scrubManagedAuthProviderResponse(response, options = {}) {
|
|
58633
60357
|
const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
58634
60358
|
const headers = new Headers(response.headers);
|
|
58635
60359
|
headers.set("cache-control", "no-store");
|
|
58636
60360
|
headers.set("pragma", "no-cache");
|
|
58637
60361
|
if (options.replacementCookies !== void 0) {
|
|
60362
|
+
const preserved = setCookieHeaders(response.headers).filter(
|
|
60363
|
+
(cookie) => options.preserveCookieNames?.some((name) => cookie.startsWith(`${name}=`))
|
|
60364
|
+
);
|
|
58638
60365
|
headers.delete("set-cookie");
|
|
60366
|
+
for (const cookie of preserved) headers.append("set-cookie", cookie);
|
|
58639
60367
|
for (const cookie of options.replacementCookies) headers.append("set-cookie", cookie);
|
|
58640
60368
|
}
|
|
58641
60369
|
let body4 = response.body;
|
|
@@ -59037,15 +60765,15 @@ import {
|
|
|
59037
60765
|
import {
|
|
59038
60766
|
issueManagedHumanUserResourceGrant,
|
|
59039
60767
|
listManagedHumanUserResourceAuthorities,
|
|
59040
|
-
requireAccessGrantAuthorization as
|
|
60768
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization15,
|
|
59041
60769
|
revokeManagedHumanUserResourceGrant,
|
|
59042
60770
|
SessionAuthorizationDeniedError as SessionAuthorizationDeniedError8,
|
|
59043
60771
|
SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError7,
|
|
59044
60772
|
SessionTenancyManagedHumanRequiredError as SessionTenancyManagedHumanRequiredError2
|
|
59045
60773
|
} from "@opengeni/core";
|
|
59046
|
-
import { nestedPostgresSqlState as
|
|
60774
|
+
import { nestedPostgresSqlState as nestedPostgresSqlState7, SessionTenancyNotActivatedError as SessionTenancyNotActivatedError2 } from "@opengeni/db";
|
|
59047
60775
|
import { HTTPException as HTTPException71 } from "hono/http-exception";
|
|
59048
|
-
import { z as
|
|
60776
|
+
import { z as z25 } from "zod";
|
|
59049
60777
|
async function body2(context, schema) {
|
|
59050
60778
|
const parsed = schema.safeParse(await context.req.json().catch(() => null));
|
|
59051
60779
|
if (!parsed.success) throw new HTTPException71(422, { message: "invalid user-resource request" });
|
|
@@ -59058,10 +60786,10 @@ function lifecycleError(error) {
|
|
|
59058
60786
|
if (error instanceof SessionAuthorizationUnavailableError7) {
|
|
59059
60787
|
throw new HTTPException71(503, { message: "session authorization unavailable" });
|
|
59060
60788
|
}
|
|
59061
|
-
if (
|
|
60789
|
+
if (nestedPostgresSqlState7(error) === "42501") {
|
|
59062
60790
|
throw new HTTPException71(403, { message: "user-resource authority denied" });
|
|
59063
60791
|
}
|
|
59064
|
-
if (
|
|
60792
|
+
if (nestedPostgresSqlState7(error) === "22023") {
|
|
59065
60793
|
throw new HTTPException71(422, { message: "invalid user-resource request" });
|
|
59066
60794
|
}
|
|
59067
60795
|
throw error;
|
|
@@ -59077,7 +60805,7 @@ function registerUserResourceAuthorityRoutes(app, deps) {
|
|
|
59077
60805
|
});
|
|
59078
60806
|
if (!query.success) throw new HTTPException71(422, { message: "explicit scope=user required" });
|
|
59079
60807
|
const workspaceId = context.req.param("workspaceId");
|
|
59080
|
-
const access = await
|
|
60808
|
+
const access = await requireAccessGrantAuthorization15(
|
|
59081
60809
|
context,
|
|
59082
60810
|
deps,
|
|
59083
60811
|
workspaceId,
|
|
@@ -59102,10 +60830,10 @@ function registerUserResourceAuthorityRoutes(app, deps) {
|
|
|
59102
60830
|
});
|
|
59103
60831
|
app.post(`${base}/:authorityId/grants`, async (context) => {
|
|
59104
60832
|
const workspaceId = context.req.param("workspaceId");
|
|
59105
|
-
const authorityId =
|
|
60833
|
+
const authorityId = z25.string().uuid().safeParse(context.req.param("authorityId"));
|
|
59106
60834
|
if (!authorityId.success) throw new HTTPException71(422, { message: "invalid authority id" });
|
|
59107
60835
|
const request = await body2(context, IssueUserResourceGrantRequest);
|
|
59108
|
-
const access = await
|
|
60836
|
+
const access = await requireAccessGrantAuthorization15(
|
|
59109
60837
|
context,
|
|
59110
60838
|
deps,
|
|
59111
60839
|
workspaceId,
|
|
@@ -59132,10 +60860,10 @@ function registerUserResourceAuthorityRoutes(app, deps) {
|
|
|
59132
60860
|
app.delete(`${base}/grants/:grantId`, async (context) => {
|
|
59133
60861
|
const query = RevokeUserResourceGrantQuery.safeParse({ scope: context.req.query("scope") });
|
|
59134
60862
|
if (!query.success) throw new HTTPException71(422, { message: "explicit scope=user required" });
|
|
59135
|
-
const grantId =
|
|
60863
|
+
const grantId = z25.string().uuid().safeParse(context.req.param("grantId"));
|
|
59136
60864
|
if (!grantId.success) throw new HTTPException71(422, { message: "invalid grant id" });
|
|
59137
60865
|
const workspaceId = context.req.param("workspaceId");
|
|
59138
|
-
const access = await
|
|
60866
|
+
const access = await requireAccessGrantAuthorization15(
|
|
59139
60867
|
context,
|
|
59140
60868
|
deps,
|
|
59141
60869
|
workspaceId,
|
|
@@ -59165,15 +60893,15 @@ import {
|
|
|
59165
60893
|
import {
|
|
59166
60894
|
issueManagedHumanUserResourceGrant as issueManagedHumanUserResourceGrant2,
|
|
59167
60895
|
listManagedHumanUserResourceAuthorities as listManagedHumanUserResourceAuthorities2,
|
|
59168
|
-
requireAccessGrantAuthorization as
|
|
60896
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization16,
|
|
59169
60897
|
revokeManagedHumanUserResourceGrant as revokeManagedHumanUserResourceGrant2,
|
|
59170
60898
|
SessionAuthorizationDeniedError as SessionAuthorizationDeniedError9,
|
|
59171
60899
|
SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError8,
|
|
59172
60900
|
SessionTenancyManagedHumanRequiredError as SessionTenancyManagedHumanRequiredError3
|
|
59173
60901
|
} from "@opengeni/core";
|
|
59174
|
-
import { nestedPostgresSqlState as
|
|
60902
|
+
import { nestedPostgresSqlState as nestedPostgresSqlState8, SessionTenancyNotActivatedError as SessionTenancyNotActivatedError3 } from "@opengeni/db";
|
|
59175
60903
|
import { HTTPException as HTTPException73 } from "hono/http-exception";
|
|
59176
|
-
import { z as
|
|
60904
|
+
import { z as z26 } from "zod";
|
|
59177
60905
|
|
|
59178
60906
|
// src/connection-authority-owner.ts
|
|
59179
60907
|
import {
|
|
@@ -59213,10 +60941,10 @@ function lifecycleError2(error) {
|
|
|
59213
60941
|
if (error instanceof SessionAuthorizationUnavailableError8) {
|
|
59214
60942
|
throw new HTTPException73(503, { message: "session authorization unavailable" });
|
|
59215
60943
|
}
|
|
59216
|
-
if (
|
|
60944
|
+
if (nestedPostgresSqlState8(error) === "42501") {
|
|
59217
60945
|
throw new HTTPException73(403, { message: "connection authority denied" });
|
|
59218
60946
|
}
|
|
59219
|
-
if (
|
|
60947
|
+
if (nestedPostgresSqlState8(error) === "22023") {
|
|
59220
60948
|
throw new HTTPException73(422, { message: "invalid connection authority" });
|
|
59221
60949
|
}
|
|
59222
60950
|
throw error;
|
|
@@ -59231,7 +60959,7 @@ function registerConnectionAuthorityRoutes(app, deps) {
|
|
|
59231
60959
|
});
|
|
59232
60960
|
if (!query.success) throw new HTTPException73(422, { message: "explicit scope=user required" });
|
|
59233
60961
|
const workspaceId = context.req.param("workspaceId");
|
|
59234
|
-
const access = await
|
|
60962
|
+
const access = await requireAccessGrantAuthorization16(
|
|
59235
60963
|
context,
|
|
59236
60964
|
deps,
|
|
59237
60965
|
workspaceId,
|
|
@@ -59253,10 +60981,10 @@ function registerConnectionAuthorityRoutes(app, deps) {
|
|
|
59253
60981
|
});
|
|
59254
60982
|
app.post(`${base}/:authorityId/grants`, async (context) => {
|
|
59255
60983
|
const workspaceId = context.req.param("workspaceId");
|
|
59256
|
-
const authorityId =
|
|
60984
|
+
const authorityId = z26.string().uuid().safeParse(context.req.param("authorityId"));
|
|
59257
60985
|
if (!authorityId.success) throw new HTTPException73(422, { message: "invalid authority id" });
|
|
59258
60986
|
const request = await body3(context, IssueConnectionUseGrantRequest2);
|
|
59259
|
-
const access = await
|
|
60987
|
+
const access = await requireAccessGrantAuthorization16(
|
|
59260
60988
|
context,
|
|
59261
60989
|
deps,
|
|
59262
60990
|
workspaceId,
|
|
@@ -59284,10 +61012,10 @@ function registerConnectionAuthorityRoutes(app, deps) {
|
|
|
59284
61012
|
app.delete(`${base}/grants/:grantId`, async (context) => {
|
|
59285
61013
|
const query = RevokeConnectionUseGrantQuery.safeParse({ scope: context.req.query("scope") });
|
|
59286
61014
|
if (!query.success) throw new HTTPException73(422, { message: "explicit scope=user required" });
|
|
59287
|
-
const grantId =
|
|
61015
|
+
const grantId = z26.string().uuid().safeParse(context.req.param("grantId"));
|
|
59288
61016
|
if (!grantId.success) throw new HTTPException73(422, { message: "invalid grant id" });
|
|
59289
61017
|
const workspaceId = context.req.param("workspaceId");
|
|
59290
|
-
const access = await
|
|
61018
|
+
const access = await requireAccessGrantAuthorization16(
|
|
59291
61019
|
context,
|
|
59292
61020
|
deps,
|
|
59293
61021
|
workspaceId,
|
|
@@ -59917,7 +61645,7 @@ import {
|
|
|
59917
61645
|
controlHumanSessionWorkstream as controlHumanSessionWorkstream2,
|
|
59918
61646
|
createSessionForRequest as createSessionForRequest2,
|
|
59919
61647
|
hasPermission as hasPermission24,
|
|
59920
|
-
requireAccessContext as
|
|
61648
|
+
requireAccessContext as requireAccessContext4,
|
|
59921
61649
|
requireAccessGrant as requireAccessGrant45,
|
|
59922
61650
|
requireSessionAuthorizationListScope as requireSessionAuthorizationListScope3
|
|
59923
61651
|
} from "@opengeni/core";
|
|
@@ -63995,7 +65723,7 @@ async function requireManagedSlackLinkHuman(c, deps) {
|
|
|
63995
65723
|
if (c.req.header("authorization")) {
|
|
63996
65724
|
throw new HTTPException74(401, { message: "managed browser sign-in required" });
|
|
63997
65725
|
}
|
|
63998
|
-
const context = await
|
|
65726
|
+
const context = await requireAccessContext4(c, deps);
|
|
63999
65727
|
if (context.mode !== "managed" || !context.subjectId.startsWith("user:")) {
|
|
64000
65728
|
throw new HTTPException74(403, { message: "managed browser sign-in required" });
|
|
64001
65729
|
}
|
|
@@ -64495,20 +66223,96 @@ function createAppComposition(deps) {
|
|
|
64495
66223
|
registerManagedAuthSessionSetRoutes(app, routeDeps);
|
|
64496
66224
|
if (managedAuth) {
|
|
64497
66225
|
app.on(["GET", "POST"], "/v1/auth/*", async (c) => {
|
|
66226
|
+
const pathname = new URL(c.req.url).pathname;
|
|
66227
|
+
const oauthCallbackProvider = managedAuthOAuthCallbackProvider(pathname);
|
|
64498
66228
|
if (deps.settings.managedAuthSessionSetMode === "legacy") {
|
|
64499
|
-
return await
|
|
64500
|
-
|
|
64501
|
-
|
|
64502
|
-
|
|
64503
|
-
|
|
64504
|
-
|
|
64505
|
-
headers.delete("x-forwarded-user");
|
|
64506
|
-
const providerRequest = new Request(c.req.raw, { headers });
|
|
66229
|
+
return oauthCallbackProvider ? await runManagedAuthProvider(
|
|
66230
|
+
oauthCallbackProvider,
|
|
66231
|
+
async () => await managedAuth.handler(c.req.raw)
|
|
66232
|
+
) : await managedAuth.handler(c.req.raw);
|
|
66233
|
+
}
|
|
66234
|
+
requireManagedAuthProviderRouteAllowed(c.req.method, pathname);
|
|
64507
66235
|
const authority = getCookie2(c, MANAGED_AUTH_SESSION_SET_COOKIE2);
|
|
64508
|
-
|
|
64509
|
-
|
|
64510
|
-
|
|
64511
|
-
|
|
66236
|
+
let providerResponse;
|
|
66237
|
+
let preserveCookieNames;
|
|
66238
|
+
if (oauthCallbackProvider) {
|
|
66239
|
+
const attempt = await resolveManagedAuthOAuthAttempt(
|
|
66240
|
+
managedAuth,
|
|
66241
|
+
c.req.raw,
|
|
66242
|
+
oauthCallbackProvider,
|
|
66243
|
+
deps.settings.publicBaseUrl
|
|
66244
|
+
);
|
|
66245
|
+
if (!attempt || !authority || attempt.authorityHash !== managedAuthSha2562(authority)) {
|
|
66246
|
+
throw new HTTPException75(409, { message: "provider_route_blocked" });
|
|
66247
|
+
}
|
|
66248
|
+
const isolated = await isolatedManagedAuthOAuthCallbackRequest(managedAuth, c.req.raw);
|
|
66249
|
+
preserveCookieNames = [isolated.stateCookieName];
|
|
66250
|
+
const handled = await runManagedAuthAttempt(
|
|
66251
|
+
attempt.transactionId,
|
|
66252
|
+
oauthCallbackProvider,
|
|
66253
|
+
async () => {
|
|
66254
|
+
const response = await managedAuth.handler(isolated.request);
|
|
66255
|
+
return {
|
|
66256
|
+
response,
|
|
66257
|
+
authSessionId: currentManagedAuthCreatedSessionId()
|
|
66258
|
+
};
|
|
66259
|
+
}
|
|
66260
|
+
);
|
|
66261
|
+
providerResponse = handled.response;
|
|
66262
|
+
if (!handled.authSessionId && managedAuthOAuthReturnMatches(
|
|
66263
|
+
providerResponse.headers.get("location") ?? "",
|
|
66264
|
+
new URL(deps.settings.publicBaseUrl).origin,
|
|
66265
|
+
attempt.transactionId,
|
|
66266
|
+
"complete"
|
|
66267
|
+
)) {
|
|
66268
|
+
const location = new URL("/account-auth", deps.settings.publicBaseUrl);
|
|
66269
|
+
location.searchParams.set("transaction", attempt.transactionId);
|
|
66270
|
+
location.searchParams.set("social", "error");
|
|
66271
|
+
providerResponse = Response.redirect(location, 302);
|
|
66272
|
+
} else if (handled.authSessionId) {
|
|
66273
|
+
try {
|
|
66274
|
+
await adoptManagedAuthSession({
|
|
66275
|
+
db: deps.db,
|
|
66276
|
+
adapter: managedAuthSessionAdapter,
|
|
66277
|
+
authority,
|
|
66278
|
+
authorityHash: attempt.authorityHash,
|
|
66279
|
+
csrfHash: managedAuthCsrfHash2(authority),
|
|
66280
|
+
operationId: managedAuthDerivedUuid2(
|
|
66281
|
+
"opengeni:managed-auth:social-completion",
|
|
66282
|
+
`${attempt.transactionId}:${attempt.provider}`
|
|
66283
|
+
),
|
|
66284
|
+
requestDigest: managedAuthSecretRequestDigest2(deps.settings.betterAuthSecret, {
|
|
66285
|
+
operation: "social_completion",
|
|
66286
|
+
transactionId: attempt.transactionId,
|
|
66287
|
+
provider: attempt.provider,
|
|
66288
|
+
expectedGeneration: attempt.expectedGeneration,
|
|
66289
|
+
expectedActorEpoch: attempt.expectedActorEpoch
|
|
66290
|
+
}),
|
|
66291
|
+
expectedGeneration: attempt.expectedGeneration,
|
|
66292
|
+
expectedActorEpoch: attempt.expectedActorEpoch,
|
|
66293
|
+
transactionId: attempt.transactionId,
|
|
66294
|
+
transactionSecretHash: attempt.transactionSecretHash,
|
|
66295
|
+
authSessionId: handled.authSessionId,
|
|
66296
|
+
mode: deps.settings.managedAuthSessionSetMode
|
|
66297
|
+
});
|
|
66298
|
+
} catch {
|
|
66299
|
+
const location = new URL("/account-auth", deps.settings.publicBaseUrl);
|
|
66300
|
+
location.searchParams.set("transaction", attempt.transactionId);
|
|
66301
|
+
location.searchParams.set("social", "error");
|
|
66302
|
+
providerResponse = Response.redirect(location, 302);
|
|
66303
|
+
}
|
|
66304
|
+
}
|
|
66305
|
+
} else {
|
|
66306
|
+
const headers = new Headers(c.req.raw.headers);
|
|
66307
|
+
headers.delete("cookie");
|
|
66308
|
+
headers.delete("authorization");
|
|
66309
|
+
headers.delete("x-forwarded-user");
|
|
66310
|
+
const providerRequest = new Request(c.req.raw, { headers });
|
|
66311
|
+
const discardProviderSession = deps.settings.managedAuthSessionSetMode === "broker" || authority !== void 0;
|
|
66312
|
+
providerResponse = discardProviderSession ? await runManagedAuthDiscardedProviderSession(
|
|
66313
|
+
async () => await managedAuth.handler(providerRequest)
|
|
66314
|
+
) : await managedAuth.handler(providerRequest);
|
|
66315
|
+
}
|
|
64512
66316
|
let replacementCookies;
|
|
64513
66317
|
if (deps.settings.managedAuthSessionSetMode === "broker") {
|
|
64514
66318
|
replacementCookies = await managedAuthSessionAdapter.createLegacySelectedSessionCookies(
|
|
@@ -64536,19 +66340,23 @@ function createAppComposition(deps) {
|
|
|
64536
66340
|
}
|
|
64537
66341
|
}
|
|
64538
66342
|
}
|
|
64539
|
-
return await scrubManagedAuthProviderResponse(providerResponse, {
|
|
66343
|
+
return await scrubManagedAuthProviderResponse(providerResponse, {
|
|
66344
|
+
replacementCookies,
|
|
66345
|
+
preserveCookieNames
|
|
66346
|
+
});
|
|
64540
66347
|
});
|
|
64541
66348
|
}
|
|
64542
|
-
app.get(
|
|
64543
|
-
|
|
64544
|
-
|
|
66349
|
+
app.get("/healthz", (c) => {
|
|
66350
|
+
const warnings = githubAppBotIdentityWarnings(deps.settings);
|
|
66351
|
+
return c.json({
|
|
64545
66352
|
service: deps.settings.serviceName,
|
|
64546
66353
|
environment: deps.settings.environment,
|
|
64547
66354
|
deploymentRevision: deps.settings.deploymentRevision,
|
|
64548
66355
|
...deps.settings.serverVersion ? { serverVersion: deps.settings.serverVersion } : {},
|
|
66356
|
+
...warnings.length > 0 ? { warnings } : {},
|
|
64549
66357
|
ok: true
|
|
64550
|
-
})
|
|
64551
|
-
);
|
|
66358
|
+
});
|
|
66359
|
+
});
|
|
64552
66360
|
app.get("/readyz", async (c) => {
|
|
64553
66361
|
const result = await runReadinessChecks(readinessChecks(deps), 2e3);
|
|
64554
66362
|
return c.json(result, result.ok ? 200 : 503);
|
|
@@ -64582,6 +66390,7 @@ function createAppComposition(deps) {
|
|
|
64582
66390
|
models: configuredModels3(catalogSettings).map(projectClientModel),
|
|
64583
66391
|
defaultReasoningEffort: deps.settings.openaiReasoningEffort,
|
|
64584
66392
|
allowedReasoningEfforts: configuredAllowedReasoningEfforts(deps.settings),
|
|
66393
|
+
defaultSandboxBackend: deps.settings.sandboxBackend,
|
|
64585
66394
|
mcpServers: deps.settings.mcpServers.map((server) => ({
|
|
64586
66395
|
id: server.id,
|
|
64587
66396
|
name: server.name ?? server.id
|
|
@@ -64828,6 +66637,10 @@ function createAppComposition(deps) {
|
|
|
64828
66637
|
});
|
|
64829
66638
|
return { app, routeDeps };
|
|
64830
66639
|
}
|
|
66640
|
+
function managedAuthOAuthCallbackProvider(pathname) {
|
|
66641
|
+
const match = pathname.match(/^\/v1\/auth\/callback\/(google|github)$/u);
|
|
66642
|
+
return match?.[1] === "google" || match?.[1] === "github" ? match[1] : null;
|
|
66643
|
+
}
|
|
64831
66644
|
function mutationOutcomeUnknown(error, method) {
|
|
64832
66645
|
if (method === "GET" || method === "HEAD" || method === "OPTIONS") return false;
|
|
64833
66646
|
const cause = error instanceof HTTPException75 ? error.cause : error;
|
|
@@ -64909,7 +66722,11 @@ function clientAuthConfig(settings) {
|
|
|
64909
66722
|
return {
|
|
64910
66723
|
mode: "managedSession",
|
|
64911
66724
|
session: "cookie",
|
|
64912
|
-
emailVerificationRequired: settings.environment !== "local"
|
|
66725
|
+
emailVerificationRequired: settings.environment !== "local",
|
|
66726
|
+
socialProviders: [
|
|
66727
|
+
...settings.managedAuthGoogleClientId && settings.managedAuthGoogleClientSecret ? ["google"] : [],
|
|
66728
|
+
...settings.managedAuthGithubClientId && settings.managedAuthGithubClientSecret ? ["github"] : []
|
|
66729
|
+
]
|
|
64913
66730
|
};
|
|
64914
66731
|
}
|
|
64915
66732
|
if (settings.productAccessMode === "configured") {
|
|
@@ -65140,6 +66957,22 @@ var routeLabelPatterns = [
|
|
|
65140
66957
|
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/status$/,
|
|
65141
66958
|
label: "/v1/workspaces/:workspaceId/codex/status"
|
|
65142
66959
|
},
|
|
66960
|
+
{
|
|
66961
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/source$/,
|
|
66962
|
+
label: "/v1/workspaces/:workspaceId/codex/source"
|
|
66963
|
+
},
|
|
66964
|
+
{
|
|
66965
|
+
pattern: /^\/v1\/organizations\/[^/]+\/codex\/(accounts|settings)$/,
|
|
66966
|
+
label: (match) => `/v1/organizations/:organizationId/codex/${match[1]}`
|
|
66967
|
+
},
|
|
66968
|
+
{
|
|
66969
|
+
pattern: /^\/v1\/organizations\/[^/]+\/codex\/connect\/(start|poll)$/,
|
|
66970
|
+
label: (match) => `/v1/organizations/:organizationId/codex/connect/${match[1]}`
|
|
66971
|
+
},
|
|
66972
|
+
{
|
|
66973
|
+
pattern: /^\/v1\/organizations\/[^/]+\/codex\/accounts\/[^/]+(?:\/activate)?$/,
|
|
66974
|
+
label: "/v1/organizations/:organizationId/codex/accounts/:accountId"
|
|
66975
|
+
},
|
|
65143
66976
|
{
|
|
65144
66977
|
pattern: /^\/v1\/workspaces\/[^/]+\/supergrok\/connect\/(start|poll)$/,
|
|
65145
66978
|
label: (match) => `/v1/workspaces/:workspaceId/supergrok/connect/${match[1]}`
|
|
@@ -65290,6 +67123,10 @@ var routeLabelPatterns = [
|
|
|
65290
67123
|
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/targets\/[^/]+\/observation$/,
|
|
65291
67124
|
label: "/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/targets/:targetId/observation"
|
|
65292
67125
|
},
|
|
67126
|
+
{
|
|
67127
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/targets\/[^/]+\/screenshot$/,
|
|
67128
|
+
label: "/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/targets/:targetId/screenshot"
|
|
67129
|
+
},
|
|
65293
67130
|
{
|
|
65294
67131
|
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/operations\/[^/]+$/,
|
|
65295
67132
|
label: "/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/operations/:operationId"
|
|
@@ -65462,6 +67299,18 @@ var routeLabelPatterns = [
|
|
|
65462
67299
|
pattern: /^\/v1\/workspaces\/[^/]+\/api-keys\/[^/]+$/,
|
|
65463
67300
|
label: "/v1/workspaces/:workspaceId/api-keys/:id"
|
|
65464
67301
|
},
|
|
67302
|
+
{
|
|
67303
|
+
pattern: /^\/v1\/organizations\/[^/]+\/api-keys$/,
|
|
67304
|
+
label: "/v1/organizations/:organizationId/api-keys"
|
|
67305
|
+
},
|
|
67306
|
+
{
|
|
67307
|
+
pattern: /^\/v1\/organizations\/[^/]+\/api-keys\/[^/]+$/,
|
|
67308
|
+
label: "/v1/organizations/:organizationId/api-keys/:id"
|
|
67309
|
+
},
|
|
67310
|
+
{
|
|
67311
|
+
pattern: /^\/v1\/workspaces\/external$/,
|
|
67312
|
+
label: "/v1/workspaces/external"
|
|
67313
|
+
},
|
|
65465
67314
|
{
|
|
65466
67315
|
pattern: /^\/v1\/workspaces\/[^/]+\/scheduled-tasks$/,
|
|
65467
67316
|
label: "/v1/workspaces/:workspaceId/scheduled-tasks"
|
|
@@ -65931,4 +67780,4 @@ export {
|
|
|
65931
67780
|
withDefaultEnabledCapabilityMcpTools,
|
|
65932
67781
|
workflowIdForSession3 as workflowIdForSession
|
|
65933
67782
|
};
|
|
65934
|
-
//# sourceMappingURL=chunk-
|
|
67783
|
+
//# sourceMappingURL=chunk-XTLI3CBH.js.map
|