@opengeni/db 0.36.0 → 1.0.1
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/canonical-human-identities.js +3 -3
- package/dist/{chunk-P3RE4D2B.js → chunk-4SR4HKKA.js} +2 -2
- package/dist/{chunk-5KTIEDX7.js → chunk-6ZYKCRFO.js} +124 -16
- package/dist/chunk-6ZYKCRFO.js.map +1 -0
- package/dist/{chunk-CQZ4QSP5.js → chunk-CUPM3FGB.js} +3 -3
- package/dist/{chunk-TAUTWOS5.js → chunk-LTG3S6ZY.js} +2 -2
- package/dist/{chunk-VKLB2ZGI.js → chunk-NXAFLCQX.js} +81 -2
- package/dist/chunk-NXAFLCQX.js.map +1 -0
- package/dist/{chunk-F32YT7MP.js → chunk-WD5ROSLL.js} +2 -2
- package/dist/{chunk-JC5QPLUM.js → chunk-ZWREYEWA.js} +48 -3
- package/dist/chunk-ZWREYEWA.js.map +1 -0
- package/dist/editable-artifact-durable-export.d.ts +10 -0
- package/dist/editable-artifact-durable-export.js +18 -3
- package/dist/editable-artifact-durable-export.js.map +1 -1
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +69 -8
- package/dist/index.js +869 -75
- package/dist/index.js.map +1 -1
- package/dist/knowledge-source-sync-schema.d.ts +730 -0
- package/dist/knowledge-source-sync.d.ts +60 -0
- package/dist/persistence-errors.d.ts +8 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +3 -3
- package/dist/schema.d.ts +21 -4
- package/dist/schema.js +5 -1
- package/dist/session-queue-commands.d.ts +1 -1
- package/dist/session-realtime-ledger.d.ts +1 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/drizzle/0238_recover_unclaimed_session_turns.sql +307 -0
- package/drizzle/0240_model_context_user_messages.sql +204 -0
- package/drizzle/0243_google_drive_object_acl_authority.sql +554 -0
- package/package.json +5 -5
- package/src/editable-artifact-durable-export.ts +27 -0
- package/src/index.ts +680 -30
- package/src/knowledge-source-sync-schema.ts +83 -0
- package/src/knowledge-source-sync.ts +530 -0
- package/src/persistence-errors.ts +60 -1
- package/src/provision-roles.ts +36 -0
- package/src/runtime-posture.ts +49 -1
- package/src/schema.ts +30 -7
- package/src/session-queue-commands.ts +13 -14
- package/src/session-realtime-context.ts +16 -0
- package/src/session-realtime-ledger.ts +53 -1
- package/dist/chunk-5KTIEDX7.js.map +0 -1
- package/dist/chunk-JC5QPLUM.js.map +0 -1
- package/dist/chunk-VKLB2ZGI.js.map +0 -1
- /package/dist/{chunk-P3RE4D2B.js.map → chunk-4SR4HKKA.js.map} +0 -0
- /package/dist/{chunk-CQZ4QSP5.js.map → chunk-CUPM3FGB.js.map} +0 -0
- /package/dist/{chunk-TAUTWOS5.js.map → chunk-LTG3S6ZY.js.map} +0 -0
- /package/dist/{chunk-F32YT7MP.js.map → chunk-WD5ROSLL.js.map} +0 -0
package/src/index.ts
CHANGED
|
@@ -156,7 +156,7 @@ import {
|
|
|
156
156
|
MODEL_ATTACHMENT_REFS_FIELD,
|
|
157
157
|
MODEL_TIMELINE_ANNOTATIONS_FIELD,
|
|
158
158
|
TimelineAnnotations,
|
|
159
|
-
|
|
159
|
+
renderUserMessageContentForModel,
|
|
160
160
|
SessionMcpApprovalPolicy as SessionMcpApprovalPolicySchema,
|
|
161
161
|
RequestHumanInteractionToolInput,
|
|
162
162
|
WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1,
|
|
@@ -4331,7 +4331,7 @@ export type EnqueueSessionTurnInput = {
|
|
|
4331
4331
|
temporalWorkflowId: string;
|
|
4332
4332
|
source: SessionTurnSource;
|
|
4333
4333
|
prompt: string;
|
|
4334
|
-
|
|
4334
|
+
modelContext?: string | null;
|
|
4335
4335
|
resources: ResourceRef[];
|
|
4336
4336
|
tools: ToolRef[];
|
|
4337
4337
|
toolsProvided?: boolean;
|
|
@@ -4350,12 +4350,8 @@ export type EnqueueSessionTurnInput = {
|
|
|
4350
4350
|
placement?: "head" | "tail";
|
|
4351
4351
|
};
|
|
4352
4352
|
|
|
4353
|
-
/**
|
|
4354
|
-
* Worker-only turn projection. Host instructions are durable execution input,
|
|
4355
|
-
* never part of the public SessionTurn/queue/HTTP contract.
|
|
4356
|
-
*/
|
|
4353
|
+
/** Worker-only turn projection with causal authority omitted from public APIs. */
|
|
4357
4354
|
export type SessionTurnForExecution = SessionTurn & {
|
|
4358
|
-
turnInstructions: string | null;
|
|
4359
4355
|
personalConnectionDelegations: McpPersonalConnectionDelegation[];
|
|
4360
4356
|
/** Worker-only causal authority; never inferred from the current worker. */
|
|
4361
4357
|
initiatingHumanSubjectId: string | null;
|
|
@@ -4581,6 +4577,89 @@ export async function requireFile(
|
|
|
4581
4577
|
return file;
|
|
4582
4578
|
}
|
|
4583
4579
|
|
|
4580
|
+
/**
|
|
4581
|
+
* Subject-bound file read for user-visible and model-injection surfaces. The
|
|
4582
|
+
* database predicate returns true for ordinary workspace files, but Drive-
|
|
4583
|
+
* derived bytes require current per-object ACL evidence for every protecting
|
|
4584
|
+
* mapping. Missing/stale/mixed denied mappings therefore look not found.
|
|
4585
|
+
*/
|
|
4586
|
+
export async function requireFileForSubject(
|
|
4587
|
+
db: Database,
|
|
4588
|
+
input: {
|
|
4589
|
+
accountId: string;
|
|
4590
|
+
workspaceId: string;
|
|
4591
|
+
subjectId: string | null;
|
|
4592
|
+
fileId: string;
|
|
4593
|
+
},
|
|
4594
|
+
): Promise<FileAsset> {
|
|
4595
|
+
return await withRlsContext(
|
|
4596
|
+
db,
|
|
4597
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
4598
|
+
async (scopedDb) => {
|
|
4599
|
+
if (input.subjectId) await setSubjectRlsContext(scopedDb, input.subjectId);
|
|
4600
|
+
const subjectIdSql = input.subjectId === null ? sql`NULL::text` : sql`${input.subjectId}`;
|
|
4601
|
+
const [row] = await scopedDb
|
|
4602
|
+
.select()
|
|
4603
|
+
.from(schema.files)
|
|
4604
|
+
.where(
|
|
4605
|
+
and(
|
|
4606
|
+
eq(schema.files.accountId, input.accountId),
|
|
4607
|
+
eq(schema.files.workspaceId, input.workspaceId),
|
|
4608
|
+
eq(schema.files.id, input.fileId),
|
|
4609
|
+
sql`google_drive_file_authorized(
|
|
4610
|
+
${input.accountId}::uuid,
|
|
4611
|
+
${input.workspaceId}::uuid,
|
|
4612
|
+
${subjectIdSql},
|
|
4613
|
+
${input.fileId}::uuid
|
|
4614
|
+
)`,
|
|
4615
|
+
),
|
|
4616
|
+
)
|
|
4617
|
+
.limit(1);
|
|
4618
|
+
if (!row) throw new Error(`File not found: ${input.fileId}`);
|
|
4619
|
+
return mapFile(row);
|
|
4620
|
+
},
|
|
4621
|
+
);
|
|
4622
|
+
}
|
|
4623
|
+
|
|
4624
|
+
/** Batch form of requireFileForSubject for durable model-history projection. */
|
|
4625
|
+
export async function getFilesForSubject(
|
|
4626
|
+
db: Database,
|
|
4627
|
+
input: {
|
|
4628
|
+
accountId: string;
|
|
4629
|
+
workspaceId: string;
|
|
4630
|
+
subjectId: string | null;
|
|
4631
|
+
fileIds: readonly string[];
|
|
4632
|
+
},
|
|
4633
|
+
): Promise<FileAsset[]> {
|
|
4634
|
+
const ids = [...new Set(input.fileIds)];
|
|
4635
|
+
if (ids.length === 0) return [];
|
|
4636
|
+
return await withRlsContext(
|
|
4637
|
+
db,
|
|
4638
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
4639
|
+
async (scopedDb) => {
|
|
4640
|
+
if (input.subjectId) await setSubjectRlsContext(scopedDb, input.subjectId);
|
|
4641
|
+
const subjectIdSql = input.subjectId === null ? sql`NULL::text` : sql`${input.subjectId}`;
|
|
4642
|
+
const rows = await scopedDb
|
|
4643
|
+
.select()
|
|
4644
|
+
.from(schema.files)
|
|
4645
|
+
.where(
|
|
4646
|
+
and(
|
|
4647
|
+
eq(schema.files.accountId, input.accountId),
|
|
4648
|
+
eq(schema.files.workspaceId, input.workspaceId),
|
|
4649
|
+
inArray(schema.files.id, ids),
|
|
4650
|
+
sql`google_drive_file_authorized(
|
|
4651
|
+
${input.accountId}::uuid,
|
|
4652
|
+
${input.workspaceId}::uuid,
|
|
4653
|
+
${subjectIdSql},
|
|
4654
|
+
${schema.files.id}
|
|
4655
|
+
)`,
|
|
4656
|
+
),
|
|
4657
|
+
);
|
|
4658
|
+
return rows.map(mapFile);
|
|
4659
|
+
},
|
|
4660
|
+
);
|
|
4661
|
+
}
|
|
4662
|
+
|
|
4584
4663
|
/** One RLS-scoped query for all attachment metadata needed by a model turn. */
|
|
4585
4664
|
export async function getFiles(
|
|
4586
4665
|
db: Database,
|
|
@@ -4603,6 +4682,7 @@ export function durableUserHistoryItem(
|
|
|
4603
4682
|
prompt: string,
|
|
4604
4683
|
resources: readonly ResourceRef[],
|
|
4605
4684
|
annotations: readonly TimelineAnnotation[] = [],
|
|
4685
|
+
modelContext?: string | null,
|
|
4606
4686
|
): Record<string, unknown> {
|
|
4607
4687
|
const attachmentRefs = resources.filter(
|
|
4608
4688
|
(resource): resource is Extract<ResourceRef, { kind: "file" }> => resource.kind === "file",
|
|
@@ -4610,7 +4690,7 @@ export function durableUserHistoryItem(
|
|
|
4610
4690
|
return {
|
|
4611
4691
|
type: "message",
|
|
4612
4692
|
role: "user",
|
|
4613
|
-
content:
|
|
4693
|
+
content: renderUserMessageContentForModel(prompt, annotations, modelContext),
|
|
4614
4694
|
...(attachmentRefs.length > 0 ? { [MODEL_ATTACHMENT_REFS_FIELD]: attachmentRefs } : {}),
|
|
4615
4695
|
...(annotations.length > 0
|
|
4616
4696
|
? {
|
|
@@ -22578,6 +22658,122 @@ export async function upsertConnectorActionPolicy(
|
|
|
22578
22658
|
);
|
|
22579
22659
|
}
|
|
22580
22660
|
|
|
22661
|
+
/**
|
|
22662
|
+
* Install a risk-appropriate connector default without overwriting an explicit
|
|
22663
|
+
* user choice that already exists for the same immutable connector action.
|
|
22664
|
+
*/
|
|
22665
|
+
export async function ensureConnectorActionPolicyDefault(
|
|
22666
|
+
db: Database,
|
|
22667
|
+
input: {
|
|
22668
|
+
accountId: string;
|
|
22669
|
+
workspaceId: string;
|
|
22670
|
+
subjectId: string;
|
|
22671
|
+
connectionId: string;
|
|
22672
|
+
serverId: string;
|
|
22673
|
+
toolName: string;
|
|
22674
|
+
actionName: string;
|
|
22675
|
+
policy: ConnectorActionPolicyDecision;
|
|
22676
|
+
},
|
|
22677
|
+
): Promise<{
|
|
22678
|
+
policy: typeof schema.connectorActionPolicies.$inferSelect;
|
|
22679
|
+
changed: boolean;
|
|
22680
|
+
}> {
|
|
22681
|
+
const scope = {
|
|
22682
|
+
connectionId: boundedConnectorActionText(
|
|
22683
|
+
input.connectionId,
|
|
22684
|
+
"connector connection id",
|
|
22685
|
+
CONNECTOR_ACTION_CONNECTION_ID_MAX,
|
|
22686
|
+
),
|
|
22687
|
+
serverId: boundedConnectorActionText(
|
|
22688
|
+
input.serverId,
|
|
22689
|
+
"connector server id",
|
|
22690
|
+
CONNECTOR_ACTION_SERVER_ID_MAX,
|
|
22691
|
+
),
|
|
22692
|
+
toolName: boundedConnectorActionText(
|
|
22693
|
+
input.toolName,
|
|
22694
|
+
"connector tool name",
|
|
22695
|
+
CONNECTOR_ACTION_NAME_MAX,
|
|
22696
|
+
),
|
|
22697
|
+
actionName: boundedConnectorActionText(
|
|
22698
|
+
input.actionName,
|
|
22699
|
+
"connector action name",
|
|
22700
|
+
CONNECTOR_ACTION_NAME_MAX,
|
|
22701
|
+
),
|
|
22702
|
+
};
|
|
22703
|
+
const subjectId = boundedConnectorActionText(input.subjectId, "policy actor", 1024);
|
|
22704
|
+
return await withRlsContext(
|
|
22705
|
+
db,
|
|
22706
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
22707
|
+
async (scopedDb) =>
|
|
22708
|
+
await scopedDb.transaction(async (tx) => {
|
|
22709
|
+
await assertWorkspaceAccountPairInScope(tx, input.accountId, input.workspaceId);
|
|
22710
|
+
const [inserted] = await tx
|
|
22711
|
+
.insert(schema.connectorActionPolicies)
|
|
22712
|
+
.values({
|
|
22713
|
+
accountId: input.accountId,
|
|
22714
|
+
workspaceId: input.workspaceId,
|
|
22715
|
+
...scope,
|
|
22716
|
+
policy: input.policy,
|
|
22717
|
+
createdBySubjectId: subjectId,
|
|
22718
|
+
updatedBySubjectId: subjectId,
|
|
22719
|
+
})
|
|
22720
|
+
.onConflictDoNothing({
|
|
22721
|
+
target: [
|
|
22722
|
+
schema.connectorActionPolicies.workspaceId,
|
|
22723
|
+
schema.connectorActionPolicies.connectionId,
|
|
22724
|
+
schema.connectorActionPolicies.serverId,
|
|
22725
|
+
schema.connectorActionPolicies.toolName,
|
|
22726
|
+
schema.connectorActionPolicies.actionName,
|
|
22727
|
+
],
|
|
22728
|
+
})
|
|
22729
|
+
.returning();
|
|
22730
|
+
if (!inserted) {
|
|
22731
|
+
const [existing] = await tx
|
|
22732
|
+
.select()
|
|
22733
|
+
.from(schema.connectorActionPolicies)
|
|
22734
|
+
.where(
|
|
22735
|
+
and(
|
|
22736
|
+
eq(schema.connectorActionPolicies.workspaceId, input.workspaceId),
|
|
22737
|
+
eq(schema.connectorActionPolicies.connectionId, scope.connectionId),
|
|
22738
|
+
eq(schema.connectorActionPolicies.serverId, scope.serverId),
|
|
22739
|
+
eq(schema.connectorActionPolicies.toolName, scope.toolName),
|
|
22740
|
+
eq(schema.connectorActionPolicies.actionName, scope.actionName),
|
|
22741
|
+
),
|
|
22742
|
+
)
|
|
22743
|
+
.limit(1);
|
|
22744
|
+
if (!existing) throw new Error("Failed to reconcile connector action policy default");
|
|
22745
|
+
return { policy: existing, changed: false };
|
|
22746
|
+
}
|
|
22747
|
+
await tx.insert(schema.auditEvents).values(
|
|
22748
|
+
withLosslessContentWriteVersion(
|
|
22749
|
+
{
|
|
22750
|
+
accountId: input.accountId,
|
|
22751
|
+
workspaceId: input.workspaceId,
|
|
22752
|
+
subjectId,
|
|
22753
|
+
action: "connector.action.policy_changed",
|
|
22754
|
+
targetType: "connector_action_policy",
|
|
22755
|
+
targetId: inserted.id,
|
|
22756
|
+
metadata: {
|
|
22757
|
+
connectionId: inserted.connectionId,
|
|
22758
|
+
serverId: inserted.serverId,
|
|
22759
|
+
toolName: inserted.toolName,
|
|
22760
|
+
actionName: inserted.actionName,
|
|
22761
|
+
policy: inserted.policy,
|
|
22762
|
+
version: inserted.version,
|
|
22763
|
+
previousPolicy: null,
|
|
22764
|
+
previousVersion: null,
|
|
22765
|
+
source: "connector_default",
|
|
22766
|
+
},
|
|
22767
|
+
},
|
|
22768
|
+
"metadata",
|
|
22769
|
+
"metadataCodecVersion",
|
|
22770
|
+
),
|
|
22771
|
+
);
|
|
22772
|
+
return { policy: inserted, changed: true };
|
|
22773
|
+
}),
|
|
22774
|
+
);
|
|
22775
|
+
}
|
|
22776
|
+
|
|
22581
22777
|
export async function prepareConnectorActionApproval(
|
|
22582
22778
|
db: Database,
|
|
22583
22779
|
identity: ConnectorActionAttemptIdentity,
|
|
@@ -22916,7 +23112,7 @@ export type SessionCreateInput = {
|
|
|
22916
23112
|
accountId: string;
|
|
22917
23113
|
workspaceId: string;
|
|
22918
23114
|
initialMessage: string;
|
|
22919
|
-
|
|
23115
|
+
initialModelContext?: string | null;
|
|
22920
23116
|
resources: ResourceRef[];
|
|
22921
23117
|
skills?: SessionSkill[];
|
|
22922
23118
|
tools?: ToolRef[];
|
|
@@ -23366,7 +23562,7 @@ async function createSessionInTransaction(
|
|
|
23366
23562
|
accountId: input.accountId,
|
|
23367
23563
|
workspaceId: input.workspaceId,
|
|
23368
23564
|
initialMessage: input.initialMessage,
|
|
23369
|
-
|
|
23565
|
+
initialModelContext: input.initialModelContext ?? null,
|
|
23370
23566
|
resources: input.resources,
|
|
23371
23567
|
skills: input.skills ?? [],
|
|
23372
23568
|
tools: input.tools ?? [],
|
|
@@ -45617,6 +45813,7 @@ export async function materializeGoalContinuation(
|
|
|
45617
45813
|
if (
|
|
45618
45814
|
!goalRead ||
|
|
45619
45815
|
goalRead.status !== "active" ||
|
|
45816
|
+
session.status === "failed" ||
|
|
45620
45817
|
session.status === "cancelled" ||
|
|
45621
45818
|
effectiveControl.state !== "active"
|
|
45622
45819
|
) {
|
|
@@ -46370,6 +46567,9 @@ export async function initializeSessionStartAtomically(
|
|
|
46370
46567
|
type: "user.message",
|
|
46371
46568
|
payload: {
|
|
46372
46569
|
...initialPayload,
|
|
46570
|
+
...(session.initialModelContext
|
|
46571
|
+
? { modelContext: session.initialModelContext }
|
|
46572
|
+
: {}),
|
|
46373
46573
|
initiator: creator.initiator,
|
|
46374
46574
|
},
|
|
46375
46575
|
clientEventId: input.clientEventId ?? `session-initial:${session.id}`,
|
|
@@ -46425,7 +46625,7 @@ export async function initializeSessionStartAtomically(
|
|
|
46425
46625
|
source: "user",
|
|
46426
46626
|
position: queueTailPosition,
|
|
46427
46627
|
prompt: canonicalInitialMessage,
|
|
46428
|
-
|
|
46628
|
+
modelContext: session.initialModelContext ?? null,
|
|
46429
46629
|
resources: session.resources,
|
|
46430
46630
|
tools: session.tools,
|
|
46431
46631
|
toolsProvided: session.toolPolicy?.mode === "explicit",
|
|
@@ -46626,7 +46826,7 @@ export async function enqueueSessionTurn(
|
|
|
46626
46826
|
source: input.source,
|
|
46627
46827
|
position,
|
|
46628
46828
|
prompt: input.prompt,
|
|
46629
|
-
|
|
46829
|
+
modelContext: input.modelContext ?? null,
|
|
46630
46830
|
resources: input.resources,
|
|
46631
46831
|
tools: input.tools,
|
|
46632
46832
|
toolsProvided: input.toolsProvided ?? false,
|
|
@@ -46974,9 +47174,14 @@ export async function claimSessionWorkForAttempt(
|
|
|
46974
47174
|
input: ClaimSessionWorkForAttemptInput,
|
|
46975
47175
|
): Promise<ClaimSessionWorkForAttemptResult> {
|
|
46976
47176
|
const { sessionId, workflowId } = input;
|
|
46977
|
-
return await
|
|
47177
|
+
return await retrySessionActivityRls(
|
|
46978
47178
|
db,
|
|
46979
47179
|
workspaceId,
|
|
47180
|
+
{
|
|
47181
|
+
stage: "session_attempts.claim",
|
|
47182
|
+
eventTypes: ["session.turn.attempt_claimed"],
|
|
47183
|
+
maxAttempts: 3,
|
|
47184
|
+
},
|
|
46980
47185
|
async (scopedDb) =>
|
|
46981
47186
|
await withSessionActivitySavepoint(scopedDb, async (tx) => {
|
|
46982
47187
|
const deliverPendingUpdates = async (
|
|
@@ -48372,6 +48577,7 @@ export async function claimSessionWorkForAttempt(
|
|
|
48372
48577
|
fromPostgresLosslessText(row.prompt, row.promptCodecVersion),
|
|
48373
48578
|
Array.isArray(row.resources) ? (row.resources as ResourceRef[]) : [],
|
|
48374
48579
|
TimelineAnnotations.parse(row.annotations),
|
|
48580
|
+
row.modelContext,
|
|
48375
48581
|
),
|
|
48376
48582
|
},
|
|
48377
48583
|
"item",
|
|
@@ -50003,6 +50209,405 @@ export async function peekSessionWork(
|
|
|
50003
50209
|
});
|
|
50004
50210
|
}
|
|
50005
50211
|
|
|
50212
|
+
export type FailSessionWorkBeforeAttemptClaimInput = {
|
|
50213
|
+
accountId: string;
|
|
50214
|
+
sessionId: string;
|
|
50215
|
+
workflowId: string;
|
|
50216
|
+
trigger: SessionWorkTrigger;
|
|
50217
|
+
error: string;
|
|
50218
|
+
};
|
|
50219
|
+
|
|
50220
|
+
export type FailSessionWorkBeforeAttemptClaimResult =
|
|
50221
|
+
| { action: "failed"; turnId: string | null; events: SessionEvent[] }
|
|
50222
|
+
| { action: "terminal"; turnId: null; events: [] }
|
|
50223
|
+
| { action: "stale"; turnId: null; events: [] };
|
|
50224
|
+
|
|
50225
|
+
/**
|
|
50226
|
+
* Terminally settle a permanent admission failure that happened before an
|
|
50227
|
+
* attempt row existed. The workflow supplies the exact trigger recorded in
|
|
50228
|
+
* Temporal history; this transaction re-evaluates it under the canonical
|
|
50229
|
+
* control -> workspace -> session -> turn lock order and refuses stale,
|
|
50230
|
+
* paused, capacity-blocked, or concurrently claimed work. No attempt row is
|
|
50231
|
+
* fabricated because provider/tool execution never started.
|
|
50232
|
+
*/
|
|
50233
|
+
export async function failSessionWorkBeforeAttemptClaim(
|
|
50234
|
+
db: Database,
|
|
50235
|
+
workspaceId: string,
|
|
50236
|
+
input: FailSessionWorkBeforeAttemptClaimInput,
|
|
50237
|
+
): Promise<FailSessionWorkBeforeAttemptClaimResult> {
|
|
50238
|
+
return await retrySessionActivityRls(
|
|
50239
|
+
db,
|
|
50240
|
+
workspaceId,
|
|
50241
|
+
{
|
|
50242
|
+
stage: "session_lifecycle_outbox.fail_before_attempt_claim",
|
|
50243
|
+
eventTypes: ["child_terminal_result", "session.status.changed", "turn.failed"],
|
|
50244
|
+
maxAttempts: 3,
|
|
50245
|
+
},
|
|
50246
|
+
async (scopedDb) =>
|
|
50247
|
+
await scopedDb.transaction(async (tx) => {
|
|
50248
|
+
const baseLocks = await lockChildLifecycleOutboxWriteRowsTx(
|
|
50249
|
+
tx as unknown as Database,
|
|
50250
|
+
workspaceId,
|
|
50251
|
+
{ sessionId: input.sessionId },
|
|
50252
|
+
);
|
|
50253
|
+
const session = baseLocks.session;
|
|
50254
|
+
if (session.accountId !== input.accountId) {
|
|
50255
|
+
throw new SessionControlInvariantError(
|
|
50256
|
+
`Session ${input.sessionId} does not belong to account ${input.accountId}`,
|
|
50257
|
+
);
|
|
50258
|
+
}
|
|
50259
|
+
if (session.status === "failed" || session.status === "cancelled") {
|
|
50260
|
+
return { action: "terminal", turnId: null, events: [] } as const;
|
|
50261
|
+
}
|
|
50262
|
+
if (
|
|
50263
|
+
session.temporalWorkflowId !== null &&
|
|
50264
|
+
session.temporalWorkflowId !== input.workflowId
|
|
50265
|
+
) {
|
|
50266
|
+
return { action: "stale", turnId: null, events: [] } as const;
|
|
50267
|
+
}
|
|
50268
|
+
const effectiveControl = await evaluateSessionControl(
|
|
50269
|
+
tx as unknown as Database,
|
|
50270
|
+
workspaceId,
|
|
50271
|
+
input.sessionId,
|
|
50272
|
+
{ workspaceControl: baseLocks.control ?? undefined },
|
|
50273
|
+
);
|
|
50274
|
+
if (!baseLocks.workspace || effectiveControl.state !== "active") {
|
|
50275
|
+
return { action: "stale", turnId: null, events: [] } as const;
|
|
50276
|
+
}
|
|
50277
|
+
|
|
50278
|
+
// The session lock serializes this check with claim. If another worker
|
|
50279
|
+
// committed ownership while the activity response was ambiguous, its
|
|
50280
|
+
// attempt is authoritative and this terminal path must no-op.
|
|
50281
|
+
const [liveAttempt] = await tx
|
|
50282
|
+
.select({ id: schema.sessionTurnAttempts.id })
|
|
50283
|
+
.from(schema.sessionTurnAttempts)
|
|
50284
|
+
.where(
|
|
50285
|
+
and(
|
|
50286
|
+
eq(schema.sessionTurnAttempts.workspaceId, workspaceId),
|
|
50287
|
+
eq(schema.sessionTurnAttempts.sessionId, input.sessionId),
|
|
50288
|
+
inArray(schema.sessionTurnAttempts.state, ["claimed", "running"]),
|
|
50289
|
+
),
|
|
50290
|
+
)
|
|
50291
|
+
.limit(1);
|
|
50292
|
+
if (liveAttempt) {
|
|
50293
|
+
return { action: "stale", turnId: null, events: [] } as const;
|
|
50294
|
+
}
|
|
50295
|
+
|
|
50296
|
+
let turn: typeof schema.sessionTurns.$inferSelect | null = null;
|
|
50297
|
+
if (session.activeTurnId) {
|
|
50298
|
+
const exactLocks = await lockSessionEventWriteRows(tx as unknown as Database, {
|
|
50299
|
+
workspaceId,
|
|
50300
|
+
controlLock: "already_locked",
|
|
50301
|
+
workspaceLock: "already_locked",
|
|
50302
|
+
turnIds: [session.activeTurnId],
|
|
50303
|
+
});
|
|
50304
|
+
turn = exactLocks.turns[0] ?? null;
|
|
50305
|
+
if (!turn || turn.accountId !== session.accountId || turn.sessionId !== input.sessionId) {
|
|
50306
|
+
// The claim failed on this same locked corrupt pointer. There is no
|
|
50307
|
+
// safe turn identity to settle, but retrying can never repair it;
|
|
50308
|
+
// fail the owning session and its session-level obligations below.
|
|
50309
|
+
turn = null;
|
|
50310
|
+
}
|
|
50311
|
+
if (turn && isTerminalSessionTurnStatus(turn.status as SessionTurnStatus)) {
|
|
50312
|
+
// Repair the corrupt session pointer without writing a second
|
|
50313
|
+
// terminal event onto an already-terminal logical turn.
|
|
50314
|
+
turn = null;
|
|
50315
|
+
} else if (turn && input.trigger.kind === "approval") {
|
|
50316
|
+
if (
|
|
50317
|
+
turn.status !== "requires_action" ||
|
|
50318
|
+
!(await isNewerApprovalTrigger(
|
|
50319
|
+
tx as unknown as Database,
|
|
50320
|
+
workspaceId,
|
|
50321
|
+
input.sessionId,
|
|
50322
|
+
turn.triggerEventId,
|
|
50323
|
+
input.trigger.triggerEventId,
|
|
50324
|
+
))
|
|
50325
|
+
) {
|
|
50326
|
+
return { action: "stale", turnId: null, events: [] } as const;
|
|
50327
|
+
}
|
|
50328
|
+
} else if (turn) {
|
|
50329
|
+
if (turn.status === "requires_action") {
|
|
50330
|
+
return { action: "stale", turnId: null, events: [] } as const;
|
|
50331
|
+
}
|
|
50332
|
+
if (turn.status === "waiting_capacity") {
|
|
50333
|
+
const [codexWaiter] = await tx
|
|
50334
|
+
.select({ id: schema.codexCapacityWaiters.id })
|
|
50335
|
+
.from(schema.codexCapacityWaiters)
|
|
50336
|
+
.where(
|
|
50337
|
+
and(
|
|
50338
|
+
eq(schema.codexCapacityWaiters.workspaceId, workspaceId),
|
|
50339
|
+
eq(schema.codexCapacityWaiters.sessionId, input.sessionId),
|
|
50340
|
+
eq(schema.codexCapacityWaiters.status, "waiting"),
|
|
50341
|
+
),
|
|
50342
|
+
)
|
|
50343
|
+
.limit(1);
|
|
50344
|
+
const xaiWaiter = await getXaiCapacityWaitForSessionInTransaction(
|
|
50345
|
+
tx as unknown as Database,
|
|
50346
|
+
workspaceId,
|
|
50347
|
+
input.sessionId,
|
|
50348
|
+
);
|
|
50349
|
+
if (codexWaiter || xaiWaiter) {
|
|
50350
|
+
return { action: "stale", turnId: null, events: [] } as const;
|
|
50351
|
+
}
|
|
50352
|
+
}
|
|
50353
|
+
}
|
|
50354
|
+
} else {
|
|
50355
|
+
if (input.trigger.kind === "approval") {
|
|
50356
|
+
return { action: "stale", turnId: null, events: [] } as const;
|
|
50357
|
+
}
|
|
50358
|
+
const [pendingAgentSteer] = await tx
|
|
50359
|
+
.select({ id: schema.sessionSystemUpdates.id })
|
|
50360
|
+
.from(schema.sessionSystemUpdates)
|
|
50361
|
+
.where(
|
|
50362
|
+
and(
|
|
50363
|
+
eq(schema.sessionSystemUpdates.workspaceId, workspaceId),
|
|
50364
|
+
eq(schema.sessionSystemUpdates.sessionId, input.sessionId),
|
|
50365
|
+
eq(schema.sessionSystemUpdates.kind, "agent_steer_instruction"),
|
|
50366
|
+
eq(schema.sessionSystemUpdates.state, "pending"),
|
|
50367
|
+
),
|
|
50368
|
+
)
|
|
50369
|
+
.orderBy(
|
|
50370
|
+
desc(schema.sessionSystemUpdates.createdAt),
|
|
50371
|
+
desc(schema.sessionSystemUpdates.id),
|
|
50372
|
+
)
|
|
50373
|
+
.limit(1);
|
|
50374
|
+
const queuedRows = await rawRows<{ id: string }>(
|
|
50375
|
+
tx as unknown as Database,
|
|
50376
|
+
sql`select id from session_turns
|
|
50377
|
+
where workspace_id = ${workspaceId} and session_id = ${input.sessionId}
|
|
50378
|
+
and status = 'queued' and source in ('user', 'api')
|
|
50379
|
+
and (
|
|
50380
|
+
${Boolean(pendingAgentSteer)} = false
|
|
50381
|
+
or metadata->>'delivery' = 'steer'
|
|
50382
|
+
)
|
|
50383
|
+
order by position asc, created_at asc, id asc
|
|
50384
|
+
limit 1`,
|
|
50385
|
+
);
|
|
50386
|
+
const queuedPreview = queuedRows[0];
|
|
50387
|
+
if (queuedPreview) {
|
|
50388
|
+
const exactLocks = await lockSessionEventWriteRows(tx as unknown as Database, {
|
|
50389
|
+
workspaceId,
|
|
50390
|
+
controlLock: "already_locked",
|
|
50391
|
+
workspaceLock: "already_locked",
|
|
50392
|
+
turnIds: [queuedPreview.id],
|
|
50393
|
+
});
|
|
50394
|
+
turn = exactLocks.turns[0] ?? null;
|
|
50395
|
+
if (
|
|
50396
|
+
!turn ||
|
|
50397
|
+
turn.accountId !== session.accountId ||
|
|
50398
|
+
turn.sessionId !== input.sessionId ||
|
|
50399
|
+
turn.status !== "queued" ||
|
|
50400
|
+
turn.activeAttemptId !== null
|
|
50401
|
+
) {
|
|
50402
|
+
return { action: "stale", turnId: null, events: [] } as const;
|
|
50403
|
+
}
|
|
50404
|
+
} else if (!session.compactRequested) {
|
|
50405
|
+
if (
|
|
50406
|
+
!pendingAgentSteer &&
|
|
50407
|
+
(await latestFinishedTurnHasFailureCodeTx(
|
|
50408
|
+
tx as unknown as Database,
|
|
50409
|
+
workspaceId,
|
|
50410
|
+
input.sessionId,
|
|
50411
|
+
"context_compaction_failed",
|
|
50412
|
+
))
|
|
50413
|
+
) {
|
|
50414
|
+
return { action: "stale", turnId: null, events: [] } as const;
|
|
50415
|
+
}
|
|
50416
|
+
const [pendingUpdate] = await tx
|
|
50417
|
+
.select({ id: schema.sessionSystemUpdates.id })
|
|
50418
|
+
.from(schema.sessionSystemUpdates)
|
|
50419
|
+
.where(
|
|
50420
|
+
and(
|
|
50421
|
+
eq(schema.sessionSystemUpdates.workspaceId, workspaceId),
|
|
50422
|
+
eq(schema.sessionSystemUpdates.sessionId, input.sessionId),
|
|
50423
|
+
eq(schema.sessionSystemUpdates.state, "pending"),
|
|
50424
|
+
),
|
|
50425
|
+
)
|
|
50426
|
+
.limit(1)
|
|
50427
|
+
.for("update");
|
|
50428
|
+
if (!pendingUpdate) {
|
|
50429
|
+
return { action: "stale", turnId: null, events: [] } as const;
|
|
50430
|
+
}
|
|
50431
|
+
}
|
|
50432
|
+
}
|
|
50433
|
+
|
|
50434
|
+
const now = new Date();
|
|
50435
|
+
let sequence = session.lastSequence;
|
|
50436
|
+
let closedToolEvents: SessionEvent[] = [];
|
|
50437
|
+
if (turn) {
|
|
50438
|
+
await cancelTurnInteractionInterventionsInTransaction(tx as unknown as Database, {
|
|
50439
|
+
accountId: session.accountId,
|
|
50440
|
+
workspaceId,
|
|
50441
|
+
sessionId: input.sessionId,
|
|
50442
|
+
turnId: turn.id,
|
|
50443
|
+
});
|
|
50444
|
+
const closedTools = await closePendingSessionToolCallsInTransaction(
|
|
50445
|
+
tx as unknown as Database,
|
|
50446
|
+
{
|
|
50447
|
+
accountId: session.accountId,
|
|
50448
|
+
workspaceId,
|
|
50449
|
+
sessionId: input.sessionId,
|
|
50450
|
+
turnId: turn.id,
|
|
50451
|
+
reason: "turn_failed",
|
|
50452
|
+
sequence,
|
|
50453
|
+
now,
|
|
50454
|
+
},
|
|
50455
|
+
);
|
|
50456
|
+
sequence = closedTools.sequence;
|
|
50457
|
+
closedToolEvents = closedTools.events;
|
|
50458
|
+
await tx
|
|
50459
|
+
.update(schema.sessionHumanInputRequests)
|
|
50460
|
+
.set({
|
|
50461
|
+
status: "cancelled",
|
|
50462
|
+
response: { outcome: "cancelled" },
|
|
50463
|
+
respondedBy: "system:turn_failed",
|
|
50464
|
+
respondedAt: now,
|
|
50465
|
+
updatedAt: now,
|
|
50466
|
+
})
|
|
50467
|
+
.where(
|
|
50468
|
+
and(
|
|
50469
|
+
eq(schema.sessionHumanInputRequests.workspaceId, workspaceId),
|
|
50470
|
+
eq(schema.sessionHumanInputRequests.sessionId, input.sessionId),
|
|
50471
|
+
eq(schema.sessionHumanInputRequests.turnId, turn.id),
|
|
50472
|
+
eq(schema.sessionHumanInputRequests.status, "pending"),
|
|
50473
|
+
),
|
|
50474
|
+
);
|
|
50475
|
+
}
|
|
50476
|
+
await settleSessionMaintenanceInTransaction(tx as unknown as Database, {
|
|
50477
|
+
accountId: session.accountId,
|
|
50478
|
+
workspaceId,
|
|
50479
|
+
sessionId: input.sessionId,
|
|
50480
|
+
});
|
|
50481
|
+
if (!turn) {
|
|
50482
|
+
await tx
|
|
50483
|
+
.update(schema.sessionSystemUpdates)
|
|
50484
|
+
.set({ state: "failed" })
|
|
50485
|
+
.where(
|
|
50486
|
+
and(
|
|
50487
|
+
eq(schema.sessionSystemUpdates.workspaceId, workspaceId),
|
|
50488
|
+
eq(schema.sessionSystemUpdates.sessionId, input.sessionId),
|
|
50489
|
+
eq(schema.sessionSystemUpdates.state, "pending"),
|
|
50490
|
+
),
|
|
50491
|
+
);
|
|
50492
|
+
}
|
|
50493
|
+
const values = [
|
|
50494
|
+
...(turn
|
|
50495
|
+
? [
|
|
50496
|
+
{
|
|
50497
|
+
accountId: session.accountId,
|
|
50498
|
+
workspaceId,
|
|
50499
|
+
sessionId: input.sessionId,
|
|
50500
|
+
sequence: ++sequence,
|
|
50501
|
+
type: "turn.failed" as const,
|
|
50502
|
+
payload: {
|
|
50503
|
+
triggerEventId: turn.triggerEventId,
|
|
50504
|
+
code: "pre_claim_failure",
|
|
50505
|
+
error: input.error,
|
|
50506
|
+
},
|
|
50507
|
+
turnId: turn.id,
|
|
50508
|
+
turnGeneration: turn.executionGeneration,
|
|
50509
|
+
turnAttemptId: null,
|
|
50510
|
+
turnAssociation: "current" as const,
|
|
50511
|
+
occurredAt: now,
|
|
50512
|
+
},
|
|
50513
|
+
]
|
|
50514
|
+
: []),
|
|
50515
|
+
{
|
|
50516
|
+
accountId: session.accountId,
|
|
50517
|
+
workspaceId,
|
|
50518
|
+
sessionId: input.sessionId,
|
|
50519
|
+
sequence: ++sequence,
|
|
50520
|
+
type: "session.status.changed" as const,
|
|
50521
|
+
payload: { status: "failed", code: "pre_claim_failure" },
|
|
50522
|
+
turnId: turn?.id ?? null,
|
|
50523
|
+
turnGeneration: turn?.executionGeneration ?? null,
|
|
50524
|
+
turnAttemptId: null,
|
|
50525
|
+
turnAssociation: turn ? ("current" as const) : null,
|
|
50526
|
+
occurredAt: now,
|
|
50527
|
+
},
|
|
50528
|
+
];
|
|
50529
|
+
const inserted = await tx
|
|
50530
|
+
.insert(schema.sessionEvents)
|
|
50531
|
+
.values(withLosslessContentWriteVersion(values, "payload", "payloadCodecVersion"))
|
|
50532
|
+
.returning();
|
|
50533
|
+
if (turn) {
|
|
50534
|
+
const failedEvent = inserted.find((event) => event.type === "turn.failed");
|
|
50535
|
+
if (!failedEvent) {
|
|
50536
|
+
throw new SessionControlInvariantError(
|
|
50537
|
+
`Pre-claim failure for turn ${turn.id} has no terminal event`,
|
|
50538
|
+
);
|
|
50539
|
+
}
|
|
50540
|
+
await projectSessionRealtimeDelegationTerminalInTransaction(tx as unknown as Database, {
|
|
50541
|
+
accountId: session.accountId,
|
|
50542
|
+
workspaceId,
|
|
50543
|
+
sessionId: input.sessionId,
|
|
50544
|
+
turnId: turn.id,
|
|
50545
|
+
turnStatus: "failed",
|
|
50546
|
+
terminalEvent: {
|
|
50547
|
+
id: failedEvent.id,
|
|
50548
|
+
type: "turn.failed",
|
|
50549
|
+
payload: sessionEventPayloadRecord(
|
|
50550
|
+
failedEvent.payload,
|
|
50551
|
+
failedEvent.payloadCodecVersion,
|
|
50552
|
+
),
|
|
50553
|
+
},
|
|
50554
|
+
now,
|
|
50555
|
+
});
|
|
50556
|
+
await tx
|
|
50557
|
+
.update(schema.sessionTurns)
|
|
50558
|
+
.set({
|
|
50559
|
+
status: "failed",
|
|
50560
|
+
activeAttemptId: null,
|
|
50561
|
+
version: turn.version + 1,
|
|
50562
|
+
finishedAt: now,
|
|
50563
|
+
updatedAt: now,
|
|
50564
|
+
})
|
|
50565
|
+
.where(
|
|
50566
|
+
and(
|
|
50567
|
+
eq(schema.sessionTurns.workspaceId, workspaceId),
|
|
50568
|
+
eq(schema.sessionTurns.sessionId, input.sessionId),
|
|
50569
|
+
eq(schema.sessionTurns.id, turn.id),
|
|
50570
|
+
),
|
|
50571
|
+
);
|
|
50572
|
+
await enqueueFailedChildOutboxForTurnTx(
|
|
50573
|
+
tx as unknown as Database,
|
|
50574
|
+
workspaceId,
|
|
50575
|
+
session,
|
|
50576
|
+
turn,
|
|
50577
|
+
);
|
|
50578
|
+
} else {
|
|
50579
|
+
await enqueueFailedChildOutboxWithoutTurnTx(
|
|
50580
|
+
tx as unknown as Database,
|
|
50581
|
+
workspaceId,
|
|
50582
|
+
session,
|
|
50583
|
+
sequence,
|
|
50584
|
+
);
|
|
50585
|
+
}
|
|
50586
|
+
await tx
|
|
50587
|
+
.update(schema.sessions)
|
|
50588
|
+
.set({
|
|
50589
|
+
status: "failed",
|
|
50590
|
+
activeTurnId: null,
|
|
50591
|
+
compactRequested: false,
|
|
50592
|
+
lastSequence: sequence,
|
|
50593
|
+
queueVersion: session.queueVersion + 1,
|
|
50594
|
+
updatedAt: now,
|
|
50595
|
+
})
|
|
50596
|
+
.where(
|
|
50597
|
+
and(
|
|
50598
|
+
eq(schema.sessions.workspaceId, workspaceId),
|
|
50599
|
+
eq(schema.sessions.id, input.sessionId),
|
|
50600
|
+
),
|
|
50601
|
+
);
|
|
50602
|
+
return {
|
|
50603
|
+
action: "failed",
|
|
50604
|
+
turnId: turn?.id ?? null,
|
|
50605
|
+
events: [...closedToolEvents, ...inserted.map(mapEvent)],
|
|
50606
|
+
} as const;
|
|
50607
|
+
}),
|
|
50608
|
+
);
|
|
50609
|
+
}
|
|
50610
|
+
|
|
50006
50611
|
/**
|
|
50007
50612
|
* Child lifecycle transactions discover immutable parentage only after taking
|
|
50008
50613
|
* the control/workspace prefix. They must then lock child and parent together,
|
|
@@ -52804,14 +53409,16 @@ function queuedSteerReplacementAttemptId(metadata: Record<string, unknown>): str
|
|
|
52804
53409
|
return null;
|
|
52805
53410
|
}
|
|
52806
53411
|
|
|
52807
|
-
async function
|
|
53412
|
+
async function enqueueFailedChildOutboxTx(
|
|
52808
53413
|
tx: Database,
|
|
52809
53414
|
workspaceId: string,
|
|
52810
|
-
session: Pick<
|
|
52811
|
-
|
|
53415
|
+
session: Pick<
|
|
53416
|
+
typeof schema.sessions.$inferSelect,
|
|
53417
|
+
"id" | "accountId" | "parentSessionId" | "parentTurnId"
|
|
53418
|
+
>,
|
|
53419
|
+
input: { turnId: string | null; dedupeKey: string },
|
|
52812
53420
|
): Promise<void> {
|
|
52813
53421
|
if (!session.parentSessionId) return;
|
|
52814
|
-
const dedupeKey = `child-completion:${turn.sessionId}:turn:${turn.id}`;
|
|
52815
53422
|
const personalConnectionDelegations = session.parentTurnId
|
|
52816
53423
|
? await personalConnectionDelegationsForTurnInTransaction(
|
|
52817
53424
|
tx,
|
|
@@ -52859,26 +53466,26 @@ async function enqueueFailedChildOutboxForTurnTx(
|
|
|
52859
53466
|
withLosslessContentWriteVersion(
|
|
52860
53467
|
withLosslessContentWriteVersion(
|
|
52861
53468
|
{
|
|
52862
|
-
accountId:
|
|
53469
|
+
accountId: session.accountId,
|
|
52863
53470
|
workspaceId,
|
|
52864
|
-
sourceSessionId:
|
|
53471
|
+
sourceSessionId: session.id,
|
|
52865
53472
|
targetSessionId: session.parentSessionId,
|
|
52866
|
-
dedupeKey,
|
|
53473
|
+
dedupeKey: input.dedupeKey,
|
|
52867
53474
|
kind: "child_terminal_result",
|
|
52868
53475
|
classification: "failure",
|
|
52869
|
-
sourceId:
|
|
53476
|
+
sourceId: session.id,
|
|
52870
53477
|
summary: "Child session failed; inspect the durable child timeline.",
|
|
52871
53478
|
payload: {
|
|
52872
53479
|
type: "child_terminal_result",
|
|
52873
|
-
childSessionId:
|
|
53480
|
+
childSessionId: session.id,
|
|
52874
53481
|
status: "failed",
|
|
52875
|
-
turnId:
|
|
53482
|
+
...(input.turnId ? { turnId: input.turnId } : {}),
|
|
52876
53483
|
},
|
|
52877
53484
|
lineage: {
|
|
52878
|
-
childSessionId:
|
|
53485
|
+
childSessionId: session.id,
|
|
52879
53486
|
parentSessionId: session.parentSessionId,
|
|
52880
53487
|
...(session.parentTurnId ? { parentTurnId: session.parentTurnId } : {}),
|
|
52881
|
-
turnId:
|
|
53488
|
+
...(input.turnId ? { turnId: input.turnId } : {}),
|
|
52882
53489
|
...(xaiAuthoritySubjectId ? { xaiAuthoritySubjectId } : {}),
|
|
52883
53490
|
},
|
|
52884
53491
|
personalConnectionDelegations,
|
|
@@ -52899,6 +53506,41 @@ async function enqueueFailedChildOutboxForTurnTx(
|
|
|
52899
53506
|
});
|
|
52900
53507
|
}
|
|
52901
53508
|
|
|
53509
|
+
async function enqueueFailedChildOutboxForTurnTx(
|
|
53510
|
+
tx: Database,
|
|
53511
|
+
workspaceId: string,
|
|
53512
|
+
session: Pick<
|
|
53513
|
+
typeof schema.sessions.$inferSelect,
|
|
53514
|
+
"id" | "accountId" | "parentSessionId" | "parentTurnId"
|
|
53515
|
+
>,
|
|
53516
|
+
turn: Pick<typeof schema.sessionTurns.$inferSelect, "id" | "accountId" | "sessionId">,
|
|
53517
|
+
): Promise<void> {
|
|
53518
|
+
if (turn.accountId !== session.accountId || turn.sessionId !== session.id) {
|
|
53519
|
+
throw new SessionControlInvariantError(
|
|
53520
|
+
`Failed child turn ${turn.id} lost session ${session.id} ownership`,
|
|
53521
|
+
);
|
|
53522
|
+
}
|
|
53523
|
+
await enqueueFailedChildOutboxTx(tx, workspaceId, session, {
|
|
53524
|
+
turnId: turn.id,
|
|
53525
|
+
dedupeKey: `child-completion:${turn.sessionId}:turn:${turn.id}`,
|
|
53526
|
+
});
|
|
53527
|
+
}
|
|
53528
|
+
|
|
53529
|
+
async function enqueueFailedChildOutboxWithoutTurnTx(
|
|
53530
|
+
tx: Database,
|
|
53531
|
+
workspaceId: string,
|
|
53532
|
+
session: Pick<
|
|
53533
|
+
typeof schema.sessions.$inferSelect,
|
|
53534
|
+
"id" | "accountId" | "parentSessionId" | "parentTurnId"
|
|
53535
|
+
>,
|
|
53536
|
+
terminalSequence: number,
|
|
53537
|
+
): Promise<void> {
|
|
53538
|
+
await enqueueFailedChildOutboxTx(tx, workspaceId, session, {
|
|
53539
|
+
turnId: null,
|
|
53540
|
+
dedupeKey: `child-completion:${session.id}:pre-claim:${terminalSequence}`,
|
|
53541
|
+
});
|
|
53542
|
+
}
|
|
53543
|
+
|
|
52902
53544
|
type ChildTerminalResultPayload = Extract<
|
|
52903
53545
|
SessionSystemUpdatePayload,
|
|
52904
53546
|
{ type: "child_terminal_result" }
|
|
@@ -53106,10 +53748,19 @@ export async function enqueueSessionWorkflowWakeInTransaction(
|
|
|
53106
53748
|
: sql`${schema.sessionWorkflowWakeOutbox.controlRevision}`,
|
|
53107
53749
|
reason: input.reason,
|
|
53108
53750
|
attempts: 0,
|
|
53109
|
-
// Coalescing a delayed retry must never postpone an
|
|
53110
|
-
// owned by another producer.
|
|
53111
|
-
//
|
|
53112
|
-
|
|
53751
|
+
// Coalescing a delayed retry must never postpone an undelivered wake
|
|
53752
|
+
// owned by another producer. Once every prior revision is delivered,
|
|
53753
|
+
// however, this is a new obligation and must honor its own deadline;
|
|
53754
|
+
// retaining the old (usually past) deadline would defeat backoff.
|
|
53755
|
+
nextAttemptAt: sql`case
|
|
53756
|
+
when ${schema.sessionWorkflowWakeOutbox.wakeRevision}
|
|
53757
|
+
> ${schema.sessionWorkflowWakeOutbox.deliveredRevision}
|
|
53758
|
+
then least(
|
|
53759
|
+
${schema.sessionWorkflowWakeOutbox.nextAttemptAt},
|
|
53760
|
+
${nextAttemptAt.toISOString()}::timestamptz
|
|
53761
|
+
)
|
|
53762
|
+
else ${nextAttemptAt.toISOString()}::timestamptz
|
|
53763
|
+
end`,
|
|
53113
53764
|
lastError: null,
|
|
53114
53765
|
updatedAt: now,
|
|
53115
53766
|
},
|
|
@@ -55307,7 +55958,6 @@ function mapSessionTurnForExecution(
|
|
|
55307
55958
|
): SessionTurnForExecution {
|
|
55308
55959
|
return {
|
|
55309
55960
|
...mapSessionTurn(row),
|
|
55310
|
-
turnInstructions: row.turnInstructions ?? null,
|
|
55311
55961
|
initiatingHumanSubjectId: row.initiatingHumanSubjectId ?? null,
|
|
55312
55962
|
personalConnectionDelegations: parsedPersonalConnectionDelegations(
|
|
55313
55963
|
row.personalConnectionDelegations,
|