@opengeni/db 0.4.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,12 +7,17 @@ import {
7
7
  capabilityInstallations,
8
8
  codexRotationSettings,
9
9
  codexSubscriptionCredentials,
10
+ connections,
10
11
  creditLedgerEntries,
11
12
  deviceEnrollmentRequests,
12
13
  enrollments,
13
14
  fileUploads,
14
15
  files,
15
16
  githubInstallations,
17
+ importBatches,
18
+ integrationOauthClients,
19
+ integrationOauthStateNonces,
20
+ knowledgeMemories,
16
21
  machineMetricsLatest,
17
22
  machineMetricsSeries,
18
23
  managedAccounts,
@@ -39,7 +44,7 @@ import {
39
44
  workspaceMemberships,
40
45
  workspacePacks,
41
46
  workspaces
42
- } from "./chunk-T2U4H4Z2.js";
47
+ } from "./chunk-ZIUCA2IO.js";
43
48
  import {
44
49
  migrate,
45
50
  runMigrations
@@ -51,10 +56,10 @@ import "./chunk-PZ5AY32C.js";
51
56
 
52
57
  // src/index.ts
53
58
  import { reasoningEffortForMetadata, CLEARED_RUN_STATE_BLOB } from "@opengeni/contracts";
54
- import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes2 } from "@opengeni/config";
59
+ import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes3 } from "@opengeni/config";
55
60
  import { isCodexBilledModel } from "@opengeni/codex";
56
61
  import { isCodexBilledModel as isCodexBilledModel2 } from "@opengeni/codex";
57
- import { and, asc, desc, eq, gt, gte, inArray, lt, ne, sql } from "drizzle-orm";
62
+ import { and, asc, desc, eq, gt, gte, inArray, isNull, lt, ne, or, sql } from "drizzle-orm";
58
63
  import { drizzle } from "drizzle-orm/postgres-js";
59
64
  import postgres from "postgres";
60
65
 
