@opengeni/db 0.3.0 → 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-NZA6YVN7.js → chunk-T2U4H4Z2.js} +8 -1
- package/dist/chunk-T2U4H4Z2.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +57 -3
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.d.ts +25 -2
- package/dist/{schema-BI0iqN9U.d.ts → schema-DuRsrmzD.d.ts} +17 -0
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +1 -1
- 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/index.ts +95 -4
- package/src/schema.ts +7 -0
- package/dist/chunk-NZA6YVN7.js.map +0 -1
|
@@ -2,7 +2,7 @@ import { SessionEventType, Permission, CapabilityKind, CapabilitySource, Schedul
|
|
|
2
2
|
import { environmentsEncryptionKeyBytes, Settings } from '@opengeni/config';
|
|
3
3
|
import { refreshCodexToken, CodexTokenSnapshot, CodexUsagePayload } from '@opengeni/codex';
|
|
4
4
|
import { PgDatabase } from 'drizzle-orm/pg-core';
|
|
5
|
-
import { s as schema, e as enrollmentOsValues, a as enrollmentExposureValues, d as deviceEnrollmentStatusValues, b as enrollmentStatusValues, c as sandboxKindValues, f as sessionRecordingCodecValues, g as sessionRecordingModeValues, h as sessionRecordingStateValues } from './schema-
|
|
5
|
+
import { s as schema, e as enrollmentOsValues, a as enrollmentExposureValues, d as deviceEnrollmentStatusValues, b as enrollmentStatusValues, c as sandboxKindValues, f as sessionRecordingCodecValues, g as sessionRecordingModeValues, h as sessionRecordingStateValues } from './schema-DuRsrmzD.js';
|
|
6
6
|
import 'drizzle-orm';
|
|
7
7
|
import './migrate.js';
|
|
8
8
|
|
|
@@ -998,6 +998,7 @@ declare function createSession(db: Database, input: {
|
|
|
998
998
|
sandboxBackend: SandboxBackend;
|
|
999
999
|
environmentId?: string | null;
|
|
1000
1000
|
firstPartyMcpPermissions?: Permission[] | null;
|
|
1001
|
+
instructions?: string | null;
|
|
1001
1002
|
parentSessionId?: string | null;
|
|
1002
1003
|
createIdempotencyKey?: string | null;
|
|
1003
1004
|
sandboxGroupId?: string | null;
|
|
@@ -1025,6 +1026,7 @@ declare function createSessionWithIdempotencyKey(db: Database, input: {
|
|
|
1025
1026
|
sandboxBackend: SandboxBackend;
|
|
1026
1027
|
environmentId?: string | null;
|
|
1027
1028
|
firstPartyMcpPermissions?: Permission[] | null;
|
|
1029
|
+
instructions?: string | null;
|
|
1028
1030
|
parentSessionId?: string | null;
|
|
1029
1031
|
createIdempotencyKey: string;
|
|
1030
1032
|
sandboxGroupId?: string | null;
|
|
@@ -1432,6 +1434,13 @@ interface LeaseSnapshot {
|
|
|
1432
1434
|
resumeState: Record<string, unknown> | null;
|
|
1433
1435
|
expiresAt: Date;
|
|
1434
1436
|
}
|
|
1437
|
+
interface LiveModalSandboxLeaseAttribution {
|
|
1438
|
+
leaseId: string;
|
|
1439
|
+
workspaceId: string;
|
|
1440
|
+
sandboxGroupId: string;
|
|
1441
|
+
instanceId: string | null;
|
|
1442
|
+
liveness: SandboxLeaseLiveness;
|
|
1443
|
+
}
|
|
1435
1444
|
interface AcquireLeaseInput {
|
|
1436
1445
|
accountId: string;
|
|
1437
1446
|
workspaceId: string;
|
|
@@ -1484,6 +1493,19 @@ declare function commitWarmingToWarm(db: Database, input: {
|
|
|
1484
1493
|
committed: boolean;
|
|
1485
1494
|
lease: LeaseSnapshot | null;
|
|
1486
1495
|
}>;
|
|
1496
|
+
declare function recordWarmingSandboxCreated(db: Database, input: {
|
|
1497
|
+
accountId: string;
|
|
1498
|
+
workspaceId: string;
|
|
1499
|
+
sandboxGroupId: string;
|
|
1500
|
+
expectedEpoch: number;
|
|
1501
|
+
instanceId: string;
|
|
1502
|
+
resumeBackendId?: string | null;
|
|
1503
|
+
resumeState?: Record<string, unknown> | null;
|
|
1504
|
+
leaseTtlMs: number;
|
|
1505
|
+
}): Promise<{
|
|
1506
|
+
recorded: boolean;
|
|
1507
|
+
lease: LeaseSnapshot | null;
|
|
1508
|
+
}>;
|
|
1487
1509
|
declare function failWarmingToCold(db: Database, input: {
|
|
1488
1510
|
accountId: string;
|
|
1489
1511
|
workspaceId: string;
|
|
@@ -1537,6 +1559,7 @@ interface MeterableWarmLease {
|
|
|
1537
1559
|
backend: string;
|
|
1538
1560
|
}
|
|
1539
1561
|
declare function listMeterableWarmLeases(db: Database): Promise<MeterableWarmLease[]>;
|
|
1562
|
+
declare function listLiveModalSandboxLeaseAttributions(db: Database): Promise<LiveModalSandboxLeaseAttribution[]>;
|
|
1540
1563
|
declare function reArmDrainingLease(db: Database, input: {
|
|
1541
1564
|
accountId: string;
|
|
1542
1565
|
workspaceId: string;
|
|
@@ -2119,4 +2142,4 @@ type LockedSessionUpdateResult = {
|
|
|
2119
2142
|
declare function appendSessionEventsWithLockedSessionUpdate(db: Database, workspaceId: string, sessionId: string, build: (session: Session, context: LockedSessionUpdateContext) => LockedSessionUpdateResult | Promise<LockedSessionUpdateResult>): Promise<SessionEvent[]>;
|
|
2120
2143
|
declare function sessionSubject(workspaceId: string, sessionId: string): string;
|
|
2121
2144
|
|
|
2122
|
-
export {
|
|
2145
|
+
export { SandboxLeaseSupersededError as $, type AccrueWarmSecondsResult as A, type BootstrapWorkspaceInput as B, CLEARED_RUN_STATE as C, type Database as D, type EnableCapabilityInstallationInput as E, type EnrollmentExposure as F, type EnrollmentOs as G, type EnrollmentRecord as H, type EnrollmentStatus as I, type ForceDrainResult as J, type GitHubInstallation as K, type GoalContinuationDecision as L, type LeaseHolderKind as M, type LeaseSnapshot as N, type ListSessionEventsOptions as O, type LiveModalSandboxLeaseAttribution as P, type ProvisionResult, type ProvisionRolesOptions, MACHINE_METRICS_SERIES_INTERVAL_MS as Q, type MachineMetricsRow as R, type MachineMetricsSample as S, type MeterableWarmLease as T, type ReapDrainable as U, type RegisterWorkspacePackInput as V, type RlsContext as W, type RlsStrategy as X, SandboxImageConflictError as Y, type SandboxKind as Z, type SandboxLeaseLiveness as _, type AcquireLeaseInput as a, createSessionWithIdempotencyKey as a$, type SandboxPtySessionRow as a0, type SandboxRecord as a1, type SessionCodexState as a2, type SessionMcpServerForRun as a3, type SessionRecordingCodec as a4, type SessionRecordingMode as a5, type SessionRecordingRow as a6, type SessionRecordingState as a7, type StreamAcknowledgment as a8, type UpdateQueuedSessionTurnInput as a9, commitWarmingToWarm as aA, completeFileUpload as aB, confirmDrainCold as aC, consumeDeviceEnrollmentRequest as aD, consumeSessionCompactionRequest as aE, countActiveApiKeysForWorkspace as aF, countActiveSessionHistoryItems as aG, countActiveSessionsForWorkspace as aH, countActiveSessionsUsingEnvironment as aI, countConsecutiveReactiveRotations as aJ, countScheduledTasksForWorkspace as aK, countScheduledTasksUsingEnvironment as aL, countSessionHistoryItems as aM, countTurnSessionHistoryItems as aN, countWorkspaceEnvironments as aO, countWorkspacesForAccount as aP, createApiKey as aQ, createDb as aR, createDeviceEnrollmentRequest as aS, createEnrollment as aT, createFileUpload as aU, createSandbox as aV, createScheduledTask as aW, createScheduledTaskRun as aX, createSession as aY, createSessionGoal as aZ, createSessionMcpServers as a_, type UpdateScheduledTaskInput as aa, type UpdateSessionMcpServerCredentialsInput as ab, type UpdateSessionMcpServerCredentialsResult as ac, type UserLookup as ad, type WakeParentForChildCompletionInput as ae, type WakeParentForChildCompletionResult as af, type WorkspaceEnvironmentForRun as ag, accrueWarmSeconds as ah, acquireLease as ai, allAccountPermissions as aj, allWorkspacePermissions as ak, appendSessionEvents as al, appendSessionEventsAndUpdateSession as am, appendSessionEventsWithLockedSessionUpdate as an, appendSessionHistoryItems as ao, applyContextCompaction as ap, applyCreditDebitUpToBalance as aq, applyCreditLedgerEntry as ar, approveDeviceEnrollmentRequest as as, bootstrapWorkspace as at, buildCodexTokenResolver as au, cancelQueuedSessionTurn as av, claimNextQueuedTurn as aw, clearSessionContext as ax, clearedContextMarkerItem as ay, closePtySession as az, type AcquireLeaseResult as b, getWorkspace as b$, createSocialConnection as b0, createSocialPost as b1, createTurn as b2, createWorkspace as b3, createWorkspaceEnvironment as b4, decryptEnvironmentValue as b5, decryptedCapabilityHeaders as b6, deleteRecording as b7, deleteScheduledTask as b8, deleteWorkspace as b9, getCodexCredentialStatus as bA, getCodexRotationSettings as bB, getDeviceEnrollmentRequestByDeviceCode as bC, getEnrollment as bD, getFile as bE, getFileUpload as bF, getLatestRunState as bG, getManagedAccount as bH, getManagedUserByEmail as bI, getOpenPtySession as bJ, getPackInstallation as bK, getPendingDeviceEnrollmentRequestByUserCode as bL, getPendingDeviceEnrollmentRequestByUserCodeGlobal as bM, getRecording as bN, getSandbox as bO, getSandboxSessionEnvelope as bP, getScheduledTask as bQ, getSession as bR, getSessionByCreateIdempotencyKey as bS, getSessionCodexState as bT, getSessionEvent as bU, getSessionGoal as bV, getSessionHistoryItems as bW, getSessionTurn as bX, getSocialConnection as bY, getStoredCapabilityHeaderCiphertext as bZ, getStreamAcknowledgment as b_, deleteWorkspaceEnvironment as ba, deleteWorkspaceEnvironmentVariable as bb, deleteWorkspacePack as bc, denyDeviceEnrollmentRequest as bd, disableCapabilityInstallation as be, disconnectAllCodexAccounts as bf, disconnectCodexAccount as bg, enableCapabilityInstallation as bh, enablePackInstallation as bi, encryptEnvironmentValue as bj, enqueueSessionTurn as bk, ensureCodexRotationSettings as bl, ensureManagedAccessForUser as bm, evaluateGoalContinuation as bn, failWarmingToCold as bo, fetchCodexUsageForAccount as bp, finalizeEnrollmentByToken as bq, findActiveApiKeyByHash as br, finishTurn as bs, forceDrainOverLimitViewerOnlyBoxes as bt, getActiveSessionHistoryItems as bu, getAnySessionInGroup as bv, getBillingBalance as bw, getBillingCustomer as bx, getCapabilityCatalogItem as by, getCapabilityInstallation as bz, type ActiveSandboxPointer as c, recordCodexAccountUsage as c$, getWorkspaceEnvironment as c0, getWorkspaceEnvironmentByName as c1, getWorkspaceEnvironmentValuesForRun as c2, getWorkspaceGrant as c3, getWorkspacePack as c4, grantWorkspaceAccess as c5, hasCreditLedgerEntry as c6, heartbeatLeaseHolder as c7, incrementTurnWorkerDeathRedispatches as c8, ingestMachineMetricsSample as c9, listSessionTurns as cA, listSessions as cB, listSocialConnections as cC, listSocialPosts as cD, listUsageEvents as cE, listWorkspaceEnvironments as cF, listWorkspaceMembers as cG, listWorkspacePacks as cH, listWorkspacesForSubject as cI, loadCodexCredentialForRun as cJ, loadWorkspaceEnvironmentForRun as cK, markFileUploadFailed as cL, markStripeWebhookProcessed as cM, mcpServerIdForCapability as cN, nextSessionHistoryPosition as cO, orphanedResultRowIndicesForRepair as cP, persistDrainSnapshot as cQ, reArmDrainingLease as cR, readActiveSandbox as cS, readLease as cT, readMachineMetricsLatest as cU, readMachineMetricsLatestForWorkspace as cV, readMachineMetricsSeries as cW, reapStaleLeaseHolders as cX, reapStaleLeaseHoldersGlobal as cY, recordAuditEvent as cZ, recordCodexAccountConnectors as c_, insertMachineMetricsSeries as ca, insertPtySession as cb, insertRecording as cc, isCodexBilledTurn as cd, isStripeWebhookProcessed as ce, listApiKeys as cf, listCapabilityCatalogItems as cg, listCapabilityInstallations as ch, listCodexAccountStatuses as ci, listDistinctEnvironmentIdsInGroup as cj, listEnabledMcpCapabilityServers as ck, listEnrollments as cl, listGitHubInstallationIdsForWorkspace as cm, listGitHubInstallationsForWorkspace as cn, listLiveModalSandboxLeaseAttributions as co, listMeterableWarmLeases as cp, listOpenPtySessions as cq, listPackInstallations as cr, listRecordings as cs, listSandboxes as ct, listScheduledTaskRuns as cu, listScheduledTasks as cv, listSessionEvents as cw, listSessionIdsInGroup as cx, listSessionMcpServerMetadata as cy, listSessionMcpServersForRun as cz, type AppendEventInput as d, upsertSandboxSessionEnvelope as d$, recordCodexTokenRefresh as d0, recordLeaseDataPlaneUrl as d1, recordLeaseTerminalDataPlaneUrl as d2, recordSessionActiveCodexCredential as d3, recordStreamAcknowledgment as d4, recordStripeWebhookEvent as d5, recordUsageEvent as d6, recordWarmingSandboxCreated as d7, registerDbBinding as d8, registerWorkspacePack as d9, setRlsContext as dA, setSessionCodexPin as dB, setSessionGoalLastContinuationTurn as dC, setSessionGoalStatus as dD, setSessionLastInputTokens as dE, setSessionStatus as dF, setTemporalWorkflowId as dG, setWorkspaceEnvironmentVariable as dH, sumUsageQuantity as dI, touchEnrollmentLastSeen as dJ, updateCodexRotationSettings as dK, updatePackInstallationStatus as dL, updatePtySessionActivity as dM, updateQueuedSessionTurn as dN, updateRecording as dO, updateScheduledTask as dP, updateScheduledTaskRun as dQ, updateSessionGoal as dR, updateSessionMcpServerCredentials as dS, updateSessionTitle as dT, updateWorkspace as dU, updateWorkspaceEnvironment as dV, upsertBillingCustomer as dW, upsertCapabilityCatalogItem as dX, upsertCodexSubscriptionCredential as dY, upsertGitHubInstallation as dZ, upsertMachineMetricsLatest as d_, releaseLeaseHolder as da, removeWorkspaceMember as db, renameCodexAccount as dc, reorderQueuedSessionTurns as dd, requestSessionCompaction as de, requeuePreemptedTurn as df, requireFile as dg, requireScheduledTask as dh, requireSession as di, requireSocialConnection as dj, requireWorkspace as dk, revokeApiKey as dl, revokeEnrollment as dm, revokeViewer as dn, rlsContextForWorkspace as dp, rlsStrategyFor as dq, sanitizeEventPayload as dr, sanitizeEventString as ds, saveRunState as dt, sessionSubject as du, setActiveCodexCredential as dv, setActiveSandbox as dw, setCodexCredentialExhausted as dx, setCodexCredentialStatus as dy, setEnrollmentHasDisplay as dz, CODEX_ROTATION_STRATEGIES as e, upsertSessionGoal as e0, wakeParentSessionForChildCompletion as e1, withAccountRls as e2, withRlsContext as e3, withWorkspaceRls as e4, withWorkspaceUsageLock as e5, workspaceCodexSubscriptionActive as e6, type ClearSessionContextResult as f, type CodexAccountStatus as g, type CodexAccountUsageSnapshot as h, type CodexAuthDeps as i, type CodexCredentialForRun as j, type CodexCredentialTokens as k, type CodexRotationSettings as l, type CodexRotationStrategy as m, type CreateCapabilityCatalogItemInput as n, type CreateDbOptions as o, type CreatePackInstallationInput as p, provisionRoles, type CreateScheduledTaskInput as q, type CreateSessionGoalInput as r, type CreateSessionMcpServerInput as s, type CreateSocialConnectionInput as t, type CreateSocialPostInput as u, type DbClient as v, type DeviceEnrollmentRequestRecord as w, type DeviceEnrollmentStatus as x, type EnabledMcpCapabilityServer as y, type EnqueueSessionTurnInput as z };
|
|
@@ -1655,6 +1655,23 @@ declare const sessions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
1655
1655
|
identity: undefined;
|
|
1656
1656
|
generated: undefined;
|
|
1657
1657
|
}, {}, {}>;
|
|
1658
|
+
instructions: drizzle_orm_pg_core.PgColumn<{
|
|
1659
|
+
name: "instructions";
|
|
1660
|
+
tableName: "sessions";
|
|
1661
|
+
dataType: "string";
|
|
1662
|
+
columnType: "PgText";
|
|
1663
|
+
data: string;
|
|
1664
|
+
driverParam: string;
|
|
1665
|
+
notNull: false;
|
|
1666
|
+
hasDefault: false;
|
|
1667
|
+
isPrimaryKey: false;
|
|
1668
|
+
isAutoincrement: false;
|
|
1669
|
+
hasRuntimeDefault: false;
|
|
1670
|
+
enumValues: [string, ...string[]];
|
|
1671
|
+
baseColumn: never;
|
|
1672
|
+
identity: undefined;
|
|
1673
|
+
generated: undefined;
|
|
1674
|
+
}, {}, {}>;
|
|
1658
1675
|
resources: drizzle_orm_pg_core.PgColumn<{
|
|
1659
1676
|
name: "resources";
|
|
1660
1677
|
tableName: "sessions";
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import 'drizzle-orm';
|
|
2
2
|
import 'drizzle-orm/pg-core';
|
|
3
|
-
export { i as agentRunStates, j as apiKeys, k as auditEvents, l as billingCustomers, m as capabilityCatalogItems, n as capabilityInstallations, o as codexRotationSettings, p as codexSubscriptionCredentials, q as creditLedgerEntries, r as deviceEnrollmentRequests, d as deviceEnrollmentStatusValues, t as documentBases, u as documentChunks, v as documents, a as enrollmentExposureValues, e as enrollmentOsValues, b as enrollmentStatusValues, w as enrollments, x as fileUploads, y as files, z as githubInstallations, A as machineMetricsLatest, B as machineMetricsSeries, C as managedAccounts, D as packInstallations, c as sandboxKindValues, E as sandboxLeaseHolders, F as sandboxLeaseLivenessValues, G as sandboxLeases, H as sandboxPtySessions, I as sandboxSessionEnvelopes, J as sandboxes, K as scheduledTaskRuns, L as scheduledTasks, M as sessionEvents, N as sessionGoals, O as sessionHistoryItems, P as sessionMcpServers, f as sessionRecordingCodecValues, g as sessionRecordingModeValues, h as sessionRecordingStateValues, Q as sessionRecordings, R as sessionTurns, S as sessions, T as socialConnections, U as socialPosts, V as stripeWebhookEvents, W as usageEvents, X as workspaceEnvironmentVariables, Y as workspaceEnvironments, Z as workspaceMemberships, _ as workspacePacks, $ as workspaces } from './schema-
|
|
3
|
+
export { i as agentRunStates, j as apiKeys, k as auditEvents, l as billingCustomers, m as capabilityCatalogItems, n as capabilityInstallations, o as codexRotationSettings, p as codexSubscriptionCredentials, q as creditLedgerEntries, r as deviceEnrollmentRequests, d as deviceEnrollmentStatusValues, t as documentBases, u as documentChunks, v as documents, a as enrollmentExposureValues, e as enrollmentOsValues, b as enrollmentStatusValues, w as enrollments, x as fileUploads, y as files, z as githubInstallations, A as machineMetricsLatest, B as machineMetricsSeries, C as managedAccounts, D as packInstallations, c as sandboxKindValues, E as sandboxLeaseHolders, F as sandboxLeaseLivenessValues, G as sandboxLeases, H as sandboxPtySessions, I as sandboxSessionEnvelopes, J as sandboxes, K as scheduledTaskRuns, L as scheduledTasks, M as sessionEvents, N as sessionGoals, O as sessionHistoryItems, P as sessionMcpServers, f as sessionRecordingCodecValues, g as sessionRecordingModeValues, h as sessionRecordingStateValues, Q as sessionRecordings, R as sessionTurns, S as sessions, T as socialConnections, U as socialPosts, V as stripeWebhookEvents, W as usageEvents, X as workspaceEnvironmentVariables, Y as workspaceEnvironments, Z as workspaceMemberships, _ as workspacePacks, $ as workspaces } from './schema-DuRsrmzD.js';
|
package/dist/schema.js
CHANGED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
-- 0036_modal_lease_orphan_reaper.sql
|
|
2
|
+
-- Modal lease leak hardening:
|
|
3
|
+
-- 1. An expired warming lease with a persisted instance_id is now surfaced as
|
|
4
|
+
-- immediately drainable instead of being reset cold and losing the only DB
|
|
5
|
+
-- pointer to the provider sandbox.
|
|
6
|
+
-- 2. The worker's provider-side Modal orphan sweep gets a SECURITY DEFINER
|
|
7
|
+
-- live-lease attribution read so it can compare Modal tags against current
|
|
8
|
+
-- lease rows across workspaces without disabling FORCE RLS.
|
|
9
|
+
|
|
10
|
+
CREATE OR REPLACE FUNCTION opengeni_private.reap_sandbox_leases(
|
|
11
|
+
p_viewer_holder_ttl_ms bigint,
|
|
12
|
+
p_idle_grace_ms bigint
|
|
13
|
+
)
|
|
14
|
+
RETURNS TABLE (workspace_id uuid, sandbox_group_id uuid, instance_id text, lease_epoch integer)
|
|
15
|
+
LANGUAGE plpgsql
|
|
16
|
+
SECURITY DEFINER
|
|
17
|
+
AS $$
|
|
18
|
+
BEGIN
|
|
19
|
+
DELETE FROM sandbox_lease_holders h
|
|
20
|
+
WHERE h.kind = 'viewer'
|
|
21
|
+
AND h.last_heartbeat_at < now() - make_interval(secs => p_viewer_holder_ttl_ms / 1000.0);
|
|
22
|
+
|
|
23
|
+
UPDATE sandbox_leases L SET
|
|
24
|
+
refcount = c.total,
|
|
25
|
+
turn_holders = c.turns,
|
|
26
|
+
viewer_holders = c.viewers,
|
|
27
|
+
liveness = CASE WHEN L.liveness = 'warm' AND c.total = 0 AND c.turns = 0
|
|
28
|
+
THEN 'draining' ELSE L.liveness END,
|
|
29
|
+
expires_at = CASE WHEN L.liveness = 'warm' AND c.total = 0 AND c.turns = 0
|
|
30
|
+
THEN now() + make_interval(secs => p_idle_grace_ms / 1000.0)
|
|
31
|
+
ELSE L.expires_at END,
|
|
32
|
+
updated_at = now()
|
|
33
|
+
FROM (
|
|
34
|
+
SELECT L2.id,
|
|
35
|
+
(SELECT count(*) FROM sandbox_lease_holders h WHERE h.lease_id = L2.id)::int AS total,
|
|
36
|
+
(SELECT count(*) FROM sandbox_lease_holders h WHERE h.lease_id = L2.id AND h.kind = 'turn')::int AS turns,
|
|
37
|
+
(SELECT count(*) FROM sandbox_lease_holders h WHERE h.lease_id = L2.id AND h.kind = 'viewer')::int AS viewers
|
|
38
|
+
FROM sandbox_leases L2
|
|
39
|
+
) c
|
|
40
|
+
WHERE L.id = c.id;
|
|
41
|
+
|
|
42
|
+
-- Warming died before provider create returned: no instance was ever tracked.
|
|
43
|
+
UPDATE sandbox_leases AS L SET
|
|
44
|
+
liveness = 'cold', instance_id = NULL,
|
|
45
|
+
resume_backend_id = NULL, resume_state = NULL,
|
|
46
|
+
data_plane_url = NULL, terminal_data_plane_url = NULL, updated_at = now()
|
|
47
|
+
WHERE L.liveness = 'warming' AND L.expires_at < now() AND L.instance_id IS NULL;
|
|
48
|
+
|
|
49
|
+
-- Warming died after provider create returned: keep the instance_id and hand it
|
|
50
|
+
-- to the normal provider termination path in this same sweep.
|
|
51
|
+
UPDATE sandbox_leases AS L SET
|
|
52
|
+
liveness = 'draining',
|
|
53
|
+
refcount = 0,
|
|
54
|
+
turn_holders = 0,
|
|
55
|
+
viewer_holders = 0,
|
|
56
|
+
data_plane_url = NULL,
|
|
57
|
+
terminal_data_plane_url = NULL,
|
|
58
|
+
expires_at = now() - interval '1 millisecond',
|
|
59
|
+
updated_at = now()
|
|
60
|
+
WHERE L.liveness = 'warming' AND L.expires_at < now() AND L.instance_id IS NOT NULL;
|
|
61
|
+
|
|
62
|
+
RETURN QUERY
|
|
63
|
+
SELECT L.workspace_id, L.sandbox_group_id, L.instance_id, L.lease_epoch
|
|
64
|
+
FROM sandbox_leases L
|
|
65
|
+
WHERE L.liveness = 'draining' AND L.expires_at < now() AND L.refcount = 0;
|
|
66
|
+
END;
|
|
67
|
+
$$;
|
|
68
|
+
|
|
69
|
+
CREATE OR REPLACE FUNCTION opengeni_private.list_live_modal_sandbox_leases()
|
|
70
|
+
RETURNS TABLE (
|
|
71
|
+
lease_id uuid,
|
|
72
|
+
workspace_id uuid,
|
|
73
|
+
sandbox_group_id uuid,
|
|
74
|
+
instance_id text,
|
|
75
|
+
liveness text
|
|
76
|
+
)
|
|
77
|
+
LANGUAGE sql
|
|
78
|
+
SECURITY DEFINER
|
|
79
|
+
AS $$
|
|
80
|
+
SELECT L.id, L.workspace_id, L.sandbox_group_id, L.instance_id, L.liveness
|
|
81
|
+
FROM sandbox_leases L
|
|
82
|
+
WHERE L.liveness IN ('warming', 'warm', 'draining')
|
|
83
|
+
AND (L.backend = 'modal' OR L.resume_backend_id = 'modal');
|
|
84
|
+
$$;
|
|
85
|
+
|
|
86
|
+
DO $$
|
|
87
|
+
BEGIN
|
|
88
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
89
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.reap_sandbox_leases(bigint, bigint) TO opengeni_app;
|
|
90
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.list_live_modal_sandbox_leases() TO opengeni_app;
|
|
91
|
+
END IF;
|
|
92
|
+
END $$;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-- Per-session agent persona/system instructions.
|
|
2
|
+
--
|
|
3
|
+
-- An optional per-agent-type prompt supplied by an embedding host at session
|
|
4
|
+
-- creation. It rides the SAME system-level instructions channel the
|
|
5
|
+
-- per-workspace agent_instructions rides, composed AFTER the workspace persona
|
|
6
|
+
-- so it refines it for this one session — never as a user-visible timeline
|
|
7
|
+
-- event. NULL means the session carried none, so every existing row keeps its
|
|
8
|
+
-- historical, byte-identical composed instructions after this migration without
|
|
9
|
+
-- a backfill. Org-visible metadata, not a secret.
|
|
10
|
+
ALTER TABLE "sessions"
|
|
11
|
+
ADD COLUMN IF NOT EXISTS "instructions" text;
|
|
12
|
+
|
|
13
|
+
DO $$
|
|
14
|
+
BEGIN
|
|
15
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
16
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO opengeni_app;
|
|
17
|
+
END IF;
|
|
18
|
+
END $$;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@opengeni/codex": "^0.2.1",
|
|
55
|
-
"@opengeni/config": "^0.2.
|
|
56
|
-
"@opengeni/contracts": "^0.
|
|
55
|
+
"@opengeni/config": "^0.2.4",
|
|
56
|
+
"@opengeni/contracts": "^0.6.0",
|
|
57
57
|
"drizzle-orm": "^0.45.2",
|
|
58
58
|
"postgres": "^3.4.7"
|
|
59
59
|
},
|
package/src/index.ts
CHANGED
|
@@ -3274,6 +3274,9 @@ export async function createSession(db: Database, input: {
|
|
|
3274
3274
|
sandboxBackend: SandboxBackend;
|
|
3275
3275
|
environmentId?: string | null;
|
|
3276
3276
|
firstPartyMcpPermissions?: Permission[] | null;
|
|
3277
|
+
// Per-session agent persona/system instructions (org-visible, not a secret).
|
|
3278
|
+
// Null/omitted ⇒ the session carries none (composed instructions unchanged).
|
|
3279
|
+
instructions?: string | null;
|
|
3277
3280
|
parentSessionId?: string | null;
|
|
3278
3281
|
createIdempotencyKey?: string | null;
|
|
3279
3282
|
// The shared-sandbox group to join. Omit (or null) for a singleton group:
|
|
@@ -3301,6 +3304,7 @@ export async function createSession(db: Database, input: {
|
|
|
3301
3304
|
sandboxGroupId: input.sandboxGroupId ?? id,
|
|
3302
3305
|
environmentId: input.environmentId ?? null,
|
|
3303
3306
|
firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
|
|
3307
|
+
instructions: input.instructions ?? null,
|
|
3304
3308
|
parentSessionId: input.parentSessionId ?? null,
|
|
3305
3309
|
createIdempotencyKey: input.createIdempotencyKey ?? null,
|
|
3306
3310
|
status: "queued",
|
|
@@ -3339,6 +3343,8 @@ export async function createSessionWithIdempotencyKey(db: Database, input: {
|
|
|
3339
3343
|
sandboxBackend: SandboxBackend;
|
|
3340
3344
|
environmentId?: string | null;
|
|
3341
3345
|
firstPartyMcpPermissions?: Permission[] | null;
|
|
3346
|
+
// Per-session agent persona/system instructions (org-visible, not a secret).
|
|
3347
|
+
instructions?: string | null;
|
|
3342
3348
|
parentSessionId?: string | null;
|
|
3343
3349
|
createIdempotencyKey: string;
|
|
3344
3350
|
// The shared-sandbox group to join. Omit (or null) for a singleton group
|
|
@@ -3365,6 +3371,7 @@ export async function createSessionWithIdempotencyKey(db: Database, input: {
|
|
|
3365
3371
|
sandboxGroupId: input.sandboxGroupId ?? id,
|
|
3366
3372
|
environmentId: input.environmentId ?? null,
|
|
3367
3373
|
firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
|
|
3374
|
+
instructions: input.instructions ?? null,
|
|
3368
3375
|
parentSessionId: input.parentSessionId ?? null,
|
|
3369
3376
|
createIdempotencyKey: input.createIdempotencyKey,
|
|
3370
3377
|
status: "queued",
|
|
@@ -4481,6 +4488,14 @@ export interface LeaseSnapshot {
|
|
|
4481
4488
|
expiresAt: Date;
|
|
4482
4489
|
}
|
|
4483
4490
|
|
|
4491
|
+
export interface LiveModalSandboxLeaseAttribution {
|
|
4492
|
+
leaseId: string;
|
|
4493
|
+
workspaceId: string;
|
|
4494
|
+
sandboxGroupId: string;
|
|
4495
|
+
instanceId: string | null;
|
|
4496
|
+
liveness: SandboxLeaseLiveness;
|
|
4497
|
+
}
|
|
4498
|
+
|
|
4484
4499
|
export interface AcquireLeaseInput {
|
|
4485
4500
|
accountId: string;
|
|
4486
4501
|
workspaceId: string;
|
|
@@ -4780,6 +4795,40 @@ export async function commitWarmingToWarm(db: Database, input: {
|
|
|
4780
4795
|
});
|
|
4781
4796
|
}
|
|
4782
4797
|
|
|
4798
|
+
// §4.2a — leak-proof create attribution. The spawner calls this immediately
|
|
4799
|
+
// after the provider create returns, before display/readiness/setup work. It
|
|
4800
|
+
// intentionally does NOT bump lease_epoch or mark the lease warm; it only makes
|
|
4801
|
+
// the just-created provider id durable while the row is still warming so a
|
|
4802
|
+
// failure/reaper/provider-side sweep can identify and stop it.
|
|
4803
|
+
export async function recordWarmingSandboxCreated(db: Database, input: {
|
|
4804
|
+
accountId: string;
|
|
4805
|
+
workspaceId: string;
|
|
4806
|
+
sandboxGroupId: string;
|
|
4807
|
+
expectedEpoch: number;
|
|
4808
|
+
instanceId: string;
|
|
4809
|
+
resumeBackendId?: string | null;
|
|
4810
|
+
resumeState?: Record<string, unknown> | null;
|
|
4811
|
+
leaseTtlMs: number;
|
|
4812
|
+
}): Promise<{ recorded: boolean; lease: LeaseSnapshot | null }> {
|
|
4813
|
+
return await withRlsContext(db, { accountId: input.accountId, workspaceId: input.workspaceId },
|
|
4814
|
+
async (scopedDb) => {
|
|
4815
|
+
const resumeStateJson = input.resumeState == null ? null : JSON.stringify(input.resumeState);
|
|
4816
|
+
const rows = await scopedDb.execute<LeaseRow>(sql`
|
|
4817
|
+
update sandbox_leases set
|
|
4818
|
+
instance_id = ${input.instanceId},
|
|
4819
|
+
resume_backend_id = ${input.resumeBackendId ?? null},
|
|
4820
|
+
resume_state = ${resumeStateJson}::jsonb,
|
|
4821
|
+
expires_at = now() + (${String(input.leaseTtlMs)} || ' milliseconds')::interval,
|
|
4822
|
+
updated_at = now()
|
|
4823
|
+
where workspace_id = ${input.workspaceId} and sandbox_group_id = ${input.sandboxGroupId}
|
|
4824
|
+
and liveness = 'warming' and lease_epoch = ${input.expectedEpoch}
|
|
4825
|
+
returning *
|
|
4826
|
+
`);
|
|
4827
|
+
if (rows.length === 0) return { recorded: false, lease: null };
|
|
4828
|
+
return { recorded: true, lease: mapLeaseRow(rows[0]!) };
|
|
4829
|
+
});
|
|
4830
|
+
}
|
|
4831
|
+
|
|
4783
4832
|
// §4.3 — caught spawn failure: warming -> cold (W3). Holders are intentionally
|
|
4784
4833
|
// left intact — the arrival that triggered the spawn still wants a box, so the
|
|
4785
4834
|
// next acquireLease re-CAS cold->warming.
|
|
@@ -4962,15 +5011,34 @@ export async function reapStaleLeaseHolders(db: Database, input: {
|
|
|
4962
5011
|
where L.id = c.id and L.workspace_id = ${input.workspaceId}
|
|
4963
5012
|
`);
|
|
4964
5013
|
|
|
4965
|
-
// (
|
|
4966
|
-
//
|
|
5014
|
+
// (c1) WARMING-death before provider create returned: no instance_id was
|
|
5015
|
+
// ever persisted, so there is no provider box to stop. Reset to cold so a
|
|
5016
|
+
// queued turn can re-acquire and re-spawn.
|
|
4967
5017
|
const warmingReset = await tx.execute<{ id: string }>(sql`
|
|
4968
5018
|
update sandbox_leases set
|
|
4969
5019
|
liveness = 'cold', instance_id = null,
|
|
4970
5020
|
resume_backend_id = null, resume_state = null,
|
|
4971
5021
|
data_plane_url = null, terminal_data_plane_url = null, updated_at = now()
|
|
4972
5022
|
where workspace_id = ${input.workspaceId}
|
|
4973
|
-
and liveness = 'warming' and expires_at < now()
|
|
5023
|
+
and liveness = 'warming' and expires_at < now() and instance_id is null
|
|
5024
|
+
returning id
|
|
5025
|
+
`);
|
|
5026
|
+
|
|
5027
|
+
// (c2) WARMING-death after provider create returned: instance_id is known,
|
|
5028
|
+
// so do NOT drop it. Convert to immediately-drainable so the caller's
|
|
5029
|
+
// provider terminate path stops the box before the lease goes cold.
|
|
5030
|
+
const warmingDrain = await tx.execute<{ id: string }>(sql`
|
|
5031
|
+
update sandbox_leases set
|
|
5032
|
+
liveness = 'draining',
|
|
5033
|
+
refcount = 0,
|
|
5034
|
+
turn_holders = 0,
|
|
5035
|
+
viewer_holders = 0,
|
|
5036
|
+
data_plane_url = null,
|
|
5037
|
+
terminal_data_plane_url = null,
|
|
5038
|
+
expires_at = now() - interval '1 millisecond',
|
|
5039
|
+
updated_at = now()
|
|
5040
|
+
where workspace_id = ${input.workspaceId}
|
|
5041
|
+
and liveness = 'warming' and expires_at < now() and instance_id is not null
|
|
4974
5042
|
returning id
|
|
4975
5043
|
`);
|
|
4976
5044
|
|
|
@@ -4985,7 +5053,7 @@ export async function reapStaleLeaseHolders(db: Database, input: {
|
|
|
4985
5053
|
|
|
4986
5054
|
return {
|
|
4987
5055
|
reapedViewers: reaped.length,
|
|
4988
|
-
warmingReset: warmingReset.length,
|
|
5056
|
+
warmingReset: warmingReset.length + warmingDrain.length,
|
|
4989
5057
|
drained: drainable.map((r) => ({
|
|
4990
5058
|
workspaceId: input.workspaceId,
|
|
4991
5059
|
sandboxGroupId: r.sandbox_group_id,
|
|
@@ -5045,6 +5113,28 @@ export async function listMeterableWarmLeases(db: Database): Promise<MeterableWa
|
|
|
5045
5113
|
}));
|
|
5046
5114
|
}
|
|
5047
5115
|
|
|
5116
|
+
// Cross-workspace live Modal lease read for the provider-side orphan sweep. The
|
|
5117
|
+
// SECURITY DEFINER function is the sanctioned RLS bypass; see migration 0036.
|
|
5118
|
+
export async function listLiveModalSandboxLeaseAttributions(db: Database): Promise<LiveModalSandboxLeaseAttribution[]> {
|
|
5119
|
+
const rows = await rawRows<{
|
|
5120
|
+
lease_id: string;
|
|
5121
|
+
workspace_id: string;
|
|
5122
|
+
sandbox_group_id: string;
|
|
5123
|
+
instance_id: string | null;
|
|
5124
|
+
liveness: SandboxLeaseLiveness;
|
|
5125
|
+
}>(db, sql`
|
|
5126
|
+
select lease_id, workspace_id, sandbox_group_id, instance_id, liveness
|
|
5127
|
+
from opengeni_private.list_live_modal_sandbox_leases()
|
|
5128
|
+
`);
|
|
5129
|
+
return rows.map((r) => ({
|
|
5130
|
+
leaseId: r.lease_id,
|
|
5131
|
+
workspaceId: r.workspace_id,
|
|
5132
|
+
sandboxGroupId: r.sandbox_group_id,
|
|
5133
|
+
instanceId: r.instance_id,
|
|
5134
|
+
liveness: r.liveness,
|
|
5135
|
+
}));
|
|
5136
|
+
}
|
|
5137
|
+
|
|
5048
5138
|
// §4.7 — explicit re-arm seam (D1). acquireLease already re-arms a draining
|
|
5049
5139
|
// lease inline; this is the standalone version for callers that learn a holder
|
|
5050
5140
|
// is wanted during the grace window without going through acquireLease first.
|
|
@@ -7618,6 +7708,7 @@ function mapSession(row: typeof schema.sessions.$inferSelect, mcpServers: Sessio
|
|
|
7618
7708
|
initialMessage: row.initialMessage,
|
|
7619
7709
|
title: row.title ?? null,
|
|
7620
7710
|
titleSource: (row.titleSource as "user" | "agent" | null) ?? null,
|
|
7711
|
+
instructions: row.instructions ?? null,
|
|
7621
7712
|
resources: row.resources as ResourceRef[],
|
|
7622
7713
|
tools: row.tools as ToolRef[],
|
|
7623
7714
|
metadata: row.metadata,
|
package/src/schema.ts
CHANGED
|
@@ -187,6 +187,13 @@ export const sessions = pgTable("sessions", {
|
|
|
187
187
|
initialMessage: text("initial_message").notNull(),
|
|
188
188
|
title: text("title"),
|
|
189
189
|
titleSource: text("title_source"),
|
|
190
|
+
// Per-session agent persona/system instructions supplied at create (the
|
|
191
|
+
// per-agent-type prompt lever for embedding hosts). NULL ⇒ the session
|
|
192
|
+
// carried none, so the composed agent instructions are byte-identical to a
|
|
193
|
+
// workspace-only persona (no backfill, no behavior change for existing rows).
|
|
194
|
+
// Composed system-level AFTER the workspace agentInstructions; never emitted
|
|
195
|
+
// as a timeline event.
|
|
196
|
+
instructions: text("instructions"),
|
|
190
197
|
resources: jsonb("resources").$type<unknown[]>().notNull().default([]),
|
|
191
198
|
tools: jsonb("tools").$type<unknown[]>().notNull().default([]),
|
|
192
199
|
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
|