@opengeni/db 0.2.2 → 0.4.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/dist/{chunk-PSX56ZTL.js → chunk-T2U4H4Z2.js} +31 -1
- package/dist/chunk-T2U4H4Z2.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +293 -15
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.d.ts +91 -9
- package/dist/{schema-fwrPBw5T.d.ts → schema-DuRsrmzD.d.ts} +269 -2
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +3 -1
- package/drizzle/0035_session_mcp_servers.sql +50 -0
- package/drizzle/0036_modal_lease_orphan_reaper.sql +92 -0
- package/drizzle/0037_session_instructions.sql +18 -0
- package/package.json +3 -3
- package/src/event-payload-sanitizer.ts +58 -1
- package/src/index.ts +392 -21
- package/src/schema.ts +29 -0
- package/dist/chunk-PSX56ZTL.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
sessionEvents,
|
|
27
27
|
sessionGoals,
|
|
28
28
|
sessionHistoryItems,
|
|
29
|
+
sessionMcpServers,
|
|
29
30
|
sessionRecordings,
|
|
30
31
|
sessionTurns,
|
|
31
32
|
sessions,
|
|
@@ -38,7 +39,7 @@ import {
|
|
|
38
39
|
workspaceMemberships,
|
|
39
40
|
workspacePacks,
|
|
40
41
|
workspaces
|
|
41
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-T2U4H4Z2.js";
|
|
42
43
|
import {
|
|
43
44
|
migrate,
|
|
44
45
|
runMigrations
|
|
@@ -144,12 +145,64 @@ function sanitizeEventPayload(payload) {
|
|
|
144
145
|
}
|
|
145
146
|
if (payload && typeof payload === "object") {
|
|
146
147
|
const entries = Object.entries(payload).map(
|
|
147
|
-
([key, value]) => [sanitizeEventString(key),
|
|
148
|
+
([key, value]) => [sanitizeEventString(key), sanitizeSensitiveEventField(key, value)]
|
|
148
149
|
);
|
|
149
150
|
return Object.fromEntries(entries);
|
|
150
151
|
}
|
|
151
152
|
return payload;
|
|
152
153
|
}
|
|
154
|
+
function sanitizeSensitiveEventField(key, value) {
|
|
155
|
+
if (key === "mcpServers") {
|
|
156
|
+
return sanitizeSessionMcpServerList(value);
|
|
157
|
+
}
|
|
158
|
+
if (key === "mcpCredentialUpdates") {
|
|
159
|
+
return sanitizeMcpCredentialUpdateList(value);
|
|
160
|
+
}
|
|
161
|
+
return sanitizeEventPayload(value);
|
|
162
|
+
}
|
|
163
|
+
function sanitizeSessionMcpServerList(value) {
|
|
164
|
+
if (!Array.isArray(value)) {
|
|
165
|
+
return sanitizeEventPayload(value);
|
|
166
|
+
}
|
|
167
|
+
return value.map((item) => {
|
|
168
|
+
if (!isPlainObject(item)) {
|
|
169
|
+
return sanitizeEventPayload(item);
|
|
170
|
+
}
|
|
171
|
+
const { headers, headersEncrypted, ...rest } = item;
|
|
172
|
+
const cleaned = sanitizeEventPayload(rest);
|
|
173
|
+
const headerNames = safeHeaderNames(headers) ?? safeHeaderNames(headersEncrypted);
|
|
174
|
+
if (headerNames) {
|
|
175
|
+
cleaned.headerNames = headerNames;
|
|
176
|
+
}
|
|
177
|
+
return cleaned;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function sanitizeMcpCredentialUpdateList(value) {
|
|
181
|
+
if (!Array.isArray(value)) {
|
|
182
|
+
return sanitizeEventPayload(value);
|
|
183
|
+
}
|
|
184
|
+
return value.map((item) => {
|
|
185
|
+
if (!isPlainObject(item)) {
|
|
186
|
+
return sanitizeEventPayload(item);
|
|
187
|
+
}
|
|
188
|
+
const { headers, headersEncrypted, ...rest } = item;
|
|
189
|
+
const cleaned = sanitizeEventPayload(rest);
|
|
190
|
+
const headerNames = safeHeaderNames(headers) ?? safeHeaderNames(headersEncrypted);
|
|
191
|
+
if (headerNames) {
|
|
192
|
+
cleaned.headerNames = headerNames;
|
|
193
|
+
}
|
|
194
|
+
return cleaned;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
function safeHeaderNames(value) {
|
|
198
|
+
if (!isPlainObject(value)) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
return Object.keys(value).map(sanitizeEventString).sort();
|
|
202
|
+
}
|
|
203
|
+
function isPlainObject(value) {
|
|
204
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
205
|
+
}
|
|
153
206
|
|
|
154
207
|
// src/index.ts
|
|
155
208
|
import { sql as sql2 } from "drizzle-orm";
|
|
@@ -388,6 +441,7 @@ var allWorkspacePermissions = [
|
|
|
388
441
|
"api_keys:manage",
|
|
389
442
|
"environments:manage",
|
|
390
443
|
"environments:use",
|
|
444
|
+
"mcp_servers:attach",
|
|
391
445
|
"goals:manage",
|
|
392
446
|
"enrollments:read",
|
|
393
447
|
"enrollments:manage"
|
|
@@ -2093,6 +2147,111 @@ function mapWorkspaceEnvironmentVariableMetadata(row) {
|
|
|
2093
2147
|
updatedAt: row.updatedAt.toISOString()
|
|
2094
2148
|
};
|
|
2095
2149
|
}
|
|
2150
|
+
function mapSessionMcpServerMetadata(row) {
|
|
2151
|
+
return {
|
|
2152
|
+
id: row.serverId,
|
|
2153
|
+
name: row.name ?? null,
|
|
2154
|
+
url: row.url,
|
|
2155
|
+
headerNames: Object.keys(row.headersEncrypted ?? {}).sort(),
|
|
2156
|
+
credentialVersion: Number(row.credentialVersion)
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
async function sessionMcpServerMetadataForSessions(db, workspaceId, sessionIds) {
|
|
2160
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2161
|
+
if (sessionIds.length === 0) {
|
|
2162
|
+
return grouped;
|
|
2163
|
+
}
|
|
2164
|
+
const rows = await db.select().from(sessionMcpServers).where(and(
|
|
2165
|
+
eq(sessionMcpServers.workspaceId, workspaceId),
|
|
2166
|
+
inArray(sessionMcpServers.sessionId, sessionIds)
|
|
2167
|
+
)).orderBy(asc(sessionMcpServers.createdAt), asc(sessionMcpServers.serverId));
|
|
2168
|
+
for (const row of rows) {
|
|
2169
|
+
const list = grouped.get(row.sessionId) ?? [];
|
|
2170
|
+
list.push(mapSessionMcpServerMetadata(row));
|
|
2171
|
+
grouped.set(row.sessionId, list);
|
|
2172
|
+
}
|
|
2173
|
+
return grouped;
|
|
2174
|
+
}
|
|
2175
|
+
async function insertSessionMcpServers(db, input) {
|
|
2176
|
+
if (input.servers.length === 0) {
|
|
2177
|
+
return [];
|
|
2178
|
+
}
|
|
2179
|
+
const rows = await db.insert(sessionMcpServers).values(input.servers.map((server) => ({
|
|
2180
|
+
accountId: input.accountId,
|
|
2181
|
+
workspaceId: input.workspaceId,
|
|
2182
|
+
sessionId: input.sessionId,
|
|
2183
|
+
serverId: server.id,
|
|
2184
|
+
name: server.name ?? null,
|
|
2185
|
+
url: server.url,
|
|
2186
|
+
allowedTools: server.allowedTools ?? null,
|
|
2187
|
+
timeoutMs: server.timeoutMs ?? null,
|
|
2188
|
+
cacheToolsList: server.cacheToolsList ?? false,
|
|
2189
|
+
headersEncrypted: server.headersEncrypted ?? {}
|
|
2190
|
+
}))).returning();
|
|
2191
|
+
return rows.map(mapSessionMcpServerMetadata);
|
|
2192
|
+
}
|
|
2193
|
+
async function createSessionMcpServers(db, input) {
|
|
2194
|
+
return await withRlsContext(
|
|
2195
|
+
db,
|
|
2196
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
2197
|
+
async (scopedDb) => await insertSessionMcpServers(scopedDb, input)
|
|
2198
|
+
);
|
|
2199
|
+
}
|
|
2200
|
+
async function listSessionMcpServerMetadata(db, workspaceId, sessionId) {
|
|
2201
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
2202
|
+
const grouped = await sessionMcpServerMetadataForSessions(scopedDb, workspaceId, [sessionId]);
|
|
2203
|
+
return grouped.get(sessionId) ?? [];
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
async function updateSessionMcpServerCredentials(db, input) {
|
|
2207
|
+
return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => await scopedDb.transaction(
|
|
2208
|
+
async (tx) => await updateSessionMcpServerCredentialsInTransaction(tx, input)
|
|
2209
|
+
));
|
|
2210
|
+
}
|
|
2211
|
+
async function updateSessionMcpServerCredentialsInTransaction(tx, input) {
|
|
2212
|
+
const servers = [];
|
|
2213
|
+
const missingIds = [];
|
|
2214
|
+
for (const update of input.updates) {
|
|
2215
|
+
const [row] = await tx.update(sessionMcpServers).set({
|
|
2216
|
+
headersEncrypted: update.headersEncrypted,
|
|
2217
|
+
credentialVersion: sql`${sessionMcpServers.credentialVersion} + 1`,
|
|
2218
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2219
|
+
}).where(and(
|
|
2220
|
+
eq(sessionMcpServers.workspaceId, input.workspaceId),
|
|
2221
|
+
eq(sessionMcpServers.sessionId, input.sessionId),
|
|
2222
|
+
eq(sessionMcpServers.serverId, update.id)
|
|
2223
|
+
)).returning();
|
|
2224
|
+
if (!row) {
|
|
2225
|
+
missingIds.push(update.id);
|
|
2226
|
+
} else {
|
|
2227
|
+
servers.push(mapSessionMcpServerMetadata(row));
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
return { servers, missingIds };
|
|
2231
|
+
}
|
|
2232
|
+
async function listSessionMcpServersForRun(db, workspaceId, sessionId, encryptionKey) {
|
|
2233
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
2234
|
+
const rows = await scopedDb.select().from(sessionMcpServers).where(and(
|
|
2235
|
+
eq(sessionMcpServers.workspaceId, workspaceId),
|
|
2236
|
+
eq(sessionMcpServers.sessionId, sessionId)
|
|
2237
|
+
)).orderBy(asc(sessionMcpServers.createdAt), asc(sessionMcpServers.serverId));
|
|
2238
|
+
return rows.map((row) => {
|
|
2239
|
+
let headers;
|
|
2240
|
+
try {
|
|
2241
|
+
headers = Object.fromEntries(Object.entries(row.headersEncrypted ?? {}).map(([name, stored]) => [name, decryptEnvironmentValue(encryptionKey, stored)]));
|
|
2242
|
+
} catch {
|
|
2243
|
+
throw new Error("session MCP server credential decryption failed");
|
|
2244
|
+
}
|
|
2245
|
+
return {
|
|
2246
|
+
...mapSessionMcpServerMetadata(row),
|
|
2247
|
+
...row.allowedTools ? { allowedTools: row.allowedTools } : {},
|
|
2248
|
+
...row.timeoutMs ? { timeoutMs: row.timeoutMs } : {},
|
|
2249
|
+
...row.cacheToolsList ? { cacheToolsList: row.cacheToolsList } : {},
|
|
2250
|
+
headers
|
|
2251
|
+
};
|
|
2252
|
+
});
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2096
2255
|
async function createSession(db, input) {
|
|
2097
2256
|
const id = crypto.randomUUID();
|
|
2098
2257
|
return await withRlsContext(db, { accountId: input.accountId, workspaceId: input.workspaceId }, async (scopedDb) => {
|
|
@@ -2110,6 +2269,7 @@ async function createSession(db, input) {
|
|
|
2110
2269
|
sandboxGroupId: input.sandboxGroupId ?? id,
|
|
2111
2270
|
environmentId: input.environmentId ?? null,
|
|
2112
2271
|
firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
|
|
2272
|
+
instructions: input.instructions ?? null,
|
|
2113
2273
|
parentSessionId: input.parentSessionId ?? null,
|
|
2114
2274
|
createIdempotencyKey: input.createIdempotencyKey ?? null,
|
|
2115
2275
|
status: "queued"
|
|
@@ -2117,7 +2277,13 @@ async function createSession(db, input) {
|
|
|
2117
2277
|
if (!row) {
|
|
2118
2278
|
throw new Error("Failed to create session");
|
|
2119
2279
|
}
|
|
2120
|
-
|
|
2280
|
+
const mcpServers = await insertSessionMcpServers(scopedDb, {
|
|
2281
|
+
accountId: input.accountId,
|
|
2282
|
+
workspaceId: input.workspaceId,
|
|
2283
|
+
sessionId: row.id,
|
|
2284
|
+
servers: input.mcpServers ?? []
|
|
2285
|
+
});
|
|
2286
|
+
return mapSession(row, mcpServers);
|
|
2121
2287
|
});
|
|
2122
2288
|
}
|
|
2123
2289
|
async function createSessionWithIdempotencyKey(db, input) {
|
|
@@ -2137,6 +2303,7 @@ async function createSessionWithIdempotencyKey(db, input) {
|
|
|
2137
2303
|
sandboxGroupId: input.sandboxGroupId ?? id,
|
|
2138
2304
|
environmentId: input.environmentId ?? null,
|
|
2139
2305
|
firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
|
|
2306
|
+
instructions: input.instructions ?? null,
|
|
2140
2307
|
parentSessionId: input.parentSessionId ?? null,
|
|
2141
2308
|
createIdempotencyKey: input.createIdempotencyKey,
|
|
2142
2309
|
status: "queued"
|
|
@@ -2145,7 +2312,13 @@ async function createSessionWithIdempotencyKey(db, input) {
|
|
|
2145
2312
|
where: sql`${sessions.createIdempotencyKey} is not null`
|
|
2146
2313
|
}).returning();
|
|
2147
2314
|
if (inserted) {
|
|
2148
|
-
|
|
2315
|
+
const mcpServers = await insertSessionMcpServers(scopedDb, {
|
|
2316
|
+
accountId: input.accountId,
|
|
2317
|
+
workspaceId: input.workspaceId,
|
|
2318
|
+
sessionId: inserted.id,
|
|
2319
|
+
servers: input.mcpServers ?? []
|
|
2320
|
+
});
|
|
2321
|
+
return { session: mapSession(inserted, mcpServers), created: true };
|
|
2149
2322
|
}
|
|
2150
2323
|
const [existing] = await scopedDb.select().from(sessions).where(and(
|
|
2151
2324
|
eq(sessions.workspaceId, input.workspaceId),
|
|
@@ -2154,7 +2327,8 @@ async function createSessionWithIdempotencyKey(db, input) {
|
|
|
2154
2327
|
if (!existing) {
|
|
2155
2328
|
throw new Error("Failed to create session under idempotency key");
|
|
2156
2329
|
}
|
|
2157
|
-
|
|
2330
|
+
const grouped = await sessionMcpServerMetadataForSessions(scopedDb, input.workspaceId, [existing.id]);
|
|
2331
|
+
return { session: mapSession(existing, grouped.get(existing.id) ?? []), created: false };
|
|
2158
2332
|
});
|
|
2159
2333
|
}
|
|
2160
2334
|
async function getSessionByCreateIdempotencyKey(db, workspaceId, createIdempotencyKey) {
|
|
@@ -2163,13 +2337,17 @@ async function getSessionByCreateIdempotencyKey(db, workspaceId, createIdempoten
|
|
|
2163
2337
|
eq(sessions.workspaceId, workspaceId),
|
|
2164
2338
|
eq(sessions.createIdempotencyKey, createIdempotencyKey)
|
|
2165
2339
|
)).limit(1);
|
|
2166
|
-
|
|
2340
|
+
if (!row) return null;
|
|
2341
|
+
const grouped = await sessionMcpServerMetadataForSessions(scopedDb, workspaceId, [row.id]);
|
|
2342
|
+
return mapSession(row, grouped.get(row.id) ?? []);
|
|
2167
2343
|
});
|
|
2168
2344
|
}
|
|
2169
2345
|
async function getSession(db, workspaceId, sessionId) {
|
|
2170
2346
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
2171
2347
|
const [row] = await scopedDb.select().from(sessions).where(and(eq(sessions.workspaceId, workspaceId), eq(sessions.id, sessionId))).limit(1);
|
|
2172
|
-
|
|
2348
|
+
if (!row) return null;
|
|
2349
|
+
const grouped = await sessionMcpServerMetadataForSessions(scopedDb, workspaceId, [row.id]);
|
|
2350
|
+
return mapSession(row, grouped.get(row.id) ?? []);
|
|
2173
2351
|
});
|
|
2174
2352
|
}
|
|
2175
2353
|
async function getAnySessionInGroup(db, workspaceId, sandboxGroupId) {
|
|
@@ -2187,7 +2365,8 @@ async function listDistinctEnvironmentIdsInGroup(db, workspaceId, sandboxGroupId
|
|
|
2187
2365
|
async function listSessions(db, workspaceId, limit = 50) {
|
|
2188
2366
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
2189
2367
|
const rows = await scopedDb.select().from(sessions).where(eq(sessions.workspaceId, workspaceId)).orderBy(desc(sessions.createdAt), desc(sessions.id)).limit(limit);
|
|
2190
|
-
|
|
2368
|
+
const grouped = await sessionMcpServerMetadataForSessions(scopedDb, workspaceId, rows.map((row) => row.id));
|
|
2369
|
+
return rows.map((row) => mapSession(row, grouped.get(row.id) ?? []));
|
|
2191
2370
|
});
|
|
2192
2371
|
}
|
|
2193
2372
|
async function countActiveSessionsForWorkspace(db, workspaceId) {
|
|
@@ -2208,12 +2387,38 @@ async function requireSession(db, workspaceId, sessionId) {
|
|
|
2208
2387
|
}
|
|
2209
2388
|
return session;
|
|
2210
2389
|
}
|
|
2211
|
-
|
|
2390
|
+
var POSTGRES_INT_MAX = 2147483647;
|
|
2391
|
+
async function listSessionEvents(db, workspaceId, sessionId, afterOrOptions = 0, legacyLimit = 500) {
|
|
2392
|
+
const options = typeof afterOrOptions === "number" ? { after: afterOrOptions, limit: legacyLimit } : afterOrOptions;
|
|
2393
|
+
const after = normalizeEventSequence(options.after, 0);
|
|
2394
|
+
const limit = normalizeEventLimit(options.limit, 500);
|
|
2395
|
+
const hasBefore = options.before !== void 0 && Number.isFinite(options.before);
|
|
2396
|
+
const before = hasBefore ? Math.floor(options.before) : void 0;
|
|
2212
2397
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
2213
|
-
const
|
|
2214
|
-
|
|
2398
|
+
const filters = [
|
|
2399
|
+
eq(sessionEvents.workspaceId, workspaceId),
|
|
2400
|
+
eq(sessionEvents.sessionId, sessionId),
|
|
2401
|
+
gt(sessionEvents.sequence, after)
|
|
2402
|
+
];
|
|
2403
|
+
if (before !== void 0 && before <= POSTGRES_INT_MAX) {
|
|
2404
|
+
filters.push(lt(sessionEvents.sequence, before));
|
|
2405
|
+
}
|
|
2406
|
+
const rows = await scopedDb.select().from(sessionEvents).where(and(...filters)).orderBy(hasBefore ? desc(sessionEvents.sequence) : asc(sessionEvents.sequence)).limit(limit);
|
|
2407
|
+
return (hasBefore ? rows.reverse() : rows).map(mapEvent);
|
|
2215
2408
|
});
|
|
2216
2409
|
}
|
|
2410
|
+
function normalizeEventSequence(value, fallback) {
|
|
2411
|
+
if (value === void 0 || !Number.isFinite(value)) {
|
|
2412
|
+
return fallback;
|
|
2413
|
+
}
|
|
2414
|
+
return Math.floor(value);
|
|
2415
|
+
}
|
|
2416
|
+
function normalizeEventLimit(value, fallback) {
|
|
2417
|
+
if (value === void 0 || !Number.isFinite(value)) {
|
|
2418
|
+
return fallback;
|
|
2419
|
+
}
|
|
2420
|
+
return Math.max(0, Math.floor(value));
|
|
2421
|
+
}
|
|
2217
2422
|
async function getSessionEvent(db, workspaceId, eventId) {
|
|
2218
2423
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
2219
2424
|
const [row] = await scopedDb.select().from(sessionEvents).where(and(eq(sessionEvents.workspaceId, workspaceId), eq(sessionEvents.id, eventId))).limit(1);
|
|
@@ -2348,6 +2553,20 @@ async function applyContextCompaction(db, input) {
|
|
|
2348
2553
|
eq(sessionHistoryItems.active, true),
|
|
2349
2554
|
lt(sessionHistoryItems.position, input.boundaryPosition)
|
|
2350
2555
|
));
|
|
2556
|
+
if (input.replacementItems && input.replacementItems.length > 0) {
|
|
2557
|
+
await tx.insert(sessionHistoryItems).values(input.replacementItems.map((entry) => ({
|
|
2558
|
+
accountId: input.accountId,
|
|
2559
|
+
workspaceId: input.workspaceId,
|
|
2560
|
+
sessionId: input.sessionId,
|
|
2561
|
+
turnId: null,
|
|
2562
|
+
position: entry.position,
|
|
2563
|
+
item: sanitizeEventPayload(entry.item),
|
|
2564
|
+
active: true
|
|
2565
|
+
}))).onConflictDoUpdate({
|
|
2566
|
+
target: [sessionHistoryItems.workspaceId, sessionHistoryItems.sessionId, sessionHistoryItems.position],
|
|
2567
|
+
set: { active: true }
|
|
2568
|
+
});
|
|
2569
|
+
}
|
|
2351
2570
|
await tx.insert(sessionHistoryItems).values({
|
|
2352
2571
|
accountId: input.accountId,
|
|
2353
2572
|
workspaceId: input.workspaceId,
|
|
@@ -2816,6 +3035,28 @@ async function commitWarmingToWarm(db, input) {
|
|
|
2816
3035
|
}
|
|
2817
3036
|
);
|
|
2818
3037
|
}
|
|
3038
|
+
async function recordWarmingSandboxCreated(db, input) {
|
|
3039
|
+
return await withRlsContext(
|
|
3040
|
+
db,
|
|
3041
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
3042
|
+
async (scopedDb) => {
|
|
3043
|
+
const resumeStateJson = input.resumeState == null ? null : JSON.stringify(input.resumeState);
|
|
3044
|
+
const rows = await scopedDb.execute(sql`
|
|
3045
|
+
update sandbox_leases set
|
|
3046
|
+
instance_id = ${input.instanceId},
|
|
3047
|
+
resume_backend_id = ${input.resumeBackendId ?? null},
|
|
3048
|
+
resume_state = ${resumeStateJson}::jsonb,
|
|
3049
|
+
expires_at = now() + (${String(input.leaseTtlMs)} || ' milliseconds')::interval,
|
|
3050
|
+
updated_at = now()
|
|
3051
|
+
where workspace_id = ${input.workspaceId} and sandbox_group_id = ${input.sandboxGroupId}
|
|
3052
|
+
and liveness = 'warming' and lease_epoch = ${input.expectedEpoch}
|
|
3053
|
+
returning *
|
|
3054
|
+
`);
|
|
3055
|
+
if (rows.length === 0) return { recorded: false, lease: null };
|
|
3056
|
+
return { recorded: true, lease: mapLeaseRow(rows[0]) };
|
|
3057
|
+
}
|
|
3058
|
+
);
|
|
3059
|
+
}
|
|
2819
3060
|
async function failWarmingToCold(db, input) {
|
|
2820
3061
|
await withRlsContext(
|
|
2821
3062
|
db,
|
|
@@ -2943,7 +3184,21 @@ async function reapStaleLeaseHolders(db, input) {
|
|
|
2943
3184
|
resume_backend_id = null, resume_state = null,
|
|
2944
3185
|
data_plane_url = null, terminal_data_plane_url = null, updated_at = now()
|
|
2945
3186
|
where workspace_id = ${input.workspaceId}
|
|
2946
|
-
and liveness = 'warming' and expires_at < now()
|
|
3187
|
+
and liveness = 'warming' and expires_at < now() and instance_id is null
|
|
3188
|
+
returning id
|
|
3189
|
+
`);
|
|
3190
|
+
const warmingDrain = await tx.execute(sql`
|
|
3191
|
+
update sandbox_leases set
|
|
3192
|
+
liveness = 'draining',
|
|
3193
|
+
refcount = 0,
|
|
3194
|
+
turn_holders = 0,
|
|
3195
|
+
viewer_holders = 0,
|
|
3196
|
+
data_plane_url = null,
|
|
3197
|
+
terminal_data_plane_url = null,
|
|
3198
|
+
expires_at = now() - interval '1 millisecond',
|
|
3199
|
+
updated_at = now()
|
|
3200
|
+
where workspace_id = ${input.workspaceId}
|
|
3201
|
+
and liveness = 'warming' and expires_at < now() and instance_id is not null
|
|
2947
3202
|
returning id
|
|
2948
3203
|
`);
|
|
2949
3204
|
const drainable = await rawRows(tx, sql`
|
|
@@ -2953,7 +3208,7 @@ async function reapStaleLeaseHolders(db, input) {
|
|
|
2953
3208
|
`);
|
|
2954
3209
|
return {
|
|
2955
3210
|
reapedViewers: reaped.length,
|
|
2956
|
-
warmingReset: warmingReset.length,
|
|
3211
|
+
warmingReset: warmingReset.length + warmingDrain.length,
|
|
2957
3212
|
drained: drainable.map((r) => ({
|
|
2958
3213
|
workspaceId: input.workspaceId,
|
|
2959
3214
|
sandboxGroupId: r.sandbox_group_id,
|
|
@@ -2988,6 +3243,19 @@ async function listMeterableWarmLeases(db) {
|
|
|
2988
3243
|
backend: r.backend
|
|
2989
3244
|
}));
|
|
2990
3245
|
}
|
|
3246
|
+
async function listLiveModalSandboxLeaseAttributions(db) {
|
|
3247
|
+
const rows = await rawRows(db, sql`
|
|
3248
|
+
select lease_id, workspace_id, sandbox_group_id, instance_id, liveness
|
|
3249
|
+
from opengeni_private.list_live_modal_sandbox_leases()
|
|
3250
|
+
`);
|
|
3251
|
+
return rows.map((r) => ({
|
|
3252
|
+
leaseId: r.lease_id,
|
|
3253
|
+
workspaceId: r.workspace_id,
|
|
3254
|
+
sandboxGroupId: r.sandbox_group_id,
|
|
3255
|
+
instanceId: r.instance_id,
|
|
3256
|
+
liveness: r.liveness
|
|
3257
|
+
}));
|
|
3258
|
+
}
|
|
2991
3259
|
async function reArmDrainingLease(db, input) {
|
|
2992
3260
|
return await withRlsContext(
|
|
2993
3261
|
db,
|
|
@@ -4482,7 +4750,9 @@ async function appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessi
|
|
|
4482
4750
|
if (!sessionRow) {
|
|
4483
4751
|
throw new Error(`Session not found: ${sessionId}`);
|
|
4484
4752
|
}
|
|
4485
|
-
const built = build(mapSession(sessionRow)
|
|
4753
|
+
const built = await build(mapSession(sessionRow), {
|
|
4754
|
+
updateSessionMcpServerCredentials: async (updates) => await updateSessionMcpServerCredentialsInTransaction(tx, { workspaceId, sessionId, updates })
|
|
4755
|
+
});
|
|
4486
4756
|
if (built.events.length === 0) {
|
|
4487
4757
|
return [];
|
|
4488
4758
|
}
|
|
@@ -4519,7 +4789,7 @@ async function appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessi
|
|
|
4519
4789
|
function sessionSubject(workspaceId, sessionId) {
|
|
4520
4790
|
return `workspaces.${workspaceId}.sessions.${sessionId}.events`;
|
|
4521
4791
|
}
|
|
4522
|
-
function mapSession(row) {
|
|
4792
|
+
function mapSession(row, mcpServers = []) {
|
|
4523
4793
|
return {
|
|
4524
4794
|
id: row.id,
|
|
4525
4795
|
accountId: row.accountId,
|
|
@@ -4528,6 +4798,7 @@ function mapSession(row) {
|
|
|
4528
4798
|
initialMessage: row.initialMessage,
|
|
4529
4799
|
title: row.title ?? null,
|
|
4530
4800
|
titleSource: row.titleSource ?? null,
|
|
4801
|
+
instructions: row.instructions ?? null,
|
|
4531
4802
|
resources: row.resources,
|
|
4532
4803
|
tools: row.tools,
|
|
4533
4804
|
metadata: row.metadata,
|
|
@@ -4542,6 +4813,7 @@ function mapSession(row) {
|
|
|
4542
4813
|
activeEpoch: Number(row.activeEpoch),
|
|
4543
4814
|
environmentId: row.environmentId,
|
|
4544
4815
|
firstPartyMcpPermissions: row.firstPartyMcpPermissions ?? null,
|
|
4816
|
+
mcpServers,
|
|
4545
4817
|
parentSessionId: row.parentSessionId ?? null,
|
|
4546
4818
|
createIdempotencyKey: row.createIdempotencyKey ?? null,
|
|
4547
4819
|
temporalWorkflowId: row.temporalWorkflowId,
|
|
@@ -4962,6 +5234,7 @@ export {
|
|
|
4962
5234
|
createScheduledTaskRun,
|
|
4963
5235
|
createSession,
|
|
4964
5236
|
createSessionGoal,
|
|
5237
|
+
createSessionMcpServers,
|
|
4965
5238
|
createSessionWithIdempotencyKey,
|
|
4966
5239
|
createSocialConnection,
|
|
4967
5240
|
createSocialPost,
|
|
@@ -5053,6 +5326,7 @@ export {
|
|
|
5053
5326
|
listEnrollments,
|
|
5054
5327
|
listGitHubInstallationIdsForWorkspace,
|
|
5055
5328
|
listGitHubInstallationsForWorkspace,
|
|
5329
|
+
listLiveModalSandboxLeaseAttributions,
|
|
5056
5330
|
listMeterableWarmLeases,
|
|
5057
5331
|
listOpenPtySessions,
|
|
5058
5332
|
listPackInstallations,
|
|
@@ -5062,6 +5336,8 @@ export {
|
|
|
5062
5336
|
listScheduledTasks,
|
|
5063
5337
|
listSessionEvents,
|
|
5064
5338
|
listSessionIdsInGroup,
|
|
5339
|
+
listSessionMcpServerMetadata,
|
|
5340
|
+
listSessionMcpServersForRun,
|
|
5065
5341
|
listSessionTurns,
|
|
5066
5342
|
listSessions,
|
|
5067
5343
|
listSocialConnections,
|
|
@@ -5099,6 +5375,7 @@ export {
|
|
|
5099
5375
|
recordStreamAcknowledgment,
|
|
5100
5376
|
recordStripeWebhookEvent,
|
|
5101
5377
|
recordUsageEvent,
|
|
5378
|
+
recordWarmingSandboxCreated,
|
|
5102
5379
|
registerDbBinding,
|
|
5103
5380
|
registerWorkspacePack,
|
|
5104
5381
|
releaseLeaseHolder,
|
|
@@ -5145,6 +5422,7 @@ export {
|
|
|
5145
5422
|
updateScheduledTask,
|
|
5146
5423
|
updateScheduledTaskRun,
|
|
5147
5424
|
updateSessionGoal,
|
|
5425
|
+
updateSessionMcpServerCredentials,
|
|
5148
5426
|
updateSessionTitle,
|
|
5149
5427
|
updateWorkspace,
|
|
5150
5428
|
updateWorkspaceEnvironment,
|