@@ -100,6 +105,22 @@ function assertKey(key) {
100
105
 
101
106
  // src/event-payload-sanitizer.ts
102
107
  var REPLACEMENT = "\uFFFD";
108
+ var REDACTED = "[redacted]";
109
+ var SENSITIVE_FIELD_NAMES = /* @__PURE__ */ new Set([
110
+ "authorization",
111
+ "headers",
112
+ "accesstoken",
113
+ "refreshtoken",
114
+ "idtoken",
115
+ "token",
116
+ "apikey",
117
+ "secret",
118
+ "clientsecret",
119
+ "credential",
120
+ "credentialencrypted",
121
+ "encryptedpkceverifier",
122
+ "codeverifier"
123
+ ]);
103
124
  function sanitizeEventString(value) {
104
125
  let needsWork = false;
105
126
  for (let i = 0; i < value.length; i++) {
@@ -158,6 +179,9 @@ function sanitizeSensitiveEventField(key, value) {
158
179
  if (key === "mcpCredentialUpdates") {
159
180
  return sanitizeMcpCredentialUpdateList(value);
160
181
  }
182
+ if (SENSITIVE_FIELD_NAMES.has(normalizeFieldName(key))) {
183
+ return REDACTED;
184
+ }
161
185
  return sanitizeEventPayload(value);
162
186
  }
163
187
  function sanitizeSessionMcpServerList(value) {
@@ -203,6 +227,9 @@ function safeHeaderNames(value) {
203
227
  function isPlainObject(value) {
204
228
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
205
229
  }
230
+ function normalizeFieldName(key) {
231
+ return key.toLowerCase().replace(/[-_]/g, "");
232
+ }
206
233
 
207
234
  // src/index.ts
208
235
  import { sql as sql2 } from "drizzle-orm";
@@ -337,6 +364,385 @@ async function fetchCodexUsageForAccount(db, settings, workspaceId, credentialId
337
364
  return normalized;
338
365
  }
339
366
 
367
+ // src/connection-token-resolver.ts
368
+ import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes2 } from "@opengeni/config";
369
+ import { Buffer as Buffer2 } from "buffer";
370
+ import { lookup } from "dns/promises";
371
+ import { isIP } from "net";
372
+ var defaultDeps2 = {
373
+ loadCredential: loadConnectionCredentialForBroker,
374
+ recordRefresh: recordConnectionTokenRefresh,
375
+ setStatus: setConnectionStatus,
376
+ recordUsed: recordConnectionUsed,
377
+ refresh: refreshOAuthConnectionCredential,
378
+ encrypt: encryptEnvironmentValue,
379
+ keyBytes: environmentsEncryptionKeyBytes2,
380
+ now: () => /* @__PURE__ */ new Date()
381
+ };
382
+ var inflight2 = /* @__PURE__ */ new Map();
383
+ var REFRESH_WINDOW_MS = 6e4;
384
+ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
385
+ const load = async (input) => {
386
+ const request = {
387
+ workspaceId: input.workspaceId,
388
+ providerDomain: input.connectionRef.providerDomain,
389
+ // I1 deliberately accepts workspace-shared connections only at runtime.
390
+ allowSubjectOwned: false
391
+ };
392
+ if (input.connectionRef.connectionId !== void 0) {
393
+ request.connectionId = input.connectionRef.connectionId;
394
+ }
395
+ if (input.connectionRef.kind !== void 0) {
396
+ request.kind = input.connectionRef.kind;
397
+ }
398
+ if (input.subjectId !== void 0) {
399
+ request.subjectId = input.subjectId;
400
+ }
401
+ return deps.loadCredential(db, settings, request);
402
+ };
403
+ const snapshot = async (cred, ref) => {
404
+ if (cred.status !== "active") {
405
+ return authNeededForStatus(cred, ref);
406
+ }
407
+ const missingScopes = missingRequestedScopes(ref.scopes, cred.grantedScopes);
408
+ if (missingScopes.length > 0) {
409
+ return {
410
+ status: "auth_needed",
411
+ reason: "insufficient_scope",
412
+ providerDomain: ref.providerDomain,
413
+ connectionId: cred.id,
414
+ scopes: missingScopes,
415
+ ...ref.resource ? { resource: ref.resource } : {}
416
+ };
417
+ }
418
+ const headers = headersForCredential(cred);
419
+ if (!headers) {
420
+ return {
421
+ status: "auth_needed",
422
+ reason: "refresh_failed",
423
+ providerDomain: ref.providerDomain,
424
+ connectionId: cred.id,
425
+ ...ref.scopes ? { scopes: ref.scopes } : {},
426
+ ...ref.resource ? { resource: ref.resource } : {}
427
+ };
428
+ }
429
+ await deps.recordUsed(db, cred.workspaceId, cred.id);
430
+ return {
431
+ status: "ok",
432
+ headers,
433
+ connectionId: cred.id,
434
+ expiresAt: cred.expiresAt
435
+ };
436
+ };
437
+ const performRefresh = async (cred, ref) => {
438
+ const key = deps.keyBytes(settings);
439
+ if (!key) {
440
+ throw new Error("OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY is not configured");
441
+ }
442
+ const refreshed = await deps.refresh(cred, ref, settings);
443
+ const refreshRecord = {
444
+ id: cred.id,
445
+ version: cred.version,
446
+ workspaceId: cred.workspaceId,
447
+ credentialEncrypted: deps.encrypt(key, JSON.stringify(refreshed.credential)),
448
+ expiresAt: refreshed.expiresAt,
449
+ lastRefreshAt: deps.now()
450
+ };
451
+ if (refreshed.grantedScopes !== void 0) {
452
+ refreshRecord.grantedScopes = refreshed.grantedScopes;
453
+ }
454
+ const persisted = await deps.recordRefresh(db, refreshRecord);
455
+ if (persisted) {
456
+ const current = await load({
457
+ workspaceId: cred.workspaceId,
458
+ serverId: "",
459
+ connectionRef: { ...ref, connectionId: cred.id }
460
+ });
461
+ if (current) {
462
+ return current;
463
+ }
464
+ }
465
+ const winner = await load({
466
+ workspaceId: cred.workspaceId,
467
+ serverId: "",
468
+ connectionRef: { ...ref, connectionId: cred.id }
469
+ });
470
+ if (winner?.status === "active") {
471
+ return winner;
472
+ }
473
+ throw new Error("connection credential changed during token refresh");
474
+ };
475
+ const refreshSingleFlight = (cred, ref) => {
476
+ const key = `${cred.id}:${cred.version}`;
477
+ const existing = inflight2.get(key);
478
+ if (existing) {
479
+ return existing;
480
+ }
481
+ const promise = performRefresh(cred, ref).finally(() => {
482
+ if (inflight2.get(key) === promise) {
483
+ inflight2.delete(key);
484
+ }
485
+ });
486
+ inflight2.set(key, promise);
487
+ return promise;
488
+ };
489
+ return async (input) => {
490
+ const ref = input.connectionRef;
491
+ let cred;
492
+ try {
493
+ cred = await load(input);
494
+ } catch {
495
+ return authNeeded(ref, "refresh_failed");
496
+ }
497
+ if (!cred) {
498
+ return authNeeded(ref, "missing_connection");
499
+ }
500
+ if (cred.status !== "active") {
501
+ return authNeededForStatus(cred, ref);
502
+ }
503
+ if (shouldRefresh(cred, input.forceRefresh === true, deps.now())) {
504
+ try {
505
+ cred = await refreshSingleFlight(cred, ref);
506
+ } catch (error) {
507
+ if (isPermanentRefreshError(error)) {
508
+ await deps.setStatus(db, input.workspaceId, "needs_reauth", error instanceof Error ? error.message : String(error), {
509
+ id: cred.id,
510
+ version: cred.version
511
+ }).catch(() => void 0);
512
+ }
513
+ return authNeeded(ref, "refresh_failed", cred.id);
514
+ }
515
+ }
516
+ return await snapshot(cred, ref);
517
+ };
518
+ }
519
+ var ConnectionRefreshHttpError = class extends Error {
520
+ httpStatus;
521
+ constructor(httpStatus) {
522
+ super(`connection refresh failed with HTTP ${httpStatus}`);
523
+ this.name = "ConnectionRefreshHttpError";
524
+ this.httpStatus = httpStatus;
525
+ }
526
+ };
527
+ function isPermanentRefreshError(error) {
528
+ return error instanceof ConnectionRefreshHttpError && error.httpStatus >= 400 && error.httpStatus < 500 && error.httpStatus !== 408 && error.httpStatus !== 429;
529
+ }
530
+ function shouldRefresh(cred, force, now) {
531
+ if (cred.kind !== "oauth2") {
532
+ return false;
533
+ }
534
+ if (force) {
535
+ return true;
536
+ }
537
+ if (!cred.expiresAt) {
538
+ return false;
539
+ }
540
+ return cred.expiresAt.getTime() <= now.getTime() + REFRESH_WINDOW_MS;
541
+ }
542
+ function authNeeded(ref, reason, connectionId) {
543
+ return {
544
+ status: "auth_needed",
545
+ reason,
546
+ providerDomain: ref.providerDomain,
547
+ ...connectionId ? { connectionId } : {},
548
+ ...ref.scopes ? { scopes: ref.scopes } : {},
549
+ ...ref.resource ? { resource: ref.resource } : {}
550
+ };
551
+ }
552
+ function authNeededForStatus(cred, ref) {
553
+ if (cred.status === "revoked") {
554
+ return authNeeded(ref, "missing_connection", cred.id);
555
+ }
556
+ return authNeeded(ref, cred.expiresAt && cred.expiresAt.getTime() <= Date.now() ? "expired" : "refresh_failed", cred.id);
557
+ }
558
+ function missingRequestedScopes(requested, granted) {
559
+ if (!requested?.length) {
560
+ return [];
561
+ }
562
+ const grantedSet = new Set(granted);
563
+ return requested.filter((scope) => !grantedSet.has(scope));
564
+ }
565
+ function headersForCredential(cred) {
566
+ if (cred.kind === "api_key") {
567
+ return stringRecord(cred.credential.headers);
568
+ }
569
+ if (cred.kind === "oauth2") {
570
+ const accessToken = stringValue(cred.credential.access_token);
571
+ if (!accessToken) {
572
+ return null;
573
+ }
574
+ const tokenType = stringValue(cred.credential.token_type) || "Bearer";
575
+ return { authorization: `${tokenType} ${accessToken}` };
576
+ }
577
+ return stringRecord(cred.credential.headers);
578
+ }
579
+ async function refreshOAuthConnectionCredential(cred, ref, settings) {
580
+ if (cred.kind !== "oauth2") {
581
+ return { credential: cred.credential, expiresAt: cred.expiresAt, grantedScopes: cred.grantedScopes };
582
+ }
583
+ const refreshToken = stringValue(cred.credential.refresh_token);
584
+ const tokenEndpoint = stringValue(cred.credential.token_endpoint) ?? stringValue(cred.metadata.tokenEndpoint) ?? stringValue(cred.metadata.token_endpoint);
585
+ if (!refreshToken || !tokenEndpoint) {
586
+ throw new Error("connection has no refresh token endpoint");
587
+ }
588
+ if (settings) {
589
+ await assertOAuthEndpointAllowed(tokenEndpoint, settings);
590
+ }
591
+ const body = new URLSearchParams();
592
+ body.set("grant_type", "refresh_token");
593
+ body.set("refresh_token", refreshToken);
594
+ const clientId = stringValue(cred.credential.client_id) ?? stringValue(cred.metadata.clientId) ?? stringValue(cred.metadata.client_id);
595
+ const clientSecret = stringValue(cred.credential.client_secret);
596
+ const authMethod = stringValue(cred.credential.token_endpoint_auth_method) ?? "none";
597
+ if (clientId) {
598
+ body.set("client_id", clientId);
599
+ }
600
+ const headers = { "content-type": "application/x-www-form-urlencoded" };
601
+ if (clientSecret && authMethod === "client_secret_post") {
602
+ body.set("client_secret", clientSecret);
603
+ } else if (clientId && clientSecret && authMethod === "client_secret_basic") {
604
+ headers.authorization = `Basic ${Buffer2.from(`${clientId}:${clientSecret}`).toString("base64")}`;
605
+ }
606
+ const resource = ref.resource ?? stringValue(cred.credential.resource);
607
+ if (resource) {
608
+ body.set("resource", resource);
609
+ }
610
+ if (ref.scopes?.length) {
611
+ body.set("scope", ref.scopes.join(" "));
612
+ }
613
+ const response = await fetch(tokenEndpoint, {
614
+ method: "POST",
615
+ headers,
616
+ body,
617
+ redirect: "manual"
618
+ });
619
+ if (response.status >= 300 && response.status < 400) {
620
+ throw new ConnectionRefreshHttpError(response.status);
621
+ }
622
+ if (!response.ok) {
623
+ throw new ConnectionRefreshHttpError(response.status);
624
+ }
625
+ const payload = await response.json();
626
+ const accessToken = stringValue(payload.access_token);
627
+ if (!accessToken) {
628
+ throw new Error("connection refresh response did not include access_token");
629
+ }
630
+ const expiresAt = expiresAtFromTokenResponse(payload, cred.expiresAt);
631
+ const scopeText = stringValue(payload.scope);
632
+ const nextCredential = {
633
+ ...cred.credential,
634
+ access_token: accessToken,
635
+ refresh_token: stringValue(payload.refresh_token) ?? refreshToken,
636
+ token_type: stringValue(payload.token_type) ?? stringValue(cred.credential.token_type) ?? "Bearer",
637
+ ...expiresAt ? { expires_at: expiresAt.toISOString() } : {},
638
+ ...resource ? { resource } : {},
639
+ ...scopeText ? { scope: scopeText } : {},
640
+ ...clientSecret ? { client_secret: clientSecret, token_endpoint_auth_method: authMethod } : {}
641
+ };
642
+ return {
643
+ credential: nextCredential,
644
+ expiresAt,
645
+ ...scopeText ? { grantedScopes: scopeText.split(/\s+/).filter(Boolean) } : {}
646
+ };
647
+ }
648
+ function expiresAtFromTokenResponse(payload, fallback) {
649
+ const expiresAt = stringValue(payload.expires_at);
650
+ if (expiresAt) {
651
+ const parsed = new Date(expiresAt);
652
+ return Number.isNaN(parsed.getTime()) ? fallback : parsed;
653
+ }
654
+ const expiresIn = typeof payload.expires_in === "number" ? payload.expires_in : void 0;
655
+ if (expiresIn && Number.isFinite(expiresIn) && expiresIn > 0) {
656
+ return new Date(Date.now() + expiresIn * 1e3);
657
+ }
658
+ return fallback;
659
+ }
660
+ function stringRecord(value) {
661
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
662
+ return null;
663
+ }
664
+ const out = {};
665
+ for (const [key, raw] of Object.entries(value)) {
666
+ if (typeof raw !== "string") {
667
+ return null;
668
+ }
669
+ out[key] = raw;
670
+ }
671
+ return out;
672
+ }
673
+ function stringValue(value) {
674
+ return typeof value === "string" && value.length > 0 ? value : void 0;
675
+ }
676
+ async function assertOAuthEndpointAllowed(rawUrl, settings) {
677
+ if (settings.integrationsAllowPrivateNetworkTargets || ["local", "test"].includes(settings.environment)) {
678
+ return;
679
+ }
680
+ const url = new URL(rawUrl);
681
+ if (url.protocol !== "https:") {
682
+ throw new Error("OAuth token endpoint must use https outside local/test");
683
+ }
684
+ const hostname = url.hostname.toLowerCase();
685
+ if (hostname === "localhost" || hostname.endsWith(".localhost")) {
686
+ throw new Error("OAuth token endpoint may not target localhost");
687
+ }
688
+ const literal = isIP(hostname);
689
+ const addresses = literal ? [hostname] : (await lookup(hostname, { all: true })).map((entry) => entry.address);
690
+ if (addresses.some(isPrivateAddress)) {
691
+ throw new Error("OAuth token endpoint may not target a private network address");
692
+ }
693
+ }
694
+ function isPrivateAddress(address) {
695
+ const normalized = normalizeAddress(address);
696
+ const mapped = ipv4FromMappedIpv6(normalized);
697
+ if (mapped) {
698
+ return isPrivateIpv4Address(mapped);
699
+ }
700
+ if (normalized.includes(":")) {
701
+ if (isIP(normalized) !== 6) {
702
+ return true;
703
+ }
704
+ return normalized === "::1" || normalized === "::" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb");
705
+ }
706
+ return isPrivateIpv4Address(normalized);
707
+ }
708
+ function normalizeAddress(address) {
709
+ const trimmed = address.trim().toLowerCase();
710
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
711
+ return trimmed.slice(1, -1);
712
+ }
713
+ return trimmed;
714
+ }
715
+ function ipv4FromMappedIpv6(address) {
716
+ if (!address.startsWith("::ffff:")) {
717
+ return null;
718
+ }
719
+ const embedded = address.slice("::ffff:".length);
720
+ if (embedded.includes(".")) {
721
+ return embedded;
722
+ }
723
+ const parts = embedded.split(":");
724
+ if (parts.length !== 2 || parts.some((part) => !/^[0-9a-f]{1,4}$/.test(part))) {
725
+ return null;
726
+ }
727
+ const high = Number.parseInt(parts[0], 16);
728
+ const low = Number.parseInt(parts[1], 16);
729
+ if (!Number.isInteger(high) || !Number.isInteger(low) || high < 0 || high > 65535 || low < 0 || low > 65535) {
730
+ return null;
731
+ }
732
+ return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
733
+ }
734
+ function isPrivateIpv4Address(address) {
735
+ if (isIP(address) !== 4) {
736
+ return true;
737
+ }
738
+ const parts = address.split(".").map((part) => Number(part));
739
+ if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
740
+ return true;
741
+ }
742
+ const [a, b] = parts;
743
+ return a === 0 || a === 10 || a === 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
744
+ }
745
+
340
746
  // src/index.ts
341
747
  var dbBindings = /* @__PURE__ */ new WeakMap();
342
748
  function rlsStrategyFor(db) {
@@ -439,6 +845,8 @@ var allWorkspacePermissions = [
439
845
  "github:manage",
440
846
  "github:use",
441
847
  "api_keys:manage",
848
+ "connections:read",
849
+ "connections:write",
442
850
  "environments:manage",
443
851
  "environments:use",
444
852
  "mcp_servers:attach",
@@ -1152,6 +1560,143 @@ async function deleteWorkspacePack(db, workspaceId, packId) {
1152
1560
  return rows.length > 0;
1153
1561
  });
1154
1562
  }
1563
+ var registryCapabilitySource = "registry";
1564
+ async function createImportBatch(db, input) {
1565
+ const [row] = await db.insert(importBatches).values({
1566
+ source: input.source,
1567
+ snapshotDate: input.snapshotDate,
1568
+ snapshotRef: input.snapshotRef ?? null,
1569
+ attributionNote: input.attributionNote,
1570
+ importedCount: input.importedCount ?? 0,
1571
+ skippedCount: input.skippedCount ?? 0,
1572
+ quarantinedCount: input.quarantinedCount ?? 0,
1573
+ logoFailureCount: input.logoFailureCount ?? 0,
1574
+ staleCount: input.staleCount ?? 0,
1575
+ details: input.details ?? {}
1576
+ }).returning();
1577
+ if (!row) {
1578
+ throw new Error("Failed to create import batch");
1579
+ }
1580
+ return mapImportBatch(row);
1581
+ }
1582
+ async function updateImportBatchCounts(db, id, input) {
1583
+ const [row] = await db.update(importBatches).set({
1584
+ importedCount: input.importedCount,
1585
+ skippedCount: input.skippedCount,
1586
+ quarantinedCount: input.quarantinedCount,
1587
+ logoFailureCount: input.logoFailureCount,
1588
+ staleCount: input.staleCount,
1589
+ ...input.details ? { details: input.details } : {},
1590
+ updatedAt: /* @__PURE__ */ new Date()
1591
+ }).where(eq(importBatches.id, id)).returning();
1592
+ if (!row) {
1593
+ throw new Error(`Import batch not found: ${id}`);
1594
+ }
1595
+ return mapImportBatch(row);
1596
+ }
1597
+ async function upsertRegistryCapabilityCatalogItem(db, input) {
1598
+ const now = /* @__PURE__ */ new Date();
1599
+ const metadata = {
1600
+ registry: "integrations.sh",
1601
+ providerDomain: input.providerDomain,
1602
+ scopesHint: input.scopesHint ?? [],
1603
+ ...input.metadata
1604
+ };
1605
+ const values = {
1606
+ id: input.id,
1607
+ accountId: null,
1608
+ workspaceId: null,
1609
+ kind: "mcp",
1610
+ source: registryCapabilitySource,
1611
+ name: input.name,
1612
+ description: input.description ?? null,
1613
+ category: "integrations",
1614
+ tags: input.tags ?? ["mcp", "integration", input.tier],
1615
+ homepageUrl: input.homepageUrl ?? `https://${input.providerDomain}`,
1616
+ endpointUrl: input.mcpUrl,
1617
+ installUrl: input.homepageUrl ?? `https://${input.providerDomain}`,
1618
+ authModel: input.authKind === "none" ? null : "credential_ref",
1619
+ providerDomain: input.providerDomain,
1620
+ surfaceType: "mcp",
1621
+ transport: input.transport,
1622
+ mcpUrl: input.mcpUrl,
1623
+ authKind: input.authKind,
1624
+ credentialFacts: input.credentialFacts,
1625
+ tier: input.tier,
1626
+ provenance: input.provenance,
1627
+ logoAssetPath: input.logoAssetPath ?? null,
1628
+ importBatchId: input.importBatchId,
1629
+ stale: false,
1630
+ staleAt: null,
1631
+ metadata,
1632
+ updatedAt: now
1633
+ };
1634
+ const updateValues = {
1635
+ id: values.id,
1636
+ kind: values.kind,
1637
+ name: values.name,
1638
+ description: values.description,
1639
+ category: values.category,
1640
+ tags: values.tags,
1641
+ homepageUrl: values.homepageUrl,
1642
+ endpointUrl: values.endpointUrl,
1643
+ installUrl: values.installUrl,
1644
+ authModel: values.authModel,
1645
+ surfaceType: values.surfaceType,
1646
+ transport: values.transport,
1647
+ authKind: values.authKind,
1648
+ credentialFacts: values.credentialFacts,
1649
+ tier: values.tier,
1650
+ provenance: values.provenance,
1651
+ logoAssetPath: sql`coalesce(excluded.logo_asset_path, ${capabilityCatalogItems.logoAssetPath})`,
1652
+ importBatchId: values.importBatchId,
1653
+ stale: false,
1654
+ staleAt: null,
1655
+ metadata: values.metadata,
1656
+ updatedAt: values.updatedAt
1657
+ };
1658
+ const [row] = await db.insert(capabilityCatalogItems).values(values).onConflictDoUpdate({
1659
+ target: [
1660
+ capabilityCatalogItems.source,
1661
+ capabilityCatalogItems.providerDomain,
1662
+ capabilityCatalogItems.mcpUrl
1663
+ ],
1664
+ set: updateValues
1665
+ }).returning();
1666
+ if (!row) {
1667
+ throw new Error("Failed to upsert registry capability catalog item");
1668
+ }
1669
+ return mapCapabilityCatalogItem(row);
1670
+ }
1671
+ async function listRegistryCatalogSurfaceKeys(db) {
1672
+ const rows = await db.select({
1673
+ id: capabilityCatalogItems.id,
1674
+ providerDomain: capabilityCatalogItems.providerDomain,
1675
+ mcpUrl: capabilityCatalogItems.mcpUrl
1676
+ }).from(capabilityCatalogItems).where(eq(capabilityCatalogItems.source, registryCapabilitySource));
1677
+ return rows.flatMap((row) => row.providerDomain && row.mcpUrl ? [{ id: row.id, providerDomain: row.providerDomain, mcpUrl: row.mcpUrl }] : []);
1678
+ }
1679
+ async function markStaleRegistryCatalogItems(db, activeKeys, importBatchId) {
1680
+ const active = new Set([...activeKeys].map((key) => `${key.providerDomain}
1681
+ ${key.mcpUrl}`));
1682
+ const existing = await listRegistryCatalogSurfaceKeys(db);
1683
+ const stale = existing.filter((row) => !active.has(`${row.providerDomain}
1684
+ ${row.mcpUrl}`));
1685
+ if (stale.length === 0) {
1686
+ return 0;
1687
+ }
1688
+ const now = /* @__PURE__ */ new Date();
1689
+ const updated = await db.update(capabilityCatalogItems).set({
1690
+ stale: true,
1691
+ staleAt: now,
1692
+ importBatchId,
1693
+ updatedAt: now
1694
+ }).where(and(
1695
+ eq(capabilityCatalogItems.source, registryCapabilitySource),
1696
+ inArray(capabilityCatalogItems.id, stale.map((row) => row.id))
1697
+ )).returning({ id: capabilityCatalogItems.id });
1698
+ return updated.length;
1699
+ }
1155
1700
  async function upsertCapabilityCatalogItem(db, input) {
1156
1701
  return await withRlsContext(db, { accountId: input.accountId, workspaceId: input.workspaceId }, async (scopedDb) => {
1157
1702
  const now = /* @__PURE__ */ new Date();
@@ -1169,6 +1714,18 @@ async function upsertCapabilityCatalogItem(db, input) {
1169
1714
  endpointUrl: input.endpointUrl ?? null,
1170
1715
  installUrl: input.installUrl ?? null,
1171
1716
  authModel: input.authModel ?? null,
1717
+ providerDomain: null,
1718
+ surfaceType: null,
1719
+ transport: null,
1720
+ mcpUrl: null,
1721
+ authKind: null,
1722
+ credentialFacts: [],
1723
+ tier: null,
1724
+ provenance: null,
1725
+ logoAssetPath: null,
1726
+ importBatchId: null,
1727
+ stale: false,
1728
+ staleAt: null,
1172
1729
  metadata: input.metadata ?? {},
1173
1730
  updatedAt: now
1174
1731
  };
@@ -1183,6 +1740,18 @@ async function upsertCapabilityCatalogItem(db, input) {
1183
1740
  endpointUrl: values.endpointUrl,
1184
1741
  installUrl: values.installUrl,
1185
1742
  authModel: values.authModel,
1743
+ providerDomain: values.providerDomain,
1744
+ surfaceType: values.surfaceType,
1745
+ transport: values.transport,
1746
+ mcpUrl: values.mcpUrl,
1747
+ authKind: values.authKind,
1748
+ credentialFacts: values.credentialFacts,
1749
+ tier: values.tier,
1750
+ provenance: values.provenance,
1751
+ logoAssetPath: values.logoAssetPath,
1752
+ importBatchId: values.importBatchId,
1753
+ stale: values.stale,
1754
+ staleAt: values.staleAt,
1186
1755
  metadata: values.metadata,
1187
1756
  updatedAt: values.updatedAt
1188
1757
  };
@@ -1198,13 +1767,25 @@ async function upsertCapabilityCatalogItem(db, input) {
1198
1767
  }
1199
1768
  async function listCapabilityCatalogItems(db, workspaceId) {
1200
1769
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
1201
- const rows = await scopedDb.select().from(capabilityCatalogItems).where(eq(capabilityCatalogItems.workspaceId, workspaceId)).orderBy(asc(capabilityCatalogItems.kind), asc(capabilityCatalogItems.name));
1770
+ const rows = await scopedDb.select().from(capabilityCatalogItems).where(or(
1771
+ eq(capabilityCatalogItems.workspaceId, workspaceId),
1772
+ and(
1773
+ isNull(capabilityCatalogItems.workspaceId),
1774
+ or(
1775
+ ne(capabilityCatalogItems.source, registryCapabilitySource),
1776
+ eq(capabilityCatalogItems.stale, false)
1777
+ )
1778
+ )
1779
+ )).orderBy(asc(capabilityCatalogItems.kind), asc(capabilityCatalogItems.name));
1202
1780
  return rows.map(mapCapabilityCatalogItem);
1203
1781
  });
1204
1782
  }
1205
1783
  async function getCapabilityCatalogItem(db, workspaceId, capabilityId) {
1206
1784
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
1207
- const [row] = await scopedDb.select().from(capabilityCatalogItems).where(and(eq(capabilityCatalogItems.workspaceId, workspaceId), eq(capabilityCatalogItems.id, capabilityId))).limit(1);
1785
+ const [row] = await scopedDb.select().from(capabilityCatalogItems).where(and(
1786
+ eq(capabilityCatalogItems.id, capabilityId),
1787
+ or(eq(capabilityCatalogItems.workspaceId, workspaceId), isNull(capabilityCatalogItems.workspaceId))
1788
+ )).orderBy(asc(sql`(${capabilityCatalogItems.workspaceId} is null)`)).limit(1);
1208
1789
  return row ? mapCapabilityCatalogItem(row) : null;
1209
1790
  });
1210
1791
  }
@@ -1270,19 +1851,30 @@ async function listEnabledMcpCapabilityServers(db, workspaceId) {
1270
1851
  item: capabilityCatalogItems,
1271
1852
  installation: capabilityInstallations
1272
1853
  }).from(capabilityInstallations).innerJoin(capabilityCatalogItems, and(
1273
- eq(capabilityInstallations.workspaceId, capabilityCatalogItems.workspaceId),
1854
+ or(
1855
+ eq(capabilityInstallations.workspaceId, capabilityCatalogItems.workspaceId),
1856
+ isNull(capabilityCatalogItems.workspaceId)
1857
+ ),
1274
1858
  eq(capabilityInstallations.capabilityId, capabilityCatalogItems.id)
1275
1859
  )).where(and(
1276
1860
  eq(capabilityInstallations.workspaceId, workspaceId),
1277
1861
  eq(capabilityInstallations.kind, "mcp"),
1278
1862
  eq(capabilityInstallations.status, "active")
1279
1863
  )).orderBy(asc(capabilityCatalogItems.name)));
1280
- return rows.flatMap(({ item, installation }) => {
1864
+ const preferredByInstallation = /* @__PURE__ */ new Map();
1865
+ for (const row of rows) {
1866
+ const existing = preferredByInstallation.get(row.installation.id);
1867
+ if (!existing || existing.item.workspaceId === null && row.item.workspaceId !== null) {
1868
+ preferredByInstallation.set(row.installation.id, row);
1869
+ }
1870
+ }
1871
+ return [...preferredByInstallation.values()].flatMap(({ item, installation }) => {
1281
1872
  if (!item.endpointUrl || !mcpConnectivityOk(installation.metadata)) {
1282
1873
  return [];
1283
1874
  }
1284
1875
  const headersEncrypted = encryptedHeadersConfig(installation.config.headersEncrypted);
1285
- if (item.authModel && !headersEncrypted) {
1876
+ const connectionRef = connectionRefConfig(installation.config.connectionRef);
1877
+ if (item.authModel && !headersEncrypted && !connectionRef) {
1286
1878
  return [];
1287
1879
  }
1288
1880
  const metadata = item.metadata;
@@ -1298,7 +1890,8 @@ async function listEnabledMcpCapabilityServers(db, workspaceId) {
1298
1890
  ...allowedTools ? { allowedTools } : {},
1299
1891
  ...timeoutMs ? { timeoutMs } : {},
1300
1892
  ...cacheToolsList !== void 0 ? { cacheToolsList } : {},
1301
- ...headersEncrypted ? { headersEncrypted } : {}
1893
+ ...headersEncrypted ? { headersEncrypted } : {},
1894
+ ...connectionRef ? { connectionRef } : {}
1302
1895
  }];
1303
1896
  });
1304
1897
  }
@@ -1329,6 +1922,361 @@ function mcpServerIdForCapability(capabilityId, metadata = {}) {
1329
1922
  const body = capabilityId.replace(/^[^:]+:/, "").toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 44) || "mcp";
1330
1923
  return `cap-${body}-${shortHash(capabilityId)}`;
1331
1924
  }
1925
+ var connectionMetadataColumns = {
1926
+ id: connections.id,
1927
+ accountId: connections.accountId,
1928
+ workspaceId: connections.workspaceId,
1929
+ subjectId: connections.subjectId,
1930
+ providerDomain: connections.providerDomain,
1931
+ kind: connections.kind,
1932
+ status: connections.status,
1933
+ grantedScopes: connections.grantedScopes,
1934
+ expiresAt: connections.expiresAt,
1935
+ lastRefreshAt: connections.lastRefreshAt,
1936
+ lastUsedAt: connections.lastUsedAt,
1937
+ lastError: connections.lastError,
1938
+ version: connections.version,
1939
+ metadata: connections.metadata,
1940
+ createdBySubjectId: connections.createdBySubjectId,
1941
+ updatedBySubjectId: connections.updatedBySubjectId,
1942
+ createdAt: connections.createdAt,
1943
+ updatedAt: connections.updatedAt
1944
+ };
1945
+ function connectionSubjectVisibility(subjectId) {
1946
+ return subjectId ? or(isNull(connections.subjectId), eq(connections.subjectId, subjectId)) : isNull(connections.subjectId);
1947
+ }
1948
+ async function createConnection(db, input) {
1949
+ return await withRlsContext(db, { accountId: input.accountId, workspaceId: input.workspaceId }, async (scopedDb) => {
1950
+ const [row] = await scopedDb.insert(connections).values({
1951
+ accountId: input.accountId,
1952
+ workspaceId: input.workspaceId,
1953
+ subjectId: input.subjectId ?? null,
1954
+ providerDomain: input.providerDomain,
1955
+ kind: input.kind,
1956
+ status: input.status ?? "active",
1957
+ credentialEncrypted: input.credentialEncrypted,
1958
+ grantedScopes: input.grantedScopes ?? [],
1959
+ expiresAt: input.expiresAt ?? null,
1960
+ metadata: input.metadata ?? {},
1961
+ createdBySubjectId: input.createdBySubjectId ?? null,
1962
+ updatedBySubjectId: input.updatedBySubjectId ?? input.createdBySubjectId ?? null
1963
+ }).returning(connectionMetadataColumns);
1964
+ if (!row) {
1965
+ throw new Error("Failed to create connection");
1966
+ }
1967
+ return mapConnectionMetadata(row);
1968
+ });
1969
+ }
1970
+ async function listConnectionsMetadata(db, workspaceId, subjectId) {
1971
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
1972
+ const rows = await scopedDb.select(connectionMetadataColumns).from(connections).where(and(eq(connections.workspaceId, workspaceId), connectionSubjectVisibility(subjectId))).orderBy(desc(connections.createdAt));
1973
+ return rows.map(mapConnectionMetadata);
1974
+ });
1975
+ }
1976
+ async function getConnectionMetadata(db, workspaceId, connectionId, subjectId) {
1977
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
1978
+ const [row] = await scopedDb.select(connectionMetadataColumns).from(connections).where(and(
1979
+ eq(connections.workspaceId, workspaceId),
1980
+ eq(connections.id, connectionId),
1981
+ connectionSubjectVisibility(subjectId)
1982
+ )).limit(1);
1983
+ return row ? mapConnectionMetadata(row) : null;
1984
+ });
1985
+ }
1986
+ async function updateConnection(db, input) {
1987
+ return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
1988
+ const set = {
1989
+ updatedAt: /* @__PURE__ */ new Date(),
1990
+ ...input.providerDomain !== void 0 ? { providerDomain: input.providerDomain } : {},
1991
+ ...input.subjectId !== void 0 ? { subjectId: input.subjectId } : {},
1992
+ ...input.kind !== void 0 ? { kind: input.kind } : {},
1993
+ ...input.status !== void 0 ? { status: input.status } : {},
1994
+ ...input.credentialEncrypted !== void 0 ? {
1995
+ credentialEncrypted: input.credentialEncrypted,
1996
+ version: sql`${connections.version} + 1`,
1997
+ lastError: null
1998
+ } : {},
1999
+ ...input.grantedScopes !== void 0 ? { grantedScopes: input.grantedScopes } : {},
2000
+ ...input.expiresAt !== void 0 ? { expiresAt: input.expiresAt } : {},
2001
+ ...input.metadata !== void 0 ? { metadata: input.metadata } : {},
2002
+ ...input.updatedBySubjectId !== void 0 ? { updatedBySubjectId: input.updatedBySubjectId } : {}
2003
+ };
2004
+ const [row] = await scopedDb.update(connections).set(set).where(and(
2005
+ eq(connections.workspaceId, input.workspaceId),
2006
+ eq(connections.id, input.connectionId),
2007
+ connectionSubjectVisibility(input.visibleToSubjectId),
2008
+ ...input.expectedVersion !== void 0 ? [eq(connections.version, input.expectedVersion)] : []
2009
+ )).returning(connectionMetadataColumns);
2010
+ return row ? mapConnectionMetadata(row) : null;
2011
+ });
2012
+ }
2013
+ async function revokeConnection(db, workspaceId, connectionId, updatedBySubjectId) {
2014
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
2015
+ const [row] = await scopedDb.update(connections).set({
2016
+ status: "revoked",
2017
+ // The version bump invalidates any in-flight refresh's (id, version) CAS,
2018
+ // so a racing refresh cannot commit and flip the row back to active.
2019
+ version: sql`${connections.version} + 1`,
2020
+ updatedBySubjectId: updatedBySubjectId ?? null,
2021
+ updatedAt: /* @__PURE__ */ new Date()
2022
+ }).where(and(
2023
+ eq(connections.workspaceId, workspaceId),
2024
+ eq(connections.id, connectionId),
2025
+ // Same visibility rule as get/update: shared rows plus the caller's own
2026
+ // subject rows. Cross-subject revocation (admin janitorial) arrives with
2027
+ // the subject-connections UX in I5, deliberately not before.
2028
+ connectionSubjectVisibility(updatedBySubjectId)
2029
+ )).returning(connectionMetadataColumns);
2030
+ return row ? mapConnectionMetadata(row) : null;
2031
+ });
2032
+ }
2033
+ async function loadConnectionCredentialForBroker(db, settings, input) {
2034
+ const key = environmentsEncryptionKeyBytes3(settings);
2035
+ if (!key) {
2036
+ throw new Error("connection credential present but OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY is not configured");
2037
+ }
2038
+ const subjectPredicate = input.allowSubjectOwned ? connectionSubjectVisibility(input.subjectId) : isNull(connections.subjectId);
2039
+ const conditions = [
2040
+ eq(connections.workspaceId, input.workspaceId),
2041
+ subjectPredicate
2042
+ ];
2043
+ if (input.connectionId) {
2044
+ conditions.push(eq(connections.id, input.connectionId));
2045
+ } else {
2046
+ conditions.push(eq(connections.providerDomain, input.providerDomain));
2047
+ if (input.kind) {
2048
+ conditions.push(eq(connections.kind, input.kind));
2049
+ }
2050
+ }
2051
+ return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
2052
+ const [row] = await scopedDb.select().from(connections).where(and(...conditions)).orderBy(desc(sql`(${connections.status} = 'active')`), desc(connections.updatedAt)).limit(1);
2053
+ if (!row) {
2054
+ return null;
2055
+ }
2056
+ let credential;
2057
+ try {
2058
+ credential = JSON.parse(decryptEnvironmentValue(key, row.credentialEncrypted));
2059
+ } catch (error) {
2060
+ throw new Error(`connection credential could not be decrypted for ${row.id}: ${error instanceof Error ? error.message : String(error)}`);
2061
+ }
2062
+ if (!credential || typeof credential !== "object" || Array.isArray(credential)) {
2063
+ throw new Error(`connection credential bundle for ${row.id} is not a JSON object`);
2064
+ }
2065
+ return {
2066
+ id: row.id,
2067
+ accountId: row.accountId,
2068
+ workspaceId: row.workspaceId,
2069
+ subjectId: row.subjectId,
2070
+ providerDomain: row.providerDomain,
2071
+ kind: row.kind,
2072
+ status: row.status,
2073
+ credential,
2074
+ grantedScopes: row.grantedScopes,
2075
+ expiresAt: row.expiresAt,
2076
+ lastRefreshAt: row.lastRefreshAt,
2077
+ version: row.version,
2078
+ metadata: row.metadata
2079
+ };
2080
+ });
2081
+ }
2082
+ async function recordConnectionTokenRefresh(db, input) {
2083
+ return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
2084
+ const set = {
2085
+ credentialEncrypted: input.credentialEncrypted,
2086
+ expiresAt: input.expiresAt,
2087
+ lastRefreshAt: input.lastRefreshAt,
2088
+ status: "active",
2089
+ lastError: null,
2090
+ version: sql`${connections.version} + 1`,
2091
+ updatedAt: /* @__PURE__ */ new Date(),
2092
+ ...input.grantedScopes !== void 0 ? { grantedScopes: input.grantedScopes } : {}
2093
+ };
2094
+ const updated = await scopedDb.update(connections).set(set).where(and(
2095
+ eq(connections.id, input.id),
2096
+ eq(connections.workspaceId, input.workspaceId),
2097
+ eq(connections.version, input.version),
2098
+ // A refresh may only ever renew a live credential; revoked/errored rows
2099
+ // stay dead even if a status change somewhere forgot to bump version.
2100
+ eq(connections.status, "active")
2101
+ )).returning({ id: connections.id });
2102
+ return updated.length > 0;
2103
+ });
2104
+ }
2105
+ async function setConnectionStatus(db, workspaceId, status, lastError, guard) {
2106
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
2107
+ const updated = await scopedDb.update(connections).set({
2108
+ status,
2109
+ lastError,
2110
+ version: sql`${connections.version} + 1`,
2111
+ updatedAt: /* @__PURE__ */ new Date()
2112
+ }).where(and(
2113
+ eq(connections.workspaceId, workspaceId),
2114
+ eq(connections.id, guard.id),
2115
+ eq(connections.version, guard.version)
2116
+ )).returning({ id: connections.id });
2117
+ return updated.length > 0;
2118
+ });
2119
+ }
2120
+ async function recordConnectionUsed(db, workspaceId, connectionId) {
2121
+ await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
2122
+ await scopedDb.update(connections).set({
2123
+ lastUsedAt: /* @__PURE__ */ new Date(),
2124
+ updatedAt: /* @__PURE__ */ new Date()
2125
+ }).where(and(eq(connections.workspaceId, workspaceId), eq(connections.id, connectionId)));
2126
+ });
2127
+ }
2128
+ async function loadIntegrationOAuthClient(db, settings, issuer) {
2129
+ const [row] = await db.select().from(integrationOauthClients).where(eq(integrationOauthClients.issuer, issuer)).limit(1);
2130
+ if (!row) {
2131
+ return null;
2132
+ }
2133
+ let clientSecret = null;
2134
+ if (row.clientSecretEncrypted) {
2135
+ const key = environmentsEncryptionKeyBytes3(settings);
2136
+ if (!key) {
2137
+ throw new Error("OAuth client secret present but OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY is not configured");
2138
+ }
2139
+ clientSecret = decryptEnvironmentValue(key, row.clientSecretEncrypted);
2140
+ }
2141
+ return {
2142
+ id: row.id,
2143
+ issuer: row.issuer,
2144
+ authorizationServer: row.authorizationServer,
2145
+ clientId: row.clientId,
2146
+ clientSecret,
2147
+ tokenEndpointAuthMethod: row.tokenEndpointAuthMethod,
2148
+ metadata: row.metadata,
2149
+ createdAt: row.createdAt,
2150
+ updatedAt: row.updatedAt
2151
+ };
2152
+ }
2153
+ async function storeIntegrationOAuthClient(db, input) {
2154
+ const [inserted] = await db.insert(integrationOauthClients).values({
2155
+ issuer: input.issuer,
2156
+ authorizationServer: input.authorizationServer,
2157
+ clientId: input.clientId,
2158
+ clientSecretEncrypted: input.clientSecretEncrypted ?? null,
2159
+ tokenEndpointAuthMethod: input.tokenEndpointAuthMethod ?? "none",
2160
+ metadata: input.metadata ?? {}
2161
+ }).onConflictDoNothing({
2162
+ target: integrationOauthClients.issuer
2163
+ }).returning();
2164
+ if (inserted) {
2165
+ return mapStoredIntegrationOAuthClient(inserted);
2166
+ }
2167
+ const [winner] = await db.select().from(integrationOauthClients).where(eq(integrationOauthClients.issuer, input.issuer)).limit(1);
2168
+ if (!winner) {
2169
+ throw new Error(`OAuth client registration conflict winner not found for issuer ${input.issuer}`);
2170
+ }
2171
+ return mapStoredIntegrationOAuthClient(winner);
2172
+ }
2173
+ function mapStoredIntegrationOAuthClient(row) {
2174
+ return {
2175
+ id: row.id,
2176
+ issuer: row.issuer,
2177
+ authorizationServer: row.authorizationServer,
2178
+ clientId: row.clientId,
2179
+ clientSecretEncrypted: row.clientSecretEncrypted,
2180
+ tokenEndpointAuthMethod: row.tokenEndpointAuthMethod,
2181
+ metadata: row.metadata,
2182
+ createdAt: row.createdAt,
2183
+ updatedAt: row.updatedAt
2184
+ };
2185
+ }
2186
+ async function consumeIntegrationOAuthStateNonce(db, input) {
2187
+ return await withRlsContext(db, { accountId: input.accountId, workspaceId: input.workspaceId }, async (scopedDb) => {
2188
+ await scopedDb.delete(integrationOauthStateNonces).where(and(
2189
+ eq(integrationOauthStateNonces.workspaceId, input.workspaceId),
2190
+ lt(integrationOauthStateNonces.expiresAt, input.now)
2191
+ ));
2192
+ const inserted = await scopedDb.insert(integrationOauthStateNonces).values({
2193
+ accountId: input.accountId,
2194
+ workspaceId: input.workspaceId,
2195
+ subjectId: input.subjectId,
2196
+ nonce: input.nonce,
2197
+ expiresAt: input.expiresAt,
2198
+ usedAt: input.now
2199
+ }).onConflictDoNothing({ target: integrationOauthStateNonces.nonce }).returning({ nonce: integrationOauthStateNonces.nonce });
2200
+ return inserted.length > 0;
2201
+ });
2202
+ }
2203
+ async function createKnowledgeMemory(db, input) {
2204
+ const text = requireDbString(input.text, "knowledge memory text");
2205
+ const scope = cleanDbString(input.scope) ?? "workspace";
2206
+ return await withRlsContext(db, { accountId: input.accountId, workspaceId: input.workspaceId }, async (scopedDb) => {
2207
+ const [row] = await scopedDb.insert(knowledgeMemories).values({
2208
+ accountId: input.accountId,
2209
+ workspaceId: input.workspaceId,
2210
+ status: input.status ?? "proposed",
2211
+ kind: input.kind ?? "semantic",
2212
+ scope,
2213
+ text,
2214
+ sourceRefs: input.sourceRefs ?? [],
2215
+ confidence: confidenceToStorage(input.confidence ?? 0.5),
2216
+ metadata: input.metadata ?? {},
2217
+ createdBySessionId: input.createdBySessionId ?? null
2218
+ }).returning();
2219
+ if (!row) {
2220
+ throw new Error("Failed to create knowledge memory");
2221
+ }
2222
+ return mapKnowledgeMemory(row);
2223
+ });
2224
+ }
2225
+ async function updateKnowledgeMemory(db, workspaceId, memoryId, input) {
2226
+ const reviewStatus = input.status === "approved" || input.status === "rejected";
2227
+ const scope = input.scope !== void 0 ? requireDbString(input.scope, "knowledge memory scope") : void 0;
2228
+ const text = input.text !== void 0 ? requireDbString(input.text, "knowledge memory text") : void 0;
2229
+ const reviewedBy = input.reviewedBy === null ? null : input.reviewedBy !== void 0 ? requireDbString(input.reviewedBy, "knowledge memory reviewer") : void 0;
2230
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
2231
+ const [row] = await scopedDb.update(knowledgeMemories).set({
2232
+ ...input.status !== void 0 ? { status: input.status } : {},
2233
+ ...input.kind !== void 0 ? { kind: input.kind } : {},
2234
+ ...scope !== void 0 ? { scope } : {},
2235
+ ...text !== void 0 ? { text } : {},
2236
+ ...input.sourceRefs !== void 0 ? { sourceRefs: input.sourceRefs } : {},
2237
+ ...input.confidence !== void 0 ? { confidence: confidenceToStorage(input.confidence) } : {},
2238
+ ...input.metadata !== void 0 ? { metadata: input.metadata } : {},
2239
+ // Re-proposing clears review metadata; an explicit reviewedBy in the same
2240
+ // update still wins via the later spread.
2241
+ ...input.status === "proposed" ? { reviewedBy: null, reviewedAt: null } : {},
2242
+ ...reviewedBy !== void 0 ? { reviewedBy } : {},
2243
+ ...reviewStatus ? { reviewedAt: /* @__PURE__ */ new Date() } : {},
2244
+ updatedAt: /* @__PURE__ */ new Date()
2245
+ }).where(and(eq(knowledgeMemories.workspaceId, workspaceId), eq(knowledgeMemories.id, memoryId))).returning();
2246
+ if (!row) {
2247
+ throw new Error(`Knowledge memory not found: ${memoryId}`);
2248
+ }
2249
+ return mapKnowledgeMemory(row);
2250
+ });
2251
+ }
2252
+ async function getKnowledgeMemory(db, workspaceId, memoryId) {
2253
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
2254
+ const [row] = await scopedDb.select().from(knowledgeMemories).where(and(eq(knowledgeMemories.workspaceId, workspaceId), eq(knowledgeMemories.id, memoryId))).limit(1);
2255
+ return row ? mapKnowledgeMemory(row) : null;
2256
+ });
2257
+ }
2258
+ async function listKnowledgeMemories(db, workspaceId, options = {}) {
2259
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
2260
+ const conditions = [eq(knowledgeMemories.workspaceId, workspaceId)];
2261
+ if (options.status) {
2262
+ conditions.push(eq(knowledgeMemories.status, options.status));
2263
+ }
2264
+ if (options.kind) {
2265
+ conditions.push(eq(knowledgeMemories.kind, options.kind));
2266
+ }
2267
+ const scope = cleanDbString(options.scope);
2268
+ if (scope) {
2269
+ conditions.push(eq(knowledgeMemories.scope, scope));
2270
+ }
2271
+ const query = cleanDbString(options.query);
2272
+ if (query) {
2273
+ conditions.push(sql`to_tsvector('simple', ${knowledgeMemories.text}) @@ plainto_tsquery('simple', ${query})`);
2274
+ }
2275
+ const limit = Math.min(Math.max(options.limit ?? 20, 1), 100);
2276
+ const rows = await scopedDb.select().from(knowledgeMemories).where(and(...conditions)).orderBy(desc(knowledgeMemories.updatedAt)).limit(limit);
2277
+ return rows.map(mapKnowledgeMemory);
2278
+ });
2279
+ }
1332
2280
  async function createSocialConnection(db, input) {
1333
2281
  return await withRlsContext(db, { accountId: input.accountId, workspaceId: input.workspaceId }, async (scopedDb) => {
1334
2282
  const [row] = await scopedDb.insert(socialConnections).values({
@@ -1687,7 +2635,7 @@ async function loadWorkspaceEnvironmentForRun(db, settings, workspaceId, environ
1687
2635
  if (!environmentId) {
1688
2636
  return null;
1689
2637
  }
1690
- const key = environmentsEncryptionKeyBytes2(settings);
2638
+ const key = environmentsEncryptionKeyBytes3(settings);
1691
2639
  if (!key) {
1692
2640
  throw new Error("workspace environment attached but OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY is not configured");
1693
2641
  }
@@ -1768,7 +2716,7 @@ async function upsertCodexSubscriptionCredential(db, input) {
1768
2716
  });
1769
2717
  }
1770
2718
  async function loadCodexCredentialForRun(db, settings, workspaceId, credentialId) {
1771
- const key = environmentsEncryptionKeyBytes2(settings);
2719
+ const key = environmentsEncryptionKeyBytes3(settings);
1772
2720
  if (!key) {
1773
2721
  throw new Error("codex credential present but OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY is not configured");
1774
2722
  }
@@ -2186,6 +3134,7 @@ async function insertSessionMcpServers(db, input) {
2186
3134
  allowedTools: server.allowedTools ?? null,
2187
3135
  timeoutMs: server.timeoutMs ?? null,
2188
3136
  cacheToolsList: server.cacheToolsList ?? false,
3137
+ requireApproval: server.requireApproval ?? null,
2189
3138
  headersEncrypted: server.headersEncrypted ?? {}
2190
3139
  }))).returning();
2191
3140
  return rows.map(mapSessionMcpServerMetadata);
@@ -2247,6 +3196,7 @@ async function listSessionMcpServersForRun(db, workspaceId, sessionId, encryptio
2247
3196
  ...row.allowedTools ? { allowedTools: row.allowedTools } : {},
2248
3197
  ...row.timeoutMs ? { timeoutMs: row.timeoutMs } : {},
2249
3198
  ...row.cacheToolsList ? { cacheToolsList: row.cacheToolsList } : {},
3199
+ ...row.requireApproval != null ? { requireApproval: row.requireApproval } : {},
2250
3200
  headers
2251
3201
  };
2252
3202
  });
@@ -2407,6 +3357,17 @@ async function listSessionEvents(db, workspaceId, sessionId, afterOrOptions = 0,
2407
3357
  return (hasBefore ? rows.reverse() : rows).map(mapEvent);
2408
3358
  });
2409
3359
  }
3360
+ async function reserveToolspaceCallForTurn(db, workspaceId, sessionId, turnId, limit) {
3361
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
3362
+ const [row] = await scopedDb.update(sessionTurns).set({ toolspaceCallCount: sql`${sessionTurns.toolspaceCallCount} + 1` }).where(and(
3363
+ eq(sessionTurns.workspaceId, workspaceId),
3364
+ eq(sessionTurns.sessionId, sessionId),
3365
+ eq(sessionTurns.id, turnId),
3366
+ sql`${sessionTurns.toolspaceCallCount} < ${limit}`
3367
+ )).returning({ count: sessionTurns.toolspaceCallCount });
3368
+ return row ? { reserved: true, count: Number(row.count) } : { reserved: false };
3369
+ });
3370
+ }
2410
3371
  function normalizeEventSequence(value, fallback) {
2411
3372
  if (value === void 0 || !Number.isFinite(value)) {
2412
3373
  return fallback;
@@ -3267,6 +4228,16 @@ async function countSandboxLeasesByLiveness(db) {
3267
4228
  }
3268
4229
  return counts;
3269
4230
  }
4231
+ async function listCreditBalancesByAccount(db) {
4232
+ const rows = await rawRows(db, sql`
4233
+ select account_id, balance_micros
4234
+ from opengeni_private.credit_balance_by_account()
4235
+ `);
4236
+ return rows.map((row) => ({
4237
+ accountId: row.account_id,
4238
+ balanceMicros: Number(row.balance_micros)
4239
+ }));
4240
+ }
3270
4241
  async function listLiveModalSandboxLeaseAttributions(db) {
3271
4242
  const rows = await rawRows(db, sql`
3272
4243
  select lease_id, workspace_id, sandbox_group_id, instance_id, liveness
@@ -3422,6 +4393,7 @@ function mapEnrollment(row) {
3422
4393
  pubkey: row.pubkey,
3423
4394
  exposure: row.exposure,
3424
4395
  hasDisplay: row.hasDisplay,
4396
+ desktopUnavailableReason: row.desktopUnavailableReason ?? null,
3425
4397
  allowScreenControl: row.allowScreenControl,
3426
4398
  status: row.status,
3427
4399
  os: row.os,
@@ -3507,14 +4479,22 @@ async function touchEnrollmentLastSeen(db, input) {
3507
4479
  ));
3508
4480
  });
3509
4481
  }
3510
- async function setEnrollmentHasDisplay(db, input) {
4482
+ async function setEnrollmentDisplayState(db, input) {
3511
4483
  return await withRlsContext(db, { accountId: input.accountId, workspaceId: input.workspaceId }, async (scopedDb) => {
3512
- const rows = await scopedDb.update(enrollments).set({ hasDisplay: input.hasDisplay, updatedAt: /* @__PURE__ */ new Date() }).where(and(
4484
+ const rows = await scopedDb.update(enrollments).set({
4485
+ hasDisplay: input.hasDisplay,
4486
+ desktopUnavailableReason: input.desktopUnavailableReason,
4487
+ updatedAt: /* @__PURE__ */ new Date()
4488
+ }).where(and(
3513
4489
  eq(enrollments.workspaceId, input.workspaceId),
3514
4490
  eq(enrollments.id, input.enrollmentId),
3515
- // Only write on a CHANGE — an unchanged display must not churn a write on
3516
- // every reconnect Hello.
3517
- ne(enrollments.hasDisplay, input.hasDisplay)
4491
+ // Only write on a CHANGE to EITHER field — an unchanged display state must
4492
+ // not churn a write on every reconnect Hello. `IS DISTINCT FROM` is the
4493
+ // null-safe inequality (a plain `ne` skips NULL rows).
4494
+ or(
4495
+ ne(enrollments.hasDisplay, input.hasDisplay),
4496
+ sql`${enrollments.desktopUnavailableReason} IS DISTINCT FROM ${input.desktopUnavailableReason}`
4497
+ )
3518
4498
  )).returning({ id: enrollments.id });
3519
4499
  return { updated: rows.length > 0 };
3520
4500
  });
@@ -5012,11 +5992,28 @@ function mapWorkspacePack(row) {
5012
5992
  updatedAt: row.updatedAt.toISOString()
5013
5993
  };
5014
5994
  }
5995
+ function mapImportBatch(row) {
5996
+ return {
5997
+ id: row.id,
5998
+ source: row.source,
5999
+ snapshotDate: row.snapshotDate.toISOString(),
6000
+ snapshotRef: row.snapshotRef,
6001
+ attributionNote: row.attributionNote,
6002
+ importedCount: row.importedCount,
6003
+ skippedCount: row.skippedCount,
6004
+ quarantinedCount: row.quarantinedCount,
6005
+ logoFailureCount: row.logoFailureCount,
6006
+ staleCount: row.staleCount,
6007
+ details: row.details,
6008
+ createdAt: row.createdAt.toISOString(),
6009
+ updatedAt: row.updatedAt.toISOString()
6010
+ };
6011
+ }
5015
6012
  function mapCapabilityCatalogItem(row) {
5016
6013
  const runtime = row.kind === "mcp" && row.endpointUrl ? {
5017
6014
  available: true,
5018
6015
  mcpServerId: mcpServerIdForCapability(row.id, row.metadata),
5019
- transport: "streamable-http",
6016
+ transport: row.transport ?? "streamable-http",
5020
6017
  notes: row.authModel ? "Requires credential headers supplied in the enable request." : null
5021
6018
  } : {
5022
6019
  available: false,
@@ -5024,8 +6021,8 @@ function mapCapabilityCatalogItem(row) {
5024
6021
  };
5025
6022
  return {
5026
6023
  id: row.id,
5027
- accountId: row.accountId,
5028
- workspaceId: row.workspaceId,
6024
+ ...row.accountId ? { accountId: row.accountId } : {},
6025
+ ...row.workspaceId ? { workspaceId: row.workspaceId } : {},
5029
6026
  kind: row.kind,
5030
6027
  source: row.source,
5031
6028
  name: row.name,
@@ -5036,10 +6033,25 @@ function mapCapabilityCatalogItem(row) {
5036
6033
  endpointUrl: row.endpointUrl,
5037
6034
  installUrl: row.installUrl,
5038
6035
  authModel: row.authModel,
6036
+ providerDomain: row.providerDomain,
6037
+ surfaceType: row.surfaceType,
6038
+ transport: row.transport,
6039
+ mcpUrl: row.mcpUrl,
6040
+ authKind: row.authKind,
6041
+ credentialFacts: row.credentialFacts,
6042
+ tier: row.tier,
6043
+ provenance: row.provenance,
6044
+ logoAssetPath: row.logoAssetPath,
6045
+ importBatchId: row.importBatchId,
6046
+ stale: row.stale,
6047
+ staleAt: row.staleAt?.toISOString() ?? null,
5039
6048
  tools: [],
5040
6049
  runtime,
5041
6050
  enabled: false,
5042
6051
  enabledReason: null,
6052
+ // Overwritten by applyCapabilityEnablement in @opengeni/core, which knows
6053
+ // the installation; a freshly-read catalog row carries no connection.
6054
+ connectionRef: null,
5043
6055
  metadata: row.metadata,
5044
6056
  createdAt: row.createdAt.toISOString(),
5045
6057
  updatedAt: row.updatedAt.toISOString()
@@ -5089,6 +6101,46 @@ function redactInstallationConfig(config) {
5089
6101
  const { headersEncrypted: _omitted, ...rest } = config;
5090
6102
  return { ...rest, headerNames: Object.keys(headersEncrypted).sort() };
5091
6103
  }
6104
+ function mapConnectionMetadata(row) {
6105
+ return {
6106
+ id: row.id,
6107
+ accountId: row.accountId,
6108
+ workspaceId: row.workspaceId,
6109
+ subjectId: row.subjectId,
6110
+ providerDomain: row.providerDomain,
6111
+ kind: row.kind,
6112
+ status: row.status,
6113
+ grantedScopes: row.grantedScopes,
6114
+ expiresAt: row.expiresAt?.toISOString() ?? null,
6115
+ lastRefreshAt: row.lastRefreshAt?.toISOString() ?? null,
6116
+ lastUsedAt: row.lastUsedAt?.toISOString() ?? null,
6117
+ lastError: row.lastError,
6118
+ version: row.version,
6119
+ metadata: row.metadata,
6120
+ createdBySubjectId: row.createdBySubjectId,
6121
+ updatedBySubjectId: row.updatedBySubjectId,
6122
+ createdAt: row.createdAt.toISOString(),
6123
+ updatedAt: row.updatedAt.toISOString()
6124
+ };
6125
+ }
6126
+ function mapKnowledgeMemory(row) {
6127
+ return {
6128
+ id: row.id,
6129
+ workspaceId: row.workspaceId,
6130
+ status: row.status,
6131
+ kind: row.kind,
6132
+ scope: row.scope,
6133
+ text: row.text,
6134
+ sourceRefs: Array.isArray(row.sourceRefs) ? row.sourceRefs : [],
6135
+ confidence: confidenceFromStorage(row.confidence),
6136
+ metadata: row.metadata,
6137
+ createdBySessionId: row.createdBySessionId,
6138
+ reviewedBy: row.reviewedBy,
6139
+ reviewedAt: row.reviewedAt ? row.reviewedAt.toISOString() : null,
6140
+ createdAt: row.createdAt.toISOString(),
6141
+ updatedAt: row.updatedAt.toISOString()
6142
+ };
6143
+ }
5092
6144
  function mapSocialConnection(row) {
5093
6145
  return {
5094
6146
  id: row.id,
@@ -5176,6 +6228,26 @@ function stringArrayConfig(value) {
5176
6228
  const values = value.filter((item) => typeof item === "string" && item.trim().length > 0);
5177
6229
  return values.length > 0 ? [...new Set(values.map((item) => item.trim()))] : void 0;
5178
6230
  }
6231
+ function cleanDbString(value) {
6232
+ const trimmed = value?.trim();
6233
+ return trimmed ? trimmed : void 0;
6234
+ }
6235
+ function requireDbString(value, field) {
6236
+ const trimmed = cleanDbString(value);
6237
+ if (!trimmed) {
6238
+ throw new Error(`${field} is required`);
6239
+ }
6240
+ return trimmed;
6241
+ }
6242
+ function confidenceToStorage(value) {
6243
+ if (!Number.isFinite(value)) {
6244
+ return 50;
6245
+ }
6246
+ return Math.round(Math.min(Math.max(value, 0), 1) * 100);
6247
+ }
6248
+ function confidenceFromStorage(value) {
6249
+ return Number((Math.min(Math.max(value, 0), 100) / 100).toFixed(2));
6250
+ }
5179
6251
  function positiveIntegerConfig(value) {
5180
6252
  if (typeof value === "number" && Number.isInteger(value) && value > 0) {
5181
6253
  return value;
@@ -5197,7 +6269,36 @@ function encryptedHeadersConfig(value) {
5197
6269
  }
5198
6270
  function mcpConnectivityOk(metadata) {
5199
6271
  const value = metadata.mcpConnectivity;
5200
- return !!value && typeof value === "object" && "status" in value && value.status === "ok";
6272
+ return !!value && typeof value === "object" && "status" in value && (value.status === "ok" || value.status === "auth_deferred");
6273
+ }
6274
+ function connectionRefConfig(value) {
6275
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
6276
+ return void 0;
6277
+ }
6278
+ const record = value;
6279
+ if (typeof record.providerDomain !== "string" || record.providerDomain.length === 0) {
6280
+ return void 0;
6281
+ }
6282
+ const ref = { providerDomain: record.providerDomain };
6283
+ if (typeof record.connectionId === "string" && record.connectionId.length > 0) {
6284
+ ref.connectionId = record.connectionId;
6285
+ }
6286
+ if (typeof record.kind === "string" && ["oauth2", "api_key", "app_install", "delegated"].includes(record.kind)) {
6287
+ ref.kind = record.kind;
6288
+ }
6289
+ if (Array.isArray(record.scopes)) {
6290
+ const scopes = record.scopes.filter((scope) => typeof scope === "string" && scope.length > 0);
6291
+ if (scopes.length > 0) {
6292
+ ref.scopes = scopes;
6293
+ }
6294
+ }
6295
+ if (typeof record.resource === "string" && record.resource.length > 0) {
6296
+ ref.resource = record.resource;
6297
+ }
6298
+ if (record.subjectScope === "workspace" || record.subjectScope === "subject") {
6299
+ ref.subjectScope = record.subjectScope;
6300
+ }
6301
+ return ref;
5201
6302
  }
5202
6303
  function shortHash(value) {
5203
6304
  let hash = 2166136261;
@@ -5210,6 +6311,7 @@ function shortHash(value) {
5210
6311
  export {
5211
6312
  CLEARED_RUN_STATE,
5212
6313
  CODEX_ROTATION_STRATEGIES,
6314
+ ConnectionRefreshHttpError,
5213
6315
  MACHINE_METRICS_SERIES_INTERVAL_MS,
5214
6316
  SandboxImageConflictError,
5215
6317
  SandboxLeaseSupersededError,
@@ -5227,6 +6329,7 @@ export {
5227
6329
  approveDeviceEnrollmentRequest,
5228
6330
  bootstrapWorkspace,
5229
6331
  buildCodexTokenResolver,
6332
+ buildConnectionTokenResolver,
5230
6333
  cancelQueuedSessionTurn,
5231
6334
  claimNextQueuedTurn,
5232
6335
  clearSessionContext,
@@ -5236,6 +6339,7 @@ export {
5236
6339
  completeFileUpload,
5237
6340
  confirmDrainCold,
5238
6341
  consumeDeviceEnrollmentRequest,
6342
+ consumeIntegrationOAuthStateNonce,
5239
6343
  consumeSessionCompactionRequest,
5240
6344
  countActiveApiKeysForWorkspace,
5241
6345
  countActiveSessionHistoryItems,
@@ -5251,10 +6355,13 @@ export {
5251
6355
  countWorkspaceEnvironments,
5252
6356
  countWorkspacesForAccount,
5253
6357
  createApiKey,
6358
+ createConnection,
5254
6359
  createDb,
5255
6360
  createDeviceEnrollmentRequest,
5256
6361
  createEnrollment,
5257
6362
  createFileUpload,
6363
+ createImportBatch,
6364
+ createKnowledgeMemory,
5258
6365
  createSandbox,
5259
6366
  createScheduledTask,
5260
6367
  createScheduledTaskRun,
@@ -5301,10 +6408,12 @@ export {
5301
6408
  getCapabilityInstallation,
5302
6409
  getCodexCredentialStatus,
5303
6410
  getCodexRotationSettings,
6411
+ getConnectionMetadata,
5304
6412
  getDeviceEnrollmentRequestByDeviceCode,
5305
6413
  getEnrollment,
5306
6414
  getFile,
5307
6415
  getFileUpload,
6416
+ getKnowledgeMemory,
5308
6417
  getLatestRunState,
5309
6418
  getManagedAccount,
5310
6419
  getManagedUserByEmail,
@@ -5342,21 +6451,26 @@ export {
5342
6451
  insertRecording,
5343
6452
  isCodexBilledModel2 as isCodexBilledModel,
5344
6453
  isCodexBilledTurn,
6454
+ isPrivateAddress,
5345
6455
  isStripeWebhookProcessed,
5346
6456
  listApiKeys,
5347
6457
  listCapabilityCatalogItems,
5348
6458
  listCapabilityInstallations,
5349
6459
  listCodexAccountStatuses,
6460
+ listConnectionsMetadata,
6461
+ listCreditBalancesByAccount,
5350
6462
  listDistinctEnvironmentIdsInGroup,
5351
6463
  listEnabledMcpCapabilityServers,
5352
6464
  listEnrollments,
5353
6465
  listGitHubInstallationIdsForWorkspace,
5354
6466
  listGitHubInstallationsForWorkspace,
6467
+ listKnowledgeMemories,
5355
6468
  listLiveModalSandboxLeaseAttributions,
5356
6469
  listMeterableWarmLeases,
5357
6470
  listOpenPtySessions,
5358
6471
  listPackInstallations,
5359
6472
  listRecordings,
6473
+ listRegistryCatalogSurfaceKeys,
5360
6474
  listSandboxes,
5361
6475
  listScheduledTaskRuns,
5362
6476
  listScheduledTasks,
@@ -5374,8 +6488,11 @@ export {
5374
6488
  listWorkspacePacks,
5375
6489
  listWorkspacesForSubject,
5376
6490
  loadCodexCredentialForRun,
6491
+ loadConnectionCredentialForBroker,
6492
+ loadIntegrationOAuthClient,
5377
6493
  loadWorkspaceEnvironmentForRun,
5378
6494
  markFileUploadFailed,
6495
+ markStaleRegistryCatalogItems,
5379
6496
  markStripeWebhookProcessed,
5380
6497
  mcpServerIdForCapability,
5381
6498
  migrate,
@@ -5395,6 +6512,8 @@ export {
5395
6512
  recordCodexAccountConnectors,
5396
6513
  recordCodexAccountUsage,
5397
6514
  recordCodexTokenRefresh,
6515
+ recordConnectionTokenRefresh,
6516
+ recordConnectionUsed,
5398
6517
  recordLeaseDataPlaneUrl,
5399
6518
  recordLeaseTerminalDataPlaneUrl,
5400
6519
  recordSessionActiveCodexCredential,
@@ -5402,6 +6521,7 @@ export {
5402
6521
  recordStripeWebhookEvent,
5403
6522
  recordUsageEvent,
5404
6523
  recordWarmingSandboxCreated,
6524
+ refreshOAuthConnectionCredential,
5405
6525
  registerDbBinding,
5406
6526
  registerWorkspacePack,
5407
6527
  releaseLeaseHolder,
@@ -5415,7 +6535,9 @@ export {
5415
6535
  requireSession,
5416
6536
  requireSocialConnection,
5417
6537
  requireWorkspace,
6538
+ reserveToolspaceCallForTurn,
5418
6539
  revokeApiKey,
6540
+ revokeConnection,
5419
6541
  revokeEnrollment,
5420
6542
  revokeViewer,
5421
6543
  rlsContextForWorkspace,
@@ -5429,7 +6551,8 @@ export {
5429
6551
  setActiveSandbox,
5430
6552
  setCodexCredentialExhausted,
5431
6553
  setCodexCredentialStatus,
5432
- setEnrollmentHasDisplay,
6554
+ setConnectionStatus,
6555
+ setEnrollmentDisplayState,
5433
6556
  setRlsContext,
5434
6557
  setSessionCodexPin,
5435
6558
  setSessionGoalLastContinuationTurn,
@@ -5438,9 +6561,13 @@ export {
5438
6561
  setSessionStatus,
5439
6562
  setTemporalWorkflowId,
5440
6563
  setWorkspaceEnvironmentVariable,
6564
+ storeIntegrationOAuthClient,
5441
6565
  sumUsageQuantity,
5442
6566
  touchEnrollmentLastSeen,
5443
6567
  updateCodexRotationSettings,
6568
+ updateConnection,
6569
+ updateImportBatchCounts,
6570
+ updateKnowledgeMemory,
5444
6571
  updatePackInstallationStatus,
5445
6572
  updatePtySessionActivity,
5446
6573
  updateQueuedSessionTurn,
@@ -5457,6 +6584,7 @@ export {
5457
6584
  upsertCodexSubscriptionCredential,
5458
6585
  upsertGitHubInstallation,
5459
6586
  upsertMachineMetricsLatest,
6587
+ upsertRegistryCapabilityCatalogItem,
5460
6588
  upsertSandboxSessionEnvelope,
5461
6589
  upsertSessionGoal,
5462
6590
  wakeParentSessionForChildCompletion,