@opengeni/db 0.22.2 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/{chunk-CYGFLLMN.js → chunk-3UCHDMKG.js} +656 -282
  2. package/dist/chunk-3UCHDMKG.js.map +1 -0
  3. package/dist/{chunk-BNGEN5QZ.js → chunk-L6ADMZHE.js} +24 -2
  4. package/dist/chunk-L6ADMZHE.js.map +1 -0
  5. package/dist/index.d.ts +39 -3
  6. package/dist/index.js +7009 -3882
  7. package/dist/index.js.map +1 -1
  8. package/dist/provision-roles.js +1 -1
  9. package/dist/runtime-posture.d.ts +3 -3
  10. package/dist/schema.d.ts +1416 -92
  11. package/dist/schema.js +11 -1
  12. package/dist/session-control.d.ts +7 -1
  13. package/dist/session-queue-commands.d.ts +8 -0
  14. package/dist/session-realtime-context.d.ts +56 -0
  15. package/dist/session-realtime-ledger.d.ts +188 -0
  16. package/dist/session-realtime-mirror.d.ts +30 -0
  17. package/dist/session-realtime-state.d.ts +2 -0
  18. package/dist/session-realtime-terminal.d.ts +40 -0
  19. package/dist/session-realtime.d.ts +59 -0
  20. package/dist/workspace-instruction-policies-schema.d.ts +239 -0
  21. package/dist/workspace-instruction-policies.d.ts +44 -0
  22. package/drizzle/0156_slack_reaction_trigger.sql +49 -0
  23. package/drizzle/0157_session_policy_role_snapshots.sql +1146 -0
  24. package/drizzle/0158_session_realtime_mode.sql +88 -0
  25. package/drizzle/0159_session_realtime_ledger.sql +198 -0
  26. package/drizzle/0160_session_realtime_delegation_terminal.sql +38 -0
  27. package/drizzle/0161_session_realtime_context_projection.sql +82 -0
  28. package/drizzle/0162_session_realtime_connection_promotion.sql +53 -0
  29. package/drizzle/0163_session_realtime_delegation_progress.sql +35 -0
  30. package/drizzle/0164_session_realtime_models.sql +28 -0
  31. package/package.json +4 -4
  32. package/src/index.ts +614 -78
  33. package/src/preference-registry.ts +11 -6
  34. package/src/provision-roles.ts +12 -0
  35. package/src/runtime-posture.ts +10 -0
  36. package/src/schema.ts +400 -43
  37. package/src/session-control.ts +596 -21
  38. package/src/session-queue-commands.ts +76 -7
  39. package/src/session-realtime-context.ts +393 -0
  40. package/src/session-realtime-ledger.ts +1790 -0
  41. package/src/session-realtime-mirror.ts +160 -0
  42. package/src/session-realtime-state.ts +25 -0
  43. package/src/session-realtime-terminal.ts +306 -0
  44. package/src/session-realtime.ts +611 -0
  45. package/src/workspace-instruction-policies-schema.ts +41 -0
  46. package/src/workspace-instruction-policies.ts +131 -2
  47. package/dist/chunk-BNGEN5QZ.js.map +0 -1
  48. package/dist/chunk-CYGFLLMN.js.map +0 -1
package/src/index.ts CHANGED
@@ -217,11 +217,25 @@ import {
217
217
  serializeEffectiveSessionControl,
218
218
  type SessionDiscoveryControl,
219
219
  type SessionCommandActor,
220
+ SessionControlConflictError,
220
221
  SessionControlInvariantError,
221
222
  updateSessionCommandReceiptResult,
222
223
  type SessionTurnAttemptOutcome,
223
224
  type WorkspaceControlRow,
224
225
  } from "./session-control";
226
+ import {
227
+ sessionRealtimeIsActiveInTransaction,
228
+ settleExpiredSessionRealtimeInTransaction,
229
+ } from "./session-realtime";
230
+ import {
231
+ isSessionRealtimeDelegationTurnMetadata,
232
+ projectSessionRealtimeDelegationProgressInTransaction,
233
+ projectSessionRealtimeDelegationTerminalInTransaction,
234
+ } from "./session-realtime-ledger";
235
+ import {
236
+ listSessionRealtimeContinuityEntriesInTransaction,
237
+ type SessionRealtimeContinuityEntry,
238
+ } from "./session-realtime-context";
225
239
  import * as schema from "./schema";
226
240
  import {
227
241
  AGENT_VISIBLE_MEMORY_STATUSES,
@@ -243,6 +257,9 @@ import {
243
257
  export { sql as dbSql } from "drizzle-orm";
244
258
  export * from "./session-control";
245
259
  export * from "./session-queue-commands";
260
+ export * from "./session-realtime";
261
+ export * from "./session-realtime-context";
262
+ export * from "./session-realtime-ledger";
246
263
  export * from "./new-session-drafts";
247
264
  export * from "./workspace-instruction-policies";
248
265
  export * from "./preference-registry";
@@ -5388,6 +5405,7 @@ export type SlackBotUserLink = {
5388
5405
  export type SlackInteractionTriggerKind =
5389
5406
  | "app_mention"
5390
5407
  | "dm"
5408
+ | "reaction"
5391
5409
  | "slash_command"
5392
5410
  | "message_shortcut"
5393
5411
  | "thread_reply";
@@ -5412,6 +5430,7 @@ export type SlackInteractionInboxEntry = {
5412
5430
  attemptCount: number;
5413
5431
  retryAt: Date | null;
5414
5432
  lastErrorCode: string | null;
5433
+ reactionContextCheckpoint: unknown | null;
5415
5434
  processedAt: Date | null;
5416
5435
  createdAt: Date;
5417
5436
  updatedAt: Date;
@@ -5557,6 +5576,7 @@ export async function enqueueSlackInteractionInbox(
5557
5576
  | "attemptCount"
5558
5577
  | "retryAt"
5559
5578
  | "lastErrorCode"
5579
+ | "reactionContextCheckpoint"
5560
5580
  | "processedAt"
5561
5581
  | "createdAt"
5562
5582
  | "updatedAt"
@@ -5619,6 +5639,7 @@ export async function settleSlackInteractionInbox(
5619
5639
  claimHolderId: null,
5620
5640
  claimExpiresAt: null,
5621
5641
  retryAt: null,
5642
+ reactionContextCheckpoint: null,
5622
5643
  processedAt: sql`now()`,
5623
5644
  lastErrorCode: input.errorCode ?? null,
5624
5645
  updatedAt: sql`now()`,
@@ -5635,6 +5656,42 @@ export async function settleSlackInteractionInbox(
5635
5656
  });
5636
5657
  }
5637
5658
 
5659
+ export async function saveSlackInteractionInboxReactionCheckpoint(
5660
+ db: Database,
5661
+ input: {
5662
+ entry: Pick<
5663
+ SlackInteractionInboxEntry,
5664
+ "id" | "accountId" | "workspaceId" | "connectionId" | "providerEventId" | "providerMessageId"
5665
+ >;
5666
+ claimHolderId: string;
5667
+ checkpoint: unknown;
5668
+ },
5669
+ ): Promise<boolean> {
5670
+ return await withRlsContext(db, input.entry, async (scopedDb) => {
5671
+ const rows = await scopedDb
5672
+ .update(schema.slackInteractionInbox)
5673
+ .set({
5674
+ reactionContextCheckpoint: input.checkpoint,
5675
+ updatedAt: sql`now()`,
5676
+ })
5677
+ .where(
5678
+ and(
5679
+ eq(schema.slackInteractionInbox.id, input.entry.id),
5680
+ eq(schema.slackInteractionInbox.accountId, input.entry.accountId),
5681
+ eq(schema.slackInteractionInbox.workspaceId, input.entry.workspaceId),
5682
+ eq(schema.slackInteractionInbox.connectionId, input.entry.connectionId),
5683
+ eq(schema.slackInteractionInbox.providerEventId, input.entry.providerEventId),
5684
+ eq(schema.slackInteractionInbox.providerMessageId, input.entry.providerMessageId),
5685
+ eq(schema.slackInteractionInbox.triggerKind, "reaction"),
5686
+ eq(schema.slackInteractionInbox.claimHolderId, input.claimHolderId),
5687
+ eq(schema.slackInteractionInbox.status, "processing"),
5688
+ ),
5689
+ )
5690
+ .returning({ id: schema.slackInteractionInbox.id });
5691
+ return rows.length === 1;
5692
+ });
5693
+ }
5694
+
5638
5695
  export async function releaseSlackInteractionInbox(
5639
5696
  db: Database,
5640
5697
  input: {
@@ -5731,6 +5788,57 @@ export async function getSlackInteractionByRoute(
5731
5788
  });
5732
5789
  }
5733
5790
 
5791
+ /**
5792
+ * Resolve an already-durable Slack task to its canonical interaction.
5793
+ *
5794
+ * Client event ids are unique only inside one session, so this lookup also
5795
+ * fences the exact workspace and Slack connection. The reservation id is the
5796
+ * canonical Slack session identity both before and after binding; joining it
5797
+ * lets a retry repair a crash after the task committed but before the route was
5798
+ * bound. Two matches indicate a cross-session collision and fail closed rather
5799
+ * than choosing an arbitrary route.
5800
+ */
5801
+ export async function getSlackInteractionByClientEventId(
5802
+ db: Database,
5803
+ workspaceId: string,
5804
+ connectionId: string,
5805
+ clientEventId: string,
5806
+ ): Promise<{ interaction: SlackInteraction; eventSessionId: string } | null> {
5807
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
5808
+ const rows = await scopedDb
5809
+ .select({
5810
+ interaction: schema.slackInteractions,
5811
+ eventSessionId: schema.sessionEvents.sessionId,
5812
+ })
5813
+ .from(schema.slackInteractions)
5814
+ .innerJoin(
5815
+ schema.sessionEvents,
5816
+ and(
5817
+ eq(schema.sessionEvents.workspaceId, schema.slackInteractions.workspaceId),
5818
+ eq(schema.sessionEvents.sessionId, schema.slackInteractions.sessionReservationId),
5819
+ ),
5820
+ )
5821
+ .where(
5822
+ and(
5823
+ eq(schema.slackInteractions.workspaceId, workspaceId),
5824
+ eq(schema.slackInteractions.connectionId, connectionId),
5825
+ eq(schema.sessionEvents.clientEventId, clientEventId),
5826
+ eq(schema.sessionEvents.type, "user.message"),
5827
+ ),
5828
+ )
5829
+ .limit(2);
5830
+ if (rows.length > 1) {
5831
+ throw new Error("Slack client event id resolved to multiple canonical interactions");
5832
+ }
5833
+ const row = rows[0];
5834
+ if (!row) return null;
5835
+ return {
5836
+ interaction: mapSlackInteraction(row.interaction),
5837
+ eventSessionId: row.eventSessionId,
5838
+ };
5839
+ });
5840
+ }
5841
+
5734
5842
  export async function getSlackInteractionSessionAccess(
5735
5843
  db: Database,
5736
5844
  workspaceId: string,
@@ -6178,6 +6286,8 @@ function mapSlackInteractionInbox(
6178
6286
  attemptCount: slackRowNumber(row, "attemptCount", "attempt_count"),
6179
6287
  retryAt: slackRowNullableDate(row, "retryAt", "retry_at"),
6180
6288
  lastErrorCode: slackRowNullableString(row, "lastErrorCode", "last_error_code"),
6289
+ reactionContextCheckpoint:
6290
+ slackRowValue(row, "reactionContextCheckpoint", "reaction_context_checkpoint") ?? null,
6181
6291
  processedAt: slackRowNullableDate(row, "processedAt", "processed_at"),
6182
6292
  createdAt: slackRowDate(row, "createdAt", "created_at"),
6183
6293
  updatedAt: slackRowDate(row, "updatedAt", "updated_at"),
@@ -16693,6 +16803,7 @@ export type SessionCreateInput = {
16693
16803
  firstPartyMcpPermissions?: Permission[] | null;
16694
16804
  firstPartyMcpTools?: FirstPartyMcpToolName[];
16695
16805
  instructions?: string | null;
16806
+ policyRole?: string | null;
16696
16807
  parentSessionId?: string | null;
16697
16808
  createIdempotencyKey?: string | null;
16698
16809
  sandboxGroupId?: string | null;
@@ -16849,6 +16960,9 @@ async function resolveSessionDepthDecision(
16849
16960
  if (!parent) {
16850
16961
  throw new Error(`Parent session not found: ${parentSessionId}`);
16851
16962
  }
16963
+ if (parent.status === "cancelled") {
16964
+ throw new SessionControlConflictError("Cancelled session subtree cannot create children");
16965
+ }
16852
16966
  }
16853
16967
 
16854
16968
  const currentParentDepth = parent?.nestedAgentDepth ?? 0;
@@ -17104,6 +17218,7 @@ async function createSessionInTransaction(
17104
17218
  firstPartyMcpTools: input.firstPartyMcpTools ?? [...DEFAULT_FIRST_PARTY_MCP_TOOLS],
17105
17219
  initialPersonalConnectionDelegations: input.personalConnectionDelegations ?? [],
17106
17220
  instructions: input.instructions ?? null,
17221
+ policyRole: input.policyRole ?? null,
17107
17222
  parentSessionId: input.parentSessionId ?? null,
17108
17223
  parentTurnId,
17109
17224
  createIdempotencyKey,
@@ -21298,6 +21413,29 @@ export async function getActiveSessionHistoryItems(
21298
21413
  });
21299
21414
  }
21300
21415
 
21416
+ /** Bounded finalized voice turns used only to resume a later realtime call. */
21417
+ export async function getSessionRealtimeContinuityEntries(
21418
+ db: Database,
21419
+ workspaceId: string,
21420
+ sessionId: string,
21421
+ maximumEntries = 20,
21422
+ ): Promise<SessionRealtimeContinuityEntry[]> {
21423
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
21424
+ const [session] = await scopedDb
21425
+ .select({ accountId: schema.sessions.accountId })
21426
+ .from(schema.sessions)
21427
+ .where(and(eq(schema.sessions.workspaceId, workspaceId), eq(schema.sessions.id, sessionId)))
21428
+ .limit(1);
21429
+ if (!session) return [];
21430
+ return await listSessionRealtimeContinuityEntriesInTransaction(scopedDb, {
21431
+ accountId: session.accountId,
21432
+ workspaceId,
21433
+ sessionId,
21434
+ maximumEntries,
21435
+ });
21436
+ });
21437
+ }
21438
+
21301
21439
  /**
21302
21440
  * Count of ACTIVE (live, model-facing) history rows for a session. This is the
21303
21441
  * length of the history the next turn is seeded from — the dual-write slice
@@ -36074,6 +36212,8 @@ export type InitializeSessionStartInput = {
36074
36212
  subjectId: string;
36075
36213
  expectedRevision: number;
36076
36214
  } | null;
36215
+ /** Persist session.created only; realtime will supply the first human turn. */
36216
+ deferInitialTurn?: boolean;
36077
36217
  };
36078
36218
 
36079
36219
  export type InitializeSessionStartResult = {
@@ -36159,6 +36299,95 @@ export async function initializeSessionStartAtomically(
36159
36299
  if (!goal) throw new Error("Failed to create initial session goal");
36160
36300
  }
36161
36301
 
36302
+ if (input.deferInitialTurn) {
36303
+ const [existingCreatedEvent] = await tx
36304
+ .select({ id: schema.sessionEvents.id })
36305
+ .from(schema.sessionEvents)
36306
+ .where(
36307
+ and(
36308
+ eq(schema.sessionEvents.workspaceId, input.workspaceId),
36309
+ eq(schema.sessionEvents.sessionId, session.id),
36310
+ eq(schema.sessionEvents.type, "session.created"),
36311
+ ),
36312
+ )
36313
+ .limit(1);
36314
+ let sequence = session.lastSequence;
36315
+ let initializedNow = false;
36316
+ let insertedEvents: Array<typeof schema.sessionEvents.$inferSelect> = [];
36317
+ if (!existingCreatedEvent) {
36318
+ insertedEvents = await tx
36319
+ .insert(schema.sessionEvents)
36320
+ .values([
36321
+ {
36322
+ accountId: session.accountId,
36323
+ workspaceId: input.workspaceId,
36324
+ sessionId: session.id,
36325
+ sequence: ++sequence,
36326
+ type: "session.created",
36327
+ payload: sanitizeEventPayload({
36328
+ ...input.createdEventPayload,
36329
+ status: "idle",
36330
+ createdBy: creator.initiator,
36331
+ }),
36332
+ },
36333
+ ...(goal
36334
+ ? [
36335
+ {
36336
+ accountId: session.accountId,
36337
+ workspaceId: input.workspaceId,
36338
+ sessionId: session.id,
36339
+ sequence: ++sequence,
36340
+ type: "goal.set" as const,
36341
+ payload: sanitizeEventPayload({
36342
+ goalId: goal.id,
36343
+ text: goal.text,
36344
+ ...(goal.successCriteria
36345
+ ? { successCriteria: goal.successCriteria }
36346
+ : {}),
36347
+ version: goal.version,
36348
+ actor: "api",
36349
+ replaced: false,
36350
+ }),
36351
+ },
36352
+ ]
36353
+ : []),
36354
+ ])
36355
+ .returning();
36356
+ initializedNow = true;
36357
+ }
36358
+ await tx
36359
+ .update(schema.sessions)
36360
+ .set({
36361
+ temporalWorkflowId,
36362
+ lastSequence: sequence,
36363
+ ...(initializedNow && session.status === "queued" ? { status: "idle" } : {}),
36364
+ updatedAt: new Date(),
36365
+ })
36366
+ .where(
36367
+ and(
36368
+ eq(schema.sessions.workspaceId, input.workspaceId),
36369
+ eq(schema.sessions.id, session.id),
36370
+ ),
36371
+ );
36372
+ if (initializedNow && input.consumeNewSessionDraft) {
36373
+ await setSubjectRlsContext(
36374
+ tx as unknown as Database,
36375
+ input.consumeNewSessionDraft.subjectId,
36376
+ );
36377
+ await seedNewSessionDraftInTransaction(tx as unknown as Database, {
36378
+ workspaceId: input.workspaceId,
36379
+ subjectId: input.consumeNewSessionDraft.subjectId,
36380
+ expectedRevision: input.consumeNewSessionDraft.expectedRevision,
36381
+ });
36382
+ }
36383
+ return {
36384
+ events: insertedEvents.map(mapEvent),
36385
+ turn: null,
36386
+ temporalWorkflowId,
36387
+ workflowWakeRevision: null,
36388
+ };
36389
+ }
36390
+
36162
36391
  const existingUserEvents = await tx
36163
36392
  .select()
36164
36393
  .from(schema.sessionEvents)
@@ -36262,6 +36491,7 @@ export async function initializeSessionStartAtomically(
36262
36491
  let queueTailPosition = Number(session.queueTailPosition);
36263
36492
  if (!turn) {
36264
36493
  queueTailPosition += 1;
36494
+ const acceptedAt = new Date();
36265
36495
  [turn] = await tx
36266
36496
  .insert(schema.sessionTurns)
36267
36497
  .values({
@@ -36293,10 +36523,14 @@ export async function initializeSessionStartAtomically(
36293
36523
  : {},
36294
36524
  lineage: {},
36295
36525
  ...initiatorColumns(creator),
36526
+ initiatingHumanSubjectId:
36527
+ creator.initiator.kind === "subject" ? creator.initiator.subjectId : null,
36296
36528
  personalConnectionDelegations: parsedPersonalConnectionDelegations(
36297
36529
  session.initialPersonalConnectionDelegations,
36298
36530
  `sessions:${session.workspaceId}:${session.id}:initial`,
36299
36531
  ),
36532
+ createdAt: acceptedAt,
36533
+ updatedAt: acceptedAt,
36300
36534
  })
36301
36535
  .returning();
36302
36536
  if (!turn) throw new Error("Failed to create initial session turn");
@@ -36430,6 +36664,7 @@ export async function enqueueSessionTurn(
36430
36664
  const position = atHead
36431
36665
  ? Number(lockedSession.queueHeadPosition) - 1
36432
36666
  : Number(lockedSession.queueTailPosition) + 1;
36667
+ const acceptedAt = new Date();
36433
36668
  const [row] = await tx
36434
36669
  .insert(schema.sessionTurns)
36435
36670
  .values({
@@ -36457,7 +36692,11 @@ export async function enqueueSessionTurn(
36457
36692
  initiator: input.initiator,
36458
36693
  context: input.initiatorContext ?? {},
36459
36694
  }),
36695
+ initiatingHumanSubjectId:
36696
+ input.initiator.kind === "subject" ? input.initiator.subjectId : null,
36460
36697
  personalConnectionDelegations: input.personalConnectionDelegations ?? [],
36698
+ createdAt: acceptedAt,
36699
+ updatedAt: acceptedAt,
36461
36700
  })
36462
36701
  .returning();
36463
36702
  if (!row) {
@@ -36893,8 +37132,14 @@ export async function claimSessionWorkForAttempt(
36893
37132
  sessionId,
36894
37133
  { workspaceControl },
36895
37134
  );
36896
- const session = prefix.sessions[0];
37135
+ let session = prefix.sessions[0];
36897
37136
  if (!session) return { action: "unclaimed", reason: "no-work" };
37137
+ // Work and realtime may coexist, but claim remains the lazy lifecycle
37138
+ // cleanup point for an expired voice lease.
37139
+ session = await settleExpiredSessionRealtimeInTransaction(
37140
+ tx as unknown as Database,
37141
+ session,
37142
+ );
36898
37143
  if (effectiveControl.state !== "active") {
36899
37144
  return { action: "unclaimed", reason: "gate-closed" };
36900
37145
  }
@@ -36974,7 +37219,37 @@ export async function claimSessionWorkForAttempt(
36974
37219
  if (connectorPolicyRows.length > 2048) {
36975
37220
  throw new Error("Connector action policy snapshot exceeds the 2048-row bound");
36976
37221
  }
36977
- return await registerSessionTurnAttemptClaim(tx as unknown as Database, {
37222
+ // New queued, compaction, and internal-update executions are born
37223
+ // while the locked session still has no active-turn pointer. Publish
37224
+ // that ownership edge before freezing governance so the database
37225
+ // functions can require the complete exact-current-attempt chain.
37226
+ // The enclosing transaction keeps this invisible unless every later
37227
+ // claim/event/session settlement step succeeds.
37228
+ if (session.activeTurnId !== turn.id) {
37229
+ if (session.activeTurnId !== null) {
37230
+ throw new SessionControlInvariantError(
37231
+ `Attempt ${input.attemptId} cannot replace active turn ${session.activeTurnId}`,
37232
+ );
37233
+ }
37234
+ const [claimedSession] = await tx
37235
+ .update(schema.sessions)
37236
+ .set({ activeTurnId: turn.id })
37237
+ .where(
37238
+ and(
37239
+ eq(schema.sessions.accountId, session.accountId),
37240
+ eq(schema.sessions.workspaceId, workspaceId),
37241
+ eq(schema.sessions.id, sessionId),
37242
+ isNull(schema.sessions.activeTurnId),
37243
+ ),
37244
+ )
37245
+ .returning({ id: schema.sessions.id });
37246
+ if (!claimedSession) {
37247
+ throw new SessionControlInvariantError(
37248
+ `Attempt ${input.attemptId} could not establish active turn ${turn.id}`,
37249
+ );
37250
+ }
37251
+ }
37252
+ const attempt = await registerSessionTurnAttemptClaim(tx as unknown as Database, {
36978
37253
  id: input.attemptId,
36979
37254
  accountId: session.accountId,
36980
37255
  workspaceId,
@@ -36988,6 +37263,42 @@ export async function claimSessionWorkForAttempt(
36988
37263
  mcpApprovalPolicies,
36989
37264
  connectorActionPolicies: connectorPolicyRows,
36990
37265
  });
37266
+ // Freeze governance only after this exact attempt is durably claimed,
37267
+ // while the claim transaction still owns the session/turn/attempt
37268
+ // locks. Later policy or preference activation applies to a future
37269
+ // attempt and can never move accepted queued/in-flight work.
37270
+ await tx.execute(sql`
37271
+ SELECT snapshot.id
37272
+ FROM workspace_instruction_policy_get_or_create_snapshot(
37273
+ ${session.accountId}::uuid,
37274
+ ${workspaceId}::uuid,
37275
+ ${sessionId}::uuid,
37276
+ ${turn.id}::uuid,
37277
+ ${input.attemptId}::uuid,
37278
+ ${turn.executionGeneration}
37279
+ ) snapshot
37280
+ `);
37281
+ const initiatingHumanSubjectId =
37282
+ turn.initiatingHumanSubjectId ??
37283
+ (turn.initiatorKind === "subject" ? turn.initiatorSubjectId : null);
37284
+ if (initiatingHumanSubjectId) {
37285
+ await tx.execute(sql`
37286
+ SELECT snapshot.id
37287
+ FROM preference_registry_get_or_create_snapshot(
37288
+ ${session.accountId}::uuid,
37289
+ ${workspaceId}::uuid,
37290
+ ${sessionId}::uuid,
37291
+ ${turn.id}::uuid,
37292
+ ${input.attemptId}::uuid,
37293
+ ${turn.executionGeneration}
37294
+ ) snapshot
37295
+ `);
37296
+ // The preference function applies the immutable initiating human
37297
+ // only transaction-locally. Clear it before the rest of claim
37298
+ // settlement so no unrelated subject-scoped read inherits it.
37299
+ await tx.execute(sql`SELECT set_config('opengeni.subject_id', '', true)`);
37300
+ }
37301
+ return attempt;
36991
37302
  };
36992
37303
  if (session.activeTurnId !== null) {
36993
37304
  const [activeTurnPreview] = await tx
@@ -37335,6 +37646,9 @@ export async function claimSessionWorkForAttempt(
37335
37646
  latencyMode: schema.sessionTurns.latencyMode,
37336
37647
  sandboxBackend: schema.sessionTurns.sandboxBackend,
37337
37648
  sandboxOs: schema.sessionTurns.sandboxOs,
37649
+ initiatingHumanSubjectId: schema.sessionTurns.initiatingHumanSubjectId,
37650
+ initiatorKind: schema.sessionTurns.initiatorKind,
37651
+ initiatorSubjectId: schema.sessionTurns.initiatorSubjectId,
37338
37652
  })
37339
37653
  .from(schema.sessionTurns)
37340
37654
  .where(
@@ -37384,8 +37698,15 @@ export async function claimSessionWorkForAttempt(
37384
37698
  },
37385
37699
  ),
37386
37700
  ...initiatorColumns(compactionInitiator),
37701
+ initiatingHumanSubjectId:
37702
+ latestStarted?.initiatingHumanSubjectId ??
37703
+ (latestStarted?.initiatorKind === "subject"
37704
+ ? latestStarted.initiatorSubjectId
37705
+ : null),
37387
37706
  personalConnectionDelegations: [],
37388
37707
  startedAt: now,
37708
+ createdAt: now,
37709
+ updatedAt: now,
37389
37710
  })
37390
37711
  .returning();
37391
37712
  if (!compactionTurn) throw new Error("Failed to create context compaction execution");
@@ -37634,6 +37955,9 @@ export async function claimSessionWorkForAttempt(
37634
37955
  tools: schema.sessionTurns.tools,
37635
37956
  sandboxBackend: schema.sessionTurns.sandboxBackend,
37636
37957
  sandboxOs: schema.sessionTurns.sandboxOs,
37958
+ initiatingHumanSubjectId: schema.sessionTurns.initiatingHumanSubjectId,
37959
+ initiatorKind: schema.sessionTurns.initiatorKind,
37960
+ initiatorSubjectId: schema.sessionTurns.initiatorSubjectId,
37637
37961
  })
37638
37962
  .from(schema.sessionTurns)
37639
37963
  .where(
@@ -37668,6 +37992,39 @@ export async function claimSessionWorkForAttempt(
37668
37992
  typeof goalPolicy?.sandboxBackend === "string"
37669
37993
  ? goalPolicy.sandboxBackend
37670
37994
  : (latestStarted?.sandboxBackend ?? session.sandboxBackend);
37995
+ let initiatingHumanSubjectId =
37996
+ internalInitiator.initiator.kind === "subject"
37997
+ ? internalInitiator.initiator.subjectId
37998
+ : null;
37999
+ if (!initiatingHumanSubjectId && routingGoalUpdate) {
38000
+ const causalTurnIdValue =
38001
+ routingGoalUpdate.lineage &&
38002
+ typeof routingGoalUpdate.lineage === "object" &&
38003
+ !Array.isArray(routingGoalUpdate.lineage)
38004
+ ? (routingGoalUpdate.lineage as Record<string, unknown>).causalTurnId
38005
+ : null;
38006
+ const causalTurnId = typeof causalTurnIdValue === "string" ? causalTurnIdValue : null;
38007
+ if (causalTurnId) {
38008
+ const [causalTurn] = await tx
38009
+ .select({
38010
+ initiatingHumanSubjectId: schema.sessionTurns.initiatingHumanSubjectId,
38011
+ initiatorKind: schema.sessionTurns.initiatorKind,
38012
+ initiatorSubjectId: schema.sessionTurns.initiatorSubjectId,
38013
+ })
38014
+ .from(schema.sessionTurns)
38015
+ .where(
38016
+ and(
38017
+ eq(schema.sessionTurns.workspaceId, workspaceId),
38018
+ eq(schema.sessionTurns.sessionId, sessionId),
38019
+ eq(schema.sessionTurns.id, causalTurnId),
38020
+ ),
38021
+ )
38022
+ .limit(1);
38023
+ initiatingHumanSubjectId =
38024
+ causalTurn?.initiatingHumanSubjectId ??
38025
+ (causalTurn?.initiatorKind === "subject" ? causalTurn.initiatorSubjectId : null);
38026
+ }
38027
+ }
37671
38028
  await tx.execute(sql`set local opengeni.session_inference_claim = '1'`);
37672
38029
  const [internalTurn] = await tx
37673
38030
  .insert(schema.sessionTurns)
@@ -37699,8 +38056,11 @@ export async function claimSessionWorkForAttempt(
37699
38056
  { id: input.dispatchId, generation: 1, triggerEventId },
37700
38057
  ),
37701
38058
  ...initiatorColumns(internalInitiator),
38059
+ initiatingHumanSubjectId,
37702
38060
  personalConnectionDelegations: internalPersonalConnectionDelegations,
37703
38061
  startedAt: now,
38062
+ createdAt: now,
38063
+ updatedAt: now,
37704
38064
  })
37705
38065
  .returning();
37706
38066
  if (!internalTurn) throw new Error("Failed to create internal update inference");
@@ -37820,13 +38180,28 @@ export async function claimSessionWorkForAttempt(
37820
38180
  }),
37821
38181
  producerCodexCredentialId: null,
37822
38182
  });
37823
- const delivered = await deliverPendingUpdates(
37824
- session.accountId,
37825
- row.id,
37826
- row.executionGeneration,
37827
- session.lastSequence + 1,
37828
- now,
37829
- );
38183
+ const providerDelegatedTurn = isSessionRealtimeDelegationTurnMetadata(row.metadata);
38184
+ // Cross-session updates are already projected through
38185
+ // delegation.context.append. Keep them pending instead of consuming
38186
+ // them as hidden context on the provider-delegated ordinary turn.
38187
+ const delivered = providerDelegatedTurn
38188
+ ? {
38189
+ count: 0,
38190
+ lastSequence: session.lastSequence,
38191
+ triggerEventId: null,
38192
+ historyItemId: null,
38193
+ historyItem: null,
38194
+ updates: [] as Array<typeof schema.sessionSystemUpdates.$inferSelect>,
38195
+ events: [] as Array<typeof schema.sessionEvents.$inferInsert>,
38196
+ event: null,
38197
+ }
38198
+ : await deliverPendingUpdates(
38199
+ session.accountId,
38200
+ row.id,
38201
+ row.executionGeneration,
38202
+ session.lastSequence + 1,
38203
+ now,
38204
+ );
37830
38205
  await persistDeliveredUpdateBatch(delivered, session.accountId, row.id);
37831
38206
  if (delivered.events.length > 0) {
37832
38207
  await tx.insert(schema.sessionEvents).values(delivered.events);
@@ -38400,15 +38775,23 @@ export async function settleSessionAttemptInterruptions(
38400
38775
  throw new Error(`Live interrupted attempt ${attemptId} lost its exact turn ownership`);
38401
38776
  }
38402
38777
 
38403
- const steer = interruptions.some((interruption) => interruption.kind === "steer");
38404
- const outcome: SessionTurnAttemptOutcome = steer ? "superseded" : "interrupted_recoverable";
38405
- const reason = steer
38406
- ? "steer"
38407
- : interruptions.some((interruption) => interruption.kind === "workspace_pause")
38408
- ? "workspace_pause"
38409
- : interruptions.some((interruption) => interruption.kind === "maintenance")
38410
- ? "maintenance"
38411
- : "session_pause";
38778
+ const terminalCancel = session.status === "cancelled";
38779
+ const steer =
38780
+ !terminalCancel && interruptions.some((interruption) => interruption.kind === "steer");
38781
+ const outcome: SessionTurnAttemptOutcome = terminalCancel
38782
+ ? "cancelled"
38783
+ : steer
38784
+ ? "superseded"
38785
+ : "interrupted_recoverable";
38786
+ const reason = terminalCancel
38787
+ ? "session_cancelled"
38788
+ : steer
38789
+ ? "steer"
38790
+ : interruptions.some((interruption) => interruption.kind === "workspace_pause")
38791
+ ? "workspace_pause"
38792
+ : interruptions.some((interruption) => interruption.kind === "maintenance")
38793
+ ? "maintenance"
38794
+ : "session_pause";
38412
38795
  let sequence = session.lastSequence;
38413
38796
  const closedTools = await closePendingSessionToolCallsInTransaction(
38414
38797
  tx as unknown as Database,
@@ -38433,38 +38816,14 @@ export async function settleSessionAttemptInterruptions(
38433
38816
  outcome,
38434
38817
  closedAt: now,
38435
38818
  });
38436
- const eventValues: Array<typeof schema.sessionEvents.$inferInsert> = steer
38819
+ const eventValues: Array<typeof schema.sessionEvents.$inferInsert> = terminalCancel
38437
38820
  ? [
38438
38821
  {
38439
38822
  accountId: session.accountId,
38440
38823
  workspaceId,
38441
38824
  sessionId,
38442
38825
  sequence: ++sequence,
38443
- type: "turn.superseded",
38444
- turnId: turn.id,
38445
- turnGeneration: turn.executionGeneration,
38446
- turnAttemptId: attemptId,
38447
- turnAssociation: "current",
38448
- payload: sanitizeEventPayload({ reason: "steer" }),
38449
- occurredAt: now,
38450
- },
38451
- {
38452
- accountId: session.accountId,
38453
- workspaceId,
38454
- sessionId,
38455
- sequence: ++sequence,
38456
- type: "session.status.changed",
38457
- payload: sanitizeEventPayload({ status: "queued" }),
38458
- occurredAt: now,
38459
- },
38460
- ]
38461
- : [
38462
- {
38463
- accountId: session.accountId,
38464
- workspaceId,
38465
- sessionId,
38466
- sequence: ++sequence,
38467
- type: "turn.recovery.requested",
38826
+ type: "turn.cancelled",
38468
38827
  turnId: turn.id,
38469
38828
  turnGeneration: turn.executionGeneration,
38470
38829
  turnAttemptId: attemptId,
@@ -38472,50 +38831,101 @@ export async function settleSessionAttemptInterruptions(
38472
38831
  payload: sanitizeEventPayload({ reason }),
38473
38832
  occurredAt: now,
38474
38833
  },
38475
- {
38476
- accountId: session.accountId,
38477
- workspaceId,
38478
- sessionId,
38479
- sequence: ++sequence,
38480
- type: "session.status.changed",
38481
- turnId: turn.id,
38482
- turnGeneration: turn.executionGeneration,
38483
- turnAttemptId: attemptId,
38484
- turnAssociation: "current",
38485
- payload: sanitizeEventPayload({ status: "recovering" }),
38486
- occurredAt: now,
38487
- },
38488
- ];
38834
+ ]
38835
+ : steer
38836
+ ? [
38837
+ {
38838
+ accountId: session.accountId,
38839
+ workspaceId,
38840
+ sessionId,
38841
+ sequence: ++sequence,
38842
+ type: "turn.superseded",
38843
+ turnId: turn.id,
38844
+ turnGeneration: turn.executionGeneration,
38845
+ turnAttemptId: attemptId,
38846
+ turnAssociation: "current",
38847
+ payload: sanitizeEventPayload({ reason: "steer" }),
38848
+ occurredAt: now,
38849
+ },
38850
+ {
38851
+ accountId: session.accountId,
38852
+ workspaceId,
38853
+ sessionId,
38854
+ sequence: ++sequence,
38855
+ type: "session.status.changed",
38856
+ payload: sanitizeEventPayload({ status: "queued" }),
38857
+ occurredAt: now,
38858
+ },
38859
+ ]
38860
+ : [
38861
+ {
38862
+ accountId: session.accountId,
38863
+ workspaceId,
38864
+ sessionId,
38865
+ sequence: ++sequence,
38866
+ type: "turn.recovery.requested",
38867
+ turnId: turn.id,
38868
+ turnGeneration: turn.executionGeneration,
38869
+ turnAttemptId: attemptId,
38870
+ turnAssociation: "current",
38871
+ payload: sanitizeEventPayload({ reason }),
38872
+ occurredAt: now,
38873
+ },
38874
+ {
38875
+ accountId: session.accountId,
38876
+ workspaceId,
38877
+ sessionId,
38878
+ sequence: ++sequence,
38879
+ type: "session.status.changed",
38880
+ turnId: turn.id,
38881
+ turnGeneration: turn.executionGeneration,
38882
+ turnAttemptId: attemptId,
38883
+ turnAssociation: "current",
38884
+ payload: sanitizeEventPayload({ status: "recovering" }),
38885
+ occurredAt: now,
38886
+ },
38887
+ ];
38489
38888
  const eventRows = await tx.insert(schema.sessionEvents).values(eventValues).returning();
38490
38889
  await tx
38491
38890
  .update(schema.sessionTurns)
38492
38891
  .set(
38493
- steer
38892
+ terminalCancel
38494
38893
  ? {
38495
- status: "superseded",
38894
+ status: "cancelled",
38496
38895
  activeAttemptId: null,
38497
38896
  metadata: metadataWithoutTurnDispatchAttempt(turn.metadata),
38897
+ cancelledBy: "system:session_cancelled",
38898
+ cancelReason: reason,
38498
38899
  version: turn.version + 1,
38499
38900
  finishedAt: turn.finishedAt ?? now,
38500
38901
  updatedAt: now,
38501
38902
  }
38502
- : {
38503
- status: "recovering",
38504
- activeAttemptId: null,
38505
- metadata: metadataWithoutTurnDispatchAttempt(turn.metadata),
38506
- cancelledBy: null,
38507
- cancelReason: null,
38508
- version: turn.version + 1,
38509
- finishedAt: null,
38510
- updatedAt: now,
38511
- },
38903
+ : steer
38904
+ ? {
38905
+ status: "superseded",
38906
+ activeAttemptId: null,
38907
+ metadata: metadataWithoutTurnDispatchAttempt(turn.metadata),
38908
+ version: turn.version + 1,
38909
+ finishedAt: turn.finishedAt ?? now,
38910
+ updatedAt: now,
38911
+ }
38912
+ : {
38913
+ status: "recovering",
38914
+ activeAttemptId: null,
38915
+ metadata: metadataWithoutTurnDispatchAttempt(turn.metadata),
38916
+ cancelledBy: null,
38917
+ cancelReason: null,
38918
+ version: turn.version + 1,
38919
+ finishedAt: null,
38920
+ updatedAt: now,
38921
+ },
38512
38922
  )
38513
38923
  .where(eq(schema.sessionTurns.id, turn.id));
38514
38924
  await tx
38515
38925
  .update(schema.sessions)
38516
38926
  .set({
38517
- status: steer ? "queued" : "recovering",
38518
- activeTurnId: steer ? null : turn.id,
38927
+ status: terminalCancel ? "cancelled" : steer ? "queued" : "recovering",
38928
+ activeTurnId: terminalCancel || steer ? null : turn.id,
38519
38929
  lastSequence: sequence,
38520
38930
  updatedAt: now,
38521
38931
  })
@@ -39348,6 +39758,13 @@ export type ApplySessionTurnSettlementResult =
39348
39758
  activeTurnId: string | null;
39349
39759
  };
39350
39760
 
39761
+ export type ApplySessionTurnSettlementHooks = {
39762
+ /** Test-only failure injection after terminal projection but before commit. */
39763
+ afterRealtimeDelegationProjection?:
39764
+ | ((projection: { entryId: string; turnId: string }) => void | Promise<void>)
39765
+ | undefined;
39766
+ };
39767
+
39351
39768
  function attemptOutcomeForTurnStatus(status: SessionTurnStatus): SessionTurnAttemptOutcome | null {
39352
39769
  switch (status) {
39353
39770
  case "completed":
@@ -39361,6 +39778,40 @@ function attemptOutcomeForTurnStatus(status: SessionTurnStatus): SessionTurnAtte
39361
39778
  }
39362
39779
  }
39363
39780
 
39781
+ type TerminalSessionTurnStatus = "completed" | "failed" | "cancelled" | "superseded";
39782
+ type TerminalSessionTurnEventType =
39783
+ | "turn.completed"
39784
+ | "turn.failed"
39785
+ | "turn.cancelled"
39786
+ | "turn.superseded";
39787
+
39788
+ function terminalSessionTurnEventType(
39789
+ status: TerminalSessionTurnStatus,
39790
+ ): TerminalSessionTurnEventType {
39791
+ switch (status) {
39792
+ case "completed":
39793
+ return "turn.completed";
39794
+ case "failed":
39795
+ return "turn.failed";
39796
+ case "cancelled":
39797
+ return "turn.cancelled";
39798
+ case "superseded":
39799
+ return "turn.superseded";
39800
+ }
39801
+ }
39802
+
39803
+ function isTerminalSessionTurnStatus(
39804
+ status: SessionTurnStatus,
39805
+ ): status is TerminalSessionTurnStatus {
39806
+ return ["completed", "failed", "cancelled", "superseded"].includes(status);
39807
+ }
39808
+
39809
+ function sessionEventPayloadRecord(payload: unknown): Record<string, unknown> {
39810
+ return payload && typeof payload === "object" && !Array.isArray(payload)
39811
+ ? (payload as Record<string, unknown>)
39812
+ : {};
39813
+ }
39814
+
39364
39815
  /**
39365
39816
  * Atomically append terminal/requires-action truth, update the exact turn, and
39366
39817
  * transition the owning session. A superseded dispatch or closed control gate
@@ -39371,6 +39822,7 @@ export async function applySessionTurnSettlement(
39371
39822
  db: Database,
39372
39823
  workspaceId: string,
39373
39824
  input: ApplySessionTurnSettlementInput,
39825
+ hooks: ApplySessionTurnSettlementHooks = {},
39374
39826
  ): Promise<ApplySessionTurnSettlementResult> {
39375
39827
  const fromStatuses = input.fromStatuses ?? ["running", "requires_action"];
39376
39828
  const eventTypes = [
@@ -39799,11 +40251,37 @@ export async function applySessionTurnSettlement(
39799
40251
  });
39800
40252
  const inserted =
39801
40253
  values.length > 0 ? await tx.insert(schema.sessionEvents).values(values).returning() : [];
39802
- const terminal =
39803
- input.turnStatus === "completed" ||
39804
- input.turnStatus === "cancelled" ||
39805
- input.turnStatus === "failed" ||
39806
- input.turnStatus === "superseded";
40254
+ const terminal = isTerminalSessionTurnStatus(input.turnStatus);
40255
+ if (isTerminalSessionTurnStatus(input.turnStatus)) {
40256
+ const terminalType = terminalSessionTurnEventType(input.turnStatus);
40257
+ const persistedTerminal = inserted.find((event) => event.type === terminalType);
40258
+ const projection = await projectSessionRealtimeDelegationTerminalInTransaction(
40259
+ tx as unknown as Database,
40260
+ {
40261
+ accountId: session.accountId,
40262
+ workspaceId,
40263
+ sessionId: input.sessionId,
40264
+ turnId: input.turnId,
40265
+ turnStatus: input.turnStatus,
40266
+ terminalEvent: {
40267
+ type: terminalType,
40268
+ payload: persistedTerminal
40269
+ ? sessionEventPayloadRecord(persistedTerminal.payload)
40270
+ : {
40271
+ code: "delegation_terminal_event_missing",
40272
+ error: `Delegated turn reached ${input.turnStatus} without its canonical terminal event.`,
40273
+ },
40274
+ },
40275
+ now,
40276
+ },
40277
+ );
40278
+ if (projection) {
40279
+ await hooks.afterRealtimeDelegationProjection?.({
40280
+ entryId: projection.entry.id,
40281
+ turnId: input.turnId,
40282
+ });
40283
+ }
40284
+ }
39807
40285
  if (input.turnStatus === "running") {
39808
40286
  // Approval resume re-enters the same logical turn after a new fenced
39809
40287
  // dispatch has advanced its trigger. It is an authorized inference
@@ -40092,6 +40570,20 @@ export async function settleCodexCredentialLeaseLoss(
40092
40570
  if (!settlementEvent) {
40093
40571
  throw new Error("Codex lease-loss settlement did not persist its checkpoint event");
40094
40572
  }
40573
+ if (!input.checkpointDurable) {
40574
+ await projectSessionRealtimeDelegationTerminalInTransaction(tx as unknown as Database, {
40575
+ accountId: input.accountId,
40576
+ workspaceId: input.workspaceId,
40577
+ sessionId: input.sessionId,
40578
+ turnId: input.turnId,
40579
+ turnStatus: "failed",
40580
+ terminalEvent: {
40581
+ type: "turn.failed",
40582
+ payload: sessionEventPayloadRecord(settlementEvent.payload),
40583
+ },
40584
+ now,
40585
+ });
40586
+ }
40095
40587
 
40096
40588
  await tx
40097
40589
  .update(schema.sessionTurns)
@@ -40821,6 +41313,22 @@ export async function recoverSessionDispatch(
40821
41313
  },
40822
41314
  ])
40823
41315
  .returning();
41316
+ const failedEvent = inserted.find((event) => event.type === "turn.failed");
41317
+ if (!failedEvent) {
41318
+ throw new Error("Worker-death exhaustion did not persist its terminal event");
41319
+ }
41320
+ await projectSessionRealtimeDelegationTerminalInTransaction(tx as unknown as Database, {
41321
+ accountId: session.accountId,
41322
+ workspaceId,
41323
+ sessionId: input.sessionId,
41324
+ turnId: turn.id,
41325
+ turnStatus: "failed",
41326
+ terminalEvent: {
41327
+ type: "turn.failed",
41328
+ payload: sessionEventPayloadRecord(failedEvent.payload),
41329
+ },
41330
+ now,
41331
+ });
40824
41332
  await tx
40825
41333
  .update(schema.sessionTurns)
40826
41334
  .set({
@@ -41559,8 +42067,14 @@ export async function enqueueSessionWorkflowWakeIfRunnable(
41559
42067
  .for("update")
41560
42068
  .limit(1);
41561
42069
  if (!workspace || !session) throw new Error(`Session not found: ${input.sessionId}`);
42070
+ const realtimeActive = await sessionRealtimeIsActiveInTransaction(
42071
+ tx as unknown as Database,
42072
+ input.workspaceId,
42073
+ input.sessionId,
42074
+ );
41562
42075
  const runnable =
41563
42076
  session.status !== "cancelled" &&
42077
+ !realtimeActive &&
41564
42078
  session.activeTurnId === null &&
41565
42079
  effectiveControl.state === "active";
41566
42080
  return runnable
@@ -42053,7 +42567,13 @@ export async function addSessionSystemUpdateWithSourceMutation(
42053
42567
  .returning();
42054
42568
  if (!event) throw new Error("Failed to create system-update pending event");
42055
42569
  await mutateSource(tx as unknown as Database, event.id);
42056
- const shouldWake = session.activeTurnId === null && effectiveControl.state === "active";
42570
+ const realtimeActive = await sessionRealtimeIsActiveInTransaction(
42571
+ tx as unknown as Database,
42572
+ input.workspaceId,
42573
+ input.sessionId,
42574
+ );
42575
+ const shouldWake =
42576
+ !realtimeActive && session.activeTurnId === null && effectiveControl.state === "active";
42057
42577
  const wake = shouldWake
42058
42578
  ? await registerInternalUpdateWakeInTransaction(tx as unknown as Database, {
42059
42579
  accountId: session.accountId,
@@ -42617,6 +43137,21 @@ export async function appendSessionEventsForTurnAttempt(
42617
43137
  };
42618
43138
  });
42619
43139
  const inserted = await tx.insert(schema.sessionEvents).values(values).returning();
43140
+ if (fence.allowed) {
43141
+ await projectSessionRealtimeDelegationProgressInTransaction(tx as unknown as Database, {
43142
+ accountId: session.accountId,
43143
+ workspaceId,
43144
+ sessionId,
43145
+ turnId,
43146
+ events: inserted.map((event) => ({
43147
+ id: event.id,
43148
+ sequence: event.sequence,
43149
+ type: event.type,
43150
+ payload: event.payload,
43151
+ })),
43152
+ now,
43153
+ });
43154
+ }
42620
43155
  await tx
42621
43156
  .update(schema.sessions)
42622
43157
  .set({
@@ -43041,6 +43576,7 @@ function mapSession(
43041
43576
  title: row.title ?? null,
43042
43577
  titleSource: (row.titleSource as "user" | "agent" | null) ?? null,
43043
43578
  instructions: row.instructions ?? null,
43579
+ policyRole: row.policyRole ?? null,
43044
43580
  resources: row.resources as ResourceRef[],
43045
43581
  skills: (row.skills as SessionSkill[]) ?? [],
43046
43582
  tools: row.tools as ToolRef[],