@opengeni/db 3.7.4 → 3.8.2-canary.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/automations.d.ts +4 -0
- package/dist/canonical-human-identities.js +3 -3
- package/dist/{chunk-O56DM5GZ.js → chunk-53A2PEHT.js} +328 -31
- package/dist/chunk-53A2PEHT.js.map +1 -0
- package/dist/{chunk-SZAWDXQC.js → chunk-5S4BQAHX.js} +3 -3
- package/dist/{chunk-S3TDDCS2.js → chunk-CITYUGMW.js} +3 -3
- package/dist/{chunk-4NAR7FBA.js → chunk-F5YOZEYQ.js} +49 -1
- package/dist/chunk-F5YOZEYQ.js.map +1 -0
- package/dist/{chunk-F6PBS6WE.js → chunk-HZV7O6JR.js} +2 -2
- package/dist/{chunk-5NNCGFQY.js → chunk-RLQVPOVR.js} +2 -2
- package/dist/{chunk-DZCYW2WT.js → chunk-UKOGHUGC.js} +2 -2
- package/dist/{chunk-US4IS6IV.js → chunk-X6TO5HII.js} +2 -2
- package/dist/{chunk-U4WPPEBH.js → chunk-Y53WEFYB.js} +2 -2
- package/dist/connection-token-resolver.d.ts +4 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +164 -14
- package/dist/index.js +7327 -5442
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +9 -0
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/model-catalog.d.ts +153 -0
- package/dist/new-session-drafts.d.ts +12 -0
- package/dist/organization-membership-lifecycle.d.ts +8 -1
- package/dist/organization-model-providers.d.ts +101 -0
- package/dist/pack-components.d.ts +1 -0
- package/dist/pr-review.d.ts +9 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +7 -7
- package/dist/schema.d.ts +989 -2
- package/dist/schema.js +11 -1
- package/dist/session-background-commands.js +3 -3
- package/dist/session-control.d.ts +35 -0
- package/dist/session-queue-commands.d.ts +17 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/dist/xai-subscription.d.ts +8 -0
- package/drizzle/0387_bound_host_export_session_payloads.sql +165 -0
- package/drizzle/0388_sandbox_provider_deadline_interactions.sql +491 -0
- package/drizzle/0389_model_catalog_and_gateway_custom_models.sql +241 -0
- package/drizzle/0390_organization_model_provider_connections.sql +197 -0
- package/drizzle/0391_sandbox_provider_deadline_interaction_followup.sql +295 -0
- package/drizzle/0392_context_compaction_pending_observability.sql +198 -0
- package/drizzle/0393_workspace_memory_and_learning_defaults.sql +102 -0
- package/drizzle/0394_session_selected_skill_activation.sql +20 -0
- package/drizzle/0395_scheduled_task_unclaimed_occurrence_invalidation.sql +154 -0
- package/drizzle/0396_model_call_equivalent_credit_cost.sql +28 -0
- package/drizzle/0397_sandbox_deadline_rotation_preemption.sql +113 -0
- package/drizzle/0398_organization_workspace_management_entry.sql +206 -0
- package/drizzle/0399_additional_managed_organization_creation.sql +691 -0
- package/package.json +6 -6
- package/src/automations.ts +9 -1
- package/src/connection-token-resolver.ts +12 -0
- package/src/index.ts +1551 -272
- package/src/insights-model-bundle.ts +54 -5
- package/src/insights.ts +53 -7
- package/src/model-catalog.ts +682 -0
- package/src/new-session-drafts.ts +66 -21
- package/src/organization-membership-lifecycle.ts +47 -5
- package/src/organization-model-providers.ts +614 -0
- package/src/pack-components.ts +15 -2
- package/src/pr-review.ts +19 -0
- package/src/provision-roles.ts +2 -0
- package/src/runtime-posture.ts +48 -0
- package/src/schema.ts +348 -32
- package/src/session-control.ts +170 -0
- package/src/session-queue-commands.ts +157 -82
- package/src/xai-subscription.ts +66 -14
- package/dist/chunk-4NAR7FBA.js.map +0 -1
- package/dist/chunk-O56DM5GZ.js.map +0 -1
- /package/dist/{chunk-SZAWDXQC.js.map → chunk-5S4BQAHX.js.map} +0 -0
- /package/dist/{chunk-S3TDDCS2.js.map → chunk-CITYUGMW.js.map} +0 -0
- /package/dist/{chunk-F6PBS6WE.js.map → chunk-HZV7O6JR.js.map} +0 -0
- /package/dist/{chunk-5NNCGFQY.js.map → chunk-RLQVPOVR.js.map} +0 -0
- /package/dist/{chunk-DZCYW2WT.js.map → chunk-UKOGHUGC.js.map} +0 -0
- /package/dist/{chunk-US4IS6IV.js.map → chunk-X6TO5HII.js.map} +0 -0
- /package/dist/{chunk-U4WPPEBH.js.map → chunk-Y53WEFYB.js.map} +0 -0
package/src/index.ts
CHANGED
|
@@ -217,8 +217,12 @@ import {
|
|
|
217
217
|
} from "@opengeni/contracts";
|
|
218
218
|
|
|
219
219
|
import {
|
|
220
|
+
approximateSessionEventTokens,
|
|
220
221
|
approvalIdentifier,
|
|
222
|
+
boundSessionEvent,
|
|
221
223
|
boundWorkspaceControlEvent,
|
|
224
|
+
sessionEventJsonBytes,
|
|
225
|
+
sessionEventPayloadTruncation,
|
|
222
226
|
workspaceControlUtf8Bytes,
|
|
223
227
|
WORKSPACE_STATE_MEMORY_SAMPLE_LIMIT,
|
|
224
228
|
SESSION_EVENT_RAW_DELTA_TYPES,
|
|
@@ -251,9 +255,17 @@ import {
|
|
|
251
255
|
HumanInputQuestion as HumanInputQuestionContract,
|
|
252
256
|
SubmitHumanInputResponseRequest,
|
|
253
257
|
TurnExecutionPolicyV1,
|
|
258
|
+
OPENROUTER_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY,
|
|
259
|
+
OPENROUTER_CREDENTIAL_OPERATION_ID_METADATA_KEY,
|
|
260
|
+
VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY,
|
|
261
|
+
VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_ID_METADATA_KEY,
|
|
254
262
|
} from "@opengeni/contracts";
|
|
255
263
|
import {
|
|
256
264
|
environmentsEncryptionKeyBytes,
|
|
265
|
+
SANDBOX_LIFECYCLE_RETRY_HANDOFF_GRACE_MS,
|
|
266
|
+
SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS,
|
|
267
|
+
WORKSPACE_OPENROUTER_CONNECTION_DOMAIN,
|
|
268
|
+
WORKSPACE_OPENROUTER_CONNECTION_ROLE,
|
|
257
269
|
VERCEL_AI_GATEWAY_CONNECTION_DOMAIN,
|
|
258
270
|
VERCEL_AI_GATEWAY_CONNECTION_ROLE,
|
|
259
271
|
type Settings,
|
|
@@ -343,6 +355,7 @@ import {
|
|
|
343
355
|
lockSessionEventWriteRows,
|
|
344
356
|
lockWorkspaceInferenceControl,
|
|
345
357
|
registerInternalUpdateWakeInTransaction,
|
|
358
|
+
registerSessionWorkflowWakeInTransaction,
|
|
346
359
|
SESSION_DISCOVERY_CONTROL_TITLE_MAX_CHARS,
|
|
347
360
|
assertSessionAuthoritySnapshot,
|
|
348
361
|
registerSessionTurnAttemptClaim,
|
|
@@ -509,6 +522,7 @@ export {
|
|
|
509
522
|
} from "./provision-roles";
|
|
510
523
|
// Workspace Memory V1 pure domain surface (gates, render, canonical prompt text).
|
|
511
524
|
export * from "./memory-domain";
|
|
525
|
+
export * from "./model-catalog";
|
|
512
526
|
|
|
513
527
|
import {
|
|
514
528
|
dbBindingFor,
|
|
@@ -3142,6 +3156,24 @@ async function lockBackgroundCommandWorkspaceLifecycle(
|
|
|
3142
3156
|
}
|
|
3143
3157
|
}
|
|
3144
3158
|
|
|
3159
|
+
async function authorizeOrganizationWorkspaceDeletion(
|
|
3160
|
+
tx: Database,
|
|
3161
|
+
input: {
|
|
3162
|
+
accountId: string;
|
|
3163
|
+
workspaceId: string;
|
|
3164
|
+
subjectId: string;
|
|
3165
|
+
},
|
|
3166
|
+
): Promise<void> {
|
|
3167
|
+
await setSubjectRlsContext(tx, input.subjectId);
|
|
3168
|
+
await tx.execute(sql`
|
|
3169
|
+
select authorize_organization_shared_workspace_administration(
|
|
3170
|
+
${input.accountId}::uuid,
|
|
3171
|
+
${input.workspaceId}::uuid,
|
|
3172
|
+
${input.subjectId}
|
|
3173
|
+
)
|
|
3174
|
+
`);
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3145
3177
|
/**
|
|
3146
3178
|
* Atomically prove a workspace has no live runtime ownership and delete it.
|
|
3147
3179
|
*
|
|
@@ -3153,10 +3185,33 @@ async function lockBackgroundCommandWorkspaceLifecycle(
|
|
|
3153
3185
|
*/
|
|
3154
3186
|
export async function deleteWorkspaceIfQuiescent(
|
|
3155
3187
|
db: Database,
|
|
3156
|
-
input: {
|
|
3188
|
+
input: {
|
|
3189
|
+
accountId: string;
|
|
3190
|
+
workspaceId: string;
|
|
3191
|
+
observer?: WorkspaceDeleteObserver;
|
|
3192
|
+
organizationAdministratorSubjectId?: string;
|
|
3193
|
+
},
|
|
3157
3194
|
): Promise<DeleteWorkspaceIfQuiescentResult> {
|
|
3158
3195
|
const transactionStartedAt = performance.now();
|
|
3159
3196
|
try {
|
|
3197
|
+
if (input.organizationAdministratorSubjectId) {
|
|
3198
|
+
// Reject a clearly unauthorized caller before it can acquire or wait on
|
|
3199
|
+
// the exclusive workspace lifecycle lock. This preflight is deliberately
|
|
3200
|
+
// not the mutation authority: the deletion transaction repeats the same
|
|
3201
|
+
// check after taking the lifecycle lock so a concurrent demotion still
|
|
3202
|
+
// fails closed without reintroducing the account/lifecycle lock cycle.
|
|
3203
|
+
await withRlsContext(
|
|
3204
|
+
db,
|
|
3205
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
3206
|
+
async (scopedDb) =>
|
|
3207
|
+
await authorizeOrganizationWorkspaceDeletion(scopedDb, {
|
|
3208
|
+
accountId: input.accountId,
|
|
3209
|
+
workspaceId: input.workspaceId,
|
|
3210
|
+
subjectId: input.organizationAdministratorSubjectId!,
|
|
3211
|
+
}),
|
|
3212
|
+
);
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3160
3215
|
const result = await withRlsContext(
|
|
3161
3216
|
db,
|
|
3162
3217
|
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
@@ -3166,7 +3221,10 @@ export async function deleteWorkspaceIfQuiescent(
|
|
|
3166
3221
|
// Adoption takes the matching shared prefix before its canonical
|
|
3167
3222
|
// session/attempt or retained-process locks. Taking the exclusive
|
|
3168
3223
|
// prefix first prevents parent-FK deletion from deadlocking with an
|
|
3169
|
-
// already-started cross-workspace adoption.
|
|
3224
|
+
// already-started cross-workspace adoption. It must also precede the
|
|
3225
|
+
// organization-administration seam: that seam takes the account row
|
|
3226
|
+
// FOR KEY SHARE, while ordinary deletion takes this lifecycle lock
|
|
3227
|
+
// before upgrading the account row to FOR UPDATE.
|
|
3170
3228
|
const lifecycleLockStartedAt = performance.now();
|
|
3171
3229
|
await lockBackgroundCommandWorkspaceLifecycle(tx, [input.workspaceId], "update");
|
|
3172
3230
|
observeWorkspaceDeletePhase(input.observer, {
|
|
@@ -3175,6 +3233,14 @@ export async function deleteWorkspaceIfQuiescent(
|
|
|
3175
3233
|
durationSeconds: workspaceDeletePhaseDuration(lifecycleLockStartedAt),
|
|
3176
3234
|
});
|
|
3177
3235
|
|
|
3236
|
+
if (input.organizationAdministratorSubjectId) {
|
|
3237
|
+
await authorizeOrganizationWorkspaceDeletion(tx, {
|
|
3238
|
+
accountId: input.accountId,
|
|
3239
|
+
workspaceId: input.workspaceId,
|
|
3240
|
+
subjectId: input.organizationAdministratorSubjectId,
|
|
3241
|
+
});
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3178
3244
|
const accountWorkspaceLockStartedAt = performance.now();
|
|
3179
3245
|
const [account] = await tx
|
|
3180
3246
|
.select({ id: schema.managedAccounts.id })
|
|
@@ -4623,6 +4689,7 @@ export type ModelCallFact = {
|
|
|
4623
4689
|
totalTokens: number | null;
|
|
4624
4690
|
pricedCostMicros: number;
|
|
4625
4691
|
estimatedProviderCostMicros: number | null;
|
|
4692
|
+
equivalentCreditCostMicros: number | null;
|
|
4626
4693
|
pricingSource: "configured_list_price" | "gateway_reported" | null;
|
|
4627
4694
|
contextContributions: readonly ModelContextContributionSummary[] | null;
|
|
4628
4695
|
occurredAt: Date;
|
|
@@ -4658,6 +4725,7 @@ function mapModelCallFact(row: typeof schema.modelCallFacts.$inferSelect): Model
|
|
|
4658
4725
|
totalTokens: row.totalTokens,
|
|
4659
4726
|
pricedCostMicros: row.pricedCostMicros,
|
|
4660
4727
|
estimatedProviderCostMicros: row.estimatedProviderCostMicros,
|
|
4728
|
+
equivalentCreditCostMicros: row.equivalentCreditCostMicros,
|
|
4661
4729
|
pricingSource:
|
|
4662
4730
|
row.pricingSource === "configured_list_price" || row.pricingSource === "gateway_reported"
|
|
4663
4731
|
? row.pricingSource
|
|
@@ -4724,6 +4792,7 @@ export async function recordModelCallFact(
|
|
|
4724
4792
|
billingPath: ModelCallBillingPath;
|
|
4725
4793
|
pricedCostMicros: number;
|
|
4726
4794
|
estimatedProviderCostMicros?: number | null;
|
|
4795
|
+
equivalentCreditCostMicros?: number | null;
|
|
4727
4796
|
pricingSource?: "configured_list_price" | "gateway_reported" | null;
|
|
4728
4797
|
contextContributions?: readonly ModelContextContributionSummary[] | null;
|
|
4729
4798
|
inputTokens?: number | null;
|
|
@@ -4747,6 +4816,20 @@ export async function recordModelCallFact(
|
|
|
4747
4816
|
"recordModelCallFact: estimatedProviderCostMicros must be null or a non-negative safe integer",
|
|
4748
4817
|
);
|
|
4749
4818
|
}
|
|
4819
|
+
if (
|
|
4820
|
+
input.equivalentCreditCostMicros != null &&
|
|
4821
|
+
(input.equivalentCreditCostMicros < 0 ||
|
|
4822
|
+
!Number.isSafeInteger(input.equivalentCreditCostMicros))
|
|
4823
|
+
) {
|
|
4824
|
+
throw new Error(
|
|
4825
|
+
"recordModelCallFact: equivalentCreditCostMicros must be null or a non-negative safe integer",
|
|
4826
|
+
);
|
|
4827
|
+
}
|
|
4828
|
+
if (input.equivalentCreditCostMicros != null && input.estimatedProviderCostMicros == null) {
|
|
4829
|
+
throw new Error(
|
|
4830
|
+
"recordModelCallFact: equivalentCreditCostMicros requires estimatedProviderCostMicros",
|
|
4831
|
+
);
|
|
4832
|
+
}
|
|
4750
4833
|
if ((input.estimatedProviderCostMicros == null) !== (input.pricingSource == null)) {
|
|
4751
4834
|
throw new Error(
|
|
4752
4835
|
"recordModelCallFact: estimatedProviderCostMicros and pricingSource must be present together",
|
|
@@ -4808,6 +4891,7 @@ export async function recordModelCallFact(
|
|
|
4808
4891
|
totalTokens: input.totalTokens ?? null,
|
|
4809
4892
|
pricedCostMicros: input.pricedCostMicros,
|
|
4810
4893
|
estimatedProviderCostMicros: input.estimatedProviderCostMicros ?? null,
|
|
4894
|
+
equivalentCreditCostMicros: input.equivalentCreditCostMicros ?? null,
|
|
4811
4895
|
pricingSource: input.pricingSource ?? null,
|
|
4812
4896
|
contextContributions,
|
|
4813
4897
|
occurredAt,
|
|
@@ -4825,6 +4909,7 @@ export async function recordModelCallFact(
|
|
|
4825
4909
|
initiatorSubjectId: sql`coalesce(${schema.modelCallFacts.initiatorSubjectId}, excluded.initiator_subject_id)`,
|
|
4826
4910
|
turnSource: sql`coalesce(${schema.modelCallFacts.turnSource}, excluded.turn_source)`,
|
|
4827
4911
|
estimatedProviderCostMicros: sql`coalesce(${schema.modelCallFacts.estimatedProviderCostMicros}, excluded.estimated_provider_cost_micros)`,
|
|
4912
|
+
equivalentCreditCostMicros: sql`coalesce(${schema.modelCallFacts.equivalentCreditCostMicros}, excluded.equivalent_credit_cost_micros)`,
|
|
4828
4913
|
pricingSource: sql`coalesce(${schema.modelCallFacts.pricingSource}, excluded.pricing_source)`,
|
|
4829
4914
|
contextContributions: sql`coalesce(${schema.modelCallFacts.contextContributions}, excluded.context_contributions)`,
|
|
4830
4915
|
},
|
|
@@ -5202,6 +5287,8 @@ export type CreateScheduledTaskInput = {
|
|
|
5202
5287
|
// The rig each run binds to (M3); active version resolved per fire at dispatch.
|
|
5203
5288
|
rigId?: string | null;
|
|
5204
5289
|
metadata: Record<string, unknown>;
|
|
5290
|
+
/** Trusted database-only admission seam. Throwing rolls the task creation back. */
|
|
5291
|
+
beforeCreateCommit?: (tx: Database) => Promise<void>;
|
|
5205
5292
|
};
|
|
5206
5293
|
|
|
5207
5294
|
export type UpdateScheduledTaskInput = Partial<{
|
|
@@ -5224,6 +5311,8 @@ export type UpdateScheduledTaskInput = Partial<{
|
|
|
5224
5311
|
authorityUpdatedBy: TurnInitiator;
|
|
5225
5312
|
authorityUpdatedByContext: TurnInitiatorContext;
|
|
5226
5313
|
authorityUpdatedByActor: AgentSessionCreationActor | null;
|
|
5314
|
+
/** Trusted database-only admission seam. Throwing rolls the task update back. */
|
|
5315
|
+
beforeUpdateCommit: (tx: Database) => Promise<void>;
|
|
5227
5316
|
}>;
|
|
5228
5317
|
|
|
5229
5318
|
export type CreatePackInstallationInput = {
|
|
@@ -5358,6 +5447,51 @@ export type CreateConnectionInput = {
|
|
|
5358
5447
|
updatedBySubjectId?: string | null;
|
|
5359
5448
|
};
|
|
5360
5449
|
|
|
5450
|
+
type UpsertWorkspaceProviderApiKeyConnectionInput = {
|
|
5451
|
+
accountId: string;
|
|
5452
|
+
workspaceId: string;
|
|
5453
|
+
operationId: string;
|
|
5454
|
+
requestDigest: string;
|
|
5455
|
+
credentialEncrypted: string;
|
|
5456
|
+
grantedScopes?: string[];
|
|
5457
|
+
expiresAt?: Date | null;
|
|
5458
|
+
metadata?: Record<string, unknown>;
|
|
5459
|
+
updatedBySubjectId: string;
|
|
5460
|
+
};
|
|
5461
|
+
|
|
5462
|
+
type RotateWorkspaceProviderApiKeyConnectionInput = {
|
|
5463
|
+
accountId: string;
|
|
5464
|
+
workspaceId: string;
|
|
5465
|
+
connectionId: string;
|
|
5466
|
+
expectedVersion: number;
|
|
5467
|
+
operationId: string;
|
|
5468
|
+
requestDigest: string;
|
|
5469
|
+
credentialEncrypted: string;
|
|
5470
|
+
grantedScopes?: string[];
|
|
5471
|
+
expiresAt?: Date | null;
|
|
5472
|
+
metadata?: Record<string, unknown>;
|
|
5473
|
+
updatedBySubjectId: string;
|
|
5474
|
+
};
|
|
5475
|
+
|
|
5476
|
+
type RevokeWorkspaceProviderApiKeyConnectionsInput = {
|
|
5477
|
+
accountId: string;
|
|
5478
|
+
workspaceId: string;
|
|
5479
|
+
connectionId: string;
|
|
5480
|
+
expectedVersion: number;
|
|
5481
|
+
updatedBySubjectId: string;
|
|
5482
|
+
};
|
|
5483
|
+
|
|
5484
|
+
export type UpsertWorkspaceVercelAiGatewayConnectionInput =
|
|
5485
|
+
UpsertWorkspaceProviderApiKeyConnectionInput;
|
|
5486
|
+
export type RotateWorkspaceVercelAiGatewayConnectionInput =
|
|
5487
|
+
RotateWorkspaceProviderApiKeyConnectionInput;
|
|
5488
|
+
export type RevokeWorkspaceVercelAiGatewayConnectionsInput =
|
|
5489
|
+
RevokeWorkspaceProviderApiKeyConnectionsInput;
|
|
5490
|
+
export type UpsertWorkspaceOpenRouterConnectionInput = UpsertWorkspaceProviderApiKeyConnectionInput;
|
|
5491
|
+
export type RotateWorkspaceOpenRouterConnectionInput = RotateWorkspaceProviderApiKeyConnectionInput;
|
|
5492
|
+
export type RevokeWorkspaceOpenRouterConnectionsInput =
|
|
5493
|
+
RevokeWorkspaceProviderApiKeyConnectionsInput;
|
|
5494
|
+
|
|
5361
5495
|
export type UpdateConnectionInput = {
|
|
5362
5496
|
workspaceId: string;
|
|
5363
5497
|
connectionId: string;
|
|
@@ -5756,6 +5890,7 @@ export type InstallPortableSkillInput = {
|
|
|
5756
5890
|
sourcePath: string;
|
|
5757
5891
|
name: string;
|
|
5758
5892
|
description: string;
|
|
5893
|
+
activationMode?: "workspace_managed" | "session_selected";
|
|
5759
5894
|
category?: string;
|
|
5760
5895
|
tags?: string[];
|
|
5761
5896
|
provenance?: "platform" | "deployment" | "registry" | "workspace";
|
|
@@ -5796,6 +5931,7 @@ export type PortableSkillRuntime = {
|
|
|
5796
5931
|
version: string;
|
|
5797
5932
|
name: string;
|
|
5798
5933
|
description: string;
|
|
5934
|
+
activationMode: "workspace_managed" | "session_selected";
|
|
5799
5935
|
sourceUrl: string;
|
|
5800
5936
|
sourceCommit: string;
|
|
5801
5937
|
sourcePath: string;
|
|
@@ -8232,6 +8368,7 @@ export async function installPortableSkill(
|
|
|
8232
8368
|
}
|
|
8233
8369
|
if (!pluginVersion) throw new Error("Failed to create portable Skill plugin version");
|
|
8234
8370
|
|
|
8371
|
+
const activationMode = input.activationMode ?? "workspace_managed";
|
|
8235
8372
|
let [facet] = await tx
|
|
8236
8373
|
.select()
|
|
8237
8374
|
.from(schema.capabilityFacets)
|
|
@@ -8249,12 +8386,17 @@ export async function installPortableSkill(
|
|
|
8249
8386
|
pluginVersionId: pluginVersion.id,
|
|
8250
8387
|
facetKey: "skill",
|
|
8251
8388
|
kind: "skill",
|
|
8252
|
-
activationMode
|
|
8389
|
+
activationMode,
|
|
8253
8390
|
required: true,
|
|
8254
8391
|
})
|
|
8255
8392
|
.returning();
|
|
8256
8393
|
}
|
|
8257
8394
|
if (!facet) throw new Error("Failed to create portable Skill facet");
|
|
8395
|
+
if (facet.activationMode !== activationMode) {
|
|
8396
|
+
throw new Error(
|
|
8397
|
+
`Portable Skill ${input.capabilityId} activation mode conflicts with immutable stored content`,
|
|
8398
|
+
);
|
|
8399
|
+
}
|
|
8258
8400
|
|
|
8259
8401
|
const [existingSkill] = await tx
|
|
8260
8402
|
.select()
|
|
@@ -8458,16 +8600,22 @@ export async function installPortableSkill(
|
|
|
8458
8600
|
);
|
|
8459
8601
|
}
|
|
8460
8602
|
|
|
8461
|
-
/**
|
|
8603
|
+
/**
|
|
8604
|
+
* Return exact text artifacts for active authoritative Skill installations.
|
|
8605
|
+
* Session-selected Skills are excluded from ambient runtime resolution unless
|
|
8606
|
+
* the caller is explicitly materializing one during session admission.
|
|
8607
|
+
*/
|
|
8462
8608
|
export async function listInstalledPortableSkills(
|
|
8463
8609
|
db: Database,
|
|
8464
8610
|
workspaceId: string,
|
|
8611
|
+
options: { includeSessionSelected?: boolean } = {},
|
|
8465
8612
|
): Promise<PortableSkillRuntime[]> {
|
|
8466
8613
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
8467
8614
|
const rows = await scopedDb
|
|
8468
8615
|
.select({
|
|
8469
8616
|
capabilityId: schema.capabilitySkillFacets.capabilityId,
|
|
8470
8617
|
facetId: schema.capabilitySkillFacets.facetId,
|
|
8618
|
+
activationMode: schema.capabilityFacets.activationMode,
|
|
8471
8619
|
manifest: schema.capabilityPluginVersions.manifest,
|
|
8472
8620
|
version: schema.capabilityPluginVersions.version,
|
|
8473
8621
|
name: schema.capabilitySkillFacets.name,
|
|
@@ -8494,6 +8642,10 @@ export async function listInstalledPortableSkills(
|
|
|
8494
8642
|
schema.capabilityPluginInstallations.id,
|
|
8495
8643
|
),
|
|
8496
8644
|
)
|
|
8645
|
+
.innerJoin(
|
|
8646
|
+
schema.capabilityFacets,
|
|
8647
|
+
eq(schema.capabilityFacets.id, schema.capabilityFacetInstallations.facetId),
|
|
8648
|
+
)
|
|
8497
8649
|
.innerJoin(
|
|
8498
8650
|
schema.capabilitySkillFacets,
|
|
8499
8651
|
eq(schema.capabilitySkillFacets.facetId, schema.capabilityFacetInstallations.facetId),
|
|
@@ -8518,6 +8670,14 @@ export async function listInstalledPortableSkills(
|
|
|
8518
8670
|
.orderBy(asc(schema.capabilitySkillFacets.name), asc(schema.capabilitySkillFiles.path));
|
|
8519
8671
|
const skills = new Map<string, PortableSkillRuntime>();
|
|
8520
8672
|
for (const row of rows) {
|
|
8673
|
+
if (row.activationMode !== "workspace_managed" && row.activationMode !== "session_selected") {
|
|
8674
|
+
throw new Error(
|
|
8675
|
+
`Installed Skill ${row.capabilityId} has invalid activation mode ${row.activationMode}`,
|
|
8676
|
+
);
|
|
8677
|
+
}
|
|
8678
|
+
if (row.activationMode === "session_selected" && !options.includeSessionSelected) {
|
|
8679
|
+
continue;
|
|
8680
|
+
}
|
|
8521
8681
|
const source = skillSourceFromManifest(row.manifest, row.capabilityId);
|
|
8522
8682
|
const existing = skills.get(row.facetId);
|
|
8523
8683
|
if (existing) {
|
|
@@ -8530,6 +8690,7 @@ export async function listInstalledPortableSkills(
|
|
|
8530
8690
|
version: row.version,
|
|
8531
8691
|
name: row.name,
|
|
8532
8692
|
description: row.description,
|
|
8693
|
+
activationMode: row.activationMode,
|
|
8533
8694
|
sourceUrl: row.sourceUrl,
|
|
8534
8695
|
sourceCommit: row.sourceCommit,
|
|
8535
8696
|
sourcePath: row.sourcePath,
|
|
@@ -9458,6 +9619,318 @@ export async function createConnection(
|
|
|
9458
9619
|
);
|
|
9459
9620
|
}
|
|
9460
9621
|
|
|
9622
|
+
type WorkspaceProviderApiKeyConnectionKind = "vercel_gateway" | "openrouter";
|
|
9623
|
+
|
|
9624
|
+
type WorkspaceProviderApiKeyConnectionSpec = {
|
|
9625
|
+
providerDomain: string;
|
|
9626
|
+
credentialRole: string;
|
|
9627
|
+
operationIdMetadataKey: string;
|
|
9628
|
+
operationDigestMetadataKey: string;
|
|
9629
|
+
label: string;
|
|
9630
|
+
};
|
|
9631
|
+
|
|
9632
|
+
function workspaceProviderApiKeyConnectionSpec(
|
|
9633
|
+
providerKind: WorkspaceProviderApiKeyConnectionKind,
|
|
9634
|
+
): WorkspaceProviderApiKeyConnectionSpec {
|
|
9635
|
+
return providerKind === "vercel_gateway"
|
|
9636
|
+
? {
|
|
9637
|
+
providerDomain: VERCEL_AI_GATEWAY_CONNECTION_DOMAIN,
|
|
9638
|
+
credentialRole: VERCEL_AI_GATEWAY_CONNECTION_ROLE,
|
|
9639
|
+
operationIdMetadataKey: VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_ID_METADATA_KEY,
|
|
9640
|
+
operationDigestMetadataKey: VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY,
|
|
9641
|
+
label: "Vercel AI Gateway",
|
|
9642
|
+
}
|
|
9643
|
+
: {
|
|
9644
|
+
providerDomain: WORKSPACE_OPENROUTER_CONNECTION_DOMAIN,
|
|
9645
|
+
credentialRole: WORKSPACE_OPENROUTER_CONNECTION_ROLE,
|
|
9646
|
+
operationIdMetadataKey: OPENROUTER_CREDENTIAL_OPERATION_ID_METADATA_KEY,
|
|
9647
|
+
operationDigestMetadataKey: OPENROUTER_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY,
|
|
9648
|
+
label: "OpenRouter",
|
|
9649
|
+
};
|
|
9650
|
+
}
|
|
9651
|
+
|
|
9652
|
+
async function lockWorkspaceProviderApiKeyConnection(
|
|
9653
|
+
db: Database,
|
|
9654
|
+
workspaceId: string,
|
|
9655
|
+
providerKind: WorkspaceProviderApiKeyConnectionKind,
|
|
9656
|
+
): Promise<void> {
|
|
9657
|
+
const lockKey =
|
|
9658
|
+
providerKind === "vercel_gateway"
|
|
9659
|
+
? `workspace-vercel-ai-gateway:${workspaceId}`
|
|
9660
|
+
: `workspace-openrouter:${workspaceId}`;
|
|
9661
|
+
await db.execute(sql`select pg_advisory_xact_lock(hashtextextended(${lockKey}, 0))`);
|
|
9662
|
+
}
|
|
9663
|
+
|
|
9664
|
+
async function upsertWorkspaceProviderApiKeyConnection(
|
|
9665
|
+
db: Database,
|
|
9666
|
+
providerKind: WorkspaceProviderApiKeyConnectionKind,
|
|
9667
|
+
input: UpsertWorkspaceProviderApiKeyConnectionInput,
|
|
9668
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
9669
|
+
const spec = workspaceProviderApiKeyConnectionSpec(providerKind);
|
|
9670
|
+
return await withRlsContext(
|
|
9671
|
+
db,
|
|
9672
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
9673
|
+
async (scopedDb) =>
|
|
9674
|
+
await scopedDb.transaction(async (txRaw) => {
|
|
9675
|
+
const tx = txRaw as unknown as Database;
|
|
9676
|
+
await lockWorkspaceProviderApiKeyConnection(tx, input.workspaceId, providerKind);
|
|
9677
|
+
const rows = await tx
|
|
9678
|
+
.select(connectionMetadataColumns)
|
|
9679
|
+
.from(schema.connections)
|
|
9680
|
+
.where(
|
|
9681
|
+
and(
|
|
9682
|
+
eq(schema.connections.accountId, input.accountId),
|
|
9683
|
+
eq(schema.connections.workspaceId, input.workspaceId),
|
|
9684
|
+
isNull(schema.connections.subjectId),
|
|
9685
|
+
sql`lower(${schema.connections.providerDomain}) = lower(${spec.providerDomain})`,
|
|
9686
|
+
eq(schema.connections.kind, "api_key"),
|
|
9687
|
+
sql`${schema.connections.metadata} ->> 'credentialRole' = ${spec.credentialRole}`,
|
|
9688
|
+
),
|
|
9689
|
+
)
|
|
9690
|
+
.orderBy(
|
|
9691
|
+
desc(sql`(${schema.connections.status} = 'active')`),
|
|
9692
|
+
desc(schema.connections.updatedAt),
|
|
9693
|
+
desc(schema.connections.createdAt),
|
|
9694
|
+
desc(schema.connections.id),
|
|
9695
|
+
)
|
|
9696
|
+
.for("update");
|
|
9697
|
+
const metadata = {
|
|
9698
|
+
...(input.metadata ?? {}),
|
|
9699
|
+
credentialRole: spec.credentialRole,
|
|
9700
|
+
[spec.operationIdMetadataKey]: input.operationId,
|
|
9701
|
+
[spec.operationDigestMetadataKey]: input.requestDigest,
|
|
9702
|
+
};
|
|
9703
|
+
const operationRow = rows.find(
|
|
9704
|
+
(row) => row.metadata[spec.operationIdMetadataKey] === input.operationId,
|
|
9705
|
+
);
|
|
9706
|
+
if (operationRow) {
|
|
9707
|
+
return operationRow.metadata[spec.operationDigestMetadataKey] === input.requestDigest
|
|
9708
|
+
? mapConnectionMetadata(operationRow)
|
|
9709
|
+
: null;
|
|
9710
|
+
}
|
|
9711
|
+
if (rows.some((row) => row.status !== "revoked")) return null;
|
|
9712
|
+
return await createConnectionInScope(tx, {
|
|
9713
|
+
accountId: input.accountId,
|
|
9714
|
+
workspaceId: input.workspaceId,
|
|
9715
|
+
subjectId: null,
|
|
9716
|
+
providerDomain: spec.providerDomain,
|
|
9717
|
+
kind: "api_key",
|
|
9718
|
+
status: "active",
|
|
9719
|
+
credentialEncrypted: input.credentialEncrypted,
|
|
9720
|
+
grantedScopes: input.grantedScopes ?? [],
|
|
9721
|
+
expiresAt: input.expiresAt ?? null,
|
|
9722
|
+
metadata,
|
|
9723
|
+
createdBySubjectId: input.updatedBySubjectId,
|
|
9724
|
+
updatedBySubjectId: input.updatedBySubjectId,
|
|
9725
|
+
});
|
|
9726
|
+
}),
|
|
9727
|
+
);
|
|
9728
|
+
}
|
|
9729
|
+
|
|
9730
|
+
async function rotateWorkspaceProviderApiKeyConnection(
|
|
9731
|
+
db: Database,
|
|
9732
|
+
providerKind: WorkspaceProviderApiKeyConnectionKind,
|
|
9733
|
+
input: RotateWorkspaceProviderApiKeyConnectionInput,
|
|
9734
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
9735
|
+
const spec = workspaceProviderApiKeyConnectionSpec(providerKind);
|
|
9736
|
+
return await withRlsContext(
|
|
9737
|
+
db,
|
|
9738
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
9739
|
+
async (scopedDb) =>
|
|
9740
|
+
await scopedDb.transaction(async (txRaw) => {
|
|
9741
|
+
const tx = txRaw as unknown as Database;
|
|
9742
|
+
await lockWorkspaceProviderApiKeyConnection(tx, input.workspaceId, providerKind);
|
|
9743
|
+
const rows = await tx
|
|
9744
|
+
.select(connectionMetadataColumns)
|
|
9745
|
+
.from(schema.connections)
|
|
9746
|
+
.where(
|
|
9747
|
+
and(
|
|
9748
|
+
eq(schema.connections.accountId, input.accountId),
|
|
9749
|
+
eq(schema.connections.workspaceId, input.workspaceId),
|
|
9750
|
+
isNull(schema.connections.subjectId),
|
|
9751
|
+
sql`lower(${schema.connections.providerDomain}) = lower(${spec.providerDomain})`,
|
|
9752
|
+
eq(schema.connections.kind, "api_key"),
|
|
9753
|
+
sql`${schema.connections.metadata} ->> 'credentialRole' = ${spec.credentialRole}`,
|
|
9754
|
+
),
|
|
9755
|
+
)
|
|
9756
|
+
.orderBy(desc(schema.connections.updatedAt), desc(schema.connections.id))
|
|
9757
|
+
.for("update");
|
|
9758
|
+
const operationRow = rows.find(
|
|
9759
|
+
(row) => row.metadata[spec.operationIdMetadataKey] === input.operationId,
|
|
9760
|
+
);
|
|
9761
|
+
if (operationRow) {
|
|
9762
|
+
return operationRow.status !== "revoked" &&
|
|
9763
|
+
operationRow.metadata[spec.operationDigestMetadataKey] === input.requestDigest
|
|
9764
|
+
? mapConnectionMetadata(operationRow)
|
|
9765
|
+
: null;
|
|
9766
|
+
}
|
|
9767
|
+
const targetRow = rows.find((row) => row.id === input.connectionId);
|
|
9768
|
+
if (
|
|
9769
|
+
!targetRow ||
|
|
9770
|
+
targetRow.status === "revoked" ||
|
|
9771
|
+
targetRow.version !== input.expectedVersion
|
|
9772
|
+
) {
|
|
9773
|
+
return null;
|
|
9774
|
+
}
|
|
9775
|
+
const metadata = {
|
|
9776
|
+
...(input.metadata ?? {}),
|
|
9777
|
+
credentialRole: spec.credentialRole,
|
|
9778
|
+
[spec.operationIdMetadataKey]: input.operationId,
|
|
9779
|
+
[spec.operationDigestMetadataKey]: input.requestDigest,
|
|
9780
|
+
};
|
|
9781
|
+
for (const row of rows.filter((candidate) => candidate.status !== "revoked")) {
|
|
9782
|
+
const revoked = await revokeConnectionInScope(
|
|
9783
|
+
tx,
|
|
9784
|
+
input.workspaceId,
|
|
9785
|
+
row.id,
|
|
9786
|
+
input.updatedBySubjectId,
|
|
9787
|
+
row.version,
|
|
9788
|
+
);
|
|
9789
|
+
if (!revoked) {
|
|
9790
|
+
throw new Error(`${spec.label} connection changed during rotation`);
|
|
9791
|
+
}
|
|
9792
|
+
}
|
|
9793
|
+
return await createConnectionInScope(tx, {
|
|
9794
|
+
accountId: input.accountId,
|
|
9795
|
+
workspaceId: input.workspaceId,
|
|
9796
|
+
subjectId: null,
|
|
9797
|
+
providerDomain: spec.providerDomain,
|
|
9798
|
+
kind: "api_key",
|
|
9799
|
+
status: "active",
|
|
9800
|
+
credentialEncrypted: input.credentialEncrypted,
|
|
9801
|
+
grantedScopes: input.grantedScopes ?? [],
|
|
9802
|
+
expiresAt: input.expiresAt ?? null,
|
|
9803
|
+
metadata,
|
|
9804
|
+
createdBySubjectId: input.updatedBySubjectId,
|
|
9805
|
+
updatedBySubjectId: input.updatedBySubjectId,
|
|
9806
|
+
});
|
|
9807
|
+
}),
|
|
9808
|
+
);
|
|
9809
|
+
}
|
|
9810
|
+
|
|
9811
|
+
async function revokeWorkspaceProviderApiKeyConnections(
|
|
9812
|
+
db: Database,
|
|
9813
|
+
providerKind: WorkspaceProviderApiKeyConnectionKind,
|
|
9814
|
+
input: RevokeWorkspaceProviderApiKeyConnectionsInput,
|
|
9815
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
9816
|
+
const spec = workspaceProviderApiKeyConnectionSpec(providerKind);
|
|
9817
|
+
return await withRlsContext(
|
|
9818
|
+
db,
|
|
9819
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
9820
|
+
async (scopedDb) =>
|
|
9821
|
+
await scopedDb.transaction(async (txRaw) => {
|
|
9822
|
+
const tx = txRaw as unknown as Database;
|
|
9823
|
+
await lockWorkspaceProviderApiKeyConnection(tx, input.workspaceId, providerKind);
|
|
9824
|
+
const [targetRow] = await tx
|
|
9825
|
+
.select(connectionMetadataColumns)
|
|
9826
|
+
.from(schema.connections)
|
|
9827
|
+
.where(
|
|
9828
|
+
and(
|
|
9829
|
+
eq(schema.connections.accountId, input.accountId),
|
|
9830
|
+
eq(schema.connections.workspaceId, input.workspaceId),
|
|
9831
|
+
eq(schema.connections.id, input.connectionId),
|
|
9832
|
+
isNull(schema.connections.subjectId),
|
|
9833
|
+
),
|
|
9834
|
+
)
|
|
9835
|
+
.for("update")
|
|
9836
|
+
.limit(1);
|
|
9837
|
+
if (
|
|
9838
|
+
!targetRow ||
|
|
9839
|
+
targetRow.providerDomain.toLowerCase() !== spec.providerDomain ||
|
|
9840
|
+
targetRow.kind !== "api_key" ||
|
|
9841
|
+
targetRow.metadata.credentialRole !== spec.credentialRole
|
|
9842
|
+
) {
|
|
9843
|
+
return null;
|
|
9844
|
+
}
|
|
9845
|
+
// A replay of an already-committed DELETE is idempotent for that exact
|
|
9846
|
+
// connection generation. It must not sweep a newer provider connection
|
|
9847
|
+
// that was established after the original response was lost.
|
|
9848
|
+
if (targetRow.status === "revoked") {
|
|
9849
|
+
return mapConnectionMetadata(targetRow);
|
|
9850
|
+
}
|
|
9851
|
+
if (targetRow.version !== input.expectedVersion) {
|
|
9852
|
+
return null;
|
|
9853
|
+
}
|
|
9854
|
+
const providerRows = await tx
|
|
9855
|
+
.select(connectionMetadataColumns)
|
|
9856
|
+
.from(schema.connections)
|
|
9857
|
+
.where(
|
|
9858
|
+
and(
|
|
9859
|
+
eq(schema.connections.accountId, input.accountId),
|
|
9860
|
+
eq(schema.connections.workspaceId, input.workspaceId),
|
|
9861
|
+
isNull(schema.connections.subjectId),
|
|
9862
|
+
sql`lower(${schema.connections.providerDomain}) = lower(${spec.providerDomain})`,
|
|
9863
|
+
eq(schema.connections.kind, "api_key"),
|
|
9864
|
+
ne(schema.connections.status, "revoked"),
|
|
9865
|
+
sql`${schema.connections.metadata} ->> 'credentialRole' = ${spec.credentialRole}`,
|
|
9866
|
+
),
|
|
9867
|
+
)
|
|
9868
|
+
.orderBy(desc(schema.connections.updatedAt), desc(schema.connections.id))
|
|
9869
|
+
.for("update");
|
|
9870
|
+
let result = mapConnectionMetadata(targetRow);
|
|
9871
|
+
for (const row of providerRows) {
|
|
9872
|
+
const revoked = await revokeConnectionInScope(
|
|
9873
|
+
tx,
|
|
9874
|
+
input.workspaceId,
|
|
9875
|
+
row.id,
|
|
9876
|
+
input.updatedBySubjectId,
|
|
9877
|
+
row.version,
|
|
9878
|
+
);
|
|
9879
|
+
if (!revoked) {
|
|
9880
|
+
throw new Error(`${spec.label} connection changed during disconnect`);
|
|
9881
|
+
}
|
|
9882
|
+
if (row.id === input.connectionId) result = revoked;
|
|
9883
|
+
}
|
|
9884
|
+
return result;
|
|
9885
|
+
}),
|
|
9886
|
+
);
|
|
9887
|
+
}
|
|
9888
|
+
|
|
9889
|
+
/** Compatibility wrapper for the existing Vercel AI Gateway create/replay contract. */
|
|
9890
|
+
export async function upsertWorkspaceVercelAiGatewayConnection(
|
|
9891
|
+
db: Database,
|
|
9892
|
+
input: UpsertWorkspaceVercelAiGatewayConnectionInput,
|
|
9893
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
9894
|
+
return await upsertWorkspaceProviderApiKeyConnection(db, "vercel_gateway", input);
|
|
9895
|
+
}
|
|
9896
|
+
|
|
9897
|
+
export async function upsertWorkspaceOpenRouterConnection(
|
|
9898
|
+
db: Database,
|
|
9899
|
+
input: UpsertWorkspaceOpenRouterConnectionInput,
|
|
9900
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
9901
|
+
return await upsertWorkspaceProviderApiKeyConnection(db, "openrouter", input);
|
|
9902
|
+
}
|
|
9903
|
+
|
|
9904
|
+
/** Compatibility wrapper for the existing Vercel AI Gateway rotation contract. */
|
|
9905
|
+
export async function rotateWorkspaceVercelAiGatewayConnection(
|
|
9906
|
+
db: Database,
|
|
9907
|
+
input: RotateWorkspaceVercelAiGatewayConnectionInput,
|
|
9908
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
9909
|
+
return await rotateWorkspaceProviderApiKeyConnection(db, "vercel_gateway", input);
|
|
9910
|
+
}
|
|
9911
|
+
|
|
9912
|
+
export async function rotateWorkspaceOpenRouterConnection(
|
|
9913
|
+
db: Database,
|
|
9914
|
+
input: RotateWorkspaceOpenRouterConnectionInput,
|
|
9915
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
9916
|
+
return await rotateWorkspaceProviderApiKeyConnection(db, "openrouter", input);
|
|
9917
|
+
}
|
|
9918
|
+
|
|
9919
|
+
/** Compatibility wrapper for the existing Vercel AI Gateway revoke contract. */
|
|
9920
|
+
export async function revokeWorkspaceVercelAiGatewayConnections(
|
|
9921
|
+
db: Database,
|
|
9922
|
+
input: RevokeWorkspaceVercelAiGatewayConnectionsInput,
|
|
9923
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
9924
|
+
return await revokeWorkspaceProviderApiKeyConnections(db, "vercel_gateway", input);
|
|
9925
|
+
}
|
|
9926
|
+
|
|
9927
|
+
export async function revokeWorkspaceOpenRouterConnections(
|
|
9928
|
+
db: Database,
|
|
9929
|
+
input: RevokeWorkspaceOpenRouterConnectionsInput,
|
|
9930
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
9931
|
+
return await revokeWorkspaceProviderApiKeyConnections(db, "openrouter", input);
|
|
9932
|
+
}
|
|
9933
|
+
|
|
9461
9934
|
/**
|
|
9462
9935
|
* Serialize one provider-principal/owner connection generation. Distinct OAuth
|
|
9463
9936
|
* starts for the same principal converge on one row, while an explicit
|
|
@@ -13493,11 +13966,21 @@ export async function loadConnectionCredentialForBroker(
|
|
|
13493
13966
|
);
|
|
13494
13967
|
}
|
|
13495
13968
|
|
|
13496
|
-
|
|
13969
|
+
async function getWorkspaceProviderApiKeyConnectionMetadata(
|
|
13497
13970
|
db: Database,
|
|
13498
13971
|
workspaceId: string,
|
|
13499
|
-
|
|
13500
|
-
|
|
13972
|
+
providerKind: WorkspaceProviderApiKeyConnectionKind,
|
|
13973
|
+
): Promise<{ connectionId: string; version: number } | null> {
|
|
13974
|
+
const spec = workspaceProviderApiKeyConnectionSpec(providerKind);
|
|
13975
|
+
const connection = (await listConnectionsMetadata(db, workspaceId, null)).find(
|
|
13976
|
+
(candidate) =>
|
|
13977
|
+
candidate.subjectId === null &&
|
|
13978
|
+
candidate.providerDomain.toLowerCase() === spec.providerDomain &&
|
|
13979
|
+
candidate.kind === "api_key" &&
|
|
13980
|
+
candidate.status === "active" &&
|
|
13981
|
+
candidate.metadata.credentialRole === spec.credentialRole,
|
|
13982
|
+
);
|
|
13983
|
+
return connection ? { connectionId: connection.id, version: connection.version } : null;
|
|
13501
13984
|
}
|
|
13502
13985
|
|
|
13503
13986
|
/** Metadata-only capability revision input; never selects/decrypts the API key. */
|
|
@@ -13505,32 +13988,44 @@ export async function getWorkspaceVercelAiGatewayConnectionMetadata(
|
|
|
13505
13988
|
db: Database,
|
|
13506
13989
|
workspaceId: string,
|
|
13507
13990
|
): Promise<{ connectionId: string; version: number } | null> {
|
|
13508
|
-
|
|
13509
|
-
(candidate) =>
|
|
13510
|
-
candidate.subjectId === null &&
|
|
13511
|
-
candidate.providerDomain === VERCEL_AI_GATEWAY_CONNECTION_DOMAIN &&
|
|
13512
|
-
candidate.kind === "api_key" &&
|
|
13513
|
-
candidate.status === "active" &&
|
|
13514
|
-
candidate.metadata.credentialRole === VERCEL_AI_GATEWAY_CONNECTION_ROLE,
|
|
13515
|
-
);
|
|
13516
|
-
return gatewayConnection
|
|
13517
|
-
? { connectionId: gatewayConnection.id, version: gatewayConnection.version }
|
|
13518
|
-
: null;
|
|
13991
|
+
return await getWorkspaceProviderApiKeyConnectionMetadata(db, workspaceId, "vercel_gateway");
|
|
13519
13992
|
}
|
|
13520
13993
|
|
|
13521
|
-
|
|
13522
|
-
|
|
13994
|
+
export async function getWorkspaceOpenRouterConnectionMetadata(
|
|
13995
|
+
db: Database,
|
|
13996
|
+
workspaceId: string,
|
|
13997
|
+
): Promise<{ connectionId: string; version: number } | null> {
|
|
13998
|
+
return await getWorkspaceProviderApiKeyConnectionMetadata(db, workspaceId, "openrouter");
|
|
13999
|
+
}
|
|
14000
|
+
|
|
14001
|
+
export async function workspaceVercelAiGatewayConnectionActive(
|
|
14002
|
+
db: Database,
|
|
14003
|
+
workspaceId: string,
|
|
14004
|
+
): Promise<boolean> {
|
|
14005
|
+
return (await getWorkspaceVercelAiGatewayConnectionMetadata(db, workspaceId)) !== null;
|
|
14006
|
+
}
|
|
14007
|
+
|
|
14008
|
+
export async function workspaceOpenRouterConnectionActive(
|
|
14009
|
+
db: Database,
|
|
14010
|
+
workspaceId: string,
|
|
14011
|
+
): Promise<boolean> {
|
|
14012
|
+
return (await getWorkspaceOpenRouterConnectionMetadata(db, workspaceId)) !== null;
|
|
14013
|
+
}
|
|
14014
|
+
|
|
14015
|
+
async function loadWorkspaceProviderApiKey(
|
|
13523
14016
|
db: Database,
|
|
13524
14017
|
settings: Settings,
|
|
13525
14018
|
workspaceId: string,
|
|
14019
|
+
providerKind: WorkspaceProviderApiKeyConnectionKind,
|
|
13526
14020
|
): Promise<string | null> {
|
|
14021
|
+
const spec = workspaceProviderApiKeyConnectionSpec(providerKind);
|
|
13527
14022
|
const metadata = (await listConnectionsMetadata(db, workspaceId, null)).find(
|
|
13528
14023
|
(connection) =>
|
|
13529
14024
|
connection.subjectId === null &&
|
|
13530
|
-
connection.providerDomain ===
|
|
14025
|
+
connection.providerDomain.toLowerCase() === spec.providerDomain &&
|
|
13531
14026
|
connection.kind === "api_key" &&
|
|
13532
14027
|
connection.status === "active" &&
|
|
13533
|
-
connection.metadata.credentialRole ===
|
|
14028
|
+
connection.metadata.credentialRole === spec.credentialRole,
|
|
13534
14029
|
);
|
|
13535
14030
|
if (!metadata) {
|
|
13536
14031
|
return null;
|
|
@@ -13538,14 +14033,14 @@ export async function loadWorkspaceVercelAiGatewayApiKey(
|
|
|
13538
14033
|
const connection = await loadConnectionCredentialForBroker(db, settings, {
|
|
13539
14034
|
workspaceId,
|
|
13540
14035
|
connectionId: metadata.id,
|
|
13541
|
-
providerDomain:
|
|
14036
|
+
providerDomain: spec.providerDomain,
|
|
13542
14037
|
kind: "api_key",
|
|
13543
14038
|
allowSubjectOwned: false,
|
|
13544
14039
|
});
|
|
13545
14040
|
if (
|
|
13546
14041
|
!connection ||
|
|
13547
14042
|
connection.status !== "active" ||
|
|
13548
|
-
connection.metadata.credentialRole !==
|
|
14043
|
+
connection.metadata.credentialRole !== spec.credentialRole
|
|
13549
14044
|
) {
|
|
13550
14045
|
return null;
|
|
13551
14046
|
}
|
|
@@ -13553,6 +14048,24 @@ export async function loadWorkspaceVercelAiGatewayApiKey(
|
|
|
13553
14048
|
return typeof apiKey === "string" && apiKey.trim().length > 0 ? apiKey : null;
|
|
13554
14049
|
}
|
|
13555
14050
|
|
|
14051
|
+
/** Resolve only the reviewed workspace-shared AI Gateway credential shape. */
|
|
14052
|
+
export async function loadWorkspaceVercelAiGatewayApiKey(
|
|
14053
|
+
db: Database,
|
|
14054
|
+
settings: Settings,
|
|
14055
|
+
workspaceId: string,
|
|
14056
|
+
): Promise<string | null> {
|
|
14057
|
+
return await loadWorkspaceProviderApiKey(db, settings, workspaceId, "vercel_gateway");
|
|
14058
|
+
}
|
|
14059
|
+
|
|
14060
|
+
/** Resolve only the reviewed workspace-shared OpenRouter credential shape. */
|
|
14061
|
+
export async function loadWorkspaceOpenRouterApiKey(
|
|
14062
|
+
db: Database,
|
|
14063
|
+
settings: Settings,
|
|
14064
|
+
workspaceId: string,
|
|
14065
|
+
): Promise<string | null> {
|
|
14066
|
+
return await loadWorkspaceProviderApiKey(db, settings, workspaceId, "openrouter");
|
|
14067
|
+
}
|
|
14068
|
+
|
|
13556
14069
|
/**
|
|
13557
14070
|
* Worker-only immutable credential lease for an asynchronous video operation.
|
|
13558
14071
|
* The operation copies the already-encrypted credential envelope before its
|
|
@@ -15546,6 +16059,7 @@ export async function createScheduledTask(
|
|
|
15546
16059
|
async (scopedDb) => {
|
|
15547
16060
|
const frozenCreator = await frozenSessionCreatorForInsert(scopedDb, input);
|
|
15548
16061
|
await setScheduledTaskAuthorityRlsContext(scopedDb, frozenCreator);
|
|
16062
|
+
await input.beforeCreateCommit?.(scopedDb);
|
|
15549
16063
|
const [row] = await scopedDb
|
|
15550
16064
|
.insert(schema.scheduledTasks)
|
|
15551
16065
|
.values({
|
|
@@ -15623,6 +16137,7 @@ export async function updateScheduledTask(
|
|
|
15623
16137
|
});
|
|
15624
16138
|
await setScheduledTaskAuthorityRlsContext(scopedDb, frozenUpdater);
|
|
15625
16139
|
}
|
|
16140
|
+
await input.beforeUpdateCommit?.(scopedDb);
|
|
15626
16141
|
const [row] = await scopedDb
|
|
15627
16142
|
.update(schema.scheduledTasks)
|
|
15628
16143
|
.set({
|
|
@@ -16209,23 +16724,69 @@ export async function createScheduledTaskRun(
|
|
|
16209
16724
|
scheduledAt?: Date | null;
|
|
16210
16725
|
firedAt?: Date;
|
|
16211
16726
|
acceptedExecutionSnapshot?: ScheduledTaskRunAcceptedExecutionType | null;
|
|
16727
|
+
/**
|
|
16728
|
+
* Trusted database-only admission seam for a fresh agent occurrence.
|
|
16729
|
+
* Throwing rolls back the run insert; exact producer replay skips it.
|
|
16730
|
+
*/
|
|
16731
|
+
beforeFreshAgentRunCommit?: (tx: Database) => Promise<void>;
|
|
16212
16732
|
},
|
|
16213
16733
|
): Promise<ScheduledTaskRun> {
|
|
16214
16734
|
return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
|
|
16215
16735
|
if ((input.taskAuthorityRevision == null) !== (input.taskExecutionDigest == null)) {
|
|
16216
16736
|
throw new Error("scheduled task run execution binding is incomplete");
|
|
16217
16737
|
}
|
|
16218
|
-
|
|
16219
|
-
|
|
16220
|
-
|
|
16221
|
-
.
|
|
16222
|
-
|
|
16223
|
-
|
|
16224
|
-
|
|
16225
|
-
|
|
16226
|
-
|
|
16227
|
-
|
|
16228
|
-
|
|
16738
|
+
let guardedAcceptedExecution: ScheduledTaskRunAcceptedExecutionType | null = null;
|
|
16739
|
+
let replayedProducerRow: typeof schema.scheduledTaskRuns.$inferSelect | undefined;
|
|
16740
|
+
if (input.beforeFreshAgentRunCommit) {
|
|
16741
|
+
if (!input.producerKey || !input.acceptedExecutionSnapshot) {
|
|
16742
|
+
throw new Error(
|
|
16743
|
+
"scheduled fresh agent run commit guard requires a producer-bound accepted execution",
|
|
16744
|
+
);
|
|
16745
|
+
}
|
|
16746
|
+
guardedAcceptedExecution = ScheduledTaskRunAcceptedExecution.parse(
|
|
16747
|
+
input.acceptedExecutionSnapshot,
|
|
16748
|
+
);
|
|
16749
|
+
const readProducerReplay = async () => {
|
|
16750
|
+
const [row] = await scopedDb
|
|
16751
|
+
.select()
|
|
16752
|
+
.from(schema.scheduledTaskRuns)
|
|
16753
|
+
.where(
|
|
16754
|
+
and(
|
|
16755
|
+
eq(schema.scheduledTaskRuns.workspaceId, input.workspaceId),
|
|
16756
|
+
eq(schema.scheduledTaskRuns.producerKey, input.producerKey!),
|
|
16757
|
+
),
|
|
16758
|
+
)
|
|
16759
|
+
.limit(1);
|
|
16760
|
+
return row;
|
|
16761
|
+
};
|
|
16762
|
+
replayedProducerRow = await readProducerReplay();
|
|
16763
|
+
// Fresh custom-model admission must precede the task row lock. Material
|
|
16764
|
+
// task updates take the same advisory lock before updating that row, and
|
|
16765
|
+
// acquiring them in the opposite order can deadlock behind a queued
|
|
16766
|
+
// exclusive custom-model mutation. An exact durable producer replay
|
|
16767
|
+
// still skips the live-model check so retirement cannot invalidate work
|
|
16768
|
+
// that was already accepted.
|
|
16769
|
+
if (!replayedProducerRow) {
|
|
16770
|
+
try {
|
|
16771
|
+
await input.beforeFreshAgentRunCommit(scopedDb);
|
|
16772
|
+
} catch (error) {
|
|
16773
|
+
// An overlapping producer can commit after the unlocked pre-read
|
|
16774
|
+
// while this retry waits behind a queued retirement. Re-read before
|
|
16775
|
+
// surfacing the mutable-model denial so the committed winner remains
|
|
16776
|
+
// replayable instead of becoming a false terminal rejection.
|
|
16777
|
+
replayedProducerRow = await readProducerReplay();
|
|
16778
|
+
if (!replayedProducerRow) throw error;
|
|
16779
|
+
}
|
|
16780
|
+
}
|
|
16781
|
+
}
|
|
16782
|
+
const taskFilter = and(
|
|
16783
|
+
eq(schema.scheduledTasks.workspaceId, input.workspaceId),
|
|
16784
|
+
eq(schema.scheduledTasks.id, input.taskId),
|
|
16785
|
+
isNull(schema.scheduledTasks.deletedAt),
|
|
16786
|
+
);
|
|
16787
|
+
const [taskRow] = input.beforeFreshAgentRunCommit
|
|
16788
|
+
? await scopedDb.select().from(schema.scheduledTasks).where(taskFilter).for("update").limit(1)
|
|
16789
|
+
: await scopedDb.select().from(schema.scheduledTasks).where(taskFilter).limit(1);
|
|
16229
16790
|
if (!taskRow) {
|
|
16230
16791
|
throw new Error(`Scheduled task not found: ${input.taskId}`);
|
|
16231
16792
|
}
|
|
@@ -16233,8 +16794,14 @@ export async function createScheduledTaskRun(
|
|
|
16233
16794
|
"agent_turn") satisfies ScheduledTaskActionKind;
|
|
16234
16795
|
const acceptedExecutionSnapshot =
|
|
16235
16796
|
actionKind === "agent_turn"
|
|
16236
|
-
?
|
|
16797
|
+
? (guardedAcceptedExecution ??
|
|
16798
|
+
ScheduledTaskRunAcceptedExecution.parse(input.acceptedExecutionSnapshot))
|
|
16237
16799
|
: null;
|
|
16800
|
+
if (input.beforeFreshAgentRunCommit && actionKind !== "agent_turn") {
|
|
16801
|
+
throw new Error(
|
|
16802
|
+
"scheduled fresh agent run commit guard requires a producer-bound accepted execution",
|
|
16803
|
+
);
|
|
16804
|
+
}
|
|
16238
16805
|
if (
|
|
16239
16806
|
acceptedExecutionSnapshot &&
|
|
16240
16807
|
(acceptedExecutionSnapshot.task.id !== input.taskId ||
|
|
@@ -16245,6 +16812,27 @@ export async function createScheduledTaskRun(
|
|
|
16245
16812
|
) {
|
|
16246
16813
|
throw new Error("scheduled task accepted execution binding changed");
|
|
16247
16814
|
}
|
|
16815
|
+
if (replayedProducerRow) {
|
|
16816
|
+
if (
|
|
16817
|
+
replayedProducerRow.actionKind !== "agent_turn" ||
|
|
16818
|
+
replayedProducerRow.taskId !== input.taskId ||
|
|
16819
|
+
replayedProducerRow.triggerType !== input.triggerType
|
|
16820
|
+
) {
|
|
16821
|
+
throw new Error("scheduled task run producer identity changed");
|
|
16822
|
+
}
|
|
16823
|
+
if (
|
|
16824
|
+
replayedProducerRow.taskAuthorityRevision !== input.taskAuthorityRevision ||
|
|
16825
|
+
replayedProducerRow.taskExecutionDigest !== input.taskExecutionDigest
|
|
16826
|
+
) {
|
|
16827
|
+
throw new Error("scheduled task run execution binding changed");
|
|
16828
|
+
}
|
|
16829
|
+
if (
|
|
16830
|
+
stableJson(replayedProducerRow.acceptedExecutionSnapshot) !==
|
|
16831
|
+
stableJson(acceptedExecutionSnapshot)
|
|
16832
|
+
) {
|
|
16833
|
+
throw new Error("scheduled task run accepted execution changed");
|
|
16834
|
+
}
|
|
16835
|
+
}
|
|
16248
16836
|
const values = {
|
|
16249
16837
|
...(input.runId ? { id: input.runId } : {}),
|
|
16250
16838
|
accountId: taskRow.accountId,
|
|
@@ -16265,42 +16853,47 @@ export async function createScheduledTaskRun(
|
|
|
16265
16853
|
)}::jsonb::text, 'UTF8'), 'sha256'), 'hex')`
|
|
16266
16854
|
: null,
|
|
16267
16855
|
};
|
|
16268
|
-
let inserted
|
|
16269
|
-
if (
|
|
16270
|
-
|
|
16271
|
-
|
|
16272
|
-
|
|
16273
|
-
|
|
16274
|
-
|
|
16275
|
-
|
|
16276
|
-
|
|
16277
|
-
|
|
16278
|
-
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
|
|
16282
|
-
|
|
16283
|
-
|
|
16284
|
-
|
|
16285
|
-
|
|
16286
|
-
|
|
16287
|
-
|
|
16288
|
-
|
|
16289
|
-
|
|
16290
|
-
|
|
16291
|
-
|
|
16856
|
+
let inserted = replayedProducerRow;
|
|
16857
|
+
if (!inserted) {
|
|
16858
|
+
if (actionKind === "agent_turn" && input.producerKey && acceptedExecutionSnapshot) {
|
|
16859
|
+
const [created] = await rawRows<{ id: string }>(
|
|
16860
|
+
scopedDb,
|
|
16861
|
+
sql`select create_scheduled_agent_run_with_admission(
|
|
16862
|
+
${input.runId ?? randomUUID()}::uuid,
|
|
16863
|
+
${taskRow.accountId}::uuid,
|
|
16864
|
+
${taskRow.workspaceId}::uuid,
|
|
16865
|
+
${input.taskId}::uuid,
|
|
16866
|
+
${input.taskAuthorityRevision}::bigint,
|
|
16867
|
+
${input.taskExecutionDigest}::text,
|
|
16868
|
+
${input.triggerType}::text,
|
|
16869
|
+
${input.producerKey}::text,
|
|
16870
|
+
${input.scheduledAt?.toISOString() ?? null}::timestamptz,
|
|
16871
|
+
${(input.firedAt ?? new Date()).toISOString()}::timestamptz,
|
|
16872
|
+
${JSON.stringify(acceptedExecutionSnapshot)}::jsonb
|
|
16873
|
+
) as id`,
|
|
16874
|
+
);
|
|
16875
|
+
if (created) {
|
|
16876
|
+
[inserted] = await scopedDb
|
|
16877
|
+
.select()
|
|
16878
|
+
.from(schema.scheduledTaskRuns)
|
|
16879
|
+
.where(eq(schema.scheduledTaskRuns.id, created.id))
|
|
16880
|
+
.limit(1);
|
|
16881
|
+
}
|
|
16882
|
+
} else {
|
|
16883
|
+
[inserted] = input.producerKey
|
|
16884
|
+
? await scopedDb
|
|
16885
|
+
.insert(schema.scheduledTaskRuns)
|
|
16886
|
+
.values(values)
|
|
16887
|
+
.onConflictDoNothing({
|
|
16888
|
+
target: [
|
|
16889
|
+
schema.scheduledTaskRuns.workspaceId,
|
|
16890
|
+
schema.scheduledTaskRuns.producerKey,
|
|
16891
|
+
],
|
|
16892
|
+
where: sql`${schema.scheduledTaskRuns.producerKey} is not null`,
|
|
16893
|
+
})
|
|
16894
|
+
.returning()
|
|
16895
|
+
: await scopedDb.insert(schema.scheduledTaskRuns).values(values).returning();
|
|
16292
16896
|
}
|
|
16293
|
-
} else {
|
|
16294
|
-
[inserted] = input.producerKey
|
|
16295
|
-
? await scopedDb
|
|
16296
|
-
.insert(schema.scheduledTaskRuns)
|
|
16297
|
-
.values(values)
|
|
16298
|
-
.onConflictDoNothing({
|
|
16299
|
-
target: [schema.scheduledTaskRuns.workspaceId, schema.scheduledTaskRuns.producerKey],
|
|
16300
|
-
where: sql`${schema.scheduledTaskRuns.producerKey} is not null`,
|
|
16301
|
-
})
|
|
16302
|
-
.returning()
|
|
16303
|
-
: await scopedDb.insert(schema.scheduledTaskRuns).values(values).returning();
|
|
16304
16897
|
}
|
|
16305
16898
|
const [row] = inserted
|
|
16306
16899
|
? [inserted]
|
|
@@ -16948,17 +17541,10 @@ async function validateScheduledTargetExecutionAtClaim(
|
|
|
16948
17541
|
runId: string;
|
|
16949
17542
|
},
|
|
16950
17543
|
): Promise<string | null> {
|
|
16951
|
-
const [liveAuthority] = await rawRows<{ denial: string | null }>(
|
|
16952
|
-
tx,
|
|
16953
|
-
sql`select validate_scheduled_agent_run_live_authority(
|
|
16954
|
-
${input.accountId}::uuid,
|
|
16955
|
-
${input.workspaceId}::uuid,
|
|
16956
|
-
${input.runId}::uuid
|
|
16957
|
-
) as denial`,
|
|
16958
|
-
);
|
|
16959
|
-
if (liveAuthority?.denial) return liveAuthority.denial;
|
|
16960
17544
|
const [run] = await tx
|
|
16961
17545
|
.select({
|
|
17546
|
+
status: schema.scheduledTaskRuns.status,
|
|
17547
|
+
error: schema.scheduledTaskRuns.error,
|
|
16962
17548
|
acceptedExecutionSnapshot: schema.scheduledTaskRuns.acceptedExecutionSnapshot,
|
|
16963
17549
|
})
|
|
16964
17550
|
.from(schema.scheduledTaskRuns)
|
|
@@ -16973,6 +17559,22 @@ async function validateScheduledTargetExecutionAtClaim(
|
|
|
16973
17559
|
.limit(1)
|
|
16974
17560
|
.for("update");
|
|
16975
17561
|
if (!run?.acceptedExecutionSnapshot) return "scheduled_authority_snapshot_missing";
|
|
17562
|
+
if (run.status !== "dispatched") {
|
|
17563
|
+
return run.status === "skipped" &&
|
|
17564
|
+
(run.error === "scheduled_task_paused_before_claim" ||
|
|
17565
|
+
run.error === "scheduled_task_deleted_before_claim")
|
|
17566
|
+
? "scheduled_task_inactive_before_claim"
|
|
17567
|
+
: "scheduled_run_terminal_before_claim";
|
|
17568
|
+
}
|
|
17569
|
+
const [liveAuthority] = await rawRows<{ denial: string | null }>(
|
|
17570
|
+
tx,
|
|
17571
|
+
sql`select validate_scheduled_agent_run_live_authority(
|
|
17572
|
+
${input.accountId}::uuid,
|
|
17573
|
+
${input.workspaceId}::uuid,
|
|
17574
|
+
${input.runId}::uuid
|
|
17575
|
+
) as denial`,
|
|
17576
|
+
);
|
|
17577
|
+
if (liveAuthority?.denial) return liveAuthority.denial;
|
|
16976
17578
|
const accepted = ScheduledTaskRunAcceptedExecution.parse(run.acceptedExecutionSnapshot);
|
|
16977
17579
|
const target = accepted.targetSessionExecution;
|
|
16978
17580
|
if (!target) return null;
|
|
@@ -21304,6 +21906,8 @@ export async function setWorkspaceCodexSubscriptionModeInTransaction(
|
|
|
21304
21906
|
workspaceId: string;
|
|
21305
21907
|
subjectId: string | null;
|
|
21306
21908
|
mode: WorkspaceCodexSubscriptionMode;
|
|
21909
|
+
/** Effective source captured under the source lock before caller-owned credential writes. */
|
|
21910
|
+
effectiveSourceBeforeMutation?: EffectiveCodexSubscriptionSource;
|
|
21307
21911
|
},
|
|
21308
21912
|
): Promise<WorkspaceCodexSubscriptionSource> {
|
|
21309
21913
|
await lockWorkspaceCodexSubscriptionSource(scopedDb, input.workspaceId);
|
|
@@ -21314,48 +21918,55 @@ export async function setWorkspaceCodexSubscriptionModeInTransaction(
|
|
|
21314
21918
|
if (current.workspaceKind === "personal" && input.mode !== "automatic") {
|
|
21315
21919
|
throw new Error("personal workspaces always use workspace Codex subscriptions");
|
|
21316
21920
|
}
|
|
21317
|
-
|
|
21318
|
-
|
|
21319
|
-
|
|
21320
|
-
|
|
21321
|
-
|
|
21322
|
-
|
|
21323
|
-
|
|
21324
|
-
|
|
21325
|
-
|
|
21326
|
-
),
|
|
21327
|
-
)
|
|
21328
|
-
.limit(1);
|
|
21329
|
-
const [liveLease] = await scopedDb
|
|
21330
|
-
.select({ id: schema.codexCredentialLeases.id })
|
|
21331
|
-
.from(schema.codexCredentialLeases)
|
|
21332
|
-
.where(
|
|
21333
|
-
and(
|
|
21334
|
-
eq(schema.codexCredentialLeases.workspaceId, input.workspaceId),
|
|
21335
|
-
gt(schema.codexCredentialLeases.leasedUntil, new Date()),
|
|
21336
|
-
),
|
|
21337
|
-
)
|
|
21338
|
-
.limit(1);
|
|
21339
|
-
if (activeCodexTurn || liveLease) {
|
|
21340
|
-
throw new Error("Codex subscription source cannot change while active turns are using it");
|
|
21341
|
-
}
|
|
21342
|
-
await scopedDb
|
|
21343
|
-
.insert(schema.workspaceCodexSubscriptionPreferences)
|
|
21344
|
-
.values({
|
|
21345
|
-
accountId: input.accountId,
|
|
21346
|
-
workspaceId: input.workspaceId,
|
|
21347
|
-
mode: input.mode,
|
|
21348
|
-
updatedBySubjectId: input.subjectId,
|
|
21349
|
-
})
|
|
21350
|
-
.onConflictDoUpdate({
|
|
21351
|
-
target: schema.workspaceCodexSubscriptionPreferences.workspaceId,
|
|
21352
|
-
set: {
|
|
21921
|
+
const effectiveSourceBeforeMutation =
|
|
21922
|
+
input.effectiveSourceBeforeMutation ?? current.effectiveSource;
|
|
21923
|
+
let next = current;
|
|
21924
|
+
if (current.mode !== input.mode) {
|
|
21925
|
+
await scopedDb
|
|
21926
|
+
.insert(schema.workspaceCodexSubscriptionPreferences)
|
|
21927
|
+
.values({
|
|
21928
|
+
accountId: input.accountId,
|
|
21929
|
+
workspaceId: input.workspaceId,
|
|
21353
21930
|
mode: input.mode,
|
|
21354
21931
|
updatedBySubjectId: input.subjectId,
|
|
21355
|
-
|
|
21356
|
-
|
|
21357
|
-
|
|
21358
|
-
|
|
21932
|
+
})
|
|
21933
|
+
.onConflictDoUpdate({
|
|
21934
|
+
target: schema.workspaceCodexSubscriptionPreferences.workspaceId,
|
|
21935
|
+
set: {
|
|
21936
|
+
mode: input.mode,
|
|
21937
|
+
updatedBySubjectId: input.subjectId,
|
|
21938
|
+
updatedAt: new Date(),
|
|
21939
|
+
},
|
|
21940
|
+
});
|
|
21941
|
+
next = await getWorkspaceCodexSubscriptionSourceScoped(scopedDb, input.workspaceId);
|
|
21942
|
+
}
|
|
21943
|
+
if (next.effectiveSource !== effectiveSourceBeforeMutation) {
|
|
21944
|
+
const [activeCodexTurn] = await scopedDb
|
|
21945
|
+
.select({ id: schema.sessionTurns.id })
|
|
21946
|
+
.from(schema.sessionTurns)
|
|
21947
|
+
.where(
|
|
21948
|
+
and(
|
|
21949
|
+
eq(schema.sessionTurns.workspaceId, input.workspaceId),
|
|
21950
|
+
inArray(schema.sessionTurns.status, ["running", "requires_action", "recovering"]),
|
|
21951
|
+
sql`${schema.sessionTurns.model} like 'codex/%'`,
|
|
21952
|
+
),
|
|
21953
|
+
)
|
|
21954
|
+
.limit(1);
|
|
21955
|
+
const [liveLease] = await scopedDb
|
|
21956
|
+
.select({ id: schema.codexCredentialLeases.id })
|
|
21957
|
+
.from(schema.codexCredentialLeases)
|
|
21958
|
+
.where(
|
|
21959
|
+
and(
|
|
21960
|
+
eq(schema.codexCredentialLeases.workspaceId, input.workspaceId),
|
|
21961
|
+
gt(schema.codexCredentialLeases.leasedUntil, new Date()),
|
|
21962
|
+
),
|
|
21963
|
+
)
|
|
21964
|
+
.limit(1);
|
|
21965
|
+
if (activeCodexTurn || liveLease) {
|
|
21966
|
+
throw new Error("Codex subscription source cannot change while active turns are using it");
|
|
21967
|
+
}
|
|
21968
|
+
}
|
|
21969
|
+
return next;
|
|
21359
21970
|
}
|
|
21360
21971
|
|
|
21361
21972
|
export async function setWorkspaceCodexSubscriptionMode(
|
|
@@ -29375,6 +29986,8 @@ export type SessionCreateInput = {
|
|
|
29375
29986
|
policyRole?: string | null;
|
|
29376
29987
|
parentSessionId?: string | null;
|
|
29377
29988
|
createIdempotencyKey?: string | null;
|
|
29989
|
+
/** Exact explicit installed-Skill selection used for keyed-create replay. */
|
|
29990
|
+
selectedInstalledSkillIds?: string[];
|
|
29378
29991
|
sandboxGroupId?: string | null;
|
|
29379
29992
|
sandboxOs?: SandboxOs;
|
|
29380
29993
|
/** Exact accepted generated-session compaction policy; internal lifecycle callers only. */
|
|
@@ -29394,7 +30007,11 @@ export type SessionCreateInput = {
|
|
|
29394
30007
|
/** Trusted, transaction-local linkage run after the exact session row and
|
|
29395
30008
|
* MCP metadata exist but before either can commit. It must perform database
|
|
29396
30009
|
* work only; throwing rolls the complete create/replay transaction back. */
|
|
29397
|
-
beforeCreateCommit?: (
|
|
30010
|
+
beforeCreateCommit?: (
|
|
30011
|
+
tx: Database,
|
|
30012
|
+
sessionId: string,
|
|
30013
|
+
context?: { created: boolean },
|
|
30014
|
+
) => Promise<void>;
|
|
29398
30015
|
};
|
|
29399
30016
|
|
|
29400
30017
|
type SessionDepthDecision =
|
|
@@ -29658,6 +30275,68 @@ async function existingSessionForCreateKey(
|
|
|
29658
30275
|
return existing;
|
|
29659
30276
|
}
|
|
29660
30277
|
|
|
30278
|
+
type SessionCreateReplayIdentity = {
|
|
30279
|
+
requestedSessionId?: string;
|
|
30280
|
+
visibility?: "user_private" | "workspace_shared";
|
|
30281
|
+
variableSetIds: string[];
|
|
30282
|
+
selectedInstalledSkillIds: string[];
|
|
30283
|
+
initialPersonalResourceAttachmentIntent?: PersonalResourceAttachmentIntent | null;
|
|
30284
|
+
};
|
|
30285
|
+
|
|
30286
|
+
// Session metadata is immutable after creation and already participates in
|
|
30287
|
+
// every historical schema fixture. Keep this reserved request-identity fact
|
|
30288
|
+
// there instead of making current code require a post-fixture sessions column.
|
|
30289
|
+
// Create admission always replaces a caller-supplied value for this key.
|
|
30290
|
+
const SESSION_CREATE_SELECTED_INSTALLED_SKILL_IDS_METADATA_KEY =
|
|
30291
|
+
"_opengeni_session_create_selected_installed_skill_ids_v1";
|
|
30292
|
+
|
|
30293
|
+
function metadataWithSelectedInstalledSkillCreateIdentity(
|
|
30294
|
+
metadata: Record<string, unknown>,
|
|
30295
|
+
selectedInstalledSkillIds: string[],
|
|
30296
|
+
): Record<string, unknown> {
|
|
30297
|
+
const next = { ...metadata };
|
|
30298
|
+
delete next[SESSION_CREATE_SELECTED_INSTALLED_SKILL_IDS_METADATA_KEY];
|
|
30299
|
+
if (selectedInstalledSkillIds.length > 0) {
|
|
30300
|
+
next[SESSION_CREATE_SELECTED_INSTALLED_SKILL_IDS_METADATA_KEY] = [...selectedInstalledSkillIds];
|
|
30301
|
+
}
|
|
30302
|
+
return next;
|
|
30303
|
+
}
|
|
30304
|
+
|
|
30305
|
+
function selectedInstalledSkillCreateIdentity(metadata: unknown): string[] {
|
|
30306
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return [];
|
|
30307
|
+
const value = (metadata as Record<string, unknown>)[
|
|
30308
|
+
SESSION_CREATE_SELECTED_INSTALLED_SKILL_IDS_METADATA_KEY
|
|
30309
|
+
];
|
|
30310
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : [];
|
|
30311
|
+
}
|
|
30312
|
+
|
|
30313
|
+
function assertSessionCreateReplayIdentity(
|
|
30314
|
+
existing: typeof schema.sessions.$inferSelect,
|
|
30315
|
+
input: SessionCreateReplayIdentity,
|
|
30316
|
+
): void {
|
|
30317
|
+
if (input.requestedSessionId && existing.id !== input.requestedSessionId) {
|
|
30318
|
+
throw new SessionIdConflictError(input.requestedSessionId);
|
|
30319
|
+
}
|
|
30320
|
+
if (
|
|
30321
|
+
stableJson(existing.initialPersonalResourceAttachmentIntent ?? null) !==
|
|
30322
|
+
stableJson(input.initialPersonalResourceAttachmentIntent ?? null)
|
|
30323
|
+
) {
|
|
30324
|
+
throw new SessionCreateIdempotencyConflictError();
|
|
30325
|
+
}
|
|
30326
|
+
if (stableJson(existing.variableSetIds ?? []) !== stableJson(input.variableSetIds)) {
|
|
30327
|
+
throw new SessionCreateIdempotencyConflictError();
|
|
30328
|
+
}
|
|
30329
|
+
if (
|
|
30330
|
+
stableJson(selectedInstalledSkillCreateIdentity(existing.metadata)) !==
|
|
30331
|
+
stableJson(input.selectedInstalledSkillIds)
|
|
30332
|
+
) {
|
|
30333
|
+
throw new SessionCreateIdempotencyConflictError();
|
|
30334
|
+
}
|
|
30335
|
+
if (existing.createRequestedVisibility !== (input.visibility ?? "workspace_shared")) {
|
|
30336
|
+
throw new SessionCreateIdempotencyConflictError();
|
|
30337
|
+
}
|
|
30338
|
+
}
|
|
30339
|
+
|
|
29661
30340
|
async function lockSessionCreateIdempotencyKey(
|
|
29662
30341
|
tx: Database,
|
|
29663
30342
|
workspaceId: string,
|
|
@@ -29747,6 +30426,11 @@ async function createSessionInTransaction(
|
|
|
29747
30426
|
): Promise<SessionCreateResult> {
|
|
29748
30427
|
const variableSetIds = input.variableSetIds ?? (input.variableSetId ? [input.variableSetId] : []);
|
|
29749
30428
|
const variableSetId = variableSetIds.at(-1) ?? null;
|
|
30429
|
+
const selectedInstalledSkillIds = input.selectedInstalledSkillIds ?? [];
|
|
30430
|
+
const sessionMetadata = metadataWithSelectedInstalledSkillCreateIdentity(
|
|
30431
|
+
input.metadata,
|
|
30432
|
+
selectedInstalledSkillIds,
|
|
30433
|
+
);
|
|
29750
30434
|
const createIdempotencyKey = input.createIdempotencyKey ?? null;
|
|
29751
30435
|
const createRequestedVisibility = input.visibility ?? "workspace_shared";
|
|
29752
30436
|
if (createRequestedVisibility === "user_private") {
|
|
@@ -29793,21 +30477,14 @@ async function createSessionInTransaction(
|
|
|
29793
30477
|
// caller-provided identity agrees with the winning request. Check this
|
|
29794
30478
|
// before any first-turn repair so a retry cannot hide a requested-ID
|
|
29795
30479
|
// conflict behind an apparently successful replay.
|
|
29796
|
-
|
|
29797
|
-
|
|
29798
|
-
|
|
29799
|
-
|
|
29800
|
-
|
|
29801
|
-
|
|
29802
|
-
|
|
29803
|
-
|
|
29804
|
-
}
|
|
29805
|
-
if (stableJson(existing.variableSetIds ?? []) !== stableJson(variableSetIds)) {
|
|
29806
|
-
throw new SessionCreateIdempotencyConflictError();
|
|
29807
|
-
}
|
|
29808
|
-
if (existing.createRequestedVisibility !== createRequestedVisibility) {
|
|
29809
|
-
throw new SessionCreateIdempotencyConflictError();
|
|
29810
|
-
}
|
|
30480
|
+
assertSessionCreateReplayIdentity(existing, {
|
|
30481
|
+
...(input.requestedSessionId ? { requestedSessionId: input.requestedSessionId } : {}),
|
|
30482
|
+
visibility: createRequestedVisibility,
|
|
30483
|
+
variableSetIds,
|
|
30484
|
+
selectedInstalledSkillIds: input.selectedInstalledSkillIds ?? [],
|
|
30485
|
+
initialPersonalResourceAttachmentIntent:
|
|
30486
|
+
input.initialPersonalResourceAttachmentIntent ?? null,
|
|
30487
|
+
});
|
|
29811
30488
|
const grouped = await sessionMcpServerMetadataForSessions(tx, input.workspaceId, [
|
|
29812
30489
|
existing.id,
|
|
29813
30490
|
]);
|
|
@@ -29819,7 +30496,7 @@ async function createSessionInTransaction(
|
|
|
29819
30496
|
if (!canonicalExisting) {
|
|
29820
30497
|
throw new Error(`Session event cursor missing for session ${existing.id}`);
|
|
29821
30498
|
}
|
|
29822
|
-
await input.beforeCreateCommit?.(tx, existing.id);
|
|
30499
|
+
await input.beforeCreateCommit?.(tx, existing.id, { created: false });
|
|
29823
30500
|
return {
|
|
29824
30501
|
session: await mapSessionWithControl(tx, canonicalExisting, grouped.get(existing.id) ?? []),
|
|
29825
30502
|
created: false,
|
|
@@ -29932,7 +30609,7 @@ async function createSessionInTransaction(
|
|
|
29932
30609
|
mode: "explicit",
|
|
29933
30610
|
inheritedFromSessionId: input.parentSessionId ?? null,
|
|
29934
30611
|
},
|
|
29935
|
-
metadata:
|
|
30612
|
+
metadata: sessionMetadata,
|
|
29936
30613
|
...creatorColumns(frozenCreator),
|
|
29937
30614
|
...(privateCreateOwnerMembershipId
|
|
29938
30615
|
? {
|
|
@@ -30006,21 +30683,14 @@ async function createSessionInTransaction(
|
|
|
30006
30683
|
createIdempotencyKey,
|
|
30007
30684
|
);
|
|
30008
30685
|
if (existing) {
|
|
30009
|
-
|
|
30010
|
-
|
|
30011
|
-
|
|
30012
|
-
|
|
30013
|
-
|
|
30014
|
-
|
|
30015
|
-
|
|
30016
|
-
|
|
30017
|
-
}
|
|
30018
|
-
if (stableJson(existing.variableSetIds ?? []) !== stableJson(variableSetIds)) {
|
|
30019
|
-
throw new SessionCreateIdempotencyConflictError();
|
|
30020
|
-
}
|
|
30021
|
-
if (existing.createRequestedVisibility !== createRequestedVisibility) {
|
|
30022
|
-
throw new SessionCreateIdempotencyConflictError();
|
|
30023
|
-
}
|
|
30686
|
+
assertSessionCreateReplayIdentity(existing, {
|
|
30687
|
+
...(input.requestedSessionId ? { requestedSessionId: input.requestedSessionId } : {}),
|
|
30688
|
+
visibility: createRequestedVisibility,
|
|
30689
|
+
variableSetIds,
|
|
30690
|
+
selectedInstalledSkillIds: input.selectedInstalledSkillIds ?? [],
|
|
30691
|
+
initialPersonalResourceAttachmentIntent:
|
|
30692
|
+
input.initialPersonalResourceAttachmentIntent ?? null,
|
|
30693
|
+
});
|
|
30024
30694
|
const grouped = await sessionMcpServerMetadataForSessions(tx, input.workspaceId, [
|
|
30025
30695
|
existing.id,
|
|
30026
30696
|
]);
|
|
@@ -30032,7 +30702,7 @@ async function createSessionInTransaction(
|
|
|
30032
30702
|
if (!canonicalExisting) {
|
|
30033
30703
|
throw new Error(`Session event cursor missing for session ${existing.id}`);
|
|
30034
30704
|
}
|
|
30035
|
-
await input.beforeCreateCommit?.(tx, existing.id);
|
|
30705
|
+
await input.beforeCreateCommit?.(tx, existing.id, { created: false });
|
|
30036
30706
|
return {
|
|
30037
30707
|
session: await mapSessionWithControl(
|
|
30038
30708
|
tx,
|
|
@@ -30069,7 +30739,7 @@ async function createSessionInTransaction(
|
|
|
30069
30739
|
sessionId: inserted.id,
|
|
30070
30740
|
servers: input.mcpServers ?? [],
|
|
30071
30741
|
});
|
|
30072
|
-
await input.beforeCreateCommit?.(tx, inserted.id);
|
|
30742
|
+
await input.beforeCreateCommit?.(tx, inserted.id, { created: true });
|
|
30073
30743
|
return {
|
|
30074
30744
|
session: await mapSessionWithControl(tx, inserted, mcpServers),
|
|
30075
30745
|
created: true,
|
|
@@ -30174,6 +30844,168 @@ export async function getSessionByCreateIdempotencyKey(
|
|
|
30174
30844
|
});
|
|
30175
30845
|
}
|
|
30176
30846
|
|
|
30847
|
+
/**
|
|
30848
|
+
* Recover a keyed create before mutable create dependencies (notably the
|
|
30849
|
+
* current model catalog) are consulted. A fully initialized queued turn gets a
|
|
30850
|
+
* fresh durable wake revision from persisted state only; an uninitialized shell
|
|
30851
|
+
* returns its persisted model so the caller can reopen it strictly as retained
|
|
30852
|
+
* while the canonical keyed transaction repairs initialization. The same
|
|
30853
|
+
* request-identity checks as transactional create replay run before any session
|
|
30854
|
+
* or committed denial is returned.
|
|
30855
|
+
*/
|
|
30856
|
+
export type InitializedSessionCreateReplay =
|
|
30857
|
+
| {
|
|
30858
|
+
outcome: "session";
|
|
30859
|
+
session: Session & { initialTurnId: string | null };
|
|
30860
|
+
temporalWorkflowId: string;
|
|
30861
|
+
workflowWakeRevision: number | null;
|
|
30862
|
+
changed: boolean;
|
|
30863
|
+
}
|
|
30864
|
+
| {
|
|
30865
|
+
outcome: "pending";
|
|
30866
|
+
session: Session;
|
|
30867
|
+
}
|
|
30868
|
+
| { outcome: "denied"; denial: SessionSpawnDenial };
|
|
30869
|
+
|
|
30870
|
+
export async function getInitializedSessionCreateReplay(
|
|
30871
|
+
db: Database,
|
|
30872
|
+
input: {
|
|
30873
|
+
accountId: string;
|
|
30874
|
+
workspaceId: string;
|
|
30875
|
+
subjectId: string;
|
|
30876
|
+
activeManagedHumanSubjectId?: string;
|
|
30877
|
+
createIdempotencyKey: string;
|
|
30878
|
+
requestedSessionId?: string;
|
|
30879
|
+
visibility?: "user_private" | "workspace_shared";
|
|
30880
|
+
variableSetIds: string[];
|
|
30881
|
+
selectedInstalledSkillIds: string[];
|
|
30882
|
+
initialPersonalResourceAttachmentIntent?: PersonalResourceAttachmentIntent | null;
|
|
30883
|
+
deferInitialTurn?: boolean;
|
|
30884
|
+
},
|
|
30885
|
+
): Promise<InitializedSessionCreateReplay | null> {
|
|
30886
|
+
return await withWorkspaceSubjectSessionActivityRls(
|
|
30887
|
+
db,
|
|
30888
|
+
input.workspaceId,
|
|
30889
|
+
input.subjectId,
|
|
30890
|
+
async (scopedDb) => {
|
|
30891
|
+
if (input.visibility === "user_private") {
|
|
30892
|
+
await scopedDb.execute(
|
|
30893
|
+
sql`select pg_advisory_xact_lock(hashtextextended(${`organization-membership:${input.accountId}`}, 0))`,
|
|
30894
|
+
);
|
|
30895
|
+
}
|
|
30896
|
+
await lockWorkspaceForSessionCreate(scopedDb, input.workspaceId, input.accountId);
|
|
30897
|
+
if (input.activeManagedHumanSubjectId) {
|
|
30898
|
+
await setSubjectRlsContext(scopedDb, input.activeManagedHumanSubjectId);
|
|
30899
|
+
await assertActiveManagedHumanOrganizationMembership(scopedDb, {
|
|
30900
|
+
accountId: input.accountId,
|
|
30901
|
+
subjectId: input.activeManagedHumanSubjectId,
|
|
30902
|
+
});
|
|
30903
|
+
}
|
|
30904
|
+
await lockSessionCreateIdempotencyKey(
|
|
30905
|
+
scopedDb,
|
|
30906
|
+
input.workspaceId,
|
|
30907
|
+
input.createIdempotencyKey,
|
|
30908
|
+
);
|
|
30909
|
+
|
|
30910
|
+
const existing = await existingSessionForCreateKey(
|
|
30911
|
+
scopedDb,
|
|
30912
|
+
input.workspaceId,
|
|
30913
|
+
input.createIdempotencyKey,
|
|
30914
|
+
);
|
|
30915
|
+
if (!existing) {
|
|
30916
|
+
const denial = await existingSpawnDenialForKey(
|
|
30917
|
+
scopedDb,
|
|
30918
|
+
input.workspaceId,
|
|
30919
|
+
input.createIdempotencyKey,
|
|
30920
|
+
);
|
|
30921
|
+
return denial ? { outcome: "denied", denial } : null;
|
|
30922
|
+
}
|
|
30923
|
+
assertSessionCreateReplayIdentity(existing, input);
|
|
30924
|
+
|
|
30925
|
+
const startEvents = await scopedDb
|
|
30926
|
+
.select({ id: schema.sessionEvents.id, type: schema.sessionEvents.type })
|
|
30927
|
+
.from(schema.sessionEvents)
|
|
30928
|
+
.where(
|
|
30929
|
+
and(
|
|
30930
|
+
eq(schema.sessionEvents.workspaceId, input.workspaceId),
|
|
30931
|
+
eq(schema.sessionEvents.sessionId, existing.id),
|
|
30932
|
+
inArray(schema.sessionEvents.type, ["session.created", "user.message"]),
|
|
30933
|
+
),
|
|
30934
|
+
)
|
|
30935
|
+
.orderBy(asc(schema.sessionEvents.sequence))
|
|
30936
|
+
.limit(2);
|
|
30937
|
+
const createdEvent = startEvents.find((event) => event.type === "session.created");
|
|
30938
|
+
const initialUserEvent = startEvents.find((event) => event.type === "user.message");
|
|
30939
|
+
|
|
30940
|
+
const grouped = await sessionMcpServerMetadataForSessions(scopedDb, input.workspaceId, [
|
|
30941
|
+
existing.id,
|
|
30942
|
+
]);
|
|
30943
|
+
const session = await mapSessionWithControl(
|
|
30944
|
+
scopedDb,
|
|
30945
|
+
existing,
|
|
30946
|
+
grouped.get(existing.id) ?? [],
|
|
30947
|
+
);
|
|
30948
|
+
if (!createdEvent) {
|
|
30949
|
+
return { outcome: "pending", session };
|
|
30950
|
+
}
|
|
30951
|
+
const [initialTurn] = await scopedDb
|
|
30952
|
+
.select({
|
|
30953
|
+
id: schema.sessionTurns.id,
|
|
30954
|
+
status: schema.sessionTurns.status,
|
|
30955
|
+
})
|
|
30956
|
+
.from(schema.sessionTurns)
|
|
30957
|
+
.where(
|
|
30958
|
+
and(
|
|
30959
|
+
eq(schema.sessionTurns.workspaceId, input.workspaceId),
|
|
30960
|
+
eq(schema.sessionTurns.sessionId, existing.id),
|
|
30961
|
+
initialUserEvent
|
|
30962
|
+
? eq(schema.sessionTurns.triggerEventId, initialUserEvent.id)
|
|
30963
|
+
: sql`false`,
|
|
30964
|
+
),
|
|
30965
|
+
)
|
|
30966
|
+
.limit(1);
|
|
30967
|
+
|
|
30968
|
+
const temporalWorkflowId = existing.temporalWorkflowId ?? `session-${existing.id}`;
|
|
30969
|
+
if (existing.status === "cancelled") {
|
|
30970
|
+
return {
|
|
30971
|
+
outcome: "session",
|
|
30972
|
+
session: { ...session, initialTurnId: initialTurn?.id ?? null },
|
|
30973
|
+
temporalWorkflowId,
|
|
30974
|
+
workflowWakeRevision: null,
|
|
30975
|
+
changed: false,
|
|
30976
|
+
};
|
|
30977
|
+
}
|
|
30978
|
+
if (input.deferInitialTurn === true) {
|
|
30979
|
+
return {
|
|
30980
|
+
outcome: "session",
|
|
30981
|
+
session: { ...session, initialTurnId: initialTurn?.id ?? null },
|
|
30982
|
+
temporalWorkflowId,
|
|
30983
|
+
workflowWakeRevision: null,
|
|
30984
|
+
changed: false,
|
|
30985
|
+
};
|
|
30986
|
+
}
|
|
30987
|
+
if (!initialTurn) return null;
|
|
30988
|
+
const workflowWakeRevision =
|
|
30989
|
+
initialTurn.status === "queued" && session.effectiveControl.state === "active"
|
|
30990
|
+
? await registerSessionWorkflowWakeInTransaction(scopedDb, {
|
|
30991
|
+
accountId: input.accountId,
|
|
30992
|
+
workspaceId: input.workspaceId,
|
|
30993
|
+
sessionId: existing.id,
|
|
30994
|
+
temporalWorkflowId,
|
|
30995
|
+
reason: "initial_session",
|
|
30996
|
+
})
|
|
30997
|
+
: null;
|
|
30998
|
+
return {
|
|
30999
|
+
outcome: "session",
|
|
31000
|
+
session: { ...session, initialTurnId: initialTurn.id },
|
|
31001
|
+
temporalWorkflowId,
|
|
31002
|
+
workflowWakeRevision,
|
|
31003
|
+
changed: workflowWakeRevision !== null,
|
|
31004
|
+
};
|
|
31005
|
+
},
|
|
31006
|
+
);
|
|
31007
|
+
}
|
|
31008
|
+
|
|
30177
31009
|
export async function getSession(
|
|
30178
31010
|
db: Database,
|
|
30179
31011
|
workspaceId: string,
|
|
@@ -33691,7 +34523,10 @@ async function sessionDiscoveryClaimsForSessions(
|
|
|
33691
34523
|
);
|
|
33692
34524
|
const grouped = new Map<string, { claims: WorkClaimDiscoverySummary[]; truncated: boolean }>();
|
|
33693
34525
|
for (const row of rows) {
|
|
33694
|
-
const entry = grouped.get(row.sessionId) ?? {
|
|
34526
|
+
const entry = grouped.get(row.sessionId) ?? {
|
|
34527
|
+
claims: [],
|
|
34528
|
+
truncated: false,
|
|
34529
|
+
};
|
|
33695
34530
|
entry.claims.push(
|
|
33696
34531
|
WorkClaimDiscoverySummarySchema.parse({
|
|
33697
34532
|
id: row.id,
|
|
@@ -34013,7 +34848,10 @@ export async function listSessionDiscoverySummaries(
|
|
|
34013
34848
|
const relatedAccess = rootRelatedIds.has(sessionId) ? "root" : "target";
|
|
34014
34849
|
const goal = goalsBySession.get(sessionId);
|
|
34015
34850
|
const latest = latestBySession.get(sessionId);
|
|
34016
|
-
const claims = workClaims.get(sessionId) ?? {
|
|
34851
|
+
const claims = workClaims.get(sessionId) ?? {
|
|
34852
|
+
claims: [],
|
|
34853
|
+
truncated: false,
|
|
34854
|
+
};
|
|
34017
34855
|
const match =
|
|
34018
34856
|
!includeWorkDiscovery ||
|
|
34019
34857
|
row.matchClass === null ||
|
|
@@ -34525,6 +35363,8 @@ export type SessionEventPage = {
|
|
|
34525
35363
|
events: SessionEvent[];
|
|
34526
35364
|
hasMore: boolean;
|
|
34527
35365
|
bytes: number;
|
|
35366
|
+
/** True only when every full-mode event is the exact retained database row. */
|
|
35367
|
+
fullPayloadsExact: boolean;
|
|
34528
35368
|
direction: SessionEventReadDirection;
|
|
34529
35369
|
coveredSequence: { first: number; last: number } | null;
|
|
34530
35370
|
nextAfter: number | null;
|
|
@@ -34554,12 +35394,21 @@ type SessionEventProjectionRow = {
|
|
|
34554
35394
|
duplicateReason: string | null;
|
|
34555
35395
|
};
|
|
34556
35396
|
|
|
35397
|
+
type SessionEventProjectionMetadataRow = {
|
|
35398
|
+
id: string;
|
|
35399
|
+
sequence: number;
|
|
35400
|
+
/** Conservative server-side size including columns omitted from the public event. */
|
|
35401
|
+
transferBytes: number;
|
|
35402
|
+
};
|
|
35403
|
+
|
|
34557
35404
|
/**
|
|
34558
35405
|
* Read one direction-aware session-event page. Full mode selects the canonical
|
|
34559
|
-
* row exactly
|
|
34560
|
-
*
|
|
34561
|
-
*
|
|
34562
|
-
*
|
|
35406
|
+
* row exactly when it fits the page; an oversized historical row that predates
|
|
35407
|
+
* the durable write guard is projected at the bounded database-read boundary
|
|
35408
|
+
* when it would otherwise strand its cursor forever. Summary/none modes derive
|
|
35409
|
+
* bounded monitoring projections in SQL without rewriting the retained source.
|
|
35410
|
+
* `bytes` is the exact UTF-8 size of `JSON.stringify(events)`; `hasMore` is true
|
|
35411
|
+
* whenever count or byte selection stopped before the durable range ended.
|
|
34563
35412
|
*/
|
|
34564
35413
|
export async function listSessionEventPage(
|
|
34565
35414
|
db: Database,
|
|
@@ -34594,6 +35443,7 @@ export async function listSessionEventPage(
|
|
|
34594
35443
|
let bytes = 2; // []
|
|
34595
35444
|
let cursor = direction === "before" ? before : after;
|
|
34596
35445
|
let hasMore = false;
|
|
35446
|
+
let fullPayloadsExact = payloadMode === "full";
|
|
34597
35447
|
let truncatedBy: SessionEventPage["truncatedBy"] = null;
|
|
34598
35448
|
|
|
34599
35449
|
for (;;) {
|
|
@@ -34638,19 +35488,89 @@ export async function listSessionEventPage(
|
|
|
34638
35488
|
filters.push(lt(schema.sessionEvents.sequence, before));
|
|
34639
35489
|
}
|
|
34640
35490
|
|
|
34641
|
-
const
|
|
34642
|
-
.
|
|
34643
|
-
|
|
34644
|
-
|
|
34645
|
-
|
|
34646
|
-
|
|
34647
|
-
|
|
34648
|
-
|
|
34649
|
-
|
|
34650
|
-
|
|
34651
|
-
|
|
34652
|
-
|
|
34653
|
-
|
|
35491
|
+
const ordering =
|
|
35492
|
+
options.authoritativeLatest || direction === "before"
|
|
35493
|
+
? desc(schema.sessionEvents.sequence)
|
|
35494
|
+
: asc(schema.sessionEvents.sequence);
|
|
35495
|
+
let rows: SessionEventProjectionRow[];
|
|
35496
|
+
let sourceRowCount: number;
|
|
35497
|
+
let sourceRowsFullyConsumed = true;
|
|
35498
|
+
let databaseReadProjected = false;
|
|
35499
|
+
if (payloadMode === "full") {
|
|
35500
|
+
// Plan the page from bounded metadata before selecting any canonical
|
|
35501
|
+
// payload. row_to_json includes private storage columns omitted by the
|
|
35502
|
+
// public event, so this conservatively bounds payload transfer without
|
|
35503
|
+
// moving an oversized legacy value through postgres.js.
|
|
35504
|
+
const metadataRows = await scopedDb
|
|
35505
|
+
.select(sessionEventProjectionMetadataSelect())
|
|
35506
|
+
.from(schema.sessionEvents)
|
|
35507
|
+
.where(and(...filters))
|
|
35508
|
+
.orderBy(ordering)
|
|
35509
|
+
.limit(queryLimit);
|
|
35510
|
+
sourceRowCount = metadataRows.length;
|
|
35511
|
+
if (metadataRows.length === 0) break;
|
|
35512
|
+
|
|
35513
|
+
const exactIds: string[] = [];
|
|
35514
|
+
let projectId: string | null = null;
|
|
35515
|
+
let estimatedBytes = bytes;
|
|
35516
|
+
for (const metadata of metadataRows) {
|
|
35517
|
+
if (events.length + exactIds.length >= requestedLimit) {
|
|
35518
|
+
hasMore = true;
|
|
35519
|
+
truncatedBy = "count";
|
|
35520
|
+
break;
|
|
35521
|
+
}
|
|
35522
|
+
const separatorBytes = events.length + exactIds.length === 0 ? 0 : 1;
|
|
35523
|
+
const transferBytes = Number(metadata.transferBytes);
|
|
35524
|
+
if (estimatedBytes + separatorBytes + transferBytes > maxBytes) {
|
|
35525
|
+
if (events.length === 0 && exactIds.length === 0) {
|
|
35526
|
+
projectId = metadata.id;
|
|
35527
|
+
} else {
|
|
35528
|
+
hasMore = true;
|
|
35529
|
+
truncatedBy = "bytes";
|
|
35530
|
+
}
|
|
35531
|
+
break;
|
|
35532
|
+
}
|
|
35533
|
+
exactIds.push(metadata.id);
|
|
35534
|
+
estimatedBytes += separatorBytes + transferBytes;
|
|
35535
|
+
}
|
|
35536
|
+
sourceRowsFullyConsumed =
|
|
35537
|
+
exactIds.length + (projectId === null ? 0 : 1) >= metadataRows.length;
|
|
35538
|
+
|
|
35539
|
+
if (projectId !== null) {
|
|
35540
|
+
rows = await scopedDb
|
|
35541
|
+
.select(sessionEventProjectionSelect("full", { databaseReadProjection: true }))
|
|
35542
|
+
.from(schema.sessionEvents)
|
|
35543
|
+
.where(and(...filters, eq(schema.sessionEvents.id, projectId)))
|
|
35544
|
+
.orderBy(ordering)
|
|
35545
|
+
.limit(1);
|
|
35546
|
+
fullPayloadsExact = false;
|
|
35547
|
+
databaseReadProjected = true;
|
|
35548
|
+
} else if (exactIds.length > 0) {
|
|
35549
|
+
rows = await scopedDb
|
|
35550
|
+
.select(sessionEventProjectionSelect("full"))
|
|
35551
|
+
.from(schema.sessionEvents)
|
|
35552
|
+
.where(and(...filters, inArray(schema.sessionEvents.id, exactIds)))
|
|
35553
|
+
.orderBy(ordering)
|
|
35554
|
+
.limit(exactIds.length);
|
|
35555
|
+
} else {
|
|
35556
|
+
rows = [];
|
|
35557
|
+
}
|
|
35558
|
+
} else {
|
|
35559
|
+
rows = await scopedDb
|
|
35560
|
+
.select(sessionEventProjectionSelect(payloadMode))
|
|
35561
|
+
.from(schema.sessionEvents)
|
|
35562
|
+
.where(and(...filters))
|
|
35563
|
+
.orderBy(ordering)
|
|
35564
|
+
.limit(queryLimit);
|
|
35565
|
+
sourceRowCount = rows.length;
|
|
35566
|
+
if (rows.length === 0) break;
|
|
35567
|
+
}
|
|
35568
|
+
|
|
35569
|
+
if (databaseReadProjected) {
|
|
35570
|
+
for (const row of rows) {
|
|
35571
|
+
settleDatabaseReadProjectionPayload(row.payload);
|
|
35572
|
+
}
|
|
35573
|
+
}
|
|
34654
35574
|
|
|
34655
35575
|
for (const row of rows) {
|
|
34656
35576
|
if (events.length >= requestedLimit) {
|
|
@@ -34658,25 +35578,33 @@ export async function listSessionEventPage(
|
|
|
34658
35578
|
truncatedBy = "count";
|
|
34659
35579
|
break;
|
|
34660
35580
|
}
|
|
34661
|
-
|
|
34662
|
-
|
|
35581
|
+
let event = mapProjectedEvent(row);
|
|
35582
|
+
let eventBytes = utf8JsonBytes(event);
|
|
34663
35583
|
const separatorBytes = events.length === 0 ? 0 : 1;
|
|
34664
35584
|
if (bytes + separatorBytes + eventBytes > maxBytes) {
|
|
34665
35585
|
if (events.length === 0) {
|
|
34666
|
-
|
|
34667
|
-
|
|
34668
|
-
|
|
35586
|
+
if (payloadMode === "full") {
|
|
35587
|
+
event = boundSessionEvent(event, { surface: "database_read_projection" });
|
|
35588
|
+
eventBytes = utf8JsonBytes(event);
|
|
35589
|
+
fullPayloadsExact = false;
|
|
35590
|
+
}
|
|
35591
|
+
if (eventBytes + 2 > maxBytes) {
|
|
35592
|
+
throw new RangeError(
|
|
35593
|
+
`A projected session event cannot fit in the database page envelope (${eventBytes + 2} > ${maxBytes} bytes)`,
|
|
35594
|
+
);
|
|
35595
|
+
}
|
|
35596
|
+
} else {
|
|
35597
|
+
hasMore = true;
|
|
35598
|
+
truncatedBy = "bytes";
|
|
35599
|
+
break;
|
|
34669
35600
|
}
|
|
34670
|
-
hasMore = true;
|
|
34671
|
-
truncatedBy = "bytes";
|
|
34672
|
-
break;
|
|
34673
35601
|
}
|
|
34674
35602
|
events.push(event);
|
|
34675
35603
|
bytes += separatorBytes + eventBytes;
|
|
34676
35604
|
cursor = event.sequence;
|
|
34677
35605
|
}
|
|
34678
35606
|
if (hasMore) break;
|
|
34679
|
-
if (
|
|
35607
|
+
if (sourceRowsFullyConsumed && sourceRowCount < queryLimit) break;
|
|
34680
35608
|
}
|
|
34681
35609
|
|
|
34682
35610
|
if (direction === "before") events.reverse();
|
|
@@ -34686,6 +35614,7 @@ export async function listSessionEventPage(
|
|
|
34686
35614
|
events,
|
|
34687
35615
|
hasMore,
|
|
34688
35616
|
bytes,
|
|
35617
|
+
fullPayloadsExact,
|
|
34689
35618
|
direction,
|
|
34690
35619
|
coveredSequence: first === null || last === null ? null : { first, last },
|
|
34691
35620
|
nextAfter: direction === "after" ? (last ?? after) : null,
|
|
@@ -34695,7 +35624,18 @@ export async function listSessionEventPage(
|
|
|
34695
35624
|
});
|
|
34696
35625
|
}
|
|
34697
35626
|
|
|
34698
|
-
function
|
|
35627
|
+
function sessionEventProjectionMetadataSelect() {
|
|
35628
|
+
return {
|
|
35629
|
+
id: schema.sessionEvents.id,
|
|
35630
|
+
sequence: schema.sessionEvents.sequence,
|
|
35631
|
+
transferBytes: sql<number>`octet_length(row_to_json(${schema.sessionEvents})::text)::int`,
|
|
35632
|
+
} satisfies Record<keyof SessionEventProjectionMetadataRow, unknown>;
|
|
35633
|
+
}
|
|
35634
|
+
|
|
35635
|
+
function sessionEventProjectionSelect(
|
|
35636
|
+
payloadMode: SessionEventPayloadMode = "full",
|
|
35637
|
+
options: { databaseReadProjection?: boolean } = {},
|
|
35638
|
+
) {
|
|
34699
35639
|
const typeInvalid = sql`(
|
|
34700
35640
|
octet_length(${schema.sessionEvents.type}) > ${SESSION_EVENT_TYPE_MAX_BYTES}
|
|
34701
35641
|
or position(E'\\n' in ${schema.sessionEvents.type}) > 0
|
|
@@ -34774,10 +35714,40 @@ function sessionEventProjectionSelect(payloadMode: SessionEventPayloadMode = "fu
|
|
|
34774
35714
|
)
|
|
34775
35715
|
else opengeni_private.project_session_event_payload(${schema.sessionEvents.payload})
|
|
34776
35716
|
end`;
|
|
35717
|
+
const databaseReadProjectedPayload = sql<unknown>`case
|
|
35718
|
+
when ${envelopeInvalid} then ${projectedPayload}
|
|
35719
|
+
else jsonb_build_object(
|
|
35720
|
+
'preview', jsonb_build_object(
|
|
35721
|
+
'head', left(${schema.sessionEvents.payload}::text, 2048),
|
|
35722
|
+
'omission', '[middle payload bytes omitted at bounded database read boundary]',
|
|
35723
|
+
'tail', right(${schema.sessionEvents.payload}::text, 2048)
|
|
35724
|
+
),
|
|
35725
|
+
'truncation', jsonb_build_object(
|
|
35726
|
+
'truncated', true,
|
|
35727
|
+
'surface', 'database_read_projection',
|
|
35728
|
+
'reason', 'payload_bytes_exceeded',
|
|
35729
|
+
'originalBytes', octet_length(${schema.sessionEvents.payload}::text),
|
|
35730
|
+
'deliveredBytes', 0,
|
|
35731
|
+
'omittedBytes', octet_length(${schema.sessionEvents.payload}::text),
|
|
35732
|
+
'estimatedOriginalTokens', ceil(
|
|
35733
|
+
octet_length(${schema.sessionEvents.payload}::text) / 4.0
|
|
35734
|
+
)::integer,
|
|
35735
|
+
'estimatedDeliveredTokens', 0,
|
|
35736
|
+
'fullEvidence', jsonb_build_object('available', false, 'reason', 'not_retained'),
|
|
35737
|
+
'details', jsonb_build_array(jsonb_build_object(
|
|
35738
|
+
'path', '$',
|
|
35739
|
+
'kind', 'object',
|
|
35740
|
+
'originalBytes', octet_length(${schema.sessionEvents.payload}::text)
|
|
35741
|
+
))
|
|
35742
|
+
)
|
|
35743
|
+
)
|
|
35744
|
+
end`;
|
|
34777
35745
|
const projectedPayloadBytes = sql<number>`octet_length((${projectedPayload})::text)`;
|
|
34778
35746
|
const selectedPayload =
|
|
34779
35747
|
payloadMode === "full"
|
|
34780
|
-
?
|
|
35748
|
+
? options.databaseReadProjection
|
|
35749
|
+
? databaseReadProjectedPayload
|
|
35750
|
+
: schema.sessionEvents.payload
|
|
34781
35751
|
: payloadMode === "none"
|
|
34782
35752
|
? sql<unknown>`jsonb_build_object(
|
|
34783
35753
|
'_monitoring', jsonb_build_object(
|
|
@@ -34804,24 +35774,59 @@ function sessionEventProjectionSelect(payloadMode: SessionEventPayloadMode = "fu
|
|
|
34804
35774
|
workspaceId: schema.sessionEvents.workspaceId,
|
|
34805
35775
|
sessionId: schema.sessionEvents.sessionId,
|
|
34806
35776
|
sequence: schema.sessionEvents.sequence,
|
|
34807
|
-
type:
|
|
35777
|
+
type:
|
|
35778
|
+
payloadMode === "full" && !options.databaseReadProjection
|
|
35779
|
+
? schema.sessionEvents.type
|
|
35780
|
+
: projectedType,
|
|
34808
35781
|
payload: selectedPayload,
|
|
34809
35782
|
payloadCodecVersion:
|
|
34810
|
-
payloadMode === "full"
|
|
35783
|
+
payloadMode === "full" && !options.databaseReadProjection
|
|
35784
|
+
? schema.sessionEvents.payloadCodecVersion
|
|
35785
|
+
: sql<number | null>`null`,
|
|
34811
35786
|
occurredAt: schema.sessionEvents.occurredAt,
|
|
34812
35787
|
clientEventId:
|
|
34813
|
-
payloadMode === "full"
|
|
35788
|
+
payloadMode === "full" && !options.databaseReadProjection
|
|
35789
|
+
? schema.sessionEvents.clientEventId
|
|
35790
|
+
: projectedClientEventId,
|
|
34814
35791
|
turnId: schema.sessionEvents.turnId,
|
|
34815
35792
|
turnGeneration: schema.sessionEvents.turnGeneration,
|
|
34816
35793
|
turnAttemptId: schema.sessionEvents.turnAttemptId,
|
|
34817
35794
|
turnAssociation:
|
|
34818
|
-
payloadMode === "full"
|
|
35795
|
+
payloadMode === "full" && !options.databaseReadProjection
|
|
35796
|
+
? schema.sessionEvents.turnAssociation
|
|
35797
|
+
: projectedTurnAssociation,
|
|
34819
35798
|
duplicateOfEventId: schema.sessionEvents.duplicateOfEventId,
|
|
34820
35799
|
duplicateReason:
|
|
34821
|
-
payloadMode === "full"
|
|
35800
|
+
payloadMode === "full" && !options.databaseReadProjection
|
|
35801
|
+
? schema.sessionEvents.duplicateReason
|
|
35802
|
+
: projectedDuplicateReason,
|
|
34822
35803
|
};
|
|
34823
35804
|
}
|
|
34824
35805
|
|
|
35806
|
+
function settleDatabaseReadProjectionPayload(payload: unknown): void {
|
|
35807
|
+
const truncation = sessionEventPayloadTruncation(payload);
|
|
35808
|
+
if (truncation?.surface !== "database_read_projection") return;
|
|
35809
|
+
for (let attempt = 0; attempt < 16; attempt += 1) {
|
|
35810
|
+
const deliveredBytes = sessionEventJsonBytes(payload);
|
|
35811
|
+
const omittedBytes =
|
|
35812
|
+
truncation.originalBytes === null
|
|
35813
|
+
? null
|
|
35814
|
+
: Math.max(0, truncation.originalBytes - deliveredBytes);
|
|
35815
|
+
const deliveredTokens = approximateSessionEventTokens(deliveredBytes);
|
|
35816
|
+
if (
|
|
35817
|
+
truncation.deliveredBytes === deliveredBytes &&
|
|
35818
|
+
truncation.omittedBytes === omittedBytes &&
|
|
35819
|
+
truncation.estimatedDeliveredTokens === deliveredTokens
|
|
35820
|
+
) {
|
|
35821
|
+
return;
|
|
35822
|
+
}
|
|
35823
|
+
truncation.deliveredBytes = deliveredBytes;
|
|
35824
|
+
truncation.omittedBytes = omittedBytes;
|
|
35825
|
+
truncation.estimatedDeliveredTokens = deliveredTokens;
|
|
35826
|
+
}
|
|
35827
|
+
throw new RangeError("Database-read session event payload byte accounting did not converge");
|
|
35828
|
+
}
|
|
35829
|
+
|
|
34825
35830
|
export async function listSessionEvents(
|
|
34826
35831
|
db: Database,
|
|
34827
35832
|
workspaceId: string,
|
|
@@ -35804,7 +36809,10 @@ export async function acceptSessionHumanInputResponse(
|
|
|
35804
36809
|
turnAssociation: "current",
|
|
35805
36810
|
sequence: session.lastSequence + 1,
|
|
35806
36811
|
type: "user.humanInputResponse",
|
|
35807
|
-
payload: {
|
|
36812
|
+
payload: {
|
|
36813
|
+
requestId: request.id,
|
|
36814
|
+
response: { outcome: "cancelled" },
|
|
36815
|
+
},
|
|
35808
36816
|
clientEventId: null,
|
|
35809
36817
|
occurredAt,
|
|
35810
36818
|
},
|
|
@@ -35840,7 +36848,10 @@ export async function acceptSessionHumanInputResponse(
|
|
|
35840
36848
|
});
|
|
35841
36849
|
await tx
|
|
35842
36850
|
.update(schema.sessions)
|
|
35843
|
-
.set({
|
|
36851
|
+
.set({
|
|
36852
|
+
lastSequence: session.lastSequence + 1,
|
|
36853
|
+
updatedAt: new Date(),
|
|
36854
|
+
})
|
|
35844
36855
|
.where(eq(schema.sessions.id, session.id));
|
|
35845
36856
|
event = mapEvent(inserted);
|
|
35846
36857
|
repairedEvents = [event];
|
|
@@ -38960,6 +39971,8 @@ type LeaseRow = {
|
|
|
38960
39971
|
archive_capture_generation: number | string | null;
|
|
38961
39972
|
archive_capture_started_at: Date | string | null;
|
|
38962
39973
|
archive_capture_deadline_at: Date | string | null;
|
|
39974
|
+
/** Transaction-local PostgreSQL-clock projection used only by bounded waiters. */
|
|
39975
|
+
archive_capture_remaining_ms?: number | string | null;
|
|
38963
39976
|
archive_capture_published_at: Date | string | null;
|
|
38964
39977
|
reaper_hold_id: string | null;
|
|
38965
39978
|
reaper_hold_until: Date | string | null;
|
|
@@ -39149,6 +40162,9 @@ export type AcquireLeaseResult =
|
|
|
39149
40162
|
| "rotation_in_progress"
|
|
39150
40163
|
| "provider_recovery_in_progress";
|
|
39151
40164
|
lease: LeaseSnapshot;
|
|
40165
|
+
/** Remaining durable capture budget from PostgreSQL's clock. Present only
|
|
40166
|
+
* for an exact active capture; callers cap it at the lifecycle ceiling. */
|
|
40167
|
+
captureRemainingMs?: number;
|
|
39152
40168
|
};
|
|
39153
40169
|
|
|
39154
40170
|
export type SandboxSharedPreparationSnapshot = {
|
|
@@ -39179,7 +40195,10 @@ export type ClaimSandboxSharedPreparationInput = {
|
|
|
39179
40195
|
};
|
|
39180
40196
|
|
|
39181
40197
|
export type ClaimSandboxSharedPreparationResult =
|
|
39182
|
-
| {
|
|
40198
|
+
| {
|
|
40199
|
+
role: "owner" | "joined" | "reused";
|
|
40200
|
+
preparation: SandboxSharedPreparationSnapshot;
|
|
40201
|
+
}
|
|
39183
40202
|
| { role: "fenced"; lease: LeaseSnapshot | null };
|
|
39184
40203
|
|
|
39185
40204
|
function validateSandboxSharedPreparationIdentity(input: ClaimSandboxSharedPreparationInput): void {
|
|
@@ -39304,7 +40323,10 @@ export async function claimSandboxSharedPreparation(
|
|
|
39304
40323
|
Number(row.lease_epoch) !== input.expectedLeaseEpoch ||
|
|
39305
40324
|
row.instance_id !== input.expectedInstanceId
|
|
39306
40325
|
) {
|
|
39307
|
-
return {
|
|
40326
|
+
return {
|
|
40327
|
+
role: "fenced" as const,
|
|
40328
|
+
lease: row ? mapLeaseRow(row) : null,
|
|
40329
|
+
};
|
|
39308
40330
|
}
|
|
39309
40331
|
const holderRows = await tx.execute<{ present: boolean }>(sql`
|
|
39310
40332
|
select exists (
|
|
@@ -39701,6 +40723,33 @@ function leaseBackendRequiresResumableInstance(row: LeaseRow): boolean {
|
|
|
39701
40723
|
return fields === undefined || fields.length > 0;
|
|
39702
40724
|
}
|
|
39703
40725
|
|
|
40726
|
+
function archiveCaptureRemainingMs(
|
|
40727
|
+
row: Pick<LeaseRow, "archive_capture_remaining_ms">,
|
|
40728
|
+
): number | null {
|
|
40729
|
+
if (row.archive_capture_remaining_ms === null || row.archive_capture_remaining_ms === undefined) {
|
|
40730
|
+
return null;
|
|
40731
|
+
}
|
|
40732
|
+
const remainingMs = Number(row.archive_capture_remaining_ms);
|
|
40733
|
+
return Number.isSafeInteger(remainingMs) &&
|
|
40734
|
+
remainingMs >= 0 &&
|
|
40735
|
+
remainingMs <= SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS
|
|
40736
|
+
? remainingMs
|
|
40737
|
+
: null;
|
|
40738
|
+
}
|
|
40739
|
+
|
|
40740
|
+
function extendSandboxTransitionDeadline(
|
|
40741
|
+
currentDeadline: number,
|
|
40742
|
+
hardDeadline: number,
|
|
40743
|
+
remainingCaptureMs: number | null | undefined,
|
|
40744
|
+
now: number,
|
|
40745
|
+
): number {
|
|
40746
|
+
if (remainingCaptureMs === null || remainingCaptureMs === undefined) return currentDeadline;
|
|
40747
|
+
return Math.min(
|
|
40748
|
+
hardDeadline,
|
|
40749
|
+
Math.max(currentDeadline, now + remainingCaptureMs + SANDBOX_LIFECYCLE_RETRY_HANDOFF_GRACE_MS),
|
|
40750
|
+
);
|
|
40751
|
+
}
|
|
40752
|
+
|
|
39704
40753
|
function providerlessLeaseBackendPredicateSql() {
|
|
39705
40754
|
const providerlessBackends = Object.entries(SANDBOX_PROVIDER_INSTANCE_ID_FIELDS_BY_BACKEND)
|
|
39706
40755
|
.filter(([, fields]) => fields.length === 0)
|
|
@@ -40259,9 +41308,19 @@ async function acquireLeaseOnce(
|
|
|
40259
41308
|
// UPDATE (block, do NOT skip) — unlike concurrent enrollment scans,
|
|
40260
41309
|
// because we WANT the loser to block then attach, not skip and lose.
|
|
40261
41310
|
const rows = await tx.execute<LeaseRow>(sql`
|
|
40262
|
-
select
|
|
40263
|
-
|
|
40264
|
-
|
|
41311
|
+
select lease.*,
|
|
41312
|
+
case
|
|
41313
|
+
when lease.archive_capture_deadline_at is null then null
|
|
41314
|
+
else greatest(
|
|
41315
|
+
0,
|
|
41316
|
+
ceil(extract(epoch from (
|
|
41317
|
+
lease.archive_capture_deadline_at - clock_timestamp()
|
|
41318
|
+
)) * 1000)
|
|
41319
|
+
)::bigint
|
|
41320
|
+
end as archive_capture_remaining_ms
|
|
41321
|
+
from sandbox_leases as lease
|
|
41322
|
+
where lease.workspace_id = ${workspaceId}
|
|
41323
|
+
and lease.sandbox_group_id = ${sandboxGroupId}
|
|
40265
41324
|
for update
|
|
40266
41325
|
`);
|
|
40267
41326
|
const row = rows[0];
|
|
@@ -40293,10 +41352,12 @@ async function acquireLeaseOnce(
|
|
|
40293
41352
|
// another holder while that operation owns the exact lease: the caller
|
|
40294
41353
|
// retries after the claim clears, before it can reach the provider.
|
|
40295
41354
|
if (row.archive_capture_id !== null) {
|
|
41355
|
+
const captureRemainingMs = archiveCaptureRemainingMs(row);
|
|
40296
41356
|
return {
|
|
40297
41357
|
role: "fenced" as const,
|
|
40298
41358
|
reason: "capture_in_progress" as const,
|
|
40299
41359
|
lease: mapLeaseRow(row),
|
|
41360
|
+
...(captureRemainingMs === null ? {} : { captureRemainingMs }),
|
|
40300
41361
|
};
|
|
40301
41362
|
}
|
|
40302
41363
|
|
|
@@ -40520,20 +41581,30 @@ export async function acquireLease(
|
|
|
40520
41581
|
}
|
|
40521
41582
|
// A bounded in-process wait must use a monotonic clock. Wall-clock steps from
|
|
40522
41583
|
// NTP/VM resume may change timestamp interpretation, but must never stretch
|
|
40523
|
-
// or prematurely consume the caller's resource budget.
|
|
40524
|
-
|
|
41584
|
+
// or prematurely consume the caller's resource budget. PostgreSQL projects
|
|
41585
|
+
// an active claim's remaining time on its own authoritative clock; that may
|
|
41586
|
+
// extend a positive caller budget after a rolling config change, but never
|
|
41587
|
+
// beyond the same one-hour lifecycle ceiling.
|
|
41588
|
+
const startedAt = performance.now();
|
|
41589
|
+
const hardDeadline = startedAt + SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS;
|
|
41590
|
+
let deadline = Math.min(hardDeadline, startedAt + captureWaitMs);
|
|
40525
41591
|
let delayMs = 25;
|
|
40526
41592
|
for (;;) {
|
|
40527
41593
|
const result = await acquireLeaseOnce(db, input);
|
|
40528
|
-
|
|
40529
|
-
|
|
40530
|
-
|
|
40531
|
-
|
|
40532
|
-
|
|
41594
|
+
const now = performance.now();
|
|
41595
|
+
if (captureWaitMs > 0 && result.role === "fenced" && result.reason === "capture_in_progress") {
|
|
41596
|
+
deadline = extendSandboxTransitionDeadline(
|
|
41597
|
+
deadline,
|
|
41598
|
+
hardDeadline,
|
|
41599
|
+
result.captureRemainingMs,
|
|
41600
|
+
now,
|
|
41601
|
+
);
|
|
41602
|
+
}
|
|
41603
|
+
if (result.role !== "fenced" || result.reason === "superseded" || now >= deadline) {
|
|
40533
41604
|
return result;
|
|
40534
41605
|
}
|
|
40535
41606
|
await waitForSandboxTransition(
|
|
40536
|
-
Math.min(delayMs, Math.max(1, deadline -
|
|
41607
|
+
Math.min(delayMs, Math.max(1, deadline - now)),
|
|
40537
41608
|
input.waitSignal,
|
|
40538
41609
|
"Sandbox lease transition wait cancelled",
|
|
40539
41610
|
);
|
|
@@ -44618,7 +45689,11 @@ export async function reapStaleLeaseHoldersGlobal(
|
|
|
44618
45689
|
// A separate lifecycle-aware function settles only stale starting/restoring/
|
|
44619
45690
|
// suspending/ending transitions. API controller mutations pulse their holder
|
|
44620
45691
|
// while in flight, so the transition TTL detects an abandoned caller rather
|
|
44621
|
-
// than imposing a maximum duration on profile capture or restore.
|
|
45692
|
+
// than imposing a maximum duration on profile capture or restore. The same
|
|
45693
|
+
// lifecycle function is also the hard provider-deadline override: once a
|
|
45694
|
+
// requested finite-lifetime Modal lease is due, its controller resources are
|
|
45695
|
+
// lost and their holders are released without turning timestamp age into a
|
|
45696
|
+
// general active-session expiry policy.
|
|
44622
45697
|
const rows = await db.transaction(async (txRaw) => {
|
|
44623
45698
|
const tx = txRaw as unknown as Database;
|
|
44624
45699
|
await tx.execute(sql`select set_config('opengeni.sandbox_recovery_protocol_v2', '1', true)`);
|
|
@@ -44723,7 +45798,10 @@ export async function countSessionRecoveryBacklog(
|
|
|
44723
45798
|
quiescence_missing: 0,
|
|
44724
45799
|
projection_stale: 0,
|
|
44725
45800
|
};
|
|
44726
|
-
const rows = await rawRows<{
|
|
45801
|
+
const rows = await rawRows<{
|
|
45802
|
+
state: SessionRecoveryBacklogState;
|
|
45803
|
+
count: number | string;
|
|
45804
|
+
}>(
|
|
44727
45805
|
db,
|
|
44728
45806
|
sql`
|
|
44729
45807
|
select state, count
|
|
@@ -44736,6 +45814,36 @@ export async function countSessionRecoveryBacklog(
|
|
|
44736
45814
|
return counts;
|
|
44737
45815
|
}
|
|
44738
45816
|
|
|
45817
|
+
export type ContextCompactionPendingSummary = {
|
|
45818
|
+
pendingCount: number;
|
|
45819
|
+
oldestStartedAt: Date | null;
|
|
45820
|
+
};
|
|
45821
|
+
|
|
45822
|
+
/**
|
|
45823
|
+
* Content-free cross-workspace projection of exact active attempts whose latest
|
|
45824
|
+
* automatic compaction landmark is still `started`. The trigger-maintained
|
|
45825
|
+
* private ledger survives turn-worker loss without exposing tenant identities.
|
|
45826
|
+
*/
|
|
45827
|
+
export async function getContextCompactionPendingSummary(
|
|
45828
|
+
db: Database,
|
|
45829
|
+
): Promise<ContextCompactionPendingSummary> {
|
|
45830
|
+
const rows = await rawRows<{
|
|
45831
|
+
pending_count: number | string;
|
|
45832
|
+
oldest_started_at: Date | string | null;
|
|
45833
|
+
}>(
|
|
45834
|
+
db,
|
|
45835
|
+
sql`
|
|
45836
|
+
select pending_count, oldest_started_at
|
|
45837
|
+
from opengeni_private.context_compaction_pending_summary()
|
|
45838
|
+
`,
|
|
45839
|
+
);
|
|
45840
|
+
const row = rows[0];
|
|
45841
|
+
return {
|
|
45842
|
+
pendingCount: Number(row?.pending_count ?? 0),
|
|
45843
|
+
oldestStartedAt: row?.oldest_started_at == null ? null : new Date(row.oldest_started_at),
|
|
45844
|
+
};
|
|
45845
|
+
}
|
|
45846
|
+
|
|
44739
45847
|
export async function countSandboxLeasesByLiveness(
|
|
44740
45848
|
db: Database,
|
|
44741
45849
|
): Promise<Record<SandboxLeaseLiveness, number>> {
|
|
@@ -45141,6 +46249,9 @@ export class SandboxWorkspaceMutationFencedError extends Error {
|
|
|
45141
46249
|
// The exact grant identity behind this writer is no longer active.
|
|
45142
46250
|
| "authority_revoked",
|
|
45143
46251
|
message: string,
|
|
46252
|
+
/** Remaining exact archive-capture budget projected by PostgreSQL. This is
|
|
46253
|
+
* observational wait input only; it never licenses capture takeover. */
|
|
46254
|
+
public readonly captureRemainingMs: number | null = null,
|
|
45144
46255
|
) {
|
|
45145
46256
|
super(message);
|
|
45146
46257
|
}
|
|
@@ -46156,6 +47267,7 @@ async function advanceWorkspaceGenerationForAuthorityOnce(
|
|
|
46156
47267
|
rotation_pending: boolean;
|
|
46157
47268
|
holder_current: boolean;
|
|
46158
47269
|
capture_in_progress: boolean;
|
|
47270
|
+
archive_capture_remaining_ms: number | string | null;
|
|
46159
47271
|
}>(sql`
|
|
46160
47272
|
select lease.workspace_generation,
|
|
46161
47273
|
(
|
|
@@ -46177,6 +47289,15 @@ async function advanceWorkspaceGenerationForAuthorityOnce(
|
|
|
46177
47289
|
and (${locked.expectedBackend}::text is null or lease.backend = ${locked.expectedBackend}::text)
|
|
46178
47290
|
) as rotation_pending,
|
|
46179
47291
|
(lease.archive_capture_id is not null) as capture_in_progress,
|
|
47292
|
+
case
|
|
47293
|
+
when lease.archive_capture_deadline_at is null then null
|
|
47294
|
+
else greatest(
|
|
47295
|
+
0,
|
|
47296
|
+
ceil(extract(epoch from (
|
|
47297
|
+
lease.archive_capture_deadline_at - clock_timestamp()
|
|
47298
|
+
)) * 1000)
|
|
47299
|
+
)::bigint
|
|
47300
|
+
end as archive_capture_remaining_ms,
|
|
46180
47301
|
exists (
|
|
46181
47302
|
select 1 from sandbox_lease_holders as holder
|
|
46182
47303
|
where holder.lease_id = lease.id
|
|
@@ -46205,9 +47326,13 @@ async function advanceWorkspaceGenerationForAuthorityOnce(
|
|
|
46205
47326
|
);
|
|
46206
47327
|
}
|
|
46207
47328
|
if (current[0]?.lease_current && current[0].capture_in_progress) {
|
|
47329
|
+
const captureRemainingMs = archiveCaptureRemainingMs({
|
|
47330
|
+
archive_capture_remaining_ms: current[0].archive_capture_remaining_ms,
|
|
47331
|
+
});
|
|
46208
47332
|
throw new SandboxWorkspaceMutationFencedError(
|
|
46209
47333
|
"capture_in_progress",
|
|
46210
47334
|
"Workspace mutation is waiting for the exact provider archive capture to settle",
|
|
47335
|
+
captureRemainingMs,
|
|
46211
47336
|
);
|
|
46212
47337
|
}
|
|
46213
47338
|
if (current[0]?.rotation_pending) {
|
|
@@ -46240,7 +47365,9 @@ async function advanceWorkspaceGenerationForAuthority(
|
|
|
46240
47365
|
if (!Number.isSafeInteger(captureWaitMs) || captureWaitMs < 0 || captureWaitMs > 60 * 60_000) {
|
|
46241
47366
|
throw new Error("Workspace archive capture wait is invalid");
|
|
46242
47367
|
}
|
|
46243
|
-
const
|
|
47368
|
+
const startedAt = performance.now();
|
|
47369
|
+
const hardDeadline = startedAt + SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS;
|
|
47370
|
+
let deadline = Math.min(hardDeadline, startedAt + captureWaitMs);
|
|
46244
47371
|
let delayMs = 25;
|
|
46245
47372
|
let captureWaitStartedAt: number | undefined;
|
|
46246
47373
|
let captureWaitOutcome: "completed" | "failed" = "failed";
|
|
@@ -46255,16 +47382,29 @@ async function advanceWorkspaceGenerationForAuthority(
|
|
|
46255
47382
|
captureWaitOutcome = "completed";
|
|
46256
47383
|
return admission;
|
|
46257
47384
|
} catch (error) {
|
|
47385
|
+
const now = performance.now();
|
|
47386
|
+
if (
|
|
47387
|
+
captureWaitMs > 0 &&
|
|
47388
|
+
error instanceof SandboxWorkspaceMutationFencedError &&
|
|
47389
|
+
error.code === "capture_in_progress"
|
|
47390
|
+
) {
|
|
47391
|
+
deadline = extendSandboxTransitionDeadline(
|
|
47392
|
+
deadline,
|
|
47393
|
+
hardDeadline,
|
|
47394
|
+
error.captureRemainingMs,
|
|
47395
|
+
now,
|
|
47396
|
+
);
|
|
47397
|
+
}
|
|
46258
47398
|
if (
|
|
46259
47399
|
!(error instanceof SandboxWorkspaceMutationFencedError) ||
|
|
46260
47400
|
error.code !== "capture_in_progress" ||
|
|
46261
|
-
|
|
47401
|
+
now >= deadline
|
|
46262
47402
|
) {
|
|
46263
47403
|
throw error;
|
|
46264
47404
|
}
|
|
46265
47405
|
captureWaitStartedAt ??= performance.now();
|
|
46266
47406
|
await waitForSandboxTransition(
|
|
46267
|
-
Math.min(delayMs, Math.max(1, deadline -
|
|
47407
|
+
Math.min(delayMs, Math.max(1, deadline - now)),
|
|
46268
47408
|
waitSignal,
|
|
46269
47409
|
"Sandbox workspace mutation wait cancelled",
|
|
46270
47410
|
);
|
|
@@ -49146,6 +50286,7 @@ export type SandboxRotationBacklog = {
|
|
|
49146
50286
|
turnBlocked: number;
|
|
49147
50287
|
directBlocked: number;
|
|
49148
50288
|
processBlocked: number;
|
|
50289
|
+
interactionBlocked: number;
|
|
49149
50290
|
};
|
|
49150
50291
|
|
|
49151
50292
|
export async function readSandboxRotationBacklog(db: Database): Promise<SandboxRotationBacklog> {
|
|
@@ -49155,7 +50296,13 @@ export async function readSandboxRotationBacklog(db: Database): Promise<SandboxR
|
|
|
49155
50296
|
turn_blocked: number | string;
|
|
49156
50297
|
direct_blocked: number | string;
|
|
49157
50298
|
process_blocked: number | string;
|
|
49158
|
-
|
|
50299
|
+
interaction_blocked: number | string;
|
|
50300
|
+
}>(
|
|
50301
|
+
db,
|
|
50302
|
+
sql`select backlog.*,
|
|
50303
|
+
opengeni_private.sandbox_rotation_interaction_blocked() as interaction_blocked
|
|
50304
|
+
from opengeni_private.sandbox_rotation_backlog() backlog`,
|
|
50305
|
+
);
|
|
49159
50306
|
const row = rows[0];
|
|
49160
50307
|
return {
|
|
49161
50308
|
requested: Number(row?.requested ?? 0),
|
|
@@ -49163,6 +50310,7 @@ export async function readSandboxRotationBacklog(db: Database): Promise<SandboxR
|
|
|
49163
50310
|
turnBlocked: Number(row?.turn_blocked ?? 0),
|
|
49164
50311
|
directBlocked: Number(row?.direct_blocked ?? 0),
|
|
49165
50312
|
processBlocked: Number(row?.process_blocked ?? 0),
|
|
50313
|
+
interactionBlocked: Number(row?.interaction_blocked ?? 0),
|
|
49166
50314
|
};
|
|
49167
50315
|
}
|
|
49168
50316
|
|
|
@@ -57695,6 +58843,7 @@ export async function materializeGoalContinuation(
|
|
|
57695
58843
|
model: string;
|
|
57696
58844
|
reasoningEffort: ReasoningEffort;
|
|
57697
58845
|
latencyMode: LatencyMode;
|
|
58846
|
+
turnExecutionPolicy?: TurnExecutionPolicyV1;
|
|
57698
58847
|
tools: ToolRef[];
|
|
57699
58848
|
sandboxBackend: SandboxBackend;
|
|
57700
58849
|
};
|
|
@@ -59959,6 +61108,7 @@ export async function claimSessionWorkForAttempt(
|
|
|
59959
61108
|
}
|
|
59960
61109
|
const validUpdates: typeof updates = [];
|
|
59961
61110
|
const cancelledUpdateIds: string[] = [];
|
|
61111
|
+
const scheduledTaskInactiveUpdateIds: string[] = [];
|
|
59962
61112
|
const authorityRejectedUpdateIds: string[] = [];
|
|
59963
61113
|
const unrecognizedUpdateIds: string[] = [];
|
|
59964
61114
|
// Retained so later consumers of the delivered batch (the child
|
|
@@ -59994,17 +61144,28 @@ export async function claimSessionWorkForAttempt(
|
|
|
59994
61144
|
})
|
|
59995
61145
|
: null;
|
|
59996
61146
|
if (update.scheduledTaskRunId && scheduledAuthorityDenial) {
|
|
59997
|
-
|
|
59998
|
-
|
|
59999
|
-
|
|
60000
|
-
|
|
60001
|
-
|
|
60002
|
-
|
|
61147
|
+
const scheduledTaskInactive =
|
|
61148
|
+
scheduledAuthorityDenial === "scheduled_task_inactive_before_claim";
|
|
61149
|
+
if (!scheduledTaskInactive) {
|
|
61150
|
+
await markScheduledTaskRunAuthorityRejectedInTransaction(
|
|
61151
|
+
tx as unknown as Database,
|
|
61152
|
+
{
|
|
61153
|
+
workspaceId,
|
|
61154
|
+
sessionId,
|
|
61155
|
+
runId: update.scheduledTaskRunId,
|
|
61156
|
+
error: scheduledAuthorityDenial,
|
|
61157
|
+
},
|
|
61158
|
+
);
|
|
61159
|
+
}
|
|
60003
61160
|
await tx
|
|
60004
61161
|
.update(schema.sessionSystemUpdates)
|
|
60005
|
-
.set({ state: "failed" })
|
|
61162
|
+
.set({ state: scheduledTaskInactive ? "cancelled" : "failed" })
|
|
60006
61163
|
.where(eq(schema.sessionSystemUpdates.id, update.id));
|
|
60007
|
-
|
|
61164
|
+
if (scheduledTaskInactive) {
|
|
61165
|
+
scheduledTaskInactiveUpdateIds.push(update.id);
|
|
61166
|
+
} else {
|
|
61167
|
+
authorityRejectedUpdateIds.push(update.id);
|
|
61168
|
+
}
|
|
60008
61169
|
continue;
|
|
60009
61170
|
}
|
|
60010
61171
|
if (input.validatePendingSystemUpdateAuthority) {
|
|
@@ -60114,6 +61275,25 @@ export async function claimSessionWorkForAttempt(
|
|
|
60114
61275
|
occurredAt,
|
|
60115
61276
|
});
|
|
60116
61277
|
}
|
|
61278
|
+
if (scheduledTaskInactiveUpdateIds.length > 0) {
|
|
61279
|
+
cancellationEvents.push({
|
|
61280
|
+
accountId,
|
|
61281
|
+
workspaceId,
|
|
61282
|
+
sessionId,
|
|
61283
|
+
turnId: null,
|
|
61284
|
+
turnGeneration: null,
|
|
61285
|
+
turnAttemptId: null,
|
|
61286
|
+
turnAssociation: null,
|
|
61287
|
+
sequence: ++sequence,
|
|
61288
|
+
type: "system.update.cancelled" as const,
|
|
61289
|
+
payload: {
|
|
61290
|
+
updateIds: scheduledTaskInactiveUpdateIds,
|
|
61291
|
+
count: scheduledTaskInactiveUpdateIds.length,
|
|
61292
|
+
reason: "scheduled_task_inactive",
|
|
61293
|
+
},
|
|
61294
|
+
occurredAt,
|
|
61295
|
+
});
|
|
61296
|
+
}
|
|
60117
61297
|
if (authorityRejectedUpdateIds.length > 0) {
|
|
60118
61298
|
cancellationEvents.push({
|
|
60119
61299
|
accountId,
|
|
@@ -60252,6 +61432,25 @@ export async function claimSessionWorkForAttempt(
|
|
|
60252
61432
|
occurredAt,
|
|
60253
61433
|
});
|
|
60254
61434
|
}
|
|
61435
|
+
if (scheduledTaskInactiveUpdateIds.length > 0) {
|
|
61436
|
+
events.push({
|
|
61437
|
+
accountId,
|
|
61438
|
+
workspaceId,
|
|
61439
|
+
sessionId,
|
|
61440
|
+
turnId,
|
|
61441
|
+
turnGeneration,
|
|
61442
|
+
turnAttemptId: input.attemptId,
|
|
61443
|
+
turnAssociation: "current",
|
|
61444
|
+
sequence: ++sequence,
|
|
61445
|
+
type: "system.update.cancelled",
|
|
61446
|
+
payload: {
|
|
61447
|
+
updateIds: scheduledTaskInactiveUpdateIds,
|
|
61448
|
+
count: scheduledTaskInactiveUpdateIds.length,
|
|
61449
|
+
reason: "scheduled_task_inactive",
|
|
61450
|
+
},
|
|
61451
|
+
occurredAt,
|
|
61452
|
+
});
|
|
61453
|
+
}
|
|
60255
61454
|
if (authorityRejectedUpdateIds.length > 0) {
|
|
60256
61455
|
events.push({
|
|
60257
61456
|
accountId,
|
|
@@ -61406,6 +62605,9 @@ export async function claimSessionWorkForAttempt(
|
|
|
61406
62605
|
typeof routingGoalUpdate.payload.policy === "object"
|
|
61407
62606
|
? (routingGoalUpdate.payload.policy as Record<string, unknown>)
|
|
61408
62607
|
: null;
|
|
62608
|
+
let frozenTurnExecutionPolicy = goalPolicy?.turnExecutionPolicy
|
|
62609
|
+
? TurnExecutionPolicyV1.parse(goalPolicy.turnExecutionPolicy)
|
|
62610
|
+
: null;
|
|
61409
62611
|
const [latestStarted] = await tx
|
|
61410
62612
|
.select({
|
|
61411
62613
|
model: schema.sessionTurns.model,
|
|
@@ -61475,6 +62677,9 @@ export async function claimSessionWorkForAttempt(
|
|
|
61475
62677
|
const accepted = ScheduledTaskRunAcceptedExecution.parse(
|
|
61476
62678
|
scheduledRun.acceptedExecutionSnapshot,
|
|
61477
62679
|
);
|
|
62680
|
+
frozenTurnExecutionPolicy = accepted.turnExecutionPolicy
|
|
62681
|
+
? TurnExecutionPolicyV1.parse(accepted.turnExecutionPolicy)
|
|
62682
|
+
: null;
|
|
61478
62683
|
const targetPolicy = accepted.targetSessionExecution;
|
|
61479
62684
|
if (targetPolicy) {
|
|
61480
62685
|
scheduledEffectiveMcpServerIds = targetPolicy.effectiveMcpServerIds;
|
|
@@ -61696,11 +62901,28 @@ export async function claimSessionWorkForAttempt(
|
|
|
61696
62901
|
sandboxBackend,
|
|
61697
62902
|
sandboxOs,
|
|
61698
62903
|
metadata: metadataWithTurnDispatchAttempt(
|
|
61699
|
-
|
|
61700
|
-
|
|
61701
|
-
|
|
61702
|
-
|
|
61703
|
-
|
|
62904
|
+
frozenTurnExecutionPolicy
|
|
62905
|
+
? metadataWithTurnExecutionPolicyV1(
|
|
62906
|
+
{
|
|
62907
|
+
internalUpdateCount: delivered.count,
|
|
62908
|
+
...(routingGoalUpdate
|
|
62909
|
+
? { goalId: routingGoalUpdate.payload.goalId }
|
|
62910
|
+
: {}),
|
|
62911
|
+
...(scheduledEffectiveMcpServerIds
|
|
62912
|
+
? { scheduledEffectiveMcpServerIds }
|
|
62913
|
+
: {}),
|
|
62914
|
+
},
|
|
62915
|
+
frozenTurnExecutionPolicy,
|
|
62916
|
+
)
|
|
62917
|
+
: {
|
|
62918
|
+
internalUpdateCount: delivered.count,
|
|
62919
|
+
...(routingGoalUpdate
|
|
62920
|
+
? { goalId: routingGoalUpdate.payload.goalId }
|
|
62921
|
+
: {}),
|
|
62922
|
+
...(scheduledEffectiveMcpServerIds
|
|
62923
|
+
? { scheduledEffectiveMcpServerIds }
|
|
62924
|
+
: {}),
|
|
62925
|
+
},
|
|
61704
62926
|
{ id: input.dispatchId, generation: 1, triggerEventId },
|
|
61705
62927
|
),
|
|
61706
62928
|
...initiatorColumns(internalInitiator),
|
|
@@ -64000,7 +65222,10 @@ export async function failSessionWorkBeforeAttemptClaim(
|
|
|
64000
65222
|
sessionId: input.sessionId,
|
|
64001
65223
|
sequence: ++sequence,
|
|
64002
65224
|
type: "user.humanInputResponse" as const,
|
|
64003
|
-
payload: {
|
|
65225
|
+
payload: {
|
|
65226
|
+
requestId: request.id,
|
|
65227
|
+
response: { outcome: "cancelled" as const },
|
|
65228
|
+
},
|
|
64004
65229
|
turnId: turn.id,
|
|
64005
65230
|
turnGeneration: request.turnGeneration,
|
|
64006
65231
|
turnAttemptId: null,
|
|
@@ -68648,8 +69873,9 @@ export type AddSessionSystemUpdateInput = {
|
|
|
68648
69873
|
scheduledTaskRunId?: string | null;
|
|
68649
69874
|
} & SessionSystemUpdateInputVariant;
|
|
68650
69875
|
|
|
68651
|
-
export type AddSessionSystemUpdateResult =
|
|
69876
|
+
export type AddSessionSystemUpdateResult<RequireIdleSession extends boolean = boolean> =
|
|
68652
69877
|
| { added: false; reason: "session_cancelled" }
|
|
69878
|
+
| (RequireIdleSession extends true ? { added: false; reason: "session_not_idle" } : never)
|
|
68653
69879
|
| {
|
|
68654
69880
|
added: boolean;
|
|
68655
69881
|
reason: "added" | "duplicate";
|
|
@@ -68664,12 +69890,14 @@ export type AddSessionSystemUpdateResult =
|
|
|
68664
69890
|
export type PrepareSessionSystemUpdateSourceResult = {
|
|
68665
69891
|
/** Private, bounded, content-free source authority appended before insert. */
|
|
68666
69892
|
lineage?: Record<string, unknown>;
|
|
69893
|
+
/** Source-owned events appended before the pending-update event. */
|
|
69894
|
+
events?: SessionEvent[];
|
|
68667
69895
|
};
|
|
68668
69896
|
|
|
68669
69897
|
export async function addSessionSystemUpdate(
|
|
68670
69898
|
db: Database,
|
|
68671
69899
|
input: AddSessionSystemUpdateInput,
|
|
68672
|
-
): Promise<AddSessionSystemUpdateResult
|
|
69900
|
+
): Promise<AddSessionSystemUpdateResult<false>> {
|
|
68673
69901
|
if (input.payload.type !== input.kind) {
|
|
68674
69902
|
throw new Error(`Internal update payload discriminator must equal kind ${input.kind}`);
|
|
68675
69903
|
}
|
|
@@ -68680,29 +69908,34 @@ export async function addSessionSystemUpdate(
|
|
|
68680
69908
|
* Persist one internal update without fabricating a user prompt or queue row.
|
|
68681
69909
|
* Dedupe and any producer/outbox mutation commit in the same transaction.
|
|
68682
69910
|
*/
|
|
68683
|
-
export async function addSessionSystemUpdateWithSourceMutation
|
|
69911
|
+
export async function addSessionSystemUpdateWithSourceMutation<
|
|
69912
|
+
RequireIdleSession extends boolean = false,
|
|
69913
|
+
>(
|
|
68684
69914
|
db: Database,
|
|
68685
69915
|
input: AddSessionSystemUpdateInput,
|
|
68686
69916
|
mutateSource: (
|
|
68687
69917
|
tx: Database,
|
|
68688
69918
|
wakeEventId: string | null,
|
|
68689
69919
|
updateId: string | null,
|
|
69920
|
+
rejectionReason: "session_cancelled" | "session_not_idle" | null,
|
|
68690
69921
|
) => Promise<void>,
|
|
68691
69922
|
options: {
|
|
69923
|
+
/** Admit only when the already-locked session is at its idle boundary. */
|
|
69924
|
+
requireIdleSession?: RequireIdleSession;
|
|
68692
69925
|
/** Runs under the locked session/source transaction before any update/event insert. */
|
|
68693
69926
|
prepareSource?: (
|
|
68694
69927
|
tx: Database,
|
|
68695
69928
|
sessionId: string,
|
|
68696
69929
|
) => Promise<PrepareSessionSystemUpdateSourceResult | void>;
|
|
68697
69930
|
} = {},
|
|
68698
|
-
): Promise<AddSessionSystemUpdateResult
|
|
69931
|
+
): Promise<AddSessionSystemUpdateResult<RequireIdleSession>> {
|
|
68699
69932
|
if (Buffer.byteLength(JSON.stringify(input.payload)) > MAX_INTERNAL_UPDATE_BYTES) {
|
|
68700
69933
|
throw new Error(`Internal update payload exceeds ${MAX_INTERNAL_UPDATE_BYTES} bytes`);
|
|
68701
69934
|
}
|
|
68702
69935
|
if (Buffer.byteLength(input.summary) > MAX_INTERNAL_UPDATE_BYTES) {
|
|
68703
69936
|
throw new Error(`Internal update summary exceeds ${MAX_INTERNAL_UPDATE_BYTES} bytes`);
|
|
68704
69937
|
}
|
|
68705
|
-
return await withSessionActivityRlsContext(
|
|
69938
|
+
return (await withSessionActivityRlsContext(
|
|
68706
69939
|
db,
|
|
68707
69940
|
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
68708
69941
|
async (scopedDb) =>
|
|
@@ -68712,8 +69945,9 @@ export async function addSessionSystemUpdateWithSourceMutation(
|
|
|
68712
69945
|
controlLock: "share",
|
|
68713
69946
|
sessionIds: [input.sessionId],
|
|
68714
69947
|
});
|
|
68715
|
-
|
|
69948
|
+
let session = locks.sessions[0];
|
|
68716
69949
|
if (!session) throw new Error(`Session not found: ${input.sessionId}`);
|
|
69950
|
+
const temporalWorkflowId = session.temporalWorkflowId;
|
|
68717
69951
|
const effectiveControl = await evaluateSessionControl(
|
|
68718
69952
|
tx as unknown as Database,
|
|
68719
69953
|
input.workspaceId,
|
|
@@ -68721,10 +69955,71 @@ export async function addSessionSystemUpdateWithSourceMutation(
|
|
|
68721
69955
|
{ workspaceControl: locks.control ?? undefined },
|
|
68722
69956
|
);
|
|
68723
69957
|
if (session.status === "cancelled") {
|
|
68724
|
-
await mutateSource(tx as unknown as Database, null, null);
|
|
69958
|
+
await mutateSource(tx as unknown as Database, null, null, "session_cancelled");
|
|
68725
69959
|
return { added: false, reason: "session_cancelled" } as const;
|
|
68726
69960
|
}
|
|
69961
|
+
const replayExistingUpdate = async (events: SessionEvent[]) => {
|
|
69962
|
+
const [existing] = await tx
|
|
69963
|
+
.select()
|
|
69964
|
+
.from(schema.sessionSystemUpdates)
|
|
69965
|
+
.where(
|
|
69966
|
+
and(
|
|
69967
|
+
eq(schema.sessionSystemUpdates.workspaceId, input.workspaceId),
|
|
69968
|
+
eq(schema.sessionSystemUpdates.sessionId, input.sessionId),
|
|
69969
|
+
eq(schema.sessionSystemUpdates.dedupeKey, input.dedupeKey),
|
|
69970
|
+
),
|
|
69971
|
+
)
|
|
69972
|
+
.limit(1);
|
|
69973
|
+
if (!existing) return null;
|
|
69974
|
+
const [pendingEvent] = await tx
|
|
69975
|
+
.select({ id: schema.sessionEvents.id })
|
|
69976
|
+
.from(schema.sessionEvents)
|
|
69977
|
+
.where(
|
|
69978
|
+
and(
|
|
69979
|
+
eq(schema.sessionEvents.workspaceId, input.workspaceId),
|
|
69980
|
+
eq(schema.sessionEvents.sessionId, input.sessionId),
|
|
69981
|
+
eq(schema.sessionEvents.type, "system.update.pending"),
|
|
69982
|
+
sql`${schema.sessionEvents.payload} ->> 'updateId' = ${existing.id}`,
|
|
69983
|
+
),
|
|
69984
|
+
)
|
|
69985
|
+
.limit(1);
|
|
69986
|
+
if (!pendingEvent) throw new Error("System-update pending event disappeared");
|
|
69987
|
+
await mutateSource(tx as unknown as Database, pendingEvent.id, existing.id, null);
|
|
69988
|
+
return {
|
|
69989
|
+
added: false,
|
|
69990
|
+
reason: "duplicate",
|
|
69991
|
+
update: mapSessionSystemUpdate(existing),
|
|
69992
|
+
shouldWake: false,
|
|
69993
|
+
workflowWakeRevision: null,
|
|
69994
|
+
wakeEventId: pendingEvent.id,
|
|
69995
|
+
temporalWorkflowId,
|
|
69996
|
+
events,
|
|
69997
|
+
} as const;
|
|
69998
|
+
};
|
|
69999
|
+
|
|
70000
|
+
if (options.requireIdleSession && session.status !== "idle") {
|
|
70001
|
+
const replayed = await replayExistingUpdate([]);
|
|
70002
|
+
if (replayed) {
|
|
70003
|
+
return replayed;
|
|
70004
|
+
}
|
|
70005
|
+
await mutateSource(tx as unknown as Database, null, null, "session_not_idle");
|
|
70006
|
+
return { added: false, reason: "session_not_idle" } as const;
|
|
70007
|
+
}
|
|
68727
70008
|
const prepared = await options.prepareSource?.(tx as unknown as Database, input.sessionId);
|
|
70009
|
+
if ((prepared?.events?.length ?? 0) > 0) {
|
|
70010
|
+
const [refreshedSession] = await tx
|
|
70011
|
+
.select()
|
|
70012
|
+
.from(schema.sessions)
|
|
70013
|
+
.where(
|
|
70014
|
+
and(
|
|
70015
|
+
eq(schema.sessions.workspaceId, input.workspaceId),
|
|
70016
|
+
eq(schema.sessions.id, input.sessionId),
|
|
70017
|
+
),
|
|
70018
|
+
)
|
|
70019
|
+
.limit(1);
|
|
70020
|
+
if (!refreshedSession) throw new Error(`Session not found: ${input.sessionId}`);
|
|
70021
|
+
session = refreshedSession;
|
|
70022
|
+
}
|
|
68728
70023
|
const lineage = {
|
|
68729
70024
|
...(input.lineage ?? {}),
|
|
68730
70025
|
...(prepared?.lineage ?? {}),
|
|
@@ -68769,42 +70064,9 @@ export async function addSessionSystemUpdateWithSourceMutation(
|
|
|
68769
70064
|
})
|
|
68770
70065
|
.returning();
|
|
68771
70066
|
if (!inserted) {
|
|
68772
|
-
const
|
|
68773
|
-
|
|
68774
|
-
|
|
68775
|
-
.where(
|
|
68776
|
-
and(
|
|
68777
|
-
eq(schema.sessionSystemUpdates.workspaceId, input.workspaceId),
|
|
68778
|
-
eq(schema.sessionSystemUpdates.sessionId, input.sessionId),
|
|
68779
|
-
eq(schema.sessionSystemUpdates.dedupeKey, input.dedupeKey),
|
|
68780
|
-
),
|
|
68781
|
-
)
|
|
68782
|
-
.limit(1);
|
|
68783
|
-
if (!existing) throw new Error("System-update dedupe row disappeared");
|
|
68784
|
-
const [pendingEvent] = await tx
|
|
68785
|
-
.select({ id: schema.sessionEvents.id })
|
|
68786
|
-
.from(schema.sessionEvents)
|
|
68787
|
-
.where(
|
|
68788
|
-
and(
|
|
68789
|
-
eq(schema.sessionEvents.workspaceId, input.workspaceId),
|
|
68790
|
-
eq(schema.sessionEvents.sessionId, input.sessionId),
|
|
68791
|
-
eq(schema.sessionEvents.type, "system.update.pending"),
|
|
68792
|
-
sql`${schema.sessionEvents.payload} ->> 'updateId' = ${existing.id}`,
|
|
68793
|
-
),
|
|
68794
|
-
)
|
|
68795
|
-
.limit(1);
|
|
68796
|
-
if (!pendingEvent) throw new Error("System-update pending event disappeared");
|
|
68797
|
-
await mutateSource(tx as unknown as Database, pendingEvent.id, existing.id);
|
|
68798
|
-
return {
|
|
68799
|
-
added: false,
|
|
68800
|
-
reason: "duplicate",
|
|
68801
|
-
update: mapSessionSystemUpdate(existing),
|
|
68802
|
-
shouldWake: false,
|
|
68803
|
-
workflowWakeRevision: null,
|
|
68804
|
-
wakeEventId: pendingEvent.id,
|
|
68805
|
-
temporalWorkflowId: session.temporalWorkflowId,
|
|
68806
|
-
events: [],
|
|
68807
|
-
};
|
|
70067
|
+
const replayed = await replayExistingUpdate(prepared?.events ?? []);
|
|
70068
|
+
if (!replayed) throw new Error("System-update dedupe row disappeared");
|
|
70069
|
+
return replayed;
|
|
68808
70070
|
}
|
|
68809
70071
|
|
|
68810
70072
|
const now = new Date();
|
|
@@ -68836,7 +70098,7 @@ export async function addSessionSystemUpdateWithSourceMutation(
|
|
|
68836
70098
|
)
|
|
68837
70099
|
.returning();
|
|
68838
70100
|
if (!event) throw new Error("Failed to create system-update pending event");
|
|
68839
|
-
await mutateSource(tx as unknown as Database, event.id, inserted.id);
|
|
70101
|
+
await mutateSource(tx as unknown as Database, event.id, inserted.id, null);
|
|
68840
70102
|
// Producer-side supersession of this child's now-stale pending notices
|
|
68841
70103
|
// on the parent: a newer child_progress replaces an older one, and a
|
|
68842
70104
|
// resolution supersedes the child_requires_action of the same exact
|
|
@@ -68980,11 +70242,16 @@ export async function addSessionSystemUpdateWithSourceMutation(
|
|
|
68980
70242
|
temporalWorkflowId: session.temporalWorkflowId ?? `session-${session.id}`,
|
|
68981
70243
|
})
|
|
68982
70244
|
: null;
|
|
70245
|
+
// An idle-gated admission consumes that boundary even when control or
|
|
70246
|
+
// realtime ownership withholds the workflow wake. Persisting `queued`
|
|
70247
|
+
// reserves the accepted work so a later distinct skip occurrence
|
|
70248
|
+
// cannot cross the same idle boundary while this update is pending.
|
|
70249
|
+
const shouldQueue = shouldWake || options.requireIdleSession === true;
|
|
68983
70250
|
await tx
|
|
68984
70251
|
.update(schema.sessions)
|
|
68985
70252
|
.set({
|
|
68986
70253
|
lastSequence: session.lastSequence + appendedSequences,
|
|
68987
|
-
...(
|
|
70254
|
+
...(shouldQueue ? { status: "queued" as const } : {}),
|
|
68988
70255
|
updatedAt: now,
|
|
68989
70256
|
})
|
|
68990
70257
|
.where(eq(schema.sessions.id, session.id));
|
|
@@ -68997,13 +70264,14 @@ export async function addSessionSystemUpdateWithSourceMutation(
|
|
|
68997
70264
|
wakeEventId: event.id,
|
|
68998
70265
|
temporalWorkflowId: session.temporalWorkflowId,
|
|
68999
70266
|
events: [
|
|
70267
|
+
...(prepared?.events ?? []),
|
|
69000
70268
|
mapEvent(event),
|
|
69001
70269
|
...(supersededEvent ? [mapEvent(supersededEvent)] : []),
|
|
69002
70270
|
...(resumedEvent ? [mapEvent(resumedEvent)] : []),
|
|
69003
70271
|
],
|
|
69004
70272
|
};
|
|
69005
70273
|
}),
|
|
69006
|
-
)
|
|
70274
|
+
)) as AddSessionSystemUpdateResult<RequireIdleSession>;
|
|
69007
70275
|
}
|
|
69008
70276
|
|
|
69009
70277
|
/**
|
|
@@ -71497,6 +72765,13 @@ function mapConnectionMetadata(row: {
|
|
|
71497
72765
|
createdAt: Date;
|
|
71498
72766
|
updatedAt: Date;
|
|
71499
72767
|
}): ConnectionMetadataWithVerification {
|
|
72768
|
+
const {
|
|
72769
|
+
[OPENROUTER_CREDENTIAL_OPERATION_ID_METADATA_KEY]: _openRouterOperationId,
|
|
72770
|
+
[OPENROUTER_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY]: _openRouterOperationDigest,
|
|
72771
|
+
[VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_ID_METADATA_KEY]: _operationId,
|
|
72772
|
+
[VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY]: _operationDigest,
|
|
72773
|
+
...publicMetadata
|
|
72774
|
+
} = row.metadata;
|
|
71500
72775
|
return {
|
|
71501
72776
|
id: row.id,
|
|
71502
72777
|
...(row.subjectId !== null && row.authorityId ? { authorityId: row.authorityId } : {}),
|
|
@@ -71514,7 +72789,7 @@ function mapConnectionMetadata(row: {
|
|
|
71514
72789
|
version: row.version,
|
|
71515
72790
|
verifiedInstallAt: row.verifiedInstallAt?.toISOString() ?? null,
|
|
71516
72791
|
verifiedInstallVersion: row.verifiedInstallVersion,
|
|
71517
|
-
metadata:
|
|
72792
|
+
metadata: publicMetadata,
|
|
71518
72793
|
createdBySubjectId: row.createdBySubjectId,
|
|
71519
72794
|
updatedBySubjectId: row.updatedBySubjectId,
|
|
71520
72795
|
createdAt: row.createdAt.toISOString(),
|
|
@@ -71852,6 +73127,9 @@ function connectionRefConfig(value: unknown): McpServerConnectionRef | undefined
|
|
|
71852
73127
|
return undefined;
|
|
71853
73128
|
}
|
|
71854
73129
|
const ref: McpServerConnectionRef = { providerDomain: record.providerDomain };
|
|
73130
|
+
if (record.authoritySource === "host") {
|
|
73131
|
+
ref.authoritySource = "host";
|
|
73132
|
+
}
|
|
71855
73133
|
if (typeof record.connectionId === "string" && record.connectionId.length > 0) {
|
|
71856
73134
|
ref.connectionId = record.connectionId;
|
|
71857
73135
|
}
|
|
@@ -72096,3 +73374,4 @@ export * from "./organization-recovery";
|
|
|
72096
73374
|
export * from "./session-tenancy";
|
|
72097
73375
|
export * from "./governed-learning-activation";
|
|
72098
73376
|
export * from "./automations";
|
|
73377
|
+
export * from "./organization-model-providers";
|