@opengeni/db 0.9.3 → 0.10.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-4LG5NBTC.js → chunk-P6PKXY5W.js} +93 -1
- package/dist/chunk-P6PKXY5W.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1332 -178
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.d.ts +406 -32
- package/dist/{schema-CdPGTHlD.d.ts → schema-CqkzrBRS.d.ts} +513 -2
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +3 -1
- package/drizzle/0053_codex_credential_leases.sql +2 -2
- package/drizzle/0057_durable_queue_control.sql +1 -1
- package/drizzle/0061_session_workflow_wake_outbox.sql +1 -1
- package/drizzle/0062_session_list_snapshot_reaper.sql +1 -1
- package/drizzle/0063_session_control_mega_foundation.sql +1 -1
- package/drizzle/0064_rotation_strategy_sharded_backfill.sql +1 -1
- package/drizzle/0065_codex_subscription_overview.sql +168 -0
- package/drizzle/0065_session_tool_policy.sql +38 -0
- package/drizzle/0067_session_event_payload_bounds.sql +2 -2
- package/drizzle/0068_workspace_control_event_bounds.sql +2 -2
- package/drizzle/0069_session_event_history_backfill.sql +2 -2
- package/drizzle/0074_session_activity_revisions.sql +2 -2
- package/drizzle/0106_session_attempt_mcp_approval_policies.sql +29 -0
- package/drizzle/0107_host_export_lineage_contract.sql +381 -0
- package/drizzle/0108_fence_invalidated_warming_epochs.sql +76 -0
- package/package.json +5 -4
- package/src/codex-token-resolver.ts +175 -14
- package/src/connection-token-resolver.ts +143 -120
- package/src/event-payload-sanitizer.ts +32 -2
- package/src/index.ts +1888 -205
- package/src/schema.ts +107 -1
- package/src/session-control.ts +2 -0
- package/src/session-queue-commands.ts +94 -21
- package/dist/chunk-4LG5NBTC.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
capabilityInstallations,
|
|
8
8
|
codexCapacityWaiters,
|
|
9
9
|
codexCredentialLeases,
|
|
10
|
+
codexResetRedemptionAttempts,
|
|
10
11
|
codexRotationSettings,
|
|
11
12
|
codexSubscriptionCredentials,
|
|
12
13
|
composerDrafts,
|
|
@@ -65,7 +66,7 @@ import {
|
|
|
65
66
|
workspaceVariableSetVariables,
|
|
66
67
|
workspaceVariableSets,
|
|
67
68
|
workspaces
|
|
68
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-P6PKXY5W.js";
|
|
69
70
|
import {
|
|
70
71
|
migrate,
|
|
71
72
|
runMigrations
|
|
@@ -87,6 +88,9 @@ import {
|
|
|
87
88
|
SESSION_EVENT_ENVELOPE_MAX_BYTES,
|
|
88
89
|
SESSION_EVENT_TYPE_MAX_BYTES,
|
|
89
90
|
resolveSessionEventTypeFilters,
|
|
91
|
+
capabilityCatalogItemIsTrustedForExposure,
|
|
92
|
+
metadataWithTurnExecutionPolicyV1 as metadataWithTurnExecutionPolicyV12,
|
|
93
|
+
readTurnExecutionPolicyV1,
|
|
90
94
|
reasoningEffortForMetadata,
|
|
91
95
|
resolveWorkspaceMemoryEnabled,
|
|
92
96
|
RigChange as RigChangeContract,
|
|
@@ -98,7 +102,8 @@ import {
|
|
|
98
102
|
HostUsageExportBatch as HostUsageExportBatchContract,
|
|
99
103
|
OPENGENI_HOST_EXPORT_SCHEMA_REVISION,
|
|
100
104
|
HumanInputQuestion as HumanInputQuestionContract,
|
|
101
|
-
SubmitHumanInputResponseRequest
|
|
105
|
+
SubmitHumanInputResponseRequest,
|
|
106
|
+
TurnExecutionPolicyV1
|
|
102
107
|
} from "@opengeni/contracts";
|
|
103
108
|
import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes3 } from "@opengeni/config";
|
|
104
109
|
import { boundModelToolOutputItem as boundModelToolOutputItem2, isCodexBilledModel } from "@opengeni/codex";
|
|
@@ -327,8 +332,21 @@ function sanitizeEventString(value) {
|
|
|
327
332
|
}
|
|
328
333
|
return out;
|
|
329
334
|
}
|
|
330
|
-
function sanitizeEventPayload(payload) {
|
|
331
|
-
|
|
335
|
+
function sanitizeEventPayload(payload, options = {}) {
|
|
336
|
+
const bounded = boundSessionEventPayload(payload, {
|
|
337
|
+
fullEvidence: options.fullEvidence
|
|
338
|
+
});
|
|
339
|
+
return sanitizeEventPayloadDeep(
|
|
340
|
+
bounded === payload ? removeProducerTruncationMetadata(bounded) : bounded
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
function removeProducerTruncationMetadata(payload) {
|
|
344
|
+
if (!isPlainObject(payload)) return payload;
|
|
345
|
+
const descriptor = Object.getOwnPropertyDescriptor(payload, "truncation");
|
|
346
|
+
if (!descriptor?.enumerable) return payload;
|
|
347
|
+
const cleaned = { ...payload };
|
|
348
|
+
delete cleaned.truncation;
|
|
349
|
+
return cleaned;
|
|
332
350
|
}
|
|
333
351
|
function sanitizeEventPayloadDeep(payload) {
|
|
334
352
|
if (typeof payload === "string") {
|
|
@@ -2478,8 +2496,10 @@ import { sql as sql5 } from "drizzle-orm";
|
|
|
2478
2496
|
|
|
2479
2497
|
// src/session-queue-commands.ts
|
|
2480
2498
|
import {
|
|
2499
|
+
metadataWithTurnExecutionPolicyV1,
|
|
2481
2500
|
mergeResourceRefs,
|
|
2482
|
-
mergeToolRefs
|
|
2501
|
+
mergeToolRefs,
|
|
2502
|
+
turnExecutionPolicyAuditMetadata
|
|
2483
2503
|
} from "@opengeni/contracts";
|
|
2484
2504
|
import { and as and4, asc as asc2, eq as eq4, inArray as inArray2, sql as sql3 } from "drizzle-orm";
|
|
2485
2505
|
var QueueCommandConflictError = class extends Error {
|
|
@@ -2684,7 +2704,7 @@ async function normalizeQueuePositions(db, workspaceId, sessionId, orderedIds) {
|
|
|
2684
2704
|
}).where(and4(eq4(sessions.workspaceId, workspaceId), eq4(sessions.id, sessionId)));
|
|
2685
2705
|
}
|
|
2686
2706
|
function draftIsNonEmpty(draft) {
|
|
2687
|
-
return draft.text.length > 0 || draft.resources.length > 0 || draft.tools.length > 0 || draft.sourceTurnId !== null;
|
|
2707
|
+
return draft.text.length > 0 || draft.resources.length > 0 || draft.tools.length > 0 || draft.toolsProvided || draft.sourceTurnId !== null;
|
|
2688
2708
|
}
|
|
2689
2709
|
async function getComposerDraftInTransaction(db, input) {
|
|
2690
2710
|
const query = db.select().from(composerDrafts).where(
|
|
@@ -2725,6 +2745,7 @@ async function saveComposerDraftInTransaction(db, input) {
|
|
|
2725
2745
|
text: input.text,
|
|
2726
2746
|
resources: input.resources,
|
|
2727
2747
|
tools: input.tools,
|
|
2748
|
+
toolsProvided: input.toolsProvided,
|
|
2728
2749
|
model: input.model,
|
|
2729
2750
|
reasoningEffort: input.reasoningEffort,
|
|
2730
2751
|
// A queue edit is still the same accepted work item. Preserve its frozen
|
|
@@ -3024,6 +3045,7 @@ async function editQueuedTurnInTransaction(db, input) {
|
|
|
3024
3045
|
text: turn.prompt,
|
|
3025
3046
|
resources: turn.resources,
|
|
3026
3047
|
tools: turn.tools,
|
|
3048
|
+
toolsProvided: turn.toolsProvided,
|
|
3027
3049
|
model: turn.model,
|
|
3028
3050
|
reasoningEffort: turn.reasoningEffort,
|
|
3029
3051
|
sourceTurnId: turn.id,
|
|
@@ -3290,6 +3312,7 @@ async function submitHumanPromptInTransaction(db, input) {
|
|
|
3290
3312
|
turnInstructions: input.turnInstructions ?? null,
|
|
3291
3313
|
resources: input.resources,
|
|
3292
3314
|
tools: input.tools,
|
|
3315
|
+
toolsProvided: input.toolsProvided === true,
|
|
3293
3316
|
model: input.model ?? null,
|
|
3294
3317
|
reasoningEffort: input.reasoningEffort ?? null,
|
|
3295
3318
|
source: input.source,
|
|
@@ -3373,12 +3396,14 @@ async function submitHumanPromptInTransaction(db, input) {
|
|
|
3373
3396
|
text: draft.text,
|
|
3374
3397
|
resources: draft.resources,
|
|
3375
3398
|
tools: draft.tools,
|
|
3399
|
+
toolsProvided: draft.toolsProvided,
|
|
3376
3400
|
model: draft.model,
|
|
3377
3401
|
reasoningEffort: draft.reasoningEffort
|
|
3378
3402
|
}) !== canonicalSessionCommandHash({
|
|
3379
3403
|
text: input.text,
|
|
3380
3404
|
resources: input.resources,
|
|
3381
3405
|
tools: input.tools,
|
|
3406
|
+
toolsProvided: input.toolsProvided === true,
|
|
3382
3407
|
model: input.model ?? session.model,
|
|
3383
3408
|
reasoningEffort: input.reasoningEffort ?? input.reasoningEffortFallback
|
|
3384
3409
|
})) {
|
|
@@ -3392,6 +3417,33 @@ async function submitHumanPromptInTransaction(db, input) {
|
|
|
3392
3417
|
);
|
|
3393
3418
|
}
|
|
3394
3419
|
}
|
|
3420
|
+
let editedSourceTurn;
|
|
3421
|
+
let editedSourceTurnInstructions;
|
|
3422
|
+
if (draft?.sourceTurnId) {
|
|
3423
|
+
const sourceLocks = await lockSessionEventWriteRows(db, {
|
|
3424
|
+
workspaceId: input.workspaceId,
|
|
3425
|
+
controlLock: "already_locked",
|
|
3426
|
+
workspaceLock: "already_locked",
|
|
3427
|
+
turnIds: [draft.sourceTurnId]
|
|
3428
|
+
});
|
|
3429
|
+
const sourceTurn = sourceLocks.turns[0];
|
|
3430
|
+
const sourceTurnVersion = draft.sourceTurnVersion;
|
|
3431
|
+
const sourceMetadata = sourceTurn?.metadata ?? {};
|
|
3432
|
+
const sourceIsExactWithdrawnRevision = sourceTurn !== void 0 && sourceTurn.accountId === input.accountId && sourceTurn.workspaceId === input.workspaceId && sourceTurn.sessionId === input.sessionId && (sourceTurn.source === "user" || sourceTurn.source === "api") && sourceTurn.status === "withdrawn_for_edit" && sourceTurnVersion !== null && sourceTurn.version === sourceTurnVersion + 1 && sourceTurn.cancelledBy === input.subjectId && sourceTurn.cancelReason === "withdrawn_for_edit" && sourceTurn.activeAttemptId === null && sourceMetadata.delivery !== "steer";
|
|
3433
|
+
if (!sourceIsExactWithdrawnRevision) {
|
|
3434
|
+
throw new QueueCommandConflictError(
|
|
3435
|
+
"EDIT_SOURCE_CHANGED",
|
|
3436
|
+
"Edited prompt source changed or is no longer withdrawn for edit",
|
|
3437
|
+
{
|
|
3438
|
+
queueVersion: session.queueVersion,
|
|
3439
|
+
draftRevision: draft.revision,
|
|
3440
|
+
...sourceTurn ? { turnVersion: sourceTurn.version } : {}
|
|
3441
|
+
}
|
|
3442
|
+
);
|
|
3443
|
+
}
|
|
3444
|
+
editedSourceTurn = sourceTurn;
|
|
3445
|
+
editedSourceTurnInstructions = sourceTurn.turnInstructions ?? null;
|
|
3446
|
+
}
|
|
3395
3447
|
for (const update of input.mcpCredentialUpdates ?? []) {
|
|
3396
3448
|
const [server] = await db.update(sessionMcpServers).set({
|
|
3397
3449
|
headersEncrypted: update.headersEncrypted,
|
|
@@ -3409,18 +3461,7 @@ async function submitHumanPromptInTransaction(db, input) {
|
|
|
3409
3461
|
const now = /* @__PURE__ */ new Date();
|
|
3410
3462
|
let frozenInitiator;
|
|
3411
3463
|
if (input.delivery === "send" && draft?.sourceTurnId) {
|
|
3412
|
-
const
|
|
3413
|
-
sessionId: sessionTurns.sessionId,
|
|
3414
|
-
initiatorKind: sessionTurns.initiatorKind,
|
|
3415
|
-
initiatorSubjectId: sessionTurns.initiatorSubjectId,
|
|
3416
|
-
initiatorContext: sessionTurns.initiatorContext
|
|
3417
|
-
}).from(sessionTurns).where(
|
|
3418
|
-
and4(
|
|
3419
|
-
eq4(sessionTurns.workspaceId, input.workspaceId),
|
|
3420
|
-
eq4(sessionTurns.sessionId, input.sessionId),
|
|
3421
|
-
eq4(sessionTurns.id, draft.sourceTurnId)
|
|
3422
|
-
)
|
|
3423
|
-
).limit(1);
|
|
3464
|
+
const sourceTurn = editedSourceTurn;
|
|
3424
3465
|
if (!sourceTurn) {
|
|
3425
3466
|
throw new SessionControlInvariantError("Edited prompt source turn is missing");
|
|
3426
3467
|
}
|
|
@@ -3456,7 +3497,7 @@ async function submitHumanPromptInTransaction(db, input) {
|
|
|
3456
3497
|
payload: sanitizeEventPayload({
|
|
3457
3498
|
text: input.text,
|
|
3458
3499
|
...input.resources.length ? { resources: input.resources } : {},
|
|
3459
|
-
...input.tools.length ? { tools: input.tools } : {},
|
|
3500
|
+
...input.toolsProvided === true ? { tools: input.tools } : input.tools.length ? { tools: input.tools } : {},
|
|
3460
3501
|
...input.model ? { model: input.model } : {},
|
|
3461
3502
|
...input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {},
|
|
3462
3503
|
delivery: input.delivery,
|
|
@@ -3477,13 +3518,14 @@ async function submitHumanPromptInTransaction(db, input) {
|
|
|
3477
3518
|
source: input.source,
|
|
3478
3519
|
position: input.delivery === "steer" ? 0 : existingQueued.length + 1,
|
|
3479
3520
|
prompt: input.text,
|
|
3480
|
-
turnInstructions: input.turnInstructions ?? null,
|
|
3521
|
+
turnInstructions: editedSourceTurnInstructions !== void 0 ? editedSourceTurnInstructions : input.turnInstructions ?? null,
|
|
3481
3522
|
resources: input.resources,
|
|
3482
3523
|
tools: input.tools,
|
|
3524
|
+
toolsProvided: input.toolsProvided === true,
|
|
3483
3525
|
model: input.model ?? session.model,
|
|
3484
3526
|
reasoningEffort: input.reasoningEffort ?? input.reasoningEffortFallback,
|
|
3485
3527
|
sandboxBackend: session.sandboxBackend,
|
|
3486
|
-
metadata: {},
|
|
3528
|
+
metadata: input.turnExecutionPolicy ? metadataWithTurnExecutionPolicyV1({}, input.turnExecutionPolicy) : {},
|
|
3487
3529
|
lineage: { actor: input.actor.type },
|
|
3488
3530
|
...initiatorColumns(frozenInitiator)
|
|
3489
3531
|
}).returning();
|
|
@@ -3527,6 +3569,7 @@ async function submitHumanPromptInTransaction(db, input) {
|
|
|
3527
3569
|
if (input.delivery === "steer") {
|
|
3528
3570
|
await db.update(sessionTurns).set({
|
|
3529
3571
|
metadata: {
|
|
3572
|
+
...turn.metadata,
|
|
3530
3573
|
delivery: "steer",
|
|
3531
3574
|
replacedTurnId,
|
|
3532
3575
|
replacedAttemptId,
|
|
@@ -3598,7 +3641,7 @@ async function submitHumanPromptInTransaction(db, input) {
|
|
|
3598
3641
|
const queueVersion = session.queueVersion + 1;
|
|
3599
3642
|
await db.update(sessions).set({
|
|
3600
3643
|
resources: mergeResourceRefs(session.resources, input.resources),
|
|
3601
|
-
tools: mergeToolRefs(session.tools, input.tools),
|
|
3644
|
+
tools: sessionToolPolicyIsFixed(session.toolPolicy) ? session.tools : mergeToolRefs(session.tools, input.tools),
|
|
3602
3645
|
activeTurnId: input.delivery === "steer" ? liveCurrentTurnId : session.activeTurnId,
|
|
3603
3646
|
status: nextStatus,
|
|
3604
3647
|
queueVersion,
|
|
@@ -3627,7 +3670,8 @@ async function submitHumanPromptInTransaction(db, input) {
|
|
|
3627
3670
|
metadata: {
|
|
3628
3671
|
operationId: reserved.receipt.id,
|
|
3629
3672
|
replacedTurnId,
|
|
3630
|
-
interruptionCount
|
|
3673
|
+
interruptionCount,
|
|
3674
|
+
...input.turnExecutionPolicy ? turnExecutionPolicyAuditMetadata(input.turnExecutionPolicy, turnId) : {}
|
|
3631
3675
|
}
|
|
3632
3676
|
});
|
|
3633
3677
|
const eventIds = eventRows.map((event) => event.id);
|
|
@@ -3643,7 +3687,10 @@ async function submitHumanPromptInTransaction(db, input) {
|
|
|
3643
3687
|
wakeRevision,
|
|
3644
3688
|
interruptionCount,
|
|
3645
3689
|
replacedTurnId,
|
|
3646
|
-
workspaceControlEventId: resumed.workspaceControlEventId
|
|
3690
|
+
workspaceControlEventId: resumed.workspaceControlEventId,
|
|
3691
|
+
...input.turnExecutionPolicy ? {
|
|
3692
|
+
executionPolicy: turnExecutionPolicyAuditMetadata(input.turnExecutionPolicy, turnId)
|
|
3693
|
+
} : {}
|
|
3647
3694
|
}
|
|
3648
3695
|
});
|
|
3649
3696
|
return {
|
|
@@ -3658,6 +3705,11 @@ async function submitHumanPromptInTransaction(db, input) {
|
|
|
3658
3705
|
replay: false
|
|
3659
3706
|
};
|
|
3660
3707
|
}
|
|
3708
|
+
function sessionToolPolicyIsFixed(value) {
|
|
3709
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
3710
|
+
const mode = value.mode;
|
|
3711
|
+
return mode === "workspace_default" || mode === "explicit" || mode === "inherited";
|
|
3712
|
+
}
|
|
3661
3713
|
async function sendAgentMessageInTransaction(db, input) {
|
|
3662
3714
|
const workspaceControl = await lockWorkspaceInferenceControl(db, input.workspaceId, "share");
|
|
3663
3715
|
await lockSessionEventWriteRows(db, {
|
|
@@ -4021,11 +4073,65 @@ import {
|
|
|
4021
4073
|
CODEX_REFRESH_FALLBACK_MS,
|
|
4022
4074
|
CODEX_REFRESH_WINDOW_MS,
|
|
4023
4075
|
CodexReloginRequired,
|
|
4076
|
+
fetchCodexRateLimitResetCredits,
|
|
4024
4077
|
fetchCodexUsage,
|
|
4025
4078
|
normalizeCodexUsage,
|
|
4026
4079
|
refreshCodexToken
|
|
4027
4080
|
} from "@opengeni/codex";
|
|
4028
4081
|
var inflight = /* @__PURE__ */ new Map();
|
|
4082
|
+
var CODEX_TOKEN_REFRESH_TIMEOUT_MS = 6e3;
|
|
4083
|
+
var systemCodexTokenDeadlineClock = {
|
|
4084
|
+
setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
|
|
4085
|
+
clearTimeout: (handle) => globalThis.clearTimeout(handle)
|
|
4086
|
+
};
|
|
4087
|
+
async function withCodexTokenDeadline(operation, options = {}) {
|
|
4088
|
+
const timeoutMs = options.timeoutMs ?? CODEX_TOKEN_REFRESH_TIMEOUT_MS;
|
|
4089
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
4090
|
+
throw new Error("Codex token refresh timeout must be positive");
|
|
4091
|
+
}
|
|
4092
|
+
const clock = options.clock ?? systemCodexTokenDeadlineClock;
|
|
4093
|
+
const signal = options.signal;
|
|
4094
|
+
return await new Promise((resolve, reject) => {
|
|
4095
|
+
let settled = false;
|
|
4096
|
+
let timeout;
|
|
4097
|
+
const cleanup = () => {
|
|
4098
|
+
if (timeout !== void 0) {
|
|
4099
|
+
clock.clearTimeout(timeout);
|
|
4100
|
+
timeout = void 0;
|
|
4101
|
+
}
|
|
4102
|
+
signal?.removeEventListener("abort", onAbort);
|
|
4103
|
+
};
|
|
4104
|
+
const settle = (outcome) => {
|
|
4105
|
+
if (settled) return;
|
|
4106
|
+
settled = true;
|
|
4107
|
+
cleanup();
|
|
4108
|
+
if (outcome.kind === "resolve") {
|
|
4109
|
+
resolve(outcome.value);
|
|
4110
|
+
} else {
|
|
4111
|
+
reject(outcome.error);
|
|
4112
|
+
}
|
|
4113
|
+
};
|
|
4114
|
+
const onAbort = () => {
|
|
4115
|
+
settle({
|
|
4116
|
+
kind: "reject",
|
|
4117
|
+
error: signal?.reason ?? new Error("Codex token refresh cancelled")
|
|
4118
|
+
});
|
|
4119
|
+
};
|
|
4120
|
+
if (signal?.aborted) {
|
|
4121
|
+
onAbort();
|
|
4122
|
+
} else {
|
|
4123
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4124
|
+
timeout = clock.setTimeout(
|
|
4125
|
+
() => settle({ kind: "reject", error: new Error("Codex token refresh timed out") }),
|
|
4126
|
+
timeoutMs
|
|
4127
|
+
);
|
|
4128
|
+
}
|
|
4129
|
+
void Promise.resolve(operation).then(
|
|
4130
|
+
(value) => settle({ kind: "resolve", value }),
|
|
4131
|
+
(error) => settle({ kind: "reject", error })
|
|
4132
|
+
);
|
|
4133
|
+
});
|
|
4134
|
+
}
|
|
4029
4135
|
var defaultDeps = {
|
|
4030
4136
|
loadCredential: loadCodexCredentialForRun,
|
|
4031
4137
|
recordRefresh: recordCodexTokenRefresh,
|
|
@@ -4043,7 +4149,7 @@ function buildCodexTokenResolver(db, settings, workspaceId, credentialId, deps =
|
|
|
4043
4149
|
});
|
|
4044
4150
|
const performRefresh = async (refreshDb, cred) => {
|
|
4045
4151
|
try {
|
|
4046
|
-
const next = await deps.refresh(cred.tokens.refreshToken);
|
|
4152
|
+
const next = await withCodexTokenDeadline(deps.refresh(cred.tokens.refreshToken));
|
|
4047
4153
|
const tokens = {
|
|
4048
4154
|
access_token: next.accessToken ?? cred.tokens.accessToken,
|
|
4049
4155
|
refresh_token: next.refreshToken ?? cred.tokens.refreshToken,
|
|
@@ -4136,10 +4242,11 @@ function errorUsagePayload(reason) {
|
|
|
4136
4242
|
weekly: null,
|
|
4137
4243
|
limitReached: false,
|
|
4138
4244
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4245
|
+
rateLimitResetCredits: null,
|
|
4139
4246
|
...reason ? { reason } : {}
|
|
4140
4247
|
};
|
|
4141
4248
|
}
|
|
4142
|
-
async function fetchCodexUsageForAccount(db, settings, workspaceId, credentialId) {
|
|
4249
|
+
async function fetchCodexUsageForAccount(db, settings, workspaceId, credentialId, fetchImpl = fetch) {
|
|
4143
4250
|
const resolver = buildCodexTokenResolver(db, settings, workspaceId, credentialId);
|
|
4144
4251
|
let token;
|
|
4145
4252
|
try {
|
|
@@ -4149,34 +4256,74 @@ async function fetchCodexUsageForAccount(db, settings, workspaceId, credentialId
|
|
|
4149
4256
|
}
|
|
4150
4257
|
let normalized;
|
|
4151
4258
|
try {
|
|
4152
|
-
const usage = await fetchCodexUsage(
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4259
|
+
const usage = await fetchCodexUsage(
|
|
4260
|
+
{
|
|
4261
|
+
accessToken: token.accessToken,
|
|
4262
|
+
chatgptAccountId: token.chatgptAccountId,
|
|
4263
|
+
isFedramp: token.isFedramp,
|
|
4264
|
+
clientVersion: CODEX_CLIENT_VERSION
|
|
4265
|
+
},
|
|
4266
|
+
fetchImpl
|
|
4267
|
+
);
|
|
4158
4268
|
normalized = normalizeCodexUsage(usage.status, usage.payload);
|
|
4159
4269
|
} catch {
|
|
4160
4270
|
return errorUsagePayload();
|
|
4161
4271
|
}
|
|
4162
|
-
|
|
4272
|
+
const parsedQuota = normalized.status !== "error" && (normalized.fiveHour != null || normalized.weekly != null);
|
|
4273
|
+
if (parsedQuota || normalized.rateLimitResetCredits) {
|
|
4274
|
+
const checkedAt = /* @__PURE__ */ new Date();
|
|
4163
4275
|
await recordCodexAccountUsage(db, workspaceId, credentialId, {
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4276
|
+
...parsedQuota ? {
|
|
4277
|
+
primaryUsedPercent: normalized.fiveHour?.percent ?? null,
|
|
4278
|
+
primaryResetAt: normalized.fiveHour?.resetAt ? new Date(normalized.fiveHour.resetAt) : null,
|
|
4279
|
+
secondaryUsedPercent: normalized.weekly?.percent ?? null,
|
|
4280
|
+
secondaryResetAt: normalized.weekly?.resetAt ? new Date(normalized.weekly.resetAt) : null,
|
|
4281
|
+
checkedAt
|
|
4282
|
+
} : {},
|
|
4283
|
+
...normalized.rateLimitResetCredits ? {
|
|
4284
|
+
resetCreditAvailableCount: normalized.rateLimitResetCredits.availableCount,
|
|
4285
|
+
resetCreditsCheckedAt: checkedAt
|
|
4286
|
+
} : {}
|
|
4169
4287
|
}).catch(() => void 0);
|
|
4170
4288
|
}
|
|
4171
4289
|
return normalized;
|
|
4172
4290
|
}
|
|
4291
|
+
async function fetchCodexRateLimitResetCreditsForAccount(db, settings, workspaceId, credentialId, fetchImpl = fetch) {
|
|
4292
|
+
const resolver = buildCodexTokenResolver(db, settings, workspaceId, credentialId);
|
|
4293
|
+
let token;
|
|
4294
|
+
try {
|
|
4295
|
+
token = await resolver.getToken();
|
|
4296
|
+
} catch (error) {
|
|
4297
|
+
return {
|
|
4298
|
+
ok: false,
|
|
4299
|
+
status: 0,
|
|
4300
|
+
reason: error instanceof CodexReloginRequired ? "needs_relogin" : "network_error"
|
|
4301
|
+
};
|
|
4302
|
+
}
|
|
4303
|
+
return await fetchCodexRateLimitResetCredits(
|
|
4304
|
+
{
|
|
4305
|
+
accessToken: token.accessToken,
|
|
4306
|
+
chatgptAccountId: token.chatgptAccountId,
|
|
4307
|
+
isFedramp: token.isFedramp,
|
|
4308
|
+
clientVersion: CODEX_CLIENT_VERSION
|
|
4309
|
+
},
|
|
4310
|
+
fetchImpl
|
|
4311
|
+
);
|
|
4312
|
+
}
|
|
4173
4313
|
|
|
4174
4314
|
// src/connection-token-resolver.ts
|
|
4175
4315
|
import {
|
|
4176
4316
|
environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes2
|
|
4177
4317
|
} from "@opengeni/config";
|
|
4318
|
+
import {
|
|
4319
|
+
OAUTH_MAX_RESPONSE_BYTES,
|
|
4320
|
+
pinnedFetch,
|
|
4321
|
+
readResponseJsonBounded,
|
|
4322
|
+
undiciFetch,
|
|
4323
|
+
validateHttpUrl
|
|
4324
|
+
} from "@opengeni/network";
|
|
4325
|
+
import { isPrivateAddress } from "@opengeni/network";
|
|
4178
4326
|
import { Buffer as Buffer2 } from "buffer";
|
|
4179
|
-
import { lookup } from "dns/promises";
|
|
4180
4327
|
import { isIP } from "net";
|
|
4181
4328
|
var HostMcpCredentialScopeError = class extends Error {
|
|
4182
4329
|
constructor(field) {
|
|
@@ -4195,6 +4342,10 @@ function buildHostConnectionTokenResolver(resolve, context) {
|
|
|
4195
4342
|
if (input.workspaceId !== context.workspaceId) {
|
|
4196
4343
|
throw new HostMcpCredentialScopeError("workspaceId");
|
|
4197
4344
|
}
|
|
4345
|
+
const destinationUrl = canonicalHttpUrl(input.destinationUrl);
|
|
4346
|
+
if (!destinationUrl || !destinationHostMatchesProvider(destinationUrl, input.connectionRef.providerDomain)) {
|
|
4347
|
+
throw new HostMcpCredentialBindingError("destinationUrl");
|
|
4348
|
+
}
|
|
4198
4349
|
const toolName = input.toolName ?? input.toolId;
|
|
4199
4350
|
const request = {
|
|
4200
4351
|
accountId: context.accountId,
|
|
@@ -4207,6 +4358,7 @@ function buildHostConnectionTokenResolver(resolve, context) {
|
|
|
4207
4358
|
initiator: context.initiator,
|
|
4208
4359
|
initiatorContext: { ...context.initiatorContext },
|
|
4209
4360
|
surface: context.surface,
|
|
4361
|
+
destinationUrl,
|
|
4210
4362
|
serverId: input.serverId,
|
|
4211
4363
|
connectionRef: {
|
|
4212
4364
|
providerDomain: input.connectionRef.providerDomain,
|
|
@@ -4384,10 +4536,13 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
|
|
|
4384
4536
|
}
|
|
4385
4537
|
return deps.loadCredential(db, settings, request);
|
|
4386
4538
|
};
|
|
4387
|
-
const snapshot = async (cred, ref) => {
|
|
4539
|
+
const snapshot = async (cred, ref, destinationUrl) => {
|
|
4388
4540
|
if (cred.status !== "active") {
|
|
4389
4541
|
return authNeededForStatus(cred, ref);
|
|
4390
4542
|
}
|
|
4543
|
+
if (!connectionBindingMatches(cred, ref, destinationUrl)) {
|
|
4544
|
+
return authNeeded(ref, "missing_connection", cred.id);
|
|
4545
|
+
}
|
|
4391
4546
|
const missingScopes = missingRequestedScopes(ref.scopes, cred.grantedScopes);
|
|
4392
4547
|
if (missingScopes.length > 0) {
|
|
4393
4548
|
return {
|
|
@@ -4443,7 +4598,6 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
|
|
|
4443
4598
|
if (persisted) {
|
|
4444
4599
|
const current = await load({
|
|
4445
4600
|
workspaceId: cred.workspaceId,
|
|
4446
|
-
serverId: "",
|
|
4447
4601
|
connectionRef: { ...ref, connectionId: cred.id }
|
|
4448
4602
|
});
|
|
4449
4603
|
if (current) {
|
|
@@ -4452,7 +4606,6 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
|
|
|
4452
4606
|
}
|
|
4453
4607
|
const winner = await load({
|
|
4454
4608
|
workspaceId: cred.workspaceId,
|
|
4455
|
-
serverId: "",
|
|
4456
4609
|
connectionRef: { ...ref, connectionId: cred.id }
|
|
4457
4610
|
});
|
|
4458
4611
|
if (winner?.status === "active") {
|
|
@@ -4491,6 +4644,9 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
|
|
|
4491
4644
|
if (cred.status !== "active") {
|
|
4492
4645
|
return authNeededForStatus(cred, ref);
|
|
4493
4646
|
}
|
|
4647
|
+
if (!connectionBindingMatches(cred, ref, input.destinationUrl)) {
|
|
4648
|
+
return authNeeded(ref, "missing_connection", cred.id);
|
|
4649
|
+
}
|
|
4494
4650
|
if (shouldRefresh(cred, input.forceRefresh === true, deps.now())) {
|
|
4495
4651
|
try {
|
|
4496
4652
|
cred = await refreshSingleFlight(cred, ref);
|
|
@@ -4510,9 +4666,54 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
|
|
|
4510
4666
|
return authNeeded(ref, "refresh_failed", cred.id);
|
|
4511
4667
|
}
|
|
4512
4668
|
}
|
|
4513
|
-
return await snapshot(cred, ref);
|
|
4669
|
+
return await snapshot(cred, ref, input.destinationUrl);
|
|
4514
4670
|
};
|
|
4515
4671
|
}
|
|
4672
|
+
function connectionBindingMatches(cred, ref, destinationUrl) {
|
|
4673
|
+
if (cred.providerDomain.toLowerCase() !== ref.providerDomain.toLowerCase()) return false;
|
|
4674
|
+
if (ref.kind && cred.kind !== ref.kind) return false;
|
|
4675
|
+
const credential = cred.credential;
|
|
4676
|
+
const metadata = cred.metadata;
|
|
4677
|
+
const boundMcpUrl = stringValue(credential.mcp_url) ?? stringValue(metadata.mcpUrl);
|
|
4678
|
+
const destination = canonicalHttpUrl(destinationUrl);
|
|
4679
|
+
if (!destination) return false;
|
|
4680
|
+
if (boundMcpUrl) {
|
|
4681
|
+
const binding = canonicalHttpUrl(boundMcpUrl);
|
|
4682
|
+
if (!binding || destination !== binding) return false;
|
|
4683
|
+
} else if (!destinationHostMatchesProvider(destination, cred.providerDomain)) {
|
|
4684
|
+
return false;
|
|
4685
|
+
}
|
|
4686
|
+
if (cred.kind !== "oauth2") return true;
|
|
4687
|
+
const boundResource = stringValue(credential.resource) ?? stringValue(metadata.resource);
|
|
4688
|
+
if (ref.resource) {
|
|
4689
|
+
if (!boundResource) return false;
|
|
4690
|
+
if (canonicalResource(ref.resource) !== canonicalResource(boundResource)) return false;
|
|
4691
|
+
}
|
|
4692
|
+
return true;
|
|
4693
|
+
}
|
|
4694
|
+
function destinationHostMatchesProvider(destinationUrl, providerDomain) {
|
|
4695
|
+
const destinationHost = new URL(destinationUrl).hostname.toLowerCase();
|
|
4696
|
+
const provider = providerDomain.trim().toLowerCase().replace(/^\.+|\.+$/g, "");
|
|
4697
|
+
return Boolean(provider) && (destinationHost === provider || destinationHost.endsWith(`.${provider}`));
|
|
4698
|
+
}
|
|
4699
|
+
function canonicalHttpUrl(value) {
|
|
4700
|
+
try {
|
|
4701
|
+
const url = new URL(value);
|
|
4702
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
4703
|
+
url.hash = "";
|
|
4704
|
+
url.hostname = url.hostname.toLowerCase();
|
|
4705
|
+
if (url.protocol === "https:" && url.port === "443" || url.protocol === "http:" && url.port === "80") {
|
|
4706
|
+
url.port = "";
|
|
4707
|
+
}
|
|
4708
|
+
url.pathname = url.pathname.replace(/\/+$/, "") || "/";
|
|
4709
|
+
return url.toString();
|
|
4710
|
+
} catch {
|
|
4711
|
+
return null;
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
function canonicalResource(value) {
|
|
4715
|
+
return canonicalHttpUrl(value) ?? value.trim();
|
|
4716
|
+
}
|
|
4516
4717
|
var ConnectionRefreshHttpError = class extends Error {
|
|
4517
4718
|
httpStatus;
|
|
4518
4719
|
constructor(httpStatus) {
|
|
@@ -4583,7 +4784,7 @@ function headersForCredential(cred) {
|
|
|
4583
4784
|
}
|
|
4584
4785
|
return stringRecord(cred.credential.headers);
|
|
4585
4786
|
}
|
|
4586
|
-
async function refreshOAuthConnectionCredential(cred, ref, settings) {
|
|
4787
|
+
async function refreshOAuthConnectionCredential(cred, ref, settings, transportOptions = {}) {
|
|
4587
4788
|
if (cred.kind !== "oauth2") {
|
|
4588
4789
|
return {
|
|
4589
4790
|
credential: cred.credential,
|
|
@@ -4596,8 +4797,14 @@ async function refreshOAuthConnectionCredential(cred, ref, settings) {
|
|
|
4596
4797
|
if (!refreshToken || !tokenEndpoint) {
|
|
4597
4798
|
throw new Error("connection has no refresh token endpoint");
|
|
4598
4799
|
}
|
|
4599
|
-
|
|
4600
|
-
|
|
4800
|
+
let validatedTokenEndpoint;
|
|
4801
|
+
try {
|
|
4802
|
+
validatedTokenEndpoint = validateHttpUrl(tokenEndpoint, {
|
|
4803
|
+
label: "OAuth refresh token endpoint",
|
|
4804
|
+
allowLoopbackHttp: settings.environment === "local" || settings.environment === "test"
|
|
4805
|
+
});
|
|
4806
|
+
} catch {
|
|
4807
|
+
throw new Error("connection has an invalid refresh token endpoint");
|
|
4601
4808
|
}
|
|
4602
4809
|
const body = new URLSearchParams();
|
|
4603
4810
|
body.set("grant_type", "refresh_token");
|
|
@@ -4623,20 +4830,35 @@ async function refreshOAuthConnectionCredential(cred, ref, settings) {
|
|
|
4623
4830
|
if (ref.scopes?.length) {
|
|
4624
4831
|
body.set("scope", ref.scopes.join(" "));
|
|
4625
4832
|
}
|
|
4626
|
-
const response = await
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4833
|
+
const response = await pinnedFetch(
|
|
4834
|
+
validatedTokenEndpoint,
|
|
4835
|
+
{
|
|
4836
|
+
method: "POST",
|
|
4837
|
+
headers,
|
|
4838
|
+
body,
|
|
4839
|
+
signal: AbortSignal.timeout(CONNECTION_REFRESH_TIMEOUT_MS)
|
|
4840
|
+
},
|
|
4841
|
+
settings,
|
|
4842
|
+
{
|
|
4843
|
+
fetchImpl: transportOptions.fetchImpl ?? undiciFetch,
|
|
4844
|
+
...transportOptions.dnsLookup ? { dnsLookup: transportOptions.dnsLookup } : {},
|
|
4845
|
+
label: "OAuth token endpoint",
|
|
4846
|
+
requireHttpsOutsideLocalTest: true
|
|
4847
|
+
}
|
|
4848
|
+
);
|
|
4633
4849
|
if (response.status >= 300 && response.status < 400) {
|
|
4850
|
+
await cancelResponseBody(response);
|
|
4634
4851
|
throw new ConnectionRefreshHttpError(response.status);
|
|
4635
4852
|
}
|
|
4636
4853
|
if (!response.ok) {
|
|
4854
|
+
await cancelResponseBody(response);
|
|
4637
4855
|
throw new ConnectionRefreshHttpError(response.status);
|
|
4638
4856
|
}
|
|
4639
|
-
const payload = await
|
|
4857
|
+
const payload = await readResponseJsonBounded(
|
|
4858
|
+
response,
|
|
4859
|
+
OAUTH_MAX_RESPONSE_BYTES,
|
|
4860
|
+
"OAuth refresh token response"
|
|
4861
|
+
);
|
|
4640
4862
|
const accessToken = stringValue(payload.access_token);
|
|
4641
4863
|
if (!accessToken) {
|
|
4642
4864
|
throw new Error("connection refresh response did not include access_token");
|
|
@@ -4687,74 +4909,8 @@ function stringRecord(value) {
|
|
|
4687
4909
|
function stringValue(value) {
|
|
4688
4910
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
4689
4911
|
}
|
|
4690
|
-
async function
|
|
4691
|
-
|
|
4692
|
-
return;
|
|
4693
|
-
}
|
|
4694
|
-
const url = new URL(rawUrl);
|
|
4695
|
-
if (url.protocol !== "https:") {
|
|
4696
|
-
throw new Error("OAuth token endpoint must use https outside local/test");
|
|
4697
|
-
}
|
|
4698
|
-
const hostname = url.hostname.toLowerCase();
|
|
4699
|
-
if (hostname === "localhost" || hostname.endsWith(".localhost")) {
|
|
4700
|
-
throw new Error("OAuth token endpoint may not target localhost");
|
|
4701
|
-
}
|
|
4702
|
-
const literal = isIP(hostname);
|
|
4703
|
-
const addresses = literal ? [hostname] : (await lookup(hostname, { all: true })).map((entry) => entry.address);
|
|
4704
|
-
if (addresses.some(isPrivateAddress)) {
|
|
4705
|
-
throw new Error("OAuth token endpoint may not target a private network address");
|
|
4706
|
-
}
|
|
4707
|
-
}
|
|
4708
|
-
function isPrivateAddress(address) {
|
|
4709
|
-
const normalized = normalizeAddress(address);
|
|
4710
|
-
const mapped = ipv4FromMappedIpv6(normalized);
|
|
4711
|
-
if (mapped) {
|
|
4712
|
-
return isPrivateIpv4Address(mapped);
|
|
4713
|
-
}
|
|
4714
|
-
if (normalized.includes(":")) {
|
|
4715
|
-
if (isIP(normalized) !== 6) {
|
|
4716
|
-
return true;
|
|
4717
|
-
}
|
|
4718
|
-
return normalized === "::1" || normalized === "::" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb");
|
|
4719
|
-
}
|
|
4720
|
-
return isPrivateIpv4Address(normalized);
|
|
4721
|
-
}
|
|
4722
|
-
function normalizeAddress(address) {
|
|
4723
|
-
const trimmed = address.trim().toLowerCase();
|
|
4724
|
-
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
4725
|
-
return trimmed.slice(1, -1);
|
|
4726
|
-
}
|
|
4727
|
-
return trimmed;
|
|
4728
|
-
}
|
|
4729
|
-
function ipv4FromMappedIpv6(address) {
|
|
4730
|
-
if (!address.startsWith("::ffff:")) {
|
|
4731
|
-
return null;
|
|
4732
|
-
}
|
|
4733
|
-
const embedded = address.slice("::ffff:".length);
|
|
4734
|
-
if (embedded.includes(".")) {
|
|
4735
|
-
return embedded;
|
|
4736
|
-
}
|
|
4737
|
-
const parts = embedded.split(":");
|
|
4738
|
-
if (parts.length !== 2 || parts.some((part) => !/^[0-9a-f]{1,4}$/.test(part))) {
|
|
4739
|
-
return null;
|
|
4740
|
-
}
|
|
4741
|
-
const high = Number.parseInt(parts[0], 16);
|
|
4742
|
-
const low = Number.parseInt(parts[1], 16);
|
|
4743
|
-
if (!Number.isInteger(high) || !Number.isInteger(low) || high < 0 || high > 65535 || low < 0 || low > 65535) {
|
|
4744
|
-
return null;
|
|
4745
|
-
}
|
|
4746
|
-
return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
|
|
4747
|
-
}
|
|
4748
|
-
function isPrivateIpv4Address(address) {
|
|
4749
|
-
if (isIP(address) !== 4) {
|
|
4750
|
-
return true;
|
|
4751
|
-
}
|
|
4752
|
-
const parts = address.split(".").map((part) => Number(part));
|
|
4753
|
-
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
4754
|
-
return true;
|
|
4755
|
-
}
|
|
4756
|
-
const [a, b] = parts;
|
|
4757
|
-
return a === 0 || a === 10 || a === 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
|
|
4912
|
+
async function cancelResponseBody(response) {
|
|
4913
|
+
await response.body?.cancel().catch(() => void 0);
|
|
4758
4914
|
}
|
|
4759
4915
|
|
|
4760
4916
|
// src/index.ts
|
|
@@ -6171,6 +6327,27 @@ async function requireFile(db, workspaceId, fileId) {
|
|
|
6171
6327
|
}
|
|
6172
6328
|
return file;
|
|
6173
6329
|
}
|
|
6330
|
+
async function getRetainedFileArtifact(db, workspaceId, fileId) {
|
|
6331
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
6332
|
+
const [row] = await scopedDb.select({
|
|
6333
|
+
file: files,
|
|
6334
|
+
uploadStatus: fileUploads.status,
|
|
6335
|
+
uploadExpiresAt: fileUploads.expiresAt
|
|
6336
|
+
}).from(files).leftJoin(
|
|
6337
|
+
fileUploads,
|
|
6338
|
+
and5(
|
|
6339
|
+
eq5(fileUploads.workspaceId, files.workspaceId),
|
|
6340
|
+
eq5(fileUploads.fileId, files.id)
|
|
6341
|
+
)
|
|
6342
|
+
).where(and5(eq5(files.workspaceId, workspaceId), eq5(files.id, fileId))).orderBy(desc(fileUploads.createdAt)).limit(1);
|
|
6343
|
+
if (!row) return null;
|
|
6344
|
+
return {
|
|
6345
|
+
file: mapFile(row.file),
|
|
6346
|
+
uploadStatus: row.uploadStatus,
|
|
6347
|
+
uploadExpiresAt: row.uploadExpiresAt
|
|
6348
|
+
};
|
|
6349
|
+
});
|
|
6350
|
+
}
|
|
6174
6351
|
async function getFileUpload(db, workspaceId, uploadId) {
|
|
6175
6352
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
6176
6353
|
const [row] = await scopedDb.select({
|
|
@@ -6728,7 +6905,14 @@ async function listCapabilityCatalogItems(db, workspaceId) {
|
|
|
6728
6905
|
)
|
|
6729
6906
|
)
|
|
6730
6907
|
).orderBy(asc3(capabilityCatalogItems.kind), asc3(capabilityCatalogItems.name));
|
|
6731
|
-
|
|
6908
|
+
const installations = await scopedDb.select().from(capabilityInstallations).where(eq5(capabilityInstallations.workspaceId, workspaceId));
|
|
6909
|
+
const installationByCapabilityId = new Map(
|
|
6910
|
+
installations.map((installation) => [installation.capabilityId, installation])
|
|
6911
|
+
);
|
|
6912
|
+
return rows.flatMap((row) => {
|
|
6913
|
+
const exposure = catalogExposureState(row, installationByCapabilityId.get(row.id) ?? null);
|
|
6914
|
+
return exposure === "blocked" ? [] : [mapCapabilityCatalogItem(row, exposure)];
|
|
6915
|
+
});
|
|
6732
6916
|
});
|
|
6733
6917
|
}
|
|
6734
6918
|
async function getCapabilityCatalogItem(db, workspaceId, capabilityId) {
|
|
@@ -6742,7 +6926,17 @@ async function getCapabilityCatalogItem(db, workspaceId, capabilityId) {
|
|
|
6742
6926
|
)
|
|
6743
6927
|
)
|
|
6744
6928
|
).orderBy(asc3(sql4`(${capabilityCatalogItems.workspaceId} is null)`)).limit(1);
|
|
6745
|
-
|
|
6929
|
+
if (!row) {
|
|
6930
|
+
return null;
|
|
6931
|
+
}
|
|
6932
|
+
const [installation] = await scopedDb.select().from(capabilityInstallations).where(
|
|
6933
|
+
and5(
|
|
6934
|
+
eq5(capabilityInstallations.workspaceId, workspaceId),
|
|
6935
|
+
eq5(capabilityInstallations.capabilityId, capabilityId)
|
|
6936
|
+
)
|
|
6937
|
+
).limit(1);
|
|
6938
|
+
const exposure = catalogExposureState(row, installation ?? null);
|
|
6939
|
+
return exposure === "blocked" ? null : mapCapabilityCatalogItem(row, exposure);
|
|
6746
6940
|
});
|
|
6747
6941
|
}
|
|
6748
6942
|
async function enableCapabilityInstallation(db, input) {
|
|
@@ -6862,7 +7056,7 @@ async function listEnabledMcpCapabilityServers(db, workspaceId) {
|
|
|
6862
7056
|
}
|
|
6863
7057
|
}
|
|
6864
7058
|
return [...preferredByInstallation.values()].flatMap(({ item, installation }) => {
|
|
6865
|
-
if (!item.endpointUrl || !mcpConnectivityOk(installation.metadata)) {
|
|
7059
|
+
if (catalogExposureState(item, installation) === "blocked" || !item.endpointUrl || !mcpConnectivityOk(installation.metadata)) {
|
|
6866
7060
|
return [];
|
|
6867
7061
|
}
|
|
6868
7062
|
const headersEncrypted = encryptedHeadersConfig(installation.config.headersEncrypted);
|
|
@@ -9403,6 +9597,30 @@ async function upsertCodexSubscriptionCredential(db, input) {
|
|
|
9403
9597
|
db,
|
|
9404
9598
|
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
9405
9599
|
async (scopedDb) => {
|
|
9600
|
+
await scopedDb.execute(
|
|
9601
|
+
sql4`select pg_advisory_xact_lock(hashtextextended(${`codex-credential-upsert:${input.workspaceId}:${input.chatgptAccountId ?? "null"}`}, 0))`
|
|
9602
|
+
);
|
|
9603
|
+
const [existing] = input.chatgptAccountId ? await scopedDb.select({
|
|
9604
|
+
id: codexSubscriptionCredentials.id,
|
|
9605
|
+
connectedBySubjectId: codexSubscriptionCredentials.connectedBySubjectId
|
|
9606
|
+
}).from(codexSubscriptionCredentials).where(
|
|
9607
|
+
and5(
|
|
9608
|
+
eq5(codexSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
9609
|
+
eq5(codexSubscriptionCredentials.chatgptAccountId, input.chatgptAccountId)
|
|
9610
|
+
)
|
|
9611
|
+
).for("update").limit(1) : [];
|
|
9612
|
+
if (existing && existing.connectedBySubjectId !== (input.connectedBySubjectId ?? null)) {
|
|
9613
|
+
const [unresolved] = await scopedDb.select({ id: codexResetRedemptionAttempts.id }).from(codexResetRedemptionAttempts).where(
|
|
9614
|
+
and5(
|
|
9615
|
+
eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
|
|
9616
|
+
eq5(codexResetRedemptionAttempts.credentialId, existing.id),
|
|
9617
|
+
eq5(codexResetRedemptionAttempts.status, "provider_started")
|
|
9618
|
+
)
|
|
9619
|
+
).limit(1);
|
|
9620
|
+
if (unresolved) {
|
|
9621
|
+
return { kind: "unresolved_redemption", id: existing.id, isNew: false };
|
|
9622
|
+
}
|
|
9623
|
+
}
|
|
9406
9624
|
const now = /* @__PURE__ */ new Date();
|
|
9407
9625
|
const [row] = await scopedDb.insert(codexSubscriptionCredentials).values({
|
|
9408
9626
|
accountId: input.accountId,
|
|
@@ -9416,6 +9634,7 @@ async function upsertCodexSubscriptionCredential(db, input) {
|
|
|
9416
9634
|
lastRefreshAt: input.lastRefreshAt,
|
|
9417
9635
|
accountEmail: input.accountEmail ?? null,
|
|
9418
9636
|
label: input.label ?? null,
|
|
9637
|
+
connectedBySubjectId: input.connectedBySubjectId ?? null,
|
|
9419
9638
|
status: "active",
|
|
9420
9639
|
lastError: null
|
|
9421
9640
|
}).onConflictDoUpdate({
|
|
@@ -9443,6 +9662,11 @@ async function upsertCodexSubscriptionCredential(db, input) {
|
|
|
9443
9662
|
// it when still null) so a re-connect never clobbers a rename.
|
|
9444
9663
|
accountEmail: input.accountEmail ?? null,
|
|
9445
9664
|
label: sql4`coalesce(${codexSubscriptionCredentials.label}, ${input.label ?? null})`,
|
|
9665
|
+
// Ownership follows the most recent connection exactly. A
|
|
9666
|
+
// configured/delegated/API-key reconnect is intentionally
|
|
9667
|
+
// nonhuman and clears the prior human owner, making the row
|
|
9668
|
+
// view-only until a direct managed-cookie human reconnects it.
|
|
9669
|
+
connectedBySubjectId: input.connectedBySubjectId ?? null,
|
|
9446
9670
|
status: "active",
|
|
9447
9671
|
lastError: null,
|
|
9448
9672
|
version: sql4`${codexSubscriptionCredentials.version} + 1`,
|
|
@@ -9457,7 +9681,7 @@ async function upsertCodexSubscriptionCredential(db, input) {
|
|
|
9457
9681
|
throw new Error("upsertCodexSubscriptionCredential returned no row");
|
|
9458
9682
|
}
|
|
9459
9683
|
const isNew = row.createdAt.getTime() === row.updatedAt.getTime();
|
|
9460
|
-
return { id: row.id, isNew };
|
|
9684
|
+
return { kind: "upserted", id: row.id, isNew };
|
|
9461
9685
|
}
|
|
9462
9686
|
);
|
|
9463
9687
|
}
|
|
@@ -10766,6 +10990,12 @@ async function listCodexAccountStatuses(db, workspaceId) {
|
|
|
10766
10990
|
planType: codexSubscriptionCredentials.planType,
|
|
10767
10991
|
status: codexSubscriptionCredentials.status,
|
|
10768
10992
|
allocatorEnabled: codexSubscriptionCredentials.allocatorEnabled,
|
|
10993
|
+
allocatorVersion: codexSubscriptionCredentials.allocatorVersion,
|
|
10994
|
+
allocatorUpdatedBySubjectId: codexSubscriptionCredentials.allocatorUpdatedBySubjectId,
|
|
10995
|
+
allocatorUpdatedAt: codexSubscriptionCredentials.allocatorUpdatedAt,
|
|
10996
|
+
resetCreditAvailableCount: codexSubscriptionCredentials.resetCreditAvailableCount,
|
|
10997
|
+
resetCreditsCheckedAt: codexSubscriptionCredentials.resetCreditsCheckedAt,
|
|
10998
|
+
connectedBySubjectId: codexSubscriptionCredentials.connectedBySubjectId,
|
|
10769
10999
|
expiresAt: codexSubscriptionCredentials.expiresAt,
|
|
10770
11000
|
lastRefreshAt: codexSubscriptionCredentials.lastRefreshAt,
|
|
10771
11001
|
lastError: codexSubscriptionCredentials.lastError,
|
|
@@ -10787,6 +11017,8 @@ async function listCodexAccountStatuses(db, workspaceId) {
|
|
|
10787
11017
|
...row,
|
|
10788
11018
|
expiresAt: codexMetadataDate(row.expiresAt),
|
|
10789
11019
|
lastRefreshAt: codexMetadataDate(row.lastRefreshAt),
|
|
11020
|
+
allocatorUpdatedAt: codexMetadataDate(row.allocatorUpdatedAt),
|
|
11021
|
+
resetCreditsCheckedAt: codexMetadataDate(row.resetCreditsCheckedAt),
|
|
10790
11022
|
primaryResetAt: codexMetadataDate(row.primaryResetAt),
|
|
10791
11023
|
secondaryResetAt: codexMetadataDate(row.secondaryResetAt),
|
|
10792
11024
|
usageCheckedAt: codexMetadataDate(row.usageCheckedAt),
|
|
@@ -10796,6 +11028,519 @@ async function listCodexAccountStatuses(db, workspaceId) {
|
|
|
10796
11028
|
}));
|
|
10797
11029
|
});
|
|
10798
11030
|
}
|
|
11031
|
+
async function updateCodexAllocatorEligibility(db, input) {
|
|
11032
|
+
return await withCodexCapacityMutation(
|
|
11033
|
+
db,
|
|
11034
|
+
{ workspaceId: input.workspaceId, reason: "codex_allocator_eligibility_changed" },
|
|
11035
|
+
async (tx) => {
|
|
11036
|
+
const [row] = await tx.select({
|
|
11037
|
+
allocatorEnabled: codexSubscriptionCredentials.allocatorEnabled,
|
|
11038
|
+
allocatorVersion: codexSubscriptionCredentials.allocatorVersion,
|
|
11039
|
+
allocatorUpdatedBySubjectId: codexSubscriptionCredentials.allocatorUpdatedBySubjectId,
|
|
11040
|
+
allocatorUpdatedAt: codexSubscriptionCredentials.allocatorUpdatedAt
|
|
11041
|
+
}).from(codexSubscriptionCredentials).where(
|
|
11042
|
+
and5(
|
|
11043
|
+
eq5(codexSubscriptionCredentials.accountId, input.accountId),
|
|
11044
|
+
eq5(codexSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
11045
|
+
eq5(codexSubscriptionCredentials.id, input.credentialId)
|
|
11046
|
+
)
|
|
11047
|
+
).for("update").limit(1);
|
|
11048
|
+
if (!row) return { result: { kind: "not_found" }, changed: false };
|
|
11049
|
+
const current = {
|
|
11050
|
+
allocatorEnabled: row.allocatorEnabled,
|
|
11051
|
+
allocatorVersion: row.allocatorVersion,
|
|
11052
|
+
allocatorUpdatedBySubjectId: row.allocatorUpdatedBySubjectId,
|
|
11053
|
+
allocatorUpdatedAt: codexMetadataDate(row.allocatorUpdatedAt)
|
|
11054
|
+
};
|
|
11055
|
+
if (row.allocatorEnabled === input.enabled) {
|
|
11056
|
+
return { result: { kind: "unchanged", ...current }, changed: false };
|
|
11057
|
+
}
|
|
11058
|
+
if (row.allocatorVersion !== input.expectedVersion) {
|
|
11059
|
+
return { result: { kind: "conflict", ...current }, changed: false };
|
|
11060
|
+
}
|
|
11061
|
+
const changedAt = /* @__PURE__ */ new Date();
|
|
11062
|
+
const [updated] = await tx.update(codexSubscriptionCredentials).set({
|
|
11063
|
+
allocatorEnabled: input.enabled,
|
|
11064
|
+
allocatorVersion: sql4`${codexSubscriptionCredentials.allocatorVersion} + 1`,
|
|
11065
|
+
allocatorUpdatedBySubjectId: input.subjectId,
|
|
11066
|
+
allocatorUpdatedAt: changedAt
|
|
11067
|
+
// Deliberately no credential version/updatedAt write.
|
|
11068
|
+
}).where(
|
|
11069
|
+
and5(
|
|
11070
|
+
eq5(codexSubscriptionCredentials.accountId, input.accountId),
|
|
11071
|
+
eq5(codexSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
11072
|
+
eq5(codexSubscriptionCredentials.id, input.credentialId),
|
|
11073
|
+
eq5(codexSubscriptionCredentials.allocatorVersion, input.expectedVersion)
|
|
11074
|
+
)
|
|
11075
|
+
).returning({
|
|
11076
|
+
allocatorEnabled: codexSubscriptionCredentials.allocatorEnabled,
|
|
11077
|
+
allocatorVersion: codexSubscriptionCredentials.allocatorVersion,
|
|
11078
|
+
allocatorUpdatedBySubjectId: codexSubscriptionCredentials.allocatorUpdatedBySubjectId,
|
|
11079
|
+
allocatorUpdatedAt: codexSubscriptionCredentials.allocatorUpdatedAt
|
|
11080
|
+
});
|
|
11081
|
+
if (!updated) {
|
|
11082
|
+
throw new Error("Codex allocator row changed while locked");
|
|
11083
|
+
}
|
|
11084
|
+
await tx.insert(auditEvents).values({
|
|
11085
|
+
accountId: input.accountId,
|
|
11086
|
+
workspaceId: input.workspaceId,
|
|
11087
|
+
subjectId: input.subjectId,
|
|
11088
|
+
action: "codex.allocator.updated",
|
|
11089
|
+
targetType: "codex_subscription_credential",
|
|
11090
|
+
targetId: input.credentialId,
|
|
11091
|
+
metadata: {
|
|
11092
|
+
allocatorEnabled: updated.allocatorEnabled,
|
|
11093
|
+
allocatorVersion: updated.allocatorVersion
|
|
11094
|
+
}
|
|
11095
|
+
});
|
|
11096
|
+
return {
|
|
11097
|
+
result: {
|
|
11098
|
+
kind: "updated",
|
|
11099
|
+
allocatorEnabled: updated.allocatorEnabled,
|
|
11100
|
+
allocatorVersion: updated.allocatorVersion,
|
|
11101
|
+
allocatorUpdatedBySubjectId: updated.allocatorUpdatedBySubjectId,
|
|
11102
|
+
allocatorUpdatedAt: codexMetadataDate(updated.allocatorUpdatedAt)
|
|
11103
|
+
},
|
|
11104
|
+
changed: true
|
|
11105
|
+
};
|
|
11106
|
+
}
|
|
11107
|
+
);
|
|
11108
|
+
}
|
|
11109
|
+
var CODEX_RESET_REDEMPTION_OUTCOMES = [
|
|
11110
|
+
"reset",
|
|
11111
|
+
"nothingToReset",
|
|
11112
|
+
"noCredit",
|
|
11113
|
+
"alreadyRedeemed"
|
|
11114
|
+
];
|
|
11115
|
+
function mapCodexResetRedemptionAttempt(row) {
|
|
11116
|
+
return {
|
|
11117
|
+
id: row.id,
|
|
11118
|
+
accountId: row.accountId,
|
|
11119
|
+
workspaceId: row.workspaceId,
|
|
11120
|
+
credentialId: row.credentialId,
|
|
11121
|
+
subjectId: row.subjectId,
|
|
11122
|
+
browserSessionHash: row.browserSessionHash,
|
|
11123
|
+
creditId: row.creditId,
|
|
11124
|
+
upstreamIdempotencyKey: row.upstreamIdempotencyKey,
|
|
11125
|
+
status: row.status,
|
|
11126
|
+
outcome: row.outcome,
|
|
11127
|
+
claimHolderId: row.claimHolderId,
|
|
11128
|
+
claimExpiresAt: codexMetadataDate(row.claimExpiresAt),
|
|
11129
|
+
confirmationExpiresAt: codexMetadataDate(row.confirmationExpiresAt),
|
|
11130
|
+
providerStartedAt: codexMetadataDate(row.providerStartedAt),
|
|
11131
|
+
completedAt: codexMetadataDate(row.completedAt),
|
|
11132
|
+
lastFailureKind: row.lastFailureKind,
|
|
11133
|
+
retryCount: row.retryCount,
|
|
11134
|
+
createdAt: codexMetadataDate(row.createdAt),
|
|
11135
|
+
updatedAt: codexMetadataDate(row.updatedAt)
|
|
11136
|
+
};
|
|
11137
|
+
}
|
|
11138
|
+
async function getCodexResetRedemptionAttempt(db, workspaceId, attemptId) {
|
|
11139
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
11140
|
+
const [row] = await scopedDb.select().from(codexResetRedemptionAttempts).where(
|
|
11141
|
+
and5(
|
|
11142
|
+
eq5(codexResetRedemptionAttempts.workspaceId, workspaceId),
|
|
11143
|
+
eq5(codexResetRedemptionAttempts.id, attemptId)
|
|
11144
|
+
)
|
|
11145
|
+
).limit(1);
|
|
11146
|
+
return row ? mapCodexResetRedemptionAttempt(row) : null;
|
|
11147
|
+
});
|
|
11148
|
+
}
|
|
11149
|
+
async function listCodexResetRedemptionRecoveries(db, input) {
|
|
11150
|
+
return await withRlsContext(
|
|
11151
|
+
db,
|
|
11152
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
11153
|
+
async (scopedDb) => {
|
|
11154
|
+
const rows = await scopedDb.select({
|
|
11155
|
+
attemptId: codexResetRedemptionAttempts.id,
|
|
11156
|
+
credentialId: codexResetRedemptionAttempts.credentialId,
|
|
11157
|
+
creditId: codexResetRedemptionAttempts.creditId,
|
|
11158
|
+
status: codexResetRedemptionAttempts.status,
|
|
11159
|
+
outcome: codexResetRedemptionAttempts.outcome,
|
|
11160
|
+
providerStartedAt: codexResetRedemptionAttempts.providerStartedAt,
|
|
11161
|
+
completedAt: codexResetRedemptionAttempts.completedAt,
|
|
11162
|
+
createdAt: codexResetRedemptionAttempts.createdAt,
|
|
11163
|
+
updatedAt: codexResetRedemptionAttempts.updatedAt
|
|
11164
|
+
}).from(codexResetRedemptionAttempts).innerJoin(
|
|
11165
|
+
codexSubscriptionCredentials,
|
|
11166
|
+
and5(
|
|
11167
|
+
eq5(
|
|
11168
|
+
codexSubscriptionCredentials.id,
|
|
11169
|
+
codexResetRedemptionAttempts.credentialId
|
|
11170
|
+
),
|
|
11171
|
+
eq5(
|
|
11172
|
+
codexSubscriptionCredentials.workspaceId,
|
|
11173
|
+
codexResetRedemptionAttempts.workspaceId
|
|
11174
|
+
)
|
|
11175
|
+
)
|
|
11176
|
+
).where(
|
|
11177
|
+
and5(
|
|
11178
|
+
eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
|
|
11179
|
+
eq5(codexResetRedemptionAttempts.subjectId, input.subjectId),
|
|
11180
|
+
eq5(codexSubscriptionCredentials.connectedBySubjectId, input.subjectId),
|
|
11181
|
+
inArray3(codexResetRedemptionAttempts.status, ["provider_started", "completed"])
|
|
11182
|
+
)
|
|
11183
|
+
).orderBy(desc(codexResetRedemptionAttempts.createdAt));
|
|
11184
|
+
return rows.map((row) => ({
|
|
11185
|
+
attemptId: row.attemptId,
|
|
11186
|
+
credentialId: row.credentialId,
|
|
11187
|
+
creditId: row.creditId,
|
|
11188
|
+
status: row.status,
|
|
11189
|
+
outcome: row.outcome,
|
|
11190
|
+
providerStartedAt: codexMetadataDate(row.providerStartedAt),
|
|
11191
|
+
completedAt: codexMetadataDate(row.completedAt),
|
|
11192
|
+
createdAt: codexMetadataDate(row.createdAt),
|
|
11193
|
+
updatedAt: codexMetadataDate(row.updatedAt)
|
|
11194
|
+
}));
|
|
11195
|
+
}
|
|
11196
|
+
);
|
|
11197
|
+
}
|
|
11198
|
+
async function adoptCodexResetRedemptionAttempt(db, input) {
|
|
11199
|
+
return await withRlsContext(
|
|
11200
|
+
db,
|
|
11201
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
11202
|
+
async (scopedDb) => await scopedDb.transaction(async (tx) => {
|
|
11203
|
+
const [credential] = await tx.select({
|
|
11204
|
+
connectedBySubjectId: codexSubscriptionCredentials.connectedBySubjectId
|
|
11205
|
+
}).from(codexSubscriptionCredentials).where(
|
|
11206
|
+
and5(
|
|
11207
|
+
eq5(codexSubscriptionCredentials.id, input.credentialId),
|
|
11208
|
+
eq5(codexSubscriptionCredentials.workspaceId, input.workspaceId)
|
|
11209
|
+
)
|
|
11210
|
+
).for("share").limit(1);
|
|
11211
|
+
if (!credential) return { kind: "not_found" };
|
|
11212
|
+
if (credential.connectedBySubjectId !== input.subjectId) {
|
|
11213
|
+
return { kind: "forbidden" };
|
|
11214
|
+
}
|
|
11215
|
+
const [attempt] = await tx.select().from(codexResetRedemptionAttempts).where(
|
|
11216
|
+
and5(
|
|
11217
|
+
eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
|
|
11218
|
+
eq5(codexResetRedemptionAttempts.id, input.attemptId)
|
|
11219
|
+
)
|
|
11220
|
+
).for("update").limit(1);
|
|
11221
|
+
if (!attempt) return { kind: "not_found" };
|
|
11222
|
+
if (attempt.accountId !== input.accountId || attempt.credentialId !== input.credentialId || attempt.creditId !== input.creditId || attempt.subjectId !== input.subjectId) {
|
|
11223
|
+
return { kind: "conflict" };
|
|
11224
|
+
}
|
|
11225
|
+
if (attempt.browserSessionHash === input.browserSessionHash) {
|
|
11226
|
+
return { kind: "current", attempt: mapCodexResetRedemptionAttempt(attempt) };
|
|
11227
|
+
}
|
|
11228
|
+
if (attempt.status !== "provider_started" && attempt.status !== "completed") {
|
|
11229
|
+
return { kind: "conflict" };
|
|
11230
|
+
}
|
|
11231
|
+
const claim = await tx.execute(sql4`
|
|
11232
|
+
select claim_expires_at > now() as claim_live
|
|
11233
|
+
from codex_reset_redemption_attempts
|
|
11234
|
+
where workspace_id = ${input.workspaceId} and id = ${input.attemptId}
|
|
11235
|
+
`);
|
|
11236
|
+
if (claim[0]?.claim_live) return { kind: "in_progress" };
|
|
11237
|
+
const [adopted] = await tx.update(codexResetRedemptionAttempts).set({
|
|
11238
|
+
browserSessionHash: input.browserSessionHash,
|
|
11239
|
+
claimHolderId: null,
|
|
11240
|
+
claimExpiresAt: null,
|
|
11241
|
+
updatedAt: sql4`now()`
|
|
11242
|
+
}).where(eq5(codexResetRedemptionAttempts.id, input.attemptId)).returning();
|
|
11243
|
+
if (!adopted) throw new Error("Codex redemption adoption returned no row");
|
|
11244
|
+
return { kind: "adopted", attempt: mapCodexResetRedemptionAttempt(adopted) };
|
|
11245
|
+
})
|
|
11246
|
+
);
|
|
11247
|
+
}
|
|
11248
|
+
async function claimCodexResetRedemption(db, input) {
|
|
11249
|
+
const claimTtlMs = input.claimTtlMs ?? 6e4;
|
|
11250
|
+
if (!Number.isFinite(claimTtlMs) || claimTtlMs <= 0) {
|
|
11251
|
+
throw new Error("Codex redemption claim TTL must be positive");
|
|
11252
|
+
}
|
|
11253
|
+
if (!Number.isFinite(input.confirmationExpiresAt.getTime()) || input.confirmationExpiresAt.getTime() <= Date.now()) {
|
|
11254
|
+
return { kind: "forbidden" };
|
|
11255
|
+
}
|
|
11256
|
+
return await withRlsContext(
|
|
11257
|
+
db,
|
|
11258
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
11259
|
+
async (scopedDb) => await scopedDb.transaction(async (tx) => {
|
|
11260
|
+
await tx.execute(
|
|
11261
|
+
sql4`select pg_advisory_xact_lock(hashtextextended(${`codex-reset-attempt:${input.id}`}, 0))`
|
|
11262
|
+
);
|
|
11263
|
+
await tx.execute(
|
|
11264
|
+
sql4`select pg_advisory_xact_lock(hashtextextended(${`codex-reset-credit:${input.workspaceId}:${input.credentialId}:${input.creditId}`}, 0))`
|
|
11265
|
+
);
|
|
11266
|
+
const [credential] = await tx.select({
|
|
11267
|
+
connectedBySubjectId: codexSubscriptionCredentials.connectedBySubjectId,
|
|
11268
|
+
status: codexSubscriptionCredentials.status
|
|
11269
|
+
}).from(codexSubscriptionCredentials).where(
|
|
11270
|
+
and5(
|
|
11271
|
+
eq5(codexSubscriptionCredentials.accountId, input.accountId),
|
|
11272
|
+
eq5(codexSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
11273
|
+
eq5(codexSubscriptionCredentials.id, input.credentialId)
|
|
11274
|
+
)
|
|
11275
|
+
).for("share").limit(1);
|
|
11276
|
+
if (!credential) return { kind: "not_found" };
|
|
11277
|
+
const [existing] = await tx.select().from(codexResetRedemptionAttempts).where(
|
|
11278
|
+
and5(
|
|
11279
|
+
eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
|
|
11280
|
+
eq5(codexResetRedemptionAttempts.id, input.id)
|
|
11281
|
+
)
|
|
11282
|
+
).for("update").limit(1);
|
|
11283
|
+
const now = /* @__PURE__ */ new Date();
|
|
11284
|
+
if (existing) {
|
|
11285
|
+
if (existing.accountId !== input.accountId || existing.credentialId !== input.credentialId || existing.subjectId !== input.subjectId || existing.browserSessionHash !== input.browserSessionHash || existing.creditId !== input.creditId) {
|
|
11286
|
+
return { kind: "conflict" };
|
|
11287
|
+
}
|
|
11288
|
+
if (credential.connectedBySubjectId !== input.subjectId) {
|
|
11289
|
+
return { kind: "forbidden" };
|
|
11290
|
+
}
|
|
11291
|
+
const mapped = mapCodexResetRedemptionAttempt(existing);
|
|
11292
|
+
if (mapped.status === "completed") return { kind: "completed", attempt: mapped };
|
|
11293
|
+
if (credential.status !== "active") {
|
|
11294
|
+
return { kind: "forbidden" };
|
|
11295
|
+
}
|
|
11296
|
+
const claimState = await tx.execute(sql4`
|
|
11297
|
+
select claim_expires_at > now() as claim_live
|
|
11298
|
+
from codex_reset_redemption_attempts
|
|
11299
|
+
where workspace_id = ${input.workspaceId} and id = ${input.id}
|
|
11300
|
+
`);
|
|
11301
|
+
if (claimState[0]?.claim_live) {
|
|
11302
|
+
return { kind: "in_progress", attempt: mapped };
|
|
11303
|
+
}
|
|
11304
|
+
const [reclaimed] = await tx.update(codexResetRedemptionAttempts).set({
|
|
11305
|
+
claimHolderId: input.claimHolderId,
|
|
11306
|
+
claimExpiresAt: sql4`now() + (${claimTtlMs} * interval '1 millisecond')`,
|
|
11307
|
+
confirmationExpiresAt: input.confirmationExpiresAt,
|
|
11308
|
+
lastFailureKind: null,
|
|
11309
|
+
retryCount: sql4`${codexResetRedemptionAttempts.retryCount} + 1`,
|
|
11310
|
+
updatedAt: now
|
|
11311
|
+
}).where(eq5(codexResetRedemptionAttempts.id, input.id)).returning();
|
|
11312
|
+
if (!reclaimed) throw new Error("Codex redemption reclaim returned no row");
|
|
11313
|
+
return {
|
|
11314
|
+
kind: "claimed",
|
|
11315
|
+
attempt: mapCodexResetRedemptionAttempt(reclaimed)
|
|
11316
|
+
};
|
|
11317
|
+
}
|
|
11318
|
+
if (credential.status !== "active" || credential.connectedBySubjectId !== input.subjectId) {
|
|
11319
|
+
return { kind: "forbidden" };
|
|
11320
|
+
}
|
|
11321
|
+
const [creditAttempt] = await tx.execute(sql4`
|
|
11322
|
+
select id, status, claim_expires_at > now() as claim_live
|
|
11323
|
+
from codex_reset_redemption_attempts
|
|
11324
|
+
where workspace_id = ${input.workspaceId}
|
|
11325
|
+
and credential_id = ${input.credentialId}
|
|
11326
|
+
and credit_id = ${input.creditId}
|
|
11327
|
+
and (status <> 'completed' or outcome in ('reset', 'alreadyRedeemed'))
|
|
11328
|
+
limit 1
|
|
11329
|
+
for update
|
|
11330
|
+
`);
|
|
11331
|
+
if (creditAttempt) {
|
|
11332
|
+
if (creditAttempt.status !== "processing" || creditAttempt.claim_live) {
|
|
11333
|
+
return { kind: "conflict" };
|
|
11334
|
+
}
|
|
11335
|
+
const removed = await tx.delete(codexResetRedemptionAttempts).where(
|
|
11336
|
+
and5(
|
|
11337
|
+
eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
|
|
11338
|
+
eq5(codexResetRedemptionAttempts.id, creditAttempt.id),
|
|
11339
|
+
eq5(codexResetRedemptionAttempts.status, "processing"),
|
|
11340
|
+
sql4`(${codexResetRedemptionAttempts.claimExpiresAt} is null or ${codexResetRedemptionAttempts.claimExpiresAt} <= now())`
|
|
11341
|
+
)
|
|
11342
|
+
).returning({ id: codexResetRedemptionAttempts.id });
|
|
11343
|
+
if (removed.length !== 1) return { kind: "conflict" };
|
|
11344
|
+
}
|
|
11345
|
+
const [created] = await tx.insert(codexResetRedemptionAttempts).values({
|
|
11346
|
+
id: input.id,
|
|
11347
|
+
accountId: input.accountId,
|
|
11348
|
+
workspaceId: input.workspaceId,
|
|
11349
|
+
credentialId: input.credentialId,
|
|
11350
|
+
subjectId: input.subjectId,
|
|
11351
|
+
browserSessionHash: input.browserSessionHash,
|
|
11352
|
+
creditId: input.creditId,
|
|
11353
|
+
status: "processing",
|
|
11354
|
+
claimHolderId: input.claimHolderId,
|
|
11355
|
+
claimExpiresAt: sql4`now() + (${claimTtlMs} * interval '1 millisecond')`,
|
|
11356
|
+
confirmationExpiresAt: input.confirmationExpiresAt
|
|
11357
|
+
}).returning();
|
|
11358
|
+
if (!created) throw new Error("Codex redemption claim returned no row");
|
|
11359
|
+
return {
|
|
11360
|
+
kind: "claimed",
|
|
11361
|
+
attempt: mapCodexResetRedemptionAttempt(created)
|
|
11362
|
+
};
|
|
11363
|
+
})
|
|
11364
|
+
);
|
|
11365
|
+
}
|
|
11366
|
+
async function fenceCodexResetRedemptionSend(db, input) {
|
|
11367
|
+
const sendLeaseMs = input.sendLeaseMs ?? 3e4;
|
|
11368
|
+
if (!Number.isFinite(sendLeaseMs) || sendLeaseMs <= 1e4) {
|
|
11369
|
+
throw new Error("Codex redemption send lease must exceed the bounded provider call");
|
|
11370
|
+
}
|
|
11371
|
+
return await withRlsContext(
|
|
11372
|
+
db,
|
|
11373
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
11374
|
+
async (scopedDb) => await scopedDb.transaction(async (tx) => {
|
|
11375
|
+
const [credential] = await tx.select({
|
|
11376
|
+
connectedBySubjectId: codexSubscriptionCredentials.connectedBySubjectId,
|
|
11377
|
+
status: codexSubscriptionCredentials.status
|
|
11378
|
+
}).from(codexSubscriptionCredentials).where(
|
|
11379
|
+
and5(
|
|
11380
|
+
eq5(codexSubscriptionCredentials.accountId, input.accountId),
|
|
11381
|
+
eq5(codexSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
11382
|
+
eq5(codexSubscriptionCredentials.id, input.credentialId)
|
|
11383
|
+
)
|
|
11384
|
+
).for("share").limit(1);
|
|
11385
|
+
const [attempt] = await tx.select().from(codexResetRedemptionAttempts).where(
|
|
11386
|
+
and5(
|
|
11387
|
+
eq5(codexResetRedemptionAttempts.accountId, input.accountId),
|
|
11388
|
+
eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
|
|
11389
|
+
eq5(codexResetRedemptionAttempts.id, input.attemptId)
|
|
11390
|
+
)
|
|
11391
|
+
).for("update").limit(1);
|
|
11392
|
+
if (!attempt) return { kind: "not_ready", reason: "not_found" };
|
|
11393
|
+
if (attempt.credentialId !== input.credentialId || attempt.subjectId !== input.subjectId || attempt.browserSessionHash !== input.browserSessionHash || attempt.claimHolderId !== input.claimHolderId) {
|
|
11394
|
+
return { kind: "not_ready", reason: "identity_mismatch" };
|
|
11395
|
+
}
|
|
11396
|
+
if (attempt.status === "completed") {
|
|
11397
|
+
return { kind: "not_ready", reason: "already_completed" };
|
|
11398
|
+
}
|
|
11399
|
+
const [liveness] = await tx.execute(sql4`
|
|
11400
|
+
select claim_expires_at > now() as claim_live,
|
|
11401
|
+
confirmation_expires_at > now() as confirmation_live
|
|
11402
|
+
from codex_reset_redemption_attempts
|
|
11403
|
+
where workspace_id = ${input.workspaceId} and id = ${input.attemptId}
|
|
11404
|
+
`);
|
|
11405
|
+
let reason;
|
|
11406
|
+
if (!liveness?.claim_live) reason = "claim_expired";
|
|
11407
|
+
else if (!liveness.confirmation_live) reason = "confirmation_expired";
|
|
11408
|
+
else if (!credential || credential.status !== "active" || credential.connectedBySubjectId !== input.subjectId) {
|
|
11409
|
+
reason = "credential_unavailable";
|
|
11410
|
+
} else {
|
|
11411
|
+
const [ready] = await tx.update(codexResetRedemptionAttempts).set({
|
|
11412
|
+
status: "provider_started",
|
|
11413
|
+
providerStartedAt: sql4`coalesce(${codexResetRedemptionAttempts.providerStartedAt}, now())`,
|
|
11414
|
+
claimExpiresAt: sql4`now() + (${sendLeaseMs} * interval '1 millisecond')`,
|
|
11415
|
+
lastFailureKind: null,
|
|
11416
|
+
updatedAt: sql4`now()`
|
|
11417
|
+
}).where(eq5(codexResetRedemptionAttempts.id, input.attemptId)).returning();
|
|
11418
|
+
if (!ready) throw new Error("Codex redemption send fence returned no row");
|
|
11419
|
+
return { kind: "ready", attempt: mapCodexResetRedemptionAttempt(ready) };
|
|
11420
|
+
}
|
|
11421
|
+
if (attempt.status === "processing") {
|
|
11422
|
+
await tx.delete(codexResetRedemptionAttempts).where(eq5(codexResetRedemptionAttempts.id, input.attemptId));
|
|
11423
|
+
} else {
|
|
11424
|
+
await tx.update(codexResetRedemptionAttempts).set({
|
|
11425
|
+
claimHolderId: null,
|
|
11426
|
+
claimExpiresAt: null,
|
|
11427
|
+
lastFailureKind: `send_fence_${reason}`,
|
|
11428
|
+
updatedAt: sql4`now()`
|
|
11429
|
+
}).where(eq5(codexResetRedemptionAttempts.id, input.attemptId));
|
|
11430
|
+
}
|
|
11431
|
+
return { kind: "not_ready", reason };
|
|
11432
|
+
})
|
|
11433
|
+
);
|
|
11434
|
+
}
|
|
11435
|
+
async function abandonCodexResetRedemptionBeforeProvider(db, input) {
|
|
11436
|
+
return await withRlsContext(
|
|
11437
|
+
db,
|
|
11438
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
11439
|
+
async (scopedDb) => {
|
|
11440
|
+
const deleted = await scopedDb.delete(codexResetRedemptionAttempts).where(
|
|
11441
|
+
and5(
|
|
11442
|
+
eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
|
|
11443
|
+
eq5(codexResetRedemptionAttempts.id, input.attemptId),
|
|
11444
|
+
eq5(codexResetRedemptionAttempts.status, "processing"),
|
|
11445
|
+
eq5(codexResetRedemptionAttempts.claimHolderId, input.claimHolderId)
|
|
11446
|
+
)
|
|
11447
|
+
).returning({ id: codexResetRedemptionAttempts.id });
|
|
11448
|
+
return deleted.length === 1;
|
|
11449
|
+
}
|
|
11450
|
+
);
|
|
11451
|
+
}
|
|
11452
|
+
async function releaseCodexResetRedemptionClaim(db, input) {
|
|
11453
|
+
return await withRlsContext(
|
|
11454
|
+
db,
|
|
11455
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
11456
|
+
async (scopedDb) => {
|
|
11457
|
+
const rows = await scopedDb.update(codexResetRedemptionAttempts).set({
|
|
11458
|
+
claimHolderId: null,
|
|
11459
|
+
claimExpiresAt: null,
|
|
11460
|
+
lastFailureKind: input.failureKind.slice(0, 100),
|
|
11461
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
11462
|
+
}).where(
|
|
11463
|
+
and5(
|
|
11464
|
+
eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
|
|
11465
|
+
eq5(codexResetRedemptionAttempts.id, input.attemptId),
|
|
11466
|
+
eq5(codexResetRedemptionAttempts.claimHolderId, input.claimHolderId),
|
|
11467
|
+
sql4`${codexResetRedemptionAttempts.status} <> 'completed'`
|
|
11468
|
+
)
|
|
11469
|
+
).returning({ id: codexResetRedemptionAttempts.id });
|
|
11470
|
+
return rows.length === 1;
|
|
11471
|
+
}
|
|
11472
|
+
);
|
|
11473
|
+
}
|
|
11474
|
+
async function completeCodexResetRedemption(db, input) {
|
|
11475
|
+
if (!CODEX_RESET_REDEMPTION_OUTCOMES.includes(input.outcome)) {
|
|
11476
|
+
throw new Error("Unknown Codex redemption outcome");
|
|
11477
|
+
}
|
|
11478
|
+
return await withCodexCapacityMutation(
|
|
11479
|
+
db,
|
|
11480
|
+
{ workspaceId: input.workspaceId, reason: "codex_reset_credit_redeemed" },
|
|
11481
|
+
async (tx) => {
|
|
11482
|
+
await tx.execute(
|
|
11483
|
+
sql4`select pg_advisory_xact_lock(hashtextextended(${`codex-reset-attempt:${input.attemptId}`}, 0))`
|
|
11484
|
+
);
|
|
11485
|
+
const [current] = await tx.select().from(codexResetRedemptionAttempts).where(
|
|
11486
|
+
and5(
|
|
11487
|
+
eq5(codexResetRedemptionAttempts.accountId, input.accountId),
|
|
11488
|
+
eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
|
|
11489
|
+
eq5(codexResetRedemptionAttempts.id, input.attemptId)
|
|
11490
|
+
)
|
|
11491
|
+
).for("update").limit(1);
|
|
11492
|
+
if (!current) return { result: null, changed: false };
|
|
11493
|
+
if (current.status === "completed") {
|
|
11494
|
+
return { result: mapCodexResetRedemptionAttempt(current), changed: false };
|
|
11495
|
+
}
|
|
11496
|
+
if (current.status !== "provider_started" || current.claimHolderId !== input.claimHolderId) {
|
|
11497
|
+
return { result: null, changed: false };
|
|
11498
|
+
}
|
|
11499
|
+
const completedAt = /* @__PURE__ */ new Date();
|
|
11500
|
+
const [completed] = await tx.update(codexResetRedemptionAttempts).set({
|
|
11501
|
+
status: "completed",
|
|
11502
|
+
outcome: input.outcome,
|
|
11503
|
+
completedAt,
|
|
11504
|
+
claimHolderId: null,
|
|
11505
|
+
claimExpiresAt: null,
|
|
11506
|
+
lastFailureKind: null,
|
|
11507
|
+
updatedAt: completedAt
|
|
11508
|
+
}).where(
|
|
11509
|
+
and5(
|
|
11510
|
+
eq5(codexResetRedemptionAttempts.accountId, input.accountId),
|
|
11511
|
+
eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
|
|
11512
|
+
eq5(codexResetRedemptionAttempts.id, input.attemptId)
|
|
11513
|
+
)
|
|
11514
|
+
).returning();
|
|
11515
|
+
if (!completed) throw new Error("Codex redemption completion returned no row");
|
|
11516
|
+
const restoresCapacity = input.outcome === "reset" || input.outcome === "alreadyRedeemed";
|
|
11517
|
+
if (restoresCapacity) {
|
|
11518
|
+
await tx.update(codexSubscriptionCredentials).set({ exhaustedUntil: null }).where(
|
|
11519
|
+
and5(
|
|
11520
|
+
eq5(codexSubscriptionCredentials.accountId, input.accountId),
|
|
11521
|
+
eq5(codexSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
11522
|
+
eq5(codexSubscriptionCredentials.id, current.credentialId)
|
|
11523
|
+
)
|
|
11524
|
+
);
|
|
11525
|
+
}
|
|
11526
|
+
await tx.insert(auditEvents).values({
|
|
11527
|
+
accountId: input.accountId,
|
|
11528
|
+
workspaceId: input.workspaceId,
|
|
11529
|
+
subjectId: current.subjectId,
|
|
11530
|
+
action: "codex.reset_credit.redemption.completed",
|
|
11531
|
+
targetType: "codex_reset_redemption_attempt",
|
|
11532
|
+
targetId: input.attemptId,
|
|
11533
|
+
metadata: { outcome: input.outcome }
|
|
11534
|
+
});
|
|
11535
|
+
return {
|
|
11536
|
+
result: mapCodexResetRedemptionAttempt(completed),
|
|
11537
|
+
// A successful/already-applied upstream reset can make durable waiters
|
|
11538
|
+
// eligible immediately even when the local cooldown was already null.
|
|
11539
|
+
changed: restoresCapacity
|
|
11540
|
+
};
|
|
11541
|
+
}
|
|
11542
|
+
);
|
|
11543
|
+
}
|
|
10799
11544
|
async function recordCodexAccountUsage(db, workspaceId, credentialId, snapshot) {
|
|
10800
11545
|
return (await recordCodexAccountUsageWithWakeTargets(db, workspaceId, credentialId, snapshot)).result;
|
|
10801
11546
|
}
|
|
@@ -10805,11 +11550,17 @@ async function recordCodexAccountUsageWithWakeTargets(db, workspaceId, credentia
|
|
|
10805
11550
|
{ workspaceId, reason: "codex_usage_refreshed" },
|
|
10806
11551
|
async (tx) => {
|
|
10807
11552
|
const updated = await tx.update(codexSubscriptionCredentials).set({
|
|
10808
|
-
|
|
10809
|
-
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
|
|
11553
|
+
...snapshot.checkedAt !== void 0 ? {
|
|
11554
|
+
primaryUsedPercent: snapshot.primaryUsedPercent ?? null,
|
|
11555
|
+
primaryResetAt: snapshot.primaryResetAt ?? null,
|
|
11556
|
+
secondaryUsedPercent: snapshot.secondaryUsedPercent ?? null,
|
|
11557
|
+
secondaryResetAt: snapshot.secondaryResetAt ?? null,
|
|
11558
|
+
usageCheckedAt: snapshot.checkedAt
|
|
11559
|
+
} : {},
|
|
11560
|
+
...snapshot.resetCreditAvailableCount !== void 0 ? {
|
|
11561
|
+
resetCreditAvailableCount: snapshot.resetCreditAvailableCount,
|
|
11562
|
+
resetCreditsCheckedAt: snapshot.resetCreditsCheckedAt ?? null
|
|
11563
|
+
} : {}
|
|
10813
11564
|
// NB: no `version` bump and no `updatedAt` touch — usage is non-credential
|
|
10814
11565
|
// metadata and must NOT race the (id, version) refresh CAS in
|
|
10815
11566
|
// recordCodexTokenRefresh / setCodexCredentialStatus.
|
|
@@ -11091,6 +11842,34 @@ async function disconnectCodexAccount(db, workspaceId, credentialId) {
|
|
|
11091
11842
|
where workspace_id = ${workspaceId}
|
|
11092
11843
|
for update
|
|
11093
11844
|
`);
|
|
11845
|
+
const [credential] = await scopedDb.select({ id: codexSubscriptionCredentials.id }).from(codexSubscriptionCredentials).where(
|
|
11846
|
+
and5(
|
|
11847
|
+
eq5(codexSubscriptionCredentials.id, credentialId),
|
|
11848
|
+
eq5(codexSubscriptionCredentials.workspaceId, workspaceId)
|
|
11849
|
+
)
|
|
11850
|
+
).for("update").limit(1);
|
|
11851
|
+
const [settingsBefore] = await scopedDb.select({ activeCredentialId: codexRotationSettings.activeCredentialId }).from(codexRotationSettings).where(eq5(codexRotationSettings.workspaceId, workspaceId)).limit(1);
|
|
11852
|
+
if (!credential) {
|
|
11853
|
+
return {
|
|
11854
|
+
removed: false,
|
|
11855
|
+
newActiveCredentialId: settingsBefore?.activeCredentialId ?? null,
|
|
11856
|
+
blockedByUnresolvedRedemption: false
|
|
11857
|
+
};
|
|
11858
|
+
}
|
|
11859
|
+
const [unresolved] = await scopedDb.select({ id: codexResetRedemptionAttempts.id }).from(codexResetRedemptionAttempts).where(
|
|
11860
|
+
and5(
|
|
11861
|
+
eq5(codexResetRedemptionAttempts.workspaceId, workspaceId),
|
|
11862
|
+
eq5(codexResetRedemptionAttempts.credentialId, credentialId),
|
|
11863
|
+
eq5(codexResetRedemptionAttempts.status, "provider_started")
|
|
11864
|
+
)
|
|
11865
|
+
).limit(1);
|
|
11866
|
+
if (unresolved) {
|
|
11867
|
+
return {
|
|
11868
|
+
removed: false,
|
|
11869
|
+
newActiveCredentialId: settingsBefore?.activeCredentialId ?? null,
|
|
11870
|
+
blockedByUnresolvedRedemption: true
|
|
11871
|
+
};
|
|
11872
|
+
}
|
|
11094
11873
|
const removedRows = await scopedDb.delete(codexSubscriptionCredentials).where(
|
|
11095
11874
|
and5(
|
|
11096
11875
|
eq5(codexSubscriptionCredentials.id, credentialId),
|
|
@@ -11103,7 +11882,8 @@ async function disconnectCodexAccount(db, workspaceId, credentialId) {
|
|
|
11103
11882
|
if (removedRows.length === 0) {
|
|
11104
11883
|
return {
|
|
11105
11884
|
removed: false,
|
|
11106
|
-
newActiveCredentialId: settingsRow?.activeCredentialId ?? null
|
|
11885
|
+
newActiveCredentialId: settingsRow?.activeCredentialId ?? null,
|
|
11886
|
+
blockedByUnresolvedRedemption: false
|
|
11107
11887
|
};
|
|
11108
11888
|
}
|
|
11109
11889
|
let newActive = settingsRow?.activeCredentialId ?? null;
|
|
@@ -11114,13 +11894,31 @@ async function disconnectCodexAccount(db, workspaceId, credentialId) {
|
|
|
11114
11894
|
await scopedDb.update(codexRotationSettings).set({ activeCredentialId: newActive, updatedAt: /* @__PURE__ */ new Date() }).where(eq5(codexRotationSettings.workspaceId, workspaceId));
|
|
11115
11895
|
}
|
|
11116
11896
|
}
|
|
11117
|
-
return {
|
|
11897
|
+
return {
|
|
11898
|
+
removed: true,
|
|
11899
|
+
newActiveCredentialId: newActive,
|
|
11900
|
+
blockedByUnresolvedRedemption: false
|
|
11901
|
+
};
|
|
11118
11902
|
});
|
|
11119
11903
|
}
|
|
11120
11904
|
async function disconnectAllCodexAccounts(db, workspaceId) {
|
|
11121
11905
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
11906
|
+
const credentials = await scopedDb.select({ id: codexSubscriptionCredentials.id }).from(codexSubscriptionCredentials).where(eq5(codexSubscriptionCredentials.workspaceId, workspaceId)).orderBy(asc3(codexSubscriptionCredentials.id)).for("update");
|
|
11907
|
+
if (credentials.length === 0) return { removed: 0, blockedCredentialIds: [] };
|
|
11908
|
+
const blocked = await scopedDb.selectDistinct({ credentialId: codexResetRedemptionAttempts.credentialId }).from(codexResetRedemptionAttempts).where(
|
|
11909
|
+
and5(
|
|
11910
|
+
eq5(codexResetRedemptionAttempts.workspaceId, workspaceId),
|
|
11911
|
+
eq5(codexResetRedemptionAttempts.status, "provider_started")
|
|
11912
|
+
)
|
|
11913
|
+
);
|
|
11914
|
+
if (blocked.length > 0) {
|
|
11915
|
+
return {
|
|
11916
|
+
removed: 0,
|
|
11917
|
+
blockedCredentialIds: blocked.map((row) => row.credentialId).sort()
|
|
11918
|
+
};
|
|
11919
|
+
}
|
|
11122
11920
|
const rows = await scopedDb.delete(codexSubscriptionCredentials).where(eq5(codexSubscriptionCredentials.workspaceId, workspaceId)).returning({ id: codexSubscriptionCredentials.id });
|
|
11123
|
-
return rows.length;
|
|
11921
|
+
return { removed: rows.length, blockedCredentialIds: [] };
|
|
11124
11922
|
});
|
|
11125
11923
|
}
|
|
11126
11924
|
async function recordAuditEvent(db, input) {
|
|
@@ -11181,6 +11979,7 @@ function mapSessionMcpServerMetadata(row) {
|
|
|
11181
11979
|
url: row.url,
|
|
11182
11980
|
headerNames: Object.keys(row.headersEncrypted ?? {}).sort(),
|
|
11183
11981
|
credentialVersion: Number(row.credentialVersion),
|
|
11982
|
+
requireApproval: row.requireApproval ?? false,
|
|
11184
11983
|
connectionRef: row.connectionRef ?? null
|
|
11185
11984
|
};
|
|
11186
11985
|
}
|
|
@@ -11290,8 +12089,60 @@ async function updateSessionMcpServerCredentialsInTransaction(tx, input) {
|
|
|
11290
12089
|
}
|
|
11291
12090
|
return { servers, missingIds };
|
|
11292
12091
|
}
|
|
11293
|
-
async function
|
|
12092
|
+
async function updateSessionMcpApprovalPolicy(db, input) {
|
|
12093
|
+
return await withWorkspaceRls(
|
|
12094
|
+
db,
|
|
12095
|
+
input.workspaceId,
|
|
12096
|
+
async (scopedDb) => await scopedDb.transaction(
|
|
12097
|
+
async (tx) => await updateSessionMcpApprovalPolicyInTransaction(tx, input)
|
|
12098
|
+
)
|
|
12099
|
+
);
|
|
12100
|
+
}
|
|
12101
|
+
async function updateSessionMcpApprovalPolicyInTransaction(tx, input) {
|
|
12102
|
+
const [existing] = await tx.select().from(sessionMcpServers).where(
|
|
12103
|
+
and5(
|
|
12104
|
+
eq5(sessionMcpServers.workspaceId, input.workspaceId),
|
|
12105
|
+
eq5(sessionMcpServers.sessionId, input.sessionId),
|
|
12106
|
+
eq5(sessionMcpServers.serverId, input.serverId)
|
|
12107
|
+
)
|
|
12108
|
+
).for("update").limit(1);
|
|
12109
|
+
if (!existing) {
|
|
12110
|
+
return { server: null, changed: false };
|
|
12111
|
+
}
|
|
12112
|
+
const current = existing.requireApproval ?? false;
|
|
12113
|
+
if (JSON.stringify(current) === JSON.stringify(input.requireApproval)) {
|
|
12114
|
+
return { server: mapSessionMcpServerMetadata(existing), changed: false };
|
|
12115
|
+
}
|
|
12116
|
+
const [updated] = await tx.update(sessionMcpServers).set({
|
|
12117
|
+
requireApproval: input.requireApproval,
|
|
12118
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
12119
|
+
}).where(
|
|
12120
|
+
and5(
|
|
12121
|
+
eq5(sessionMcpServers.workspaceId, input.workspaceId),
|
|
12122
|
+
eq5(sessionMcpServers.sessionId, input.sessionId),
|
|
12123
|
+
eq5(sessionMcpServers.serverId, input.serverId)
|
|
12124
|
+
)
|
|
12125
|
+
).returning();
|
|
12126
|
+
if (!updated) {
|
|
12127
|
+
throw new Error(`Session MCP server disappeared during policy update: ${input.serverId}`);
|
|
12128
|
+
}
|
|
12129
|
+
return { server: mapSessionMcpServerMetadata(updated), changed: true };
|
|
12130
|
+
}
|
|
12131
|
+
async function listSessionMcpServersForRun(db, workspaceId, sessionId, attemptId, encryptionKey) {
|
|
11294
12132
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
12133
|
+
const [attempt] = await scopedDb.select({
|
|
12134
|
+
sessionId: sessionTurnAttempts.sessionId,
|
|
12135
|
+
mcpApprovalPolicies: sessionTurnAttempts.mcpApprovalPolicies
|
|
12136
|
+
}).from(sessionTurnAttempts).where(
|
|
12137
|
+
and5(
|
|
12138
|
+
eq5(sessionTurnAttempts.workspaceId, workspaceId),
|
|
12139
|
+
eq5(sessionTurnAttempts.id, attemptId),
|
|
12140
|
+
inArray3(sessionTurnAttempts.state, ["claimed", "running"])
|
|
12141
|
+
)
|
|
12142
|
+
).limit(1);
|
|
12143
|
+
if (!attempt || attempt.sessionId !== sessionId) {
|
|
12144
|
+
throw new Error(`session MCP policy snapshot is unavailable for attempt ${attemptId}`);
|
|
12145
|
+
}
|
|
11295
12146
|
const rows = await scopedDb.select().from(sessionMcpServers).where(
|
|
11296
12147
|
and5(
|
|
11297
12148
|
eq5(sessionMcpServers.workspaceId, workspaceId),
|
|
@@ -11299,6 +12150,11 @@ async function listSessionMcpServersForRun(db, workspaceId, sessionId, encryptio
|
|
|
11299
12150
|
)
|
|
11300
12151
|
).orderBy(asc3(sessionMcpServers.createdAt), asc3(sessionMcpServers.serverId));
|
|
11301
12152
|
return rows.map((row) => {
|
|
12153
|
+
if (!Object.hasOwn(attempt.mcpApprovalPolicies, row.serverId)) {
|
|
12154
|
+
throw new Error(
|
|
12155
|
+
`session MCP policy snapshot is missing server ${row.serverId} for attempt ${attemptId}`
|
|
12156
|
+
);
|
|
12157
|
+
}
|
|
11302
12158
|
let headers;
|
|
11303
12159
|
try {
|
|
11304
12160
|
if (!encryptionKey && Object.keys(row.headersEncrypted ?? {}).length > 0) {
|
|
@@ -11318,7 +12174,7 @@ async function listSessionMcpServersForRun(db, workspaceId, sessionId, encryptio
|
|
|
11318
12174
|
...row.allowedTools ? { allowedTools: row.allowedTools } : {},
|
|
11319
12175
|
...row.timeoutMs ? { timeoutMs: row.timeoutMs } : {},
|
|
11320
12176
|
...row.cacheToolsList ? { cacheToolsList: row.cacheToolsList } : {},
|
|
11321
|
-
|
|
12177
|
+
requireApproval: attempt.mcpApprovalPolicies[row.serverId],
|
|
11322
12178
|
headers
|
|
11323
12179
|
};
|
|
11324
12180
|
});
|
|
@@ -11369,6 +12225,7 @@ async function createSession(db, input) {
|
|
|
11369
12225
|
initialTurnInstructions: input.initialTurnInstructions ?? null,
|
|
11370
12226
|
resources: input.resources,
|
|
11371
12227
|
tools: input.tools ?? [],
|
|
12228
|
+
toolPolicy: input.toolPolicy ?? null,
|
|
11372
12229
|
metadata: input.metadata,
|
|
11373
12230
|
...creatorColumns(frozenCreator),
|
|
11374
12231
|
model: input.model,
|
|
@@ -11416,6 +12273,7 @@ async function createSessionWithIdempotencyKey(db, input) {
|
|
|
11416
12273
|
initialTurnInstructions: input.initialTurnInstructions ?? null,
|
|
11417
12274
|
resources: input.resources,
|
|
11418
12275
|
tools: input.tools ?? [],
|
|
12276
|
+
toolPolicy: input.toolPolicy ?? null,
|
|
11419
12277
|
metadata: input.metadata,
|
|
11420
12278
|
...creatorColumns(frozenCreator),
|
|
11421
12279
|
model: input.model,
|
|
@@ -12695,6 +13553,17 @@ async function listSessionEventPage(db, workspaceId, sessionId, options = {}) {
|
|
|
12695
13553
|
eq5(sessionEvents.sessionId, sessionId),
|
|
12696
13554
|
gt(sessionEvents.sequence, after)
|
|
12697
13555
|
];
|
|
13556
|
+
if (options.authoritativeLatest) {
|
|
13557
|
+
filters.push(
|
|
13558
|
+
and5(
|
|
13559
|
+
or(
|
|
13560
|
+
isNull(sessionEvents.turnAssociation),
|
|
13561
|
+
eq5(sessionEvents.turnAssociation, "current")
|
|
13562
|
+
),
|
|
13563
|
+
isNull(sessionEvents.duplicateOfEventId)
|
|
13564
|
+
)
|
|
13565
|
+
);
|
|
13566
|
+
}
|
|
12698
13567
|
if (typeFilters.includeTypes.length > 0) {
|
|
12699
13568
|
filters.push(inArray3(sessionEvents.type, typeFilters.includeTypes));
|
|
12700
13569
|
}
|
|
@@ -12712,7 +13581,7 @@ async function listSessionEventPage(db, workspaceId, sessionId, options = {}) {
|
|
|
12712
13581
|
filters.push(lt(sessionEvents.sequence, before));
|
|
12713
13582
|
}
|
|
12714
13583
|
const rows = await scopedDb.select(sessionEventProjectionSelect(payloadMode)).from(sessionEvents).where(and5(...filters)).orderBy(
|
|
12715
|
-
direction === "before" ? desc(sessionEvents.sequence) : asc3(sessionEvents.sequence)
|
|
13584
|
+
options.authoritativeLatest ? desc(sessionEvents.sequence) : direction === "before" ? desc(sessionEvents.sequence) : asc3(sessionEvents.sequence)
|
|
12716
13585
|
).limit(queryLimit);
|
|
12717
13586
|
if (rows.length === 0) break;
|
|
12718
13587
|
for (const row of rows) {
|
|
@@ -12906,21 +13775,67 @@ async function listSessionEvents(db, workspaceId, sessionId, afterOrOptions = 0,
|
|
|
12906
13775
|
return (direction === "before" ? rows.reverse() : rows).map(mapProjectedEvent);
|
|
12907
13776
|
});
|
|
12908
13777
|
}
|
|
12909
|
-
async function
|
|
13778
|
+
async function admitToolspaceTurnAttempt(db, workspaceId, claims) {
|
|
12910
13779
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12915
|
-
|
|
12916
|
-
|
|
12917
|
-
|
|
12918
|
-
|
|
12919
|
-
|
|
12920
|
-
|
|
12921
|
-
return row ? { reserved: true, count: Number(row.count) } : { reserved: false };
|
|
13780
|
+
return await scopedDb.transaction(async (tx) => {
|
|
13781
|
+
const fence = await lockTurnAttemptWriteFenceTx(tx, {
|
|
13782
|
+
workspaceId,
|
|
13783
|
+
sessionId: claims.sessionId,
|
|
13784
|
+
turnId: claims.turnId,
|
|
13785
|
+
attemptId: claims.attemptId,
|
|
13786
|
+
executionGeneration: claims.executionGeneration
|
|
13787
|
+
});
|
|
13788
|
+
return fence.allowed && fence.turn.status === "running";
|
|
13789
|
+
});
|
|
12922
13790
|
});
|
|
12923
13791
|
}
|
|
13792
|
+
async function reserveToolspaceCallForAttempt(db, input) {
|
|
13793
|
+
return await withRlsContext(
|
|
13794
|
+
db,
|
|
13795
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
13796
|
+
async (scopedDb) => await scopedDb.transaction(async (tx) => {
|
|
13797
|
+
const fence = await lockTurnAttemptWriteFenceTx(tx, {
|
|
13798
|
+
workspaceId: input.workspaceId,
|
|
13799
|
+
sessionId: input.sessionId,
|
|
13800
|
+
turnId: input.turnId,
|
|
13801
|
+
executionGeneration: input.executionGeneration,
|
|
13802
|
+
attemptId: input.attemptId
|
|
13803
|
+
});
|
|
13804
|
+
if (!fence.allowed) {
|
|
13805
|
+
return { reserved: false, reason: fence.reason };
|
|
13806
|
+
}
|
|
13807
|
+
if (fence.turn.status !== "running") {
|
|
13808
|
+
return { reserved: false, reason: "turn_terminal" };
|
|
13809
|
+
}
|
|
13810
|
+
if (Number(fence.turn.toolspaceCallCount) >= input.limit) {
|
|
13811
|
+
return { reserved: false, reason: "budget_exhausted" };
|
|
13812
|
+
}
|
|
13813
|
+
const [row] = await tx.update(sessionTurns).set({
|
|
13814
|
+
toolspaceCallCount: sql4`${sessionTurns.toolspaceCallCount} + 1`
|
|
13815
|
+
}).where(
|
|
13816
|
+
and5(
|
|
13817
|
+
eq5(sessionTurns.workspaceId, input.workspaceId),
|
|
13818
|
+
eq5(sessionTurns.sessionId, input.sessionId),
|
|
13819
|
+
eq5(sessionTurns.id, input.turnId),
|
|
13820
|
+
eq5(sessionTurns.executionGeneration, input.executionGeneration),
|
|
13821
|
+
eq5(sessionTurns.activeAttemptId, input.attemptId),
|
|
13822
|
+
sql4`${sessionTurns.toolspaceCallCount} < ${input.limit}`
|
|
13823
|
+
)
|
|
13824
|
+
).returning({ count: sessionTurns.toolspaceCallCount });
|
|
13825
|
+
if (!row) {
|
|
13826
|
+
throw new Error("Toolspace call reservation lost its locked turn");
|
|
13827
|
+
}
|
|
13828
|
+
return {
|
|
13829
|
+
reserved: true,
|
|
13830
|
+
count: Number(row.count),
|
|
13831
|
+
turn: mapSessionTurnForExecution({
|
|
13832
|
+
...fence.turn,
|
|
13833
|
+
toolspaceCallCount: Number(row.count)
|
|
13834
|
+
})
|
|
13835
|
+
};
|
|
13836
|
+
})
|
|
13837
|
+
);
|
|
13838
|
+
}
|
|
12924
13839
|
function normalizeEventSequence(value, fallback) {
|
|
12925
13840
|
if (value === void 0 || !Number.isFinite(value)) {
|
|
12926
13841
|
return fallback;
|
|
@@ -13441,6 +14356,56 @@ async function lockTurnAttemptWriteFenceTx(tx, input) {
|
|
|
13441
14356
|
}
|
|
13442
14357
|
return { allowed: true, workspace, session, turn, attempt };
|
|
13443
14358
|
}
|
|
14359
|
+
async function installOrReadTurnExecutionPolicyForAttempt(db, input) {
|
|
14360
|
+
return await withRlsContext(
|
|
14361
|
+
db,
|
|
14362
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
14363
|
+
async (scopedDb) => await scopedDb.transaction(async (tx) => {
|
|
14364
|
+
const fence = await lockTurnAttemptWriteFenceTx(tx, {
|
|
14365
|
+
workspaceId: input.workspaceId,
|
|
14366
|
+
sessionId: input.sessionId,
|
|
14367
|
+
turnId: input.turnId,
|
|
14368
|
+
executionGeneration: input.executionGeneration,
|
|
14369
|
+
attemptId: input.attemptId
|
|
14370
|
+
});
|
|
14371
|
+
if (!fence.allowed) {
|
|
14372
|
+
return { accepted: false, reason: fence.reason };
|
|
14373
|
+
}
|
|
14374
|
+
const existing = readTurnExecutionPolicyV1(fence.turn.metadata);
|
|
14375
|
+
if (existing.kind === "valid") {
|
|
14376
|
+
return {
|
|
14377
|
+
accepted: true,
|
|
14378
|
+
installed: false,
|
|
14379
|
+
policy: existing.policy,
|
|
14380
|
+
turn: mapSessionTurn(fence.turn)
|
|
14381
|
+
};
|
|
14382
|
+
}
|
|
14383
|
+
const policy = TurnExecutionPolicyV1.parse(input.policyForAbsent);
|
|
14384
|
+
const [updated] = await tx.update(sessionTurns).set({
|
|
14385
|
+
metadata: metadataWithTurnExecutionPolicyV12(fence.turn.metadata, policy),
|
|
14386
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
14387
|
+
}).where(
|
|
14388
|
+
and5(
|
|
14389
|
+
eq5(sessionTurns.workspaceId, input.workspaceId),
|
|
14390
|
+
eq5(sessionTurns.sessionId, input.sessionId),
|
|
14391
|
+
eq5(sessionTurns.id, input.turnId),
|
|
14392
|
+
eq5(sessionTurns.status, "running"),
|
|
14393
|
+
eq5(sessionTurns.executionGeneration, input.executionGeneration),
|
|
14394
|
+
eq5(sessionTurns.activeAttemptId, input.attemptId)
|
|
14395
|
+
)
|
|
14396
|
+
).returning();
|
|
14397
|
+
if (!updated) {
|
|
14398
|
+
throw new Error("Turn execution policy owner changed while its row was locked");
|
|
14399
|
+
}
|
|
14400
|
+
return {
|
|
14401
|
+
accepted: true,
|
|
14402
|
+
installed: true,
|
|
14403
|
+
policy,
|
|
14404
|
+
turn: mapSessionTurn(updated)
|
|
14405
|
+
};
|
|
14406
|
+
})
|
|
14407
|
+
);
|
|
14408
|
+
}
|
|
13444
14409
|
async function appendSessionHistoryItems(db, input) {
|
|
13445
14410
|
if (input.items.length === 0) {
|
|
13446
14411
|
return true;
|
|
@@ -13519,6 +14484,33 @@ async function registerPendingSessionToolCall(db, input) {
|
|
|
13519
14484
|
})
|
|
13520
14485
|
);
|
|
13521
14486
|
}
|
|
14487
|
+
async function clearPendingSessionToolspaceCall(db, input) {
|
|
14488
|
+
return await withRlsContext(
|
|
14489
|
+
db,
|
|
14490
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
14491
|
+
async (scopedDb) => await scopedDb.transaction(async (tx) => {
|
|
14492
|
+
const fence = await lockTurnAttemptWriteFenceTx(tx, {
|
|
14493
|
+
workspaceId: input.workspaceId,
|
|
14494
|
+
sessionId: input.sessionId,
|
|
14495
|
+
turnId: input.turnId,
|
|
14496
|
+
executionGeneration: input.executionGeneration,
|
|
14497
|
+
attemptId: input.attemptId
|
|
14498
|
+
});
|
|
14499
|
+
if (!fence.allowed) return { accepted: false, cleared: false };
|
|
14500
|
+
const deleted = await tx.delete(sessionPendingToolCalls).where(
|
|
14501
|
+
and5(
|
|
14502
|
+
eq5(sessionPendingToolCalls.workspaceId, input.workspaceId),
|
|
14503
|
+
eq5(sessionPendingToolCalls.sessionId, input.sessionId),
|
|
14504
|
+
eq5(sessionPendingToolCalls.turnId, input.turnId),
|
|
14505
|
+
eq5(sessionPendingToolCalls.attemptId, input.attemptId),
|
|
14506
|
+
eq5(sessionPendingToolCalls.callId, input.callId),
|
|
14507
|
+
eq5(sessionPendingToolCalls.callType, "toolspace_call")
|
|
14508
|
+
)
|
|
14509
|
+
).returning({ id: sessionPendingToolCalls.id });
|
|
14510
|
+
return { accepted: true, cleared: deleted.length === 1 };
|
|
14511
|
+
})
|
|
14512
|
+
);
|
|
14513
|
+
}
|
|
13522
14514
|
async function recordPendingSessionToolCallResult(db, input) {
|
|
13523
14515
|
return await withRlsContext(
|
|
13524
14516
|
db,
|
|
@@ -14385,13 +15377,17 @@ async function acquireLease(db, input) {
|
|
|
14385
15377
|
on conflict (workspace_id, sandbox_group_id) do nothing
|
|
14386
15378
|
`);
|
|
14387
15379
|
const rows = await tx.execute(sql4`
|
|
14388
|
-
select
|
|
15380
|
+
select *, (liveness = 'draining' and expires_at <= now()) as draining_expired
|
|
15381
|
+
from sandbox_leases
|
|
14389
15382
|
where workspace_id = ${workspaceId} and sandbox_group_id = ${sandboxGroupId}
|
|
14390
15383
|
for update
|
|
14391
15384
|
`);
|
|
14392
15385
|
const row = rows[0];
|
|
14393
15386
|
if (!row) throw new Error(`Lease row vanished post-insert: ${sandboxGroupId}`);
|
|
14394
15387
|
let liveness = row.liveness;
|
|
15388
|
+
if (liveness === "draining" && row.draining_expired) {
|
|
15389
|
+
return { role: "fenced", lease: mapLeaseRow(row) };
|
|
15390
|
+
}
|
|
14395
15391
|
const imageConflict = image !== null && row.image !== null && row.image !== image;
|
|
14396
15392
|
const rigConflict = rigVersionId !== null && row.rig_version_id !== null && row.rig_version_id !== rigVersionId;
|
|
14397
15393
|
if (liveness !== "cold" && (imageConflict || rigConflict)) {
|
|
@@ -14570,6 +15566,7 @@ async function failWarmingToCold(db, input) {
|
|
|
14570
15566
|
update sandbox_leases set
|
|
14571
15567
|
liveness = 'cold', instance_id = null,
|
|
14572
15568
|
data_plane_url = null, terminal_data_plane_url = null, updated_at = now(),
|
|
15569
|
+
lease_epoch = lease_epoch + 1,
|
|
14573
15570
|
resume_state = case
|
|
14574
15571
|
when (resume_state #>> '{sessionState,workspaceArchive}') is not null
|
|
14575
15572
|
then jsonb_build_object(
|
|
@@ -14719,7 +15716,8 @@ async function reapStaleLeaseHolders(db, input) {
|
|
|
14719
15716
|
update sandbox_leases set
|
|
14720
15717
|
liveness = 'cold', instance_id = null,
|
|
14721
15718
|
resume_backend_id = null, resume_state = null,
|
|
14722
|
-
data_plane_url = null, terminal_data_plane_url = null,
|
|
15719
|
+
data_plane_url = null, terminal_data_plane_url = null,
|
|
15720
|
+
lease_epoch = lease_epoch + 1, updated_at = now()
|
|
14723
15721
|
where workspace_id = ${input.workspaceId}
|
|
14724
15722
|
and liveness = 'warming' and expires_at < now() and instance_id is null
|
|
14725
15723
|
returning id
|
|
@@ -14732,6 +15730,7 @@ async function reapStaleLeaseHolders(db, input) {
|
|
|
14732
15730
|
viewer_holders = 0,
|
|
14733
15731
|
data_plane_url = null,
|
|
14734
15732
|
terminal_data_plane_url = null,
|
|
15733
|
+
lease_epoch = lease_epoch + 1,
|
|
14735
15734
|
expires_at = now() - interval '1 millisecond',
|
|
14736
15735
|
updated_at = now()
|
|
14737
15736
|
where workspace_id = ${input.workspaceId}
|
|
@@ -14882,6 +15881,7 @@ async function reArmDrainingLease(db, input) {
|
|
|
14882
15881
|
updated_at = now()
|
|
14883
15882
|
where workspace_id = ${input.workspaceId} and sandbox_group_id = ${input.sandboxGroupId}
|
|
14884
15883
|
and liveness = 'draining'
|
|
15884
|
+
and expires_at > now()
|
|
14885
15885
|
returning id
|
|
14886
15886
|
`);
|
|
14887
15887
|
return { rearmed: rows.length > 0 };
|
|
@@ -16998,6 +17998,7 @@ async function initializeSessionStartAtomically(db, input) {
|
|
|
16998
17998
|
turnInstructions: session.initialTurnInstructions ?? null,
|
|
16999
17999
|
resources: session.resources,
|
|
17000
18000
|
tools: session.tools,
|
|
18001
|
+
toolsProvided: session.toolPolicy?.mode === "explicit",
|
|
17001
18002
|
model: session.model,
|
|
17002
18003
|
reasoningEffort: reasoningEffortForMetadata(
|
|
17003
18004
|
session.metadata,
|
|
@@ -17005,7 +18006,7 @@ async function initializeSessionStartAtomically(db, input) {
|
|
|
17005
18006
|
),
|
|
17006
18007
|
sandboxBackend: session.sandboxBackend,
|
|
17007
18008
|
sandboxOs: session.sandboxOs,
|
|
17008
|
-
metadata: {},
|
|
18009
|
+
metadata: input.turnExecutionPolicy ? metadataWithTurnExecutionPolicyV12({}, input.turnExecutionPolicy) : {},
|
|
17009
18010
|
lineage: {},
|
|
17010
18011
|
...initiatorColumns(creator)
|
|
17011
18012
|
}).returning();
|
|
@@ -17114,6 +18115,7 @@ async function enqueueSessionTurn(db, input) {
|
|
|
17114
18115
|
turnInstructions: input.turnInstructions ?? null,
|
|
17115
18116
|
resources: input.resources,
|
|
17116
18117
|
tools: input.tools,
|
|
18118
|
+
toolsProvided: input.toolsProvided ?? false,
|
|
17117
18119
|
model: input.model,
|
|
17118
18120
|
reasoningEffort: input.reasoningEffort,
|
|
17119
18121
|
sandboxBackend: input.sandboxBackend,
|
|
@@ -17325,18 +18327,33 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
|
|
|
17325
18327
|
if (unquiescedInterruption) {
|
|
17326
18328
|
return { action: "unclaimed", reason: "control-pending" };
|
|
17327
18329
|
}
|
|
17328
|
-
const registerAttempt = async (turn) =>
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
|
|
17332
|
-
|
|
17333
|
-
|
|
17334
|
-
|
|
17335
|
-
|
|
17336
|
-
|
|
17337
|
-
|
|
17338
|
-
|
|
17339
|
-
|
|
18330
|
+
const registerAttempt = async (turn) => {
|
|
18331
|
+
const policyRows = await tx.select({
|
|
18332
|
+
serverId: sessionMcpServers.serverId,
|
|
18333
|
+
requireApproval: sessionMcpServers.requireApproval
|
|
18334
|
+
}).from(sessionMcpServers).where(
|
|
18335
|
+
and5(
|
|
18336
|
+
eq5(sessionMcpServers.workspaceId, workspaceId),
|
|
18337
|
+
eq5(sessionMcpServers.sessionId, sessionId)
|
|
18338
|
+
)
|
|
18339
|
+
).orderBy(asc3(sessionMcpServers.serverId));
|
|
18340
|
+
const mcpApprovalPolicies = Object.fromEntries(
|
|
18341
|
+
policyRows.map((row2) => [row2.serverId, row2.requireApproval ?? false])
|
|
18342
|
+
);
|
|
18343
|
+
return await registerSessionTurnAttemptClaim(tx, {
|
|
18344
|
+
id: input.attemptId,
|
|
18345
|
+
accountId: session.accountId,
|
|
18346
|
+
workspaceId,
|
|
18347
|
+
sessionId,
|
|
18348
|
+
turnId: turn.id,
|
|
18349
|
+
executionGeneration: turn.executionGeneration,
|
|
18350
|
+
temporalWorkflowId: workflowId,
|
|
18351
|
+
temporalWorkflowRunId: input.workflowRunId,
|
|
18352
|
+
temporalActivityId: input.dispatchId,
|
|
18353
|
+
verifiedControlRevision: Number(workspaceControl.revision),
|
|
18354
|
+
mcpApprovalPolicies
|
|
18355
|
+
});
|
|
18356
|
+
};
|
|
17340
18357
|
if (session.activeTurnId !== null) {
|
|
17341
18358
|
const [activeTurnPreview] = await tx.select().from(sessionTurns).where(
|
|
17342
18359
|
and5(
|
|
@@ -19083,7 +20100,8 @@ async function applySessionTurnSettlement(db, workspaceId, input) {
|
|
|
19083
20100
|
sequence: ++sequence,
|
|
19084
20101
|
type: event.type,
|
|
19085
20102
|
payload: sanitizeEventPayload(
|
|
19086
|
-
event.type === "session.status.changed" && payload.status === input.sessionStatus && effectiveSessionStatus !== input.sessionStatus ? { ...payload, status: effectiveSessionStatus } : payload
|
|
20103
|
+
event.type === "session.status.changed" && payload.status === input.sessionStatus && effectiveSessionStatus !== input.sessionStatus ? { ...payload, status: effectiveSessionStatus } : payload,
|
|
20104
|
+
{ fullEvidence: event.retainedOutputEvidence }
|
|
19087
20105
|
),
|
|
19088
20106
|
clientEventId: event.clientEventId ?? null,
|
|
19089
20107
|
turnId: input.turnId,
|
|
@@ -19876,6 +20894,41 @@ async function getSessionTurn(db, workspaceId, turnId) {
|
|
|
19876
20894
|
return row ? mapSessionTurn(row) : null;
|
|
19877
20895
|
});
|
|
19878
20896
|
}
|
|
20897
|
+
async function getActiveSessionTurnForExecution(db, workspaceId, sessionId) {
|
|
20898
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
20899
|
+
const [row] = await scopedDb.select({ turn: sessionTurns }).from(sessions).innerJoin(
|
|
20900
|
+
sessionTurns,
|
|
20901
|
+
and5(
|
|
20902
|
+
eq5(sessionTurns.workspaceId, sessions.workspaceId),
|
|
20903
|
+
eq5(sessionTurns.id, sessions.activeTurnId)
|
|
20904
|
+
)
|
|
20905
|
+
).innerJoin(
|
|
20906
|
+
sessionTurnAttempts,
|
|
20907
|
+
and5(
|
|
20908
|
+
eq5(sessionTurnAttempts.workspaceId, sessionTurns.workspaceId),
|
|
20909
|
+
eq5(sessionTurnAttempts.id, sessionTurns.activeAttemptId)
|
|
20910
|
+
)
|
|
20911
|
+
).where(
|
|
20912
|
+
and5(
|
|
20913
|
+
eq5(sessions.workspaceId, workspaceId),
|
|
20914
|
+
eq5(sessions.id, sessionId),
|
|
20915
|
+
eq5(sessionTurns.sessionId, sessionId),
|
|
20916
|
+
eq5(sessionTurnAttempts.sessionId, sessionId),
|
|
20917
|
+
eq5(sessionTurnAttempts.turnId, sessionTurns.id),
|
|
20918
|
+
inArray3(sessionTurnAttempts.state, ["claimed", "running"]),
|
|
20919
|
+
inArray3(sessionTurns.status, ["running", "recovering", "waiting_capacity"]),
|
|
20920
|
+
sql4`not exists (
|
|
20921
|
+
select 1
|
|
20922
|
+
from ${sessionAttemptInterruptions} interruption
|
|
20923
|
+
where interruption.workspace_id = ${workspaceId}
|
|
20924
|
+
and interruption.attempt_id = ${sessionTurnAttempts.id}
|
|
20925
|
+
and interruption.state in ('pending', 'delivered', 'acknowledged')
|
|
20926
|
+
)`
|
|
20927
|
+
)
|
|
20928
|
+
).limit(1);
|
|
20929
|
+
return row ? mapSessionTurnForExecution(row.turn) : null;
|
|
20930
|
+
});
|
|
20931
|
+
}
|
|
19879
20932
|
async function getSessionTurnForAttempt(db, workspaceId, sessionId, attemptId) {
|
|
19880
20933
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
19881
20934
|
const [row] = await scopedDb.select({ turn: sessionTurns }).from(sessionTurnAttempts).innerJoin(
|
|
@@ -20146,11 +21199,37 @@ async function claimPendingSessionWorkflowWakes(db, limit = 100) {
|
|
|
20146
21199
|
}));
|
|
20147
21200
|
}
|
|
20148
21201
|
async function markSessionWorkflowWakeDelivered(db, input) {
|
|
20149
|
-
await withRlsContext(
|
|
21202
|
+
return await withRlsContext(
|
|
20150
21203
|
db,
|
|
20151
21204
|
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
20152
|
-
async (scopedDb) => {
|
|
20153
|
-
const
|
|
21205
|
+
async (scopedDb) => await scopedDb.transaction(async (tx) => {
|
|
21206
|
+
const locks = await lockSessionEventWriteRows(tx, {
|
|
21207
|
+
workspaceId: input.workspaceId,
|
|
21208
|
+
controlLock: "share",
|
|
21209
|
+
sessionIds: [input.sessionId]
|
|
21210
|
+
});
|
|
21211
|
+
const session = locks.sessions[0];
|
|
21212
|
+
if (!session) throw new Error(`Session not found: ${input.sessionId}`);
|
|
21213
|
+
const effectiveControl = await evaluateSessionControl(
|
|
21214
|
+
tx,
|
|
21215
|
+
input.workspaceId,
|
|
21216
|
+
input.sessionId,
|
|
21217
|
+
{ workspaceControl: locks.control ?? void 0 }
|
|
21218
|
+
);
|
|
21219
|
+
if (effectiveControl.state === "active") {
|
|
21220
|
+
const [pendingAgentSteer] = await tx.select({ id: sessionSystemUpdates.id }).from(sessionSystemUpdates).where(
|
|
21221
|
+
and5(
|
|
21222
|
+
eq5(sessionSystemUpdates.workspaceId, input.workspaceId),
|
|
21223
|
+
eq5(sessionSystemUpdates.sessionId, input.sessionId),
|
|
21224
|
+
eq5(sessionSystemUpdates.kind, "agent_steer_instruction"),
|
|
21225
|
+
eq5(sessionSystemUpdates.state, "pending")
|
|
21226
|
+
)
|
|
21227
|
+
).limit(1);
|
|
21228
|
+
if (pendingAgentSteer) {
|
|
21229
|
+
return { action: "pending_admission", blocker: "pending_agent_steer" };
|
|
21230
|
+
}
|
|
21231
|
+
}
|
|
21232
|
+
const [row] = await tx.update(sessionWorkflowWakeOutbox).set({
|
|
20154
21233
|
deliveredRevision: sql4`greatest(${sessionWorkflowWakeOutbox.deliveredRevision}, ${input.wakeRevision})`,
|
|
20155
21234
|
attempts: sql4`case when ${sessionWorkflowWakeOutbox.wakeRevision} = ${input.wakeRevision} then 0 else ${sessionWorkflowWakeOutbox.attempts} end`,
|
|
20156
21235
|
lastError: sql4`case when ${sessionWorkflowWakeOutbox.wakeRevision} = ${input.wakeRevision} then null else ${sessionWorkflowWakeOutbox.lastError} end`,
|
|
@@ -20167,7 +21246,8 @@ async function markSessionWorkflowWakeDelivered(db, input) {
|
|
|
20167
21246
|
`Workflow wake revision ${input.wakeRevision} is not current for session ${input.sessionId}`
|
|
20168
21247
|
);
|
|
20169
21248
|
}
|
|
20170
|
-
|
|
21249
|
+
return { action: "acknowledged" };
|
|
21250
|
+
})
|
|
20171
21251
|
);
|
|
20172
21252
|
}
|
|
20173
21253
|
async function markSessionWorkflowWakeFailed(db, input, error) {
|
|
@@ -20528,7 +21608,9 @@ async function appendSessionEvents(db, workspaceId, sessionId, inputs) {
|
|
|
20528
21608
|
sessionId,
|
|
20529
21609
|
sequence: ++sequence,
|
|
20530
21610
|
type: input.type,
|
|
20531
|
-
payload: sanitizeEventPayload(input.payload ?? {}
|
|
21611
|
+
payload: sanitizeEventPayload(input.payload ?? {}, {
|
|
21612
|
+
fullEvidence: input.retainedOutputEvidence
|
|
21613
|
+
}),
|
|
20532
21614
|
clientEventId: input.clientEventId ?? null,
|
|
20533
21615
|
turnId: input.turnId ?? null,
|
|
20534
21616
|
turnGeneration: input.turnGeneration ?? null,
|
|
@@ -20780,7 +21862,9 @@ async function appendSessionEventsForTurnAttempt(db, workspaceId, sessionId, tur
|
|
|
20780
21862
|
sessionId,
|
|
20781
21863
|
sequence: ++sequence,
|
|
20782
21864
|
type: input.type,
|
|
20783
|
-
payload: sanitizeEventPayload(input.payload ?? {}
|
|
21865
|
+
payload: sanitizeEventPayload(input.payload ?? {}, {
|
|
21866
|
+
fullEvidence: input.retainedOutputEvidence
|
|
21867
|
+
}),
|
|
20784
21868
|
clientEventId: input.clientEventId ?? null,
|
|
20785
21869
|
turnId,
|
|
20786
21870
|
turnGeneration: executionGeneration,
|
|
@@ -20838,7 +21922,9 @@ async function appendSessionEventToSandboxGroup(db, workspaceId, sandboxGroupId,
|
|
|
20838
21922
|
sessionId: row.id,
|
|
20839
21923
|
sequence: row.lastSequence + 1,
|
|
20840
21924
|
type: input.type,
|
|
20841
|
-
payload: sanitizeEventPayload(input.payload ?? {}
|
|
21925
|
+
payload: sanitizeEventPayload(input.payload ?? {}, {
|
|
21926
|
+
fullEvidence: input.retainedOutputEvidence
|
|
21927
|
+
}),
|
|
20842
21928
|
clientEventId: input.clientEventId ?? null,
|
|
20843
21929
|
turnId: input.turnId ?? null,
|
|
20844
21930
|
turnGeneration: input.turnGeneration ?? null,
|
|
@@ -20893,7 +21979,9 @@ async function appendSessionEventsAndUpdateSession(db, workspaceId, sessionId, i
|
|
|
20893
21979
|
sessionId,
|
|
20894
21980
|
sequence: ++sequence,
|
|
20895
21981
|
type: input.type,
|
|
20896
|
-
payload: sanitizeEventPayload(input.payload ?? {}
|
|
21982
|
+
payload: sanitizeEventPayload(input.payload ?? {}, {
|
|
21983
|
+
fullEvidence: input.retainedOutputEvidence
|
|
21984
|
+
}),
|
|
20897
21985
|
clientEventId: input.clientEventId ?? null,
|
|
20898
21986
|
turnId: input.turnId ?? null,
|
|
20899
21987
|
turnGeneration: input.turnGeneration ?? null,
|
|
@@ -20946,6 +22034,12 @@ async function appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessi
|
|
|
20946
22034
|
sessionId,
|
|
20947
22035
|
updates
|
|
20948
22036
|
}),
|
|
22037
|
+
updateSessionMcpApprovalPolicy: async (serverId, requireApproval) => await updateSessionMcpApprovalPolicyInTransaction(tx, {
|
|
22038
|
+
workspaceId,
|
|
22039
|
+
sessionId,
|
|
22040
|
+
serverId,
|
|
22041
|
+
requireApproval
|
|
22042
|
+
}),
|
|
20949
22043
|
listPendingSessionTurns: async () => {
|
|
20950
22044
|
const rows = await tx.select().from(sessionTurns).where(
|
|
20951
22045
|
and5(
|
|
@@ -20975,7 +22069,9 @@ async function appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessi
|
|
|
20975
22069
|
sessionId,
|
|
20976
22070
|
sequence: ++sequence,
|
|
20977
22071
|
type: input.type,
|
|
20978
|
-
payload: sanitizeEventPayload(input.payload ?? {}
|
|
22072
|
+
payload: sanitizeEventPayload(input.payload ?? {}, {
|
|
22073
|
+
fullEvidence: input.retainedOutputEvidence
|
|
22074
|
+
}),
|
|
20979
22075
|
clientEventId: input.clientEventId ?? null,
|
|
20980
22076
|
turnId: input.turnId ?? null,
|
|
20981
22077
|
turnGeneration: input.turnGeneration ?? null,
|
|
@@ -21034,6 +22130,10 @@ function mapSession(row, effectiveControl, mcpServers = [], pin = mapSessionPin(
|
|
|
21034
22130
|
instructions: row.instructions ?? null,
|
|
21035
22131
|
resources: row.resources,
|
|
21036
22132
|
tools: row.tools,
|
|
22133
|
+
toolPolicy: row.toolPolicy ?? {
|
|
22134
|
+
mode: "legacy",
|
|
22135
|
+
inheritedFromSessionId: null
|
|
22136
|
+
},
|
|
21037
22137
|
metadata: row.metadata,
|
|
21038
22138
|
createdBy: initiatorFromStorage(
|
|
21039
22139
|
row.createdByKind,
|
|
@@ -21127,6 +22227,7 @@ function mapSessionTurn(row) {
|
|
|
21127
22227
|
prompt: row.prompt,
|
|
21128
22228
|
resources: row.resources,
|
|
21129
22229
|
tools: row.tools,
|
|
22230
|
+
toolsProvided: row.toolsProvided,
|
|
21130
22231
|
model: row.model,
|
|
21131
22232
|
reasoningEffort: row.reasoningEffort,
|
|
21132
22233
|
sandboxBackend: row.sandboxBackend,
|
|
@@ -21277,15 +22378,50 @@ function mapImportBatch(row) {
|
|
|
21277
22378
|
updatedAt: row.updatedAt.toISOString()
|
|
21278
22379
|
};
|
|
21279
22380
|
}
|
|
21280
|
-
function
|
|
22381
|
+
function catalogExposureState(item, installation) {
|
|
22382
|
+
if (capabilityCatalogItemIsTrustedForExposure({
|
|
22383
|
+
source: item.source,
|
|
22384
|
+
stale: item.stale,
|
|
22385
|
+
authKind: item.authKind,
|
|
22386
|
+
metadata: item.metadata
|
|
22387
|
+
})) {
|
|
22388
|
+
return "trusted";
|
|
22389
|
+
}
|
|
22390
|
+
if (item.source !== registryCapabilitySource || item.stale || Object.prototype.hasOwnProperty.call(item.metadata, "mcpProbe") || !item.authKind || item.authKind === "unknown" || installation?.status !== "active" || !mcpConnectivityOk(installation.metadata)) {
|
|
22391
|
+
return "blocked";
|
|
22392
|
+
}
|
|
22393
|
+
const hasCredentialBinding = !!encryptedHeadersConfig(installation.config.headersEncrypted) || !!connectionRefConfig(installation.config.connectionRef);
|
|
22394
|
+
if (item.authModel && !hasCredentialBinding) {
|
|
22395
|
+
return "blocked";
|
|
22396
|
+
}
|
|
22397
|
+
return "legacy_active";
|
|
22398
|
+
}
|
|
22399
|
+
function mapCapabilityCatalogItem(row, exposure = capabilityCatalogItemIsTrustedForExposure({
|
|
22400
|
+
source: row.source,
|
|
22401
|
+
stale: row.stale,
|
|
22402
|
+
authKind: row.authKind,
|
|
22403
|
+
metadata: row.metadata
|
|
22404
|
+
}) ? "trusted" : "unverified") {
|
|
22405
|
+
const catalogTrust = exposure === "legacy_active" ? {
|
|
22406
|
+
state: "legacy_active",
|
|
22407
|
+
reason: "active_installation_compatibility"
|
|
22408
|
+
} : exposure === "trusted" ? {
|
|
22409
|
+
state: "trusted",
|
|
22410
|
+
reason: row.source === registryCapabilitySource ? "verified_probe" : "trusted_source"
|
|
22411
|
+
} : {
|
|
22412
|
+
state: "unverified",
|
|
22413
|
+
reason: "missing_verification"
|
|
22414
|
+
};
|
|
21281
22415
|
const runtime = row.kind === "mcp" && row.endpointUrl ? {
|
|
21282
22416
|
available: true,
|
|
21283
22417
|
mcpServerId: mcpServerIdForCapability(row.id, row.metadata),
|
|
21284
22418
|
transport: row.transport ?? "streamable-http",
|
|
21285
|
-
notes: row.authModel ? "Requires credential headers supplied in the enable request." : null
|
|
22419
|
+
notes: row.authModel ? "Requires credential headers supplied in the enable request." : null,
|
|
22420
|
+
catalogTrust
|
|
21286
22421
|
} : {
|
|
21287
22422
|
available: false,
|
|
21288
|
-
notes: row.kind === "mcp" ? "Remote streamable HTTP endpoint is required for runtime use." : null
|
|
22423
|
+
notes: row.kind === "mcp" ? "Remote streamable HTTP endpoint is required for runtime use." : null,
|
|
22424
|
+
catalogTrust
|
|
21289
22425
|
};
|
|
21290
22426
|
return {
|
|
21291
22427
|
id: row.id,
|
|
@@ -21628,6 +22764,7 @@ export {
|
|
|
21628
22764
|
CODEX_CAPACITY_REFRESH_MAX_MS,
|
|
21629
22765
|
CODEX_CAPACITY_REFRESH_MIN_MS,
|
|
21630
22766
|
CODEX_CREDENTIAL_LEASE_TTL_MS,
|
|
22767
|
+
CODEX_RESET_REDEMPTION_OUTCOMES,
|
|
21631
22768
|
CODEX_ROTATION_STRATEGIES,
|
|
21632
22769
|
ConnectionRefreshHttpError,
|
|
21633
22770
|
HostExportPayloadError,
|
|
@@ -21678,6 +22815,7 @@ export {
|
|
|
21678
22815
|
WORKSPACE_MEMORY_BLOCK_EMPTY,
|
|
21679
22816
|
WORKSPACE_MEMORY_BLOCK_HEADER_POPULATED,
|
|
21680
22817
|
WORKSPACE_MEMORY_BLOCK_TOKEN_BUDGET,
|
|
22818
|
+
abandonCodexResetRedemptionBeforeProvider,
|
|
21681
22819
|
abandonRecordingForTurnAttempt,
|
|
21682
22820
|
acceptSessionApprovalDecision,
|
|
21683
22821
|
acceptSessionHumanInputResponse,
|
|
@@ -21688,6 +22826,8 @@ export {
|
|
|
21688
22826
|
activateRigVersion,
|
|
21689
22827
|
addSessionSystemUpdate,
|
|
21690
22828
|
addSessionSystemUpdateWithSourceMutation,
|
|
22829
|
+
admitToolspaceTurnAttempt,
|
|
22830
|
+
adoptCodexResetRedemptionAttempt,
|
|
21691
22831
|
allAccountPermissions,
|
|
21692
22832
|
allWorkspacePermissions,
|
|
21693
22833
|
appendSessionEventToSandboxGroup,
|
|
@@ -21713,6 +22853,7 @@ export {
|
|
|
21713
22853
|
buildConnectionTokenResolver,
|
|
21714
22854
|
buildHostConnectionTokenResolver,
|
|
21715
22855
|
canonicalSessionCommandHash,
|
|
22856
|
+
claimCodexResetRedemption,
|
|
21716
22857
|
claimExpiredFileUploadCleanup,
|
|
21717
22858
|
claimFileUploadCleanup,
|
|
21718
22859
|
claimHostExportBatch,
|
|
@@ -21721,6 +22862,7 @@ export {
|
|
|
21721
22862
|
claimSessionWorkForAttempt,
|
|
21722
22863
|
clearDurablePendingSessionToolCalls,
|
|
21723
22864
|
clearEnrollmentWentOffline,
|
|
22865
|
+
clearPendingSessionToolspaceCall,
|
|
21724
22866
|
clearSessionContext,
|
|
21725
22867
|
clearSessionGoal,
|
|
21726
22868
|
clearedContextMarkerItem,
|
|
@@ -21728,6 +22870,7 @@ export {
|
|
|
21728
22870
|
closeSessionTurnAttemptInTransaction,
|
|
21729
22871
|
codexCapacityRefreshBackoffMs,
|
|
21730
22872
|
commitWarmingToWarm,
|
|
22873
|
+
completeCodexResetRedemption,
|
|
21731
22874
|
completeExpiredFileUploadCleanup,
|
|
21732
22875
|
completeFileUpload,
|
|
21733
22876
|
completeFileUploadCleanup,
|
|
@@ -21822,12 +22965,15 @@ export {
|
|
|
21822
22965
|
expireSessionHumanInputRequest,
|
|
21823
22966
|
failHostExportBatch,
|
|
21824
22967
|
failWarmingToCold,
|
|
22968
|
+
fenceCodexResetRedemptionSend,
|
|
22969
|
+
fetchCodexRateLimitResetCreditsForAccount,
|
|
21825
22970
|
fetchCodexUsageForAccount,
|
|
21826
22971
|
finalizeEnrollmentByToken,
|
|
21827
22972
|
findActiveApiKeyByHash,
|
|
21828
22973
|
forceDrainOverLimitViewerOnlyBoxes,
|
|
21829
22974
|
frozenInitiatorForCommandActor,
|
|
21830
22975
|
getActiveSessionHistoryItems,
|
|
22976
|
+
getActiveSessionTurnForExecution,
|
|
21831
22977
|
getAnySessionInGroup,
|
|
21832
22978
|
getBillingBalance,
|
|
21833
22979
|
getBillingCustomer,
|
|
@@ -21835,6 +22981,7 @@ export {
|
|
|
21835
22981
|
getCapabilityInstallation,
|
|
21836
22982
|
getCodexCapacityWaitForSession,
|
|
21837
22983
|
getCodexCredentialStatus,
|
|
22984
|
+
getCodexResetRedemptionAttempt,
|
|
21838
22985
|
getCodexRotationSettings,
|
|
21839
22986
|
getComposerDraftInTransaction,
|
|
21840
22987
|
getConnectionMetadata,
|
|
@@ -21856,6 +23003,7 @@ export {
|
|
|
21856
23003
|
getPendingDeviceEnrollmentRequestByUserCode,
|
|
21857
23004
|
getPendingDeviceEnrollmentRequestByUserCodeGlobal,
|
|
21858
23005
|
getRecording,
|
|
23006
|
+
getRetainedFileArtifact,
|
|
21859
23007
|
getRig,
|
|
21860
23008
|
getRigByName,
|
|
21861
23009
|
getRigChange,
|
|
@@ -21908,6 +23056,7 @@ export {
|
|
|
21908
23056
|
insertPtySession,
|
|
21909
23057
|
insertRecording,
|
|
21910
23058
|
insertWorkspaceCapture,
|
|
23059
|
+
installOrReadTurnExecutionPolicyForAttempt,
|
|
21911
23060
|
interruptedToolCallResult,
|
|
21912
23061
|
isCodexBilledModel2 as isCodexBilledModel,
|
|
21913
23062
|
isCodexBilledTurn,
|
|
@@ -21923,6 +23072,7 @@ export {
|
|
|
21923
23072
|
listCapabilityCatalogItems,
|
|
21924
23073
|
listCapabilityInstallations,
|
|
21925
23074
|
listCodexAccountStatuses,
|
|
23075
|
+
listCodexResetRedemptionRecoveries,
|
|
21926
23076
|
listConnectionsMetadata,
|
|
21927
23077
|
listCreditBalancesByAccount,
|
|
21928
23078
|
listDistinctRigVersionIdsInGroup,
|
|
@@ -22044,6 +23194,7 @@ export {
|
|
|
22044
23194
|
registerSessionWorkflowWakeInTransaction,
|
|
22045
23195
|
registerWorkspacePack,
|
|
22046
23196
|
releaseCodexCredentialLease,
|
|
23197
|
+
releaseCodexResetRedemptionClaim,
|
|
22047
23198
|
releaseLeaseHolder,
|
|
22048
23199
|
removeWorkspaceMember,
|
|
22049
23200
|
renameCodexAccount,
|
|
@@ -22057,7 +23208,7 @@ export {
|
|
|
22057
23208
|
requireSocialConnection,
|
|
22058
23209
|
requireWorkspace,
|
|
22059
23210
|
reserveSessionCommandReceipt,
|
|
22060
|
-
|
|
23211
|
+
reserveToolspaceCallForAttempt,
|
|
22061
23212
|
resolveWorkspaceMemoryBlock,
|
|
22062
23213
|
resumeHostExportConsumer,
|
|
22063
23214
|
retireHostExportConsumer,
|
|
@@ -22120,6 +23271,7 @@ export {
|
|
|
22120
23271
|
supersedeSessionCurrentDirectionInTransaction,
|
|
22121
23272
|
touchEnrollmentLastSeen,
|
|
22122
23273
|
touchLeaseHolder,
|
|
23274
|
+
updateCodexAllocatorEligibility,
|
|
22123
23275
|
updateCodexRotationSettings,
|
|
22124
23276
|
updateConnection,
|
|
22125
23277
|
updateImportBatchCounts,
|
|
@@ -22133,6 +23285,7 @@ export {
|
|
|
22133
23285
|
updateScheduledTaskRun,
|
|
22134
23286
|
updateSessionCommandReceiptResult,
|
|
22135
23287
|
updateSessionGoal,
|
|
23288
|
+
updateSessionMcpApprovalPolicy,
|
|
22136
23289
|
updateSessionMcpServerCredentials,
|
|
22137
23290
|
updateSessionTitle,
|
|
22138
23291
|
updateVariableSet,
|
|
@@ -22152,6 +23305,7 @@ export {
|
|
|
22152
23305
|
withAccountRls,
|
|
22153
23306
|
withCodexCapacityMutation,
|
|
22154
23307
|
withCodexCredentialRefreshLock,
|
|
23308
|
+
withCodexTokenDeadline,
|
|
22155
23309
|
withRlsContext,
|
|
22156
23310
|
withWorkspaceRls,
|
|
22157
23311
|
withWorkspaceSubjectRls,
|