@opengeni/db 0.4.0 → 0.6.0
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/LICENSE +190 -0
- package/dist/{chunk-T2U4H4Z2.js → chunk-ZIUCA2IO.js} +150 -5
- package/dist/chunk-ZIUCA2IO.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1161 -21
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.d.ts +286 -5
- package/dist/{schema-DuRsrmzD.d.ts → schema-Dsz6UHNv.d.ts} +2391 -854
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +11 -1
- package/drizzle/0038_enrollment_desktop_unavailable_reason.sql +18 -0
- package/drizzle/0038_observability_counts.sql +32 -0
- package/drizzle/0039_connections.sql +62 -0
- package/drizzle/0039_session_mcp_require_approval.sql +7 -0
- package/drizzle/0040_schema_agnostic_opengeni_app_grants.sql +36 -0
- package/drizzle/0041_knowledge_layer.sql +73 -0
- package/drizzle/0042_integration_oauth_state.sql +56 -0
- package/drizzle/0043_integrations_catalog_imports.sql +104 -0
- package/drizzle/0043_toolspace_call_budget.sql +14 -0
- package/package.json +5 -10
- package/src/connection-token-resolver.ts +481 -0
- package/src/event-payload-sanitizer.ts +23 -0
- package/src/index.ts +1086 -23
- package/src/schema.ts +150 -2
- package/dist/chunk-T2U4H4Z2.js.map +0 -1
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-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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;
|
|
@@ -3243,6 +4204,30 @@ async function listMeterableWarmLeases(db) {
|
|
|
3243
4204
|
backend: r.backend
|
|
3244
4205
|
}));
|
|
3245
4206
|
}
|
|
4207
|
+
async function countQueuedTurns(db) {
|
|
4208
|
+
const rows = await rawRows(db, sql`
|
|
4209
|
+
select opengeni_private.count_queued_turns() as count
|
|
4210
|
+
`);
|
|
4211
|
+
return Number(rows[0]?.count ?? 0);
|
|
4212
|
+
}
|
|
4213
|
+
async function countSandboxLeasesByLiveness(db) {
|
|
4214
|
+
const counts = {
|
|
4215
|
+
cold: 0,
|
|
4216
|
+
warming: 0,
|
|
4217
|
+
warm: 0,
|
|
4218
|
+
draining: 0
|
|
4219
|
+
};
|
|
4220
|
+
const rows = await rawRows(db, sql`
|
|
4221
|
+
select liveness, count
|
|
4222
|
+
from opengeni_private.count_sandbox_leases_by_liveness()
|
|
4223
|
+
`);
|
|
4224
|
+
for (const row of rows) {
|
|
4225
|
+
if (row.liveness in counts) {
|
|
4226
|
+
counts[row.liveness] = Number(row.count);
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
return counts;
|
|
4230
|
+
}
|
|
3246
4231
|
async function listLiveModalSandboxLeaseAttributions(db) {
|
|
3247
4232
|
const rows = await rawRows(db, sql`
|
|
3248
4233
|
select lease_id, workspace_id, sandbox_group_id, instance_id, liveness
|
|
@@ -3398,6 +4383,7 @@ function mapEnrollment(row) {
|
|
|
3398
4383
|
pubkey: row.pubkey,
|
|
3399
4384
|
exposure: row.exposure,
|
|
3400
4385
|
hasDisplay: row.hasDisplay,
|
|
4386
|
+
desktopUnavailableReason: row.desktopUnavailableReason ?? null,
|
|
3401
4387
|
allowScreenControl: row.allowScreenControl,
|
|
3402
4388
|
status: row.status,
|
|
3403
4389
|
os: row.os,
|
|
@@ -3483,14 +4469,22 @@ async function touchEnrollmentLastSeen(db, input) {
|
|
|
3483
4469
|
));
|
|
3484
4470
|
});
|
|
3485
4471
|
}
|
|
3486
|
-
async function
|
|
4472
|
+
async function setEnrollmentDisplayState(db, input) {
|
|
3487
4473
|
return await withRlsContext(db, { accountId: input.accountId, workspaceId: input.workspaceId }, async (scopedDb) => {
|
|
3488
|
-
const rows = await scopedDb.update(enrollments).set({
|
|
4474
|
+
const rows = await scopedDb.update(enrollments).set({
|
|
4475
|
+
hasDisplay: input.hasDisplay,
|
|
4476
|
+
desktopUnavailableReason: input.desktopUnavailableReason,
|
|
4477
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
4478
|
+
}).where(and(
|
|
3489
4479
|
eq(enrollments.workspaceId, input.workspaceId),
|
|
3490
4480
|
eq(enrollments.id, input.enrollmentId),
|
|
3491
|
-
// Only write on a CHANGE — an unchanged display must
|
|
3492
|
-
// every reconnect Hello.
|
|
3493
|
-
|
|
4481
|
+
// Only write on a CHANGE to EITHER field — an unchanged display state must
|
|
4482
|
+
// not churn a write on every reconnect Hello. `IS DISTINCT FROM` is the
|
|
4483
|
+
// null-safe inequality (a plain `ne` skips NULL rows).
|
|
4484
|
+
or(
|
|
4485
|
+
ne(enrollments.hasDisplay, input.hasDisplay),
|
|
4486
|
+
sql`${enrollments.desktopUnavailableReason} IS DISTINCT FROM ${input.desktopUnavailableReason}`
|
|
4487
|
+
)
|
|
3494
4488
|
)).returning({ id: enrollments.id });
|
|
3495
4489
|
return { updated: rows.length > 0 };
|
|
3496
4490
|
});
|
|
@@ -4988,11 +5982,28 @@ function mapWorkspacePack(row) {
|
|
|
4988
5982
|
updatedAt: row.updatedAt.toISOString()
|
|
4989
5983
|
};
|
|
4990
5984
|
}
|
|
5985
|
+
function mapImportBatch(row) {
|
|
5986
|
+
return {
|
|
5987
|
+
id: row.id,
|
|
5988
|
+
source: row.source,
|
|
5989
|
+
snapshotDate: row.snapshotDate.toISOString(),
|
|
5990
|
+
snapshotRef: row.snapshotRef,
|
|
5991
|
+
attributionNote: row.attributionNote,
|
|
5992
|
+
importedCount: row.importedCount,
|
|
5993
|
+
skippedCount: row.skippedCount,
|
|
5994
|
+
quarantinedCount: row.quarantinedCount,
|
|
5995
|
+
logoFailureCount: row.logoFailureCount,
|
|
5996
|
+
staleCount: row.staleCount,
|
|
5997
|
+
details: row.details,
|
|
5998
|
+
createdAt: row.createdAt.toISOString(),
|
|
5999
|
+
updatedAt: row.updatedAt.toISOString()
|
|
6000
|
+
};
|
|
6001
|
+
}
|
|
4991
6002
|
function mapCapabilityCatalogItem(row) {
|
|
4992
6003
|
const runtime = row.kind === "mcp" && row.endpointUrl ? {
|
|
4993
6004
|
available: true,
|
|
4994
6005
|
mcpServerId: mcpServerIdForCapability(row.id, row.metadata),
|
|
4995
|
-
transport: "streamable-http",
|
|
6006
|
+
transport: row.transport ?? "streamable-http",
|
|
4996
6007
|
notes: row.authModel ? "Requires credential headers supplied in the enable request." : null
|
|
4997
6008
|
} : {
|
|
4998
6009
|
available: false,
|
|
@@ -5000,8 +6011,8 @@ function mapCapabilityCatalogItem(row) {
|
|
|
5000
6011
|
};
|
|
5001
6012
|
return {
|
|
5002
6013
|
id: row.id,
|
|
5003
|
-
accountId: row.accountId,
|
|
5004
|
-
workspaceId: row.workspaceId,
|
|
6014
|
+
...row.accountId ? { accountId: row.accountId } : {},
|
|
6015
|
+
...row.workspaceId ? { workspaceId: row.workspaceId } : {},
|
|
5005
6016
|
kind: row.kind,
|
|
5006
6017
|
source: row.source,
|
|
5007
6018
|
name: row.name,
|
|
@@ -5012,6 +6023,18 @@ function mapCapabilityCatalogItem(row) {
|
|
|
5012
6023
|
endpointUrl: row.endpointUrl,
|
|
5013
6024
|
installUrl: row.installUrl,
|
|
5014
6025
|
authModel: row.authModel,
|
|
6026
|
+
providerDomain: row.providerDomain,
|
|
6027
|
+
surfaceType: row.surfaceType,
|
|
6028
|
+
transport: row.transport,
|
|
6029
|
+
mcpUrl: row.mcpUrl,
|
|
6030
|
+
authKind: row.authKind,
|
|
6031
|
+
credentialFacts: row.credentialFacts,
|
|
6032
|
+
tier: row.tier,
|
|
6033
|
+
provenance: row.provenance,
|
|
6034
|
+
logoAssetPath: row.logoAssetPath,
|
|
6035
|
+
importBatchId: row.importBatchId,
|
|
6036
|
+
stale: row.stale,
|
|
6037
|
+
staleAt: row.staleAt?.toISOString() ?? null,
|
|
5015
6038
|
tools: [],
|
|
5016
6039
|
runtime,
|
|
5017
6040
|
enabled: false,
|
|
@@ -5065,6 +6088,46 @@ function redactInstallationConfig(config) {
|
|
|
5065
6088
|
const { headersEncrypted: _omitted, ...rest } = config;
|
|
5066
6089
|
return { ...rest, headerNames: Object.keys(headersEncrypted).sort() };
|
|
5067
6090
|
}
|
|
6091
|
+
function mapConnectionMetadata(row) {
|
|
6092
|
+
return {
|
|
6093
|
+
id: row.id,
|
|
6094
|
+
accountId: row.accountId,
|
|
6095
|
+
workspaceId: row.workspaceId,
|
|
6096
|
+
subjectId: row.subjectId,
|
|
6097
|
+
providerDomain: row.providerDomain,
|
|
6098
|
+
kind: row.kind,
|
|
6099
|
+
status: row.status,
|
|
6100
|
+
grantedScopes: row.grantedScopes,
|
|
6101
|
+
expiresAt: row.expiresAt?.toISOString() ?? null,
|
|
6102
|
+
lastRefreshAt: row.lastRefreshAt?.toISOString() ?? null,
|
|
6103
|
+
lastUsedAt: row.lastUsedAt?.toISOString() ?? null,
|
|
6104
|
+
lastError: row.lastError,
|
|
6105
|
+
version: row.version,
|
|
6106
|
+
metadata: row.metadata,
|
|
6107
|
+
createdBySubjectId: row.createdBySubjectId,
|
|
6108
|
+
updatedBySubjectId: row.updatedBySubjectId,
|
|
6109
|
+
createdAt: row.createdAt.toISOString(),
|
|
6110
|
+
updatedAt: row.updatedAt.toISOString()
|
|
6111
|
+
};
|
|
6112
|
+
}
|
|
6113
|
+
function mapKnowledgeMemory(row) {
|
|
6114
|
+
return {
|
|
6115
|
+
id: row.id,
|
|
6116
|
+
workspaceId: row.workspaceId,
|
|
6117
|
+
status: row.status,
|
|
6118
|
+
kind: row.kind,
|
|
6119
|
+
scope: row.scope,
|
|
6120
|
+
text: row.text,
|
|
6121
|
+
sourceRefs: Array.isArray(row.sourceRefs) ? row.sourceRefs : [],
|
|
6122
|
+
confidence: confidenceFromStorage(row.confidence),
|
|
6123
|
+
metadata: row.metadata,
|
|
6124
|
+
createdBySessionId: row.createdBySessionId,
|
|
6125
|
+
reviewedBy: row.reviewedBy,
|
|
6126
|
+
reviewedAt: row.reviewedAt ? row.reviewedAt.toISOString() : null,
|
|
6127
|
+
createdAt: row.createdAt.toISOString(),
|
|
6128
|
+
updatedAt: row.updatedAt.toISOString()
|
|
6129
|
+
};
|
|
6130
|
+
}
|
|
5068
6131
|
function mapSocialConnection(row) {
|
|
5069
6132
|
return {
|
|
5070
6133
|
id: row.id,
|
|
@@ -5152,6 +6215,26 @@ function stringArrayConfig(value) {
|
|
|
5152
6215
|
const values = value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
5153
6216
|
return values.length > 0 ? [...new Set(values.map((item) => item.trim()))] : void 0;
|
|
5154
6217
|
}
|
|
6218
|
+
function cleanDbString(value) {
|
|
6219
|
+
const trimmed = value?.trim();
|
|
6220
|
+
return trimmed ? trimmed : void 0;
|
|
6221
|
+
}
|
|
6222
|
+
function requireDbString(value, field) {
|
|
6223
|
+
const trimmed = cleanDbString(value);
|
|
6224
|
+
if (!trimmed) {
|
|
6225
|
+
throw new Error(`${field} is required`);
|
|
6226
|
+
}
|
|
6227
|
+
return trimmed;
|
|
6228
|
+
}
|
|
6229
|
+
function confidenceToStorage(value) {
|
|
6230
|
+
if (!Number.isFinite(value)) {
|
|
6231
|
+
return 50;
|
|
6232
|
+
}
|
|
6233
|
+
return Math.round(Math.min(Math.max(value, 0), 1) * 100);
|
|
6234
|
+
}
|
|
6235
|
+
function confidenceFromStorage(value) {
|
|
6236
|
+
return Number((Math.min(Math.max(value, 0), 100) / 100).toFixed(2));
|
|
6237
|
+
}
|
|
5155
6238
|
function positiveIntegerConfig(value) {
|
|
5156
6239
|
if (typeof value === "number" && Number.isInteger(value) && value > 0) {
|
|
5157
6240
|
return value;
|
|
@@ -5173,7 +6256,36 @@ function encryptedHeadersConfig(value) {
|
|
|
5173
6256
|
}
|
|
5174
6257
|
function mcpConnectivityOk(metadata) {
|
|
5175
6258
|
const value = metadata.mcpConnectivity;
|
|
5176
|
-
return !!value && typeof value === "object" && "status" in value && value.status === "ok";
|
|
6259
|
+
return !!value && typeof value === "object" && "status" in value && (value.status === "ok" || value.status === "auth_deferred");
|
|
6260
|
+
}
|
|
6261
|
+
function connectionRefConfig(value) {
|
|
6262
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
6263
|
+
return void 0;
|
|
6264
|
+
}
|
|
6265
|
+
const record = value;
|
|
6266
|
+
if (typeof record.providerDomain !== "string" || record.providerDomain.length === 0) {
|
|
6267
|
+
return void 0;
|
|
6268
|
+
}
|
|
6269
|
+
const ref = { providerDomain: record.providerDomain };
|
|
6270
|
+
if (typeof record.connectionId === "string" && record.connectionId.length > 0) {
|
|
6271
|
+
ref.connectionId = record.connectionId;
|
|
6272
|
+
}
|
|
6273
|
+
if (typeof record.kind === "string" && ["oauth2", "api_key", "app_install", "delegated"].includes(record.kind)) {
|
|
6274
|
+
ref.kind = record.kind;
|
|
6275
|
+
}
|
|
6276
|
+
if (Array.isArray(record.scopes)) {
|
|
6277
|
+
const scopes = record.scopes.filter((scope) => typeof scope === "string" && scope.length > 0);
|
|
6278
|
+
if (scopes.length > 0) {
|
|
6279
|
+
ref.scopes = scopes;
|
|
6280
|
+
}
|
|
6281
|
+
}
|
|
6282
|
+
if (typeof record.resource === "string" && record.resource.length > 0) {
|
|
6283
|
+
ref.resource = record.resource;
|
|
6284
|
+
}
|
|
6285
|
+
if (record.subjectScope === "workspace" || record.subjectScope === "subject") {
|
|
6286
|
+
ref.subjectScope = record.subjectScope;
|
|
6287
|
+
}
|
|
6288
|
+
return ref;
|
|
5177
6289
|
}
|
|
5178
6290
|
function shortHash(value) {
|
|
5179
6291
|
let hash = 2166136261;
|
|
@@ -5186,6 +6298,7 @@ function shortHash(value) {
|
|
|
5186
6298
|
export {
|
|
5187
6299
|
CLEARED_RUN_STATE,
|
|
5188
6300
|
CODEX_ROTATION_STRATEGIES,
|
|
6301
|
+
ConnectionRefreshHttpError,
|
|
5189
6302
|
MACHINE_METRICS_SERIES_INTERVAL_MS,
|
|
5190
6303
|
SandboxImageConflictError,
|
|
5191
6304
|
SandboxLeaseSupersededError,
|
|
@@ -5203,6 +6316,7 @@ export {
|
|
|
5203
6316
|
approveDeviceEnrollmentRequest,
|
|
5204
6317
|
bootstrapWorkspace,
|
|
5205
6318
|
buildCodexTokenResolver,
|
|
6319
|
+
buildConnectionTokenResolver,
|
|
5206
6320
|
cancelQueuedSessionTurn,
|
|
5207
6321
|
claimNextQueuedTurn,
|
|
5208
6322
|
clearSessionContext,
|
|
@@ -5212,12 +6326,15 @@ export {
|
|
|
5212
6326
|
completeFileUpload,
|
|
5213
6327
|
confirmDrainCold,
|
|
5214
6328
|
consumeDeviceEnrollmentRequest,
|
|
6329
|
+
consumeIntegrationOAuthStateNonce,
|
|
5215
6330
|
consumeSessionCompactionRequest,
|
|
5216
6331
|
countActiveApiKeysForWorkspace,
|
|
5217
6332
|
countActiveSessionHistoryItems,
|
|
5218
6333
|
countActiveSessionsForWorkspace,
|
|
5219
6334
|
countActiveSessionsUsingEnvironment,
|
|
5220
6335
|
countConsecutiveReactiveRotations,
|
|
6336
|
+
countQueuedTurns,
|
|
6337
|
+
countSandboxLeasesByLiveness,
|
|
5221
6338
|
countScheduledTasksForWorkspace,
|
|
5222
6339
|
countScheduledTasksUsingEnvironment,
|
|
5223
6340
|
countSessionHistoryItems,
|
|
@@ -5225,10 +6342,13 @@ export {
|
|
|
5225
6342
|
countWorkspaceEnvironments,
|
|
5226
6343
|
countWorkspacesForAccount,
|
|
5227
6344
|
createApiKey,
|
|
6345
|
+
createConnection,
|
|
5228
6346
|
createDb,
|
|
5229
6347
|
createDeviceEnrollmentRequest,
|
|
5230
6348
|
createEnrollment,
|
|
5231
6349
|
createFileUpload,
|
|
6350
|
+
createImportBatch,
|
|
6351
|
+
createKnowledgeMemory,
|
|
5232
6352
|
createSandbox,
|
|
5233
6353
|
createScheduledTask,
|
|
5234
6354
|
createScheduledTaskRun,
|
|
@@ -5275,10 +6395,12 @@ export {
|
|
|
5275
6395
|
getCapabilityInstallation,
|
|
5276
6396
|
getCodexCredentialStatus,
|
|
5277
6397
|
getCodexRotationSettings,
|
|
6398
|
+
getConnectionMetadata,
|
|
5278
6399
|
getDeviceEnrollmentRequestByDeviceCode,
|
|
5279
6400
|
getEnrollment,
|
|
5280
6401
|
getFile,
|
|
5281
6402
|
getFileUpload,
|
|
6403
|
+
getKnowledgeMemory,
|
|
5282
6404
|
getLatestRunState,
|
|
5283
6405
|
getManagedAccount,
|
|
5284
6406
|
getManagedUserByEmail,
|
|
@@ -5316,21 +6438,25 @@ export {
|
|
|
5316
6438
|
insertRecording,
|
|
5317
6439
|
isCodexBilledModel2 as isCodexBilledModel,
|
|
5318
6440
|
isCodexBilledTurn,
|
|
6441
|
+
isPrivateAddress,
|
|
5319
6442
|
isStripeWebhookProcessed,
|
|
5320
6443
|
listApiKeys,
|
|
5321
6444
|
listCapabilityCatalogItems,
|
|
5322
6445
|
listCapabilityInstallations,
|
|
5323
6446
|
listCodexAccountStatuses,
|
|
6447
|
+
listConnectionsMetadata,
|
|
5324
6448
|
listDistinctEnvironmentIdsInGroup,
|
|
5325
6449
|
listEnabledMcpCapabilityServers,
|
|
5326
6450
|
listEnrollments,
|
|
5327
6451
|
listGitHubInstallationIdsForWorkspace,
|
|
5328
6452
|
listGitHubInstallationsForWorkspace,
|
|
6453
|
+
listKnowledgeMemories,
|
|
5329
6454
|
listLiveModalSandboxLeaseAttributions,
|
|
5330
6455
|
listMeterableWarmLeases,
|
|
5331
6456
|
listOpenPtySessions,
|
|
5332
6457
|
listPackInstallations,
|
|
5333
6458
|
listRecordings,
|
|
6459
|
+
listRegistryCatalogSurfaceKeys,
|
|
5334
6460
|
listSandboxes,
|
|
5335
6461
|
listScheduledTaskRuns,
|
|
5336
6462
|
listScheduledTasks,
|
|
@@ -5348,8 +6474,11 @@ export {
|
|
|
5348
6474
|
listWorkspacePacks,
|
|
5349
6475
|
listWorkspacesForSubject,
|
|
5350
6476
|
loadCodexCredentialForRun,
|
|
6477
|
+
loadConnectionCredentialForBroker,
|
|
6478
|
+
loadIntegrationOAuthClient,
|
|
5351
6479
|
loadWorkspaceEnvironmentForRun,
|
|
5352
6480
|
markFileUploadFailed,
|
|
6481
|
+
markStaleRegistryCatalogItems,
|
|
5353
6482
|
markStripeWebhookProcessed,
|
|
5354
6483
|
mcpServerIdForCapability,
|
|
5355
6484
|
migrate,
|
|
@@ -5369,6 +6498,8 @@ export {
|
|
|
5369
6498
|
recordCodexAccountConnectors,
|
|
5370
6499
|
recordCodexAccountUsage,
|
|
5371
6500
|
recordCodexTokenRefresh,
|
|
6501
|
+
recordConnectionTokenRefresh,
|
|
6502
|
+
recordConnectionUsed,
|
|
5372
6503
|
recordLeaseDataPlaneUrl,
|
|
5373
6504
|
recordLeaseTerminalDataPlaneUrl,
|
|
5374
6505
|
recordSessionActiveCodexCredential,
|
|
@@ -5376,6 +6507,7 @@ export {
|
|
|
5376
6507
|
recordStripeWebhookEvent,
|
|
5377
6508
|
recordUsageEvent,
|
|
5378
6509
|
recordWarmingSandboxCreated,
|
|
6510
|
+
refreshOAuthConnectionCredential,
|
|
5379
6511
|
registerDbBinding,
|
|
5380
6512
|
registerWorkspacePack,
|
|
5381
6513
|
releaseLeaseHolder,
|
|
@@ -5389,7 +6521,9 @@ export {
|
|
|
5389
6521
|
requireSession,
|
|
5390
6522
|
requireSocialConnection,
|
|
5391
6523
|
requireWorkspace,
|
|
6524
|
+
reserveToolspaceCallForTurn,
|
|
5392
6525
|
revokeApiKey,
|
|
6526
|
+
revokeConnection,
|
|
5393
6527
|
revokeEnrollment,
|
|
5394
6528
|
revokeViewer,
|
|
5395
6529
|
rlsContextForWorkspace,
|
|
@@ -5403,7 +6537,8 @@ export {
|
|
|
5403
6537
|
setActiveSandbox,
|
|
5404
6538
|
setCodexCredentialExhausted,
|
|
5405
6539
|
setCodexCredentialStatus,
|
|
5406
|
-
|
|
6540
|
+
setConnectionStatus,
|
|
6541
|
+
setEnrollmentDisplayState,
|
|
5407
6542
|
setRlsContext,
|
|
5408
6543
|
setSessionCodexPin,
|
|
5409
6544
|
setSessionGoalLastContinuationTurn,
|
|
@@ -5412,9 +6547,13 @@ export {
|
|
|
5412
6547
|
setSessionStatus,
|
|
5413
6548
|
setTemporalWorkflowId,
|
|
5414
6549
|
setWorkspaceEnvironmentVariable,
|
|
6550
|
+
storeIntegrationOAuthClient,
|
|
5415
6551
|
sumUsageQuantity,
|
|
5416
6552
|
touchEnrollmentLastSeen,
|
|
5417
6553
|
updateCodexRotationSettings,
|
|
6554
|
+
updateConnection,
|
|
6555
|
+
updateImportBatchCounts,
|
|
6556
|
+
updateKnowledgeMemory,
|
|
5418
6557
|
updatePackInstallationStatus,
|
|
5419
6558
|
updatePtySessionActivity,
|
|
5420
6559
|
updateQueuedSessionTurn,
|
|
@@ -5431,6 +6570,7 @@ export {
|
|
|
5431
6570
|
upsertCodexSubscriptionCredential,
|
|
5432
6571
|
upsertGitHubInstallation,
|
|
5433
6572
|
upsertMachineMetricsLatest,
|
|
6573
|
+
upsertRegistryCapabilityCatalogItem,
|
|
5434
6574
|
upsertSandboxSessionEnvelope,
|
|
5435
6575
|
upsertSessionGoal,
|
|
5436
6576
|
wakeParentSessionForChildCompletion,
|