@opengeni/db 0.31.1 → 0.36.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.
Files changed (125) hide show
  1. package/dist/browser-auth.d.ts +209 -1
  2. package/dist/browser-downloads.d.ts +51 -0
  3. package/dist/browser-identities.d.ts +11 -2
  4. package/dist/browser-sessions.d.ts +83 -1
  5. package/dist/browser-state-artifacts.d.ts +75 -1
  6. package/dist/canonical-human-identities.d.ts +48 -0
  7. package/dist/canonical-human-identities.js +28 -0
  8. package/dist/canonical-human-identities.js.map +1 -0
  9. package/dist/capability-integrations.d.ts +8 -6
  10. package/dist/{chunk-MWDVXQOL.js → chunk-5KTIEDX7.js} +5773 -4212
  11. package/dist/chunk-5KTIEDX7.js.map +1 -0
  12. package/dist/chunk-FPVAD4CO.js +117 -0
  13. package/dist/chunk-FPVAD4CO.js.map +1 -0
  14. package/dist/chunk-GYO7ED2Q.js +259 -0
  15. package/dist/chunk-GYO7ED2Q.js.map +1 -0
  16. package/dist/{chunk-F5FT7BEZ.js → chunk-HSOWSYOX.js} +2 -2
  17. package/dist/{chunk-YHZXQ7HP.js → chunk-VKLB2ZGI.js} +446 -19
  18. package/dist/chunk-VKLB2ZGI.js.map +1 -0
  19. package/dist/{chunk-NBE6CHRI.js → chunk-VTPRIXKG.js} +49 -9
  20. package/dist/chunk-VTPRIXKG.js.map +1 -0
  21. package/dist/{chunk-ISI2TIUG.js → chunk-Z7VYMNPZ.js} +76 -4
  22. package/dist/chunk-Z7VYMNPZ.js.map +1 -0
  23. package/dist/database.d.ts +5 -0
  24. package/dist/editable-artifact-durable-export.js +2 -2
  25. package/dist/editable-artifacts.js +3 -3
  26. package/dist/index.d.ts +467 -29
  27. package/dist/index.js +23247 -16232
  28. package/dist/index.js.map +1 -1
  29. package/dist/insights.d.ts +23 -7
  30. package/dist/integration-bindings.d.ts +24 -24
  31. package/dist/integration-facets.d.ts +99 -0
  32. package/dist/interaction-revisions.d.ts +11 -0
  33. package/dist/interaction-schema.d.ts +698 -34
  34. package/dist/persistence-errors.d.ts +8 -0
  35. package/dist/provision-roles.js +1 -1
  36. package/dist/runtime-posture.d.ts +8 -7
  37. package/dist/schema.d.ts +9464 -5412
  38. package/dist/schema.js +51 -9
  39. package/dist/session-control.d.ts +29 -3
  40. package/dist/session-tenancy.d.ts +44 -0
  41. package/dist/session-tenancy.js +16 -0
  42. package/dist/session-tenancy.js.map +1 -0
  43. package/dist/slack-task-policy-schema.d.ts +847 -0
  44. package/dist/slack-task-policy.d.ts +65 -0
  45. package/dist/task-notes-schema.d.ts +924 -0
  46. package/dist/task-notes.d.ts +85 -0
  47. package/dist/video-generation.d.ts +9 -0
  48. package/dist/video-generation.js +5 -3
  49. package/dist/xai-subscription.d.ts +326 -0
  50. package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
  51. package/drizzle/0213_browser_interaction_authority.sql +67 -0
  52. package/drizzle/0214_browser_download_saves.sql +34 -0
  53. package/drizzle/0215_browser_controller_host.sql +385 -0
  54. package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
  55. package/drizzle/0217_external_browser_auth_operations.sql +22 -0
  56. package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
  57. package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
  58. package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
  59. package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
  60. package/drizzle/0220_session_channels.sql +54 -0
  61. package/drizzle/0221_sessions_channel_index.sql +5 -0
  62. package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
  63. package/drizzle/0222_sessions_channel_fk.sql +89 -0
  64. package/drizzle/0223_pending_tool_event_output.sql +55 -0
  65. package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
  66. package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
  67. package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
  68. package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
  69. package/drizzle/0227_slack_native_actions.sql +145 -0
  70. package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
  71. package/drizzle/0228_slack_task_policy.sql +400 -0
  72. package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
  73. package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
  74. package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
  75. package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
  76. package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
  77. package/drizzle/0234_xai_subscription_authority.sql +1139 -0
  78. package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
  79. package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
  80. package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
  81. package/drizzle/0237_interaction_transition_reaper.sql +263 -0
  82. package/drizzle/0238_goal_persistence_policy.sql +471 -0
  83. package/drizzle/0238_recover_unclaimed_session_turns.sql +307 -0
  84. package/drizzle/0238_supergrok_realtime_model.sql +29 -0
  85. package/drizzle/0239_supergrok_video_funding.sql +50 -0
  86. package/drizzle/0239_task_tree_notes.sql +990 -0
  87. package/package.json +13 -5
  88. package/src/browser-auth.ts +2600 -123
  89. package/src/browser-downloads.ts +517 -0
  90. package/src/browser-identities.ts +220 -3
  91. package/src/browser-sessions.ts +334 -45
  92. package/src/browser-state-artifacts.ts +402 -12
  93. package/src/canonical-human-identities.ts +317 -0
  94. package/src/capability-integrations.ts +121 -213
  95. package/src/database.ts +41 -1
  96. package/src/index.ts +4625 -411
  97. package/src/insights.ts +109 -28
  98. package/src/integration-bindings.ts +101 -101
  99. package/src/{integration-features.ts → integration-facets.ts} +187 -184
  100. package/src/interaction-revisions.ts +33 -3
  101. package/src/interaction-schema.ts +311 -8
  102. package/src/pack-components.ts +43 -43
  103. package/src/pack-installations.ts +0 -43
  104. package/src/persistence-errors.ts +60 -1
  105. package/src/plugin-packages.ts +9 -9
  106. package/src/provision-roles.ts +232 -0
  107. package/src/runtime-posture.ts +227 -14
  108. package/src/schema.ts +1188 -75
  109. package/src/session-control.ts +122 -37
  110. package/src/session-queue-commands.ts +78 -1
  111. package/src/session-tenancy.ts +188 -0
  112. package/src/session-tool-call-settlement.ts +9 -3
  113. package/src/slack-task-policy-schema.ts +130 -0
  114. package/src/slack-task-policy.ts +341 -0
  115. package/src/task-notes-schema.ts +135 -0
  116. package/src/task-notes.ts +178 -0
  117. package/src/video-generation.ts +90 -12
  118. package/src/workspace-artifacts.ts +1 -1
  119. package/src/xai-subscription.ts +1887 -0
  120. package/dist/chunk-ISI2TIUG.js.map +0 -1
  121. package/dist/chunk-MWDVXQOL.js.map +0 -1
  122. package/dist/chunk-NBE6CHRI.js.map +0 -1
  123. package/dist/chunk-YHZXQ7HP.js.map +0 -1
  124. package/dist/integration-features.d.ts +0 -99
  125. /package/dist/{chunk-F5FT7BEZ.js.map → chunk-HSOWSYOX.js.map} +0 -0
@@ -200,7 +200,7 @@ export class SessionCommandIdempotencyError extends Error {
200
200
 
201
201
  export class AgentCommandAuthorityError extends Error {
202
202
  constructor(
203
- readonly code: "CALLER_STALE" | "CALLER_INTERRUPTED" | "SELF_OR_ANCESTOR_PAUSE" | "SELF_STEER",
203
+ readonly code: "CALLER_STALE" | "CALLER_INTERRUPTED" | "TARGET_NOT_VERTICAL" | "SELF_STEER",
204
204
  message: string,
205
205
  ) {
206
206
  super(message);
@@ -232,10 +232,12 @@ export async function assertAgentCommandAuthorityInTransaction(
232
232
  attemptIds: [input.actor.attemptId],
233
233
  });
234
234
  const lockedSessions = authorityLocks.sessions;
235
- if (!lockedSessions.some((row) => row.id === input.actor.sessionId)) {
235
+ const callerSession = lockedSessions.find((row) => row.id === input.actor.sessionId);
236
+ if (!callerSession) {
236
237
  throw new AgentCommandAuthorityError("CALLER_STALE", "The calling session no longer exists");
237
238
  }
238
- if (!lockedSessions.some((row) => row.id === input.targetSessionId)) {
239
+ const targetSession = lockedSessions.find((row) => row.id === input.targetSessionId);
240
+ if (!targetSession) {
239
241
  throw new SessionControlInvariantError(`Target session not found: ${input.targetSessionId}`);
240
242
  }
241
243
  const rows = await db.execute<{
@@ -298,37 +300,21 @@ export async function assertAgentCommandAuthorityInTransaction(
298
300
  if (input.action === "steer" && input.targetSessionId === input.actor.sessionId) {
299
301
  throw new AgentCommandAuthorityError("SELF_STEER", "An agent cannot steer its own session");
300
302
  }
301
- if (input.action !== "pause") return;
302
- const ancestry = await db.execute<{
303
- containsCaller: boolean;
304
- invalid: boolean;
305
- }>(sql`
306
- with recursive caller_ancestry(id, parent_id, depth, path, cycle) as (
307
- select session.id, session.parent_session_id, 0, array[session.id], false
308
- from ${schema.sessions} session
309
- where session.workspace_id = ${input.workspaceId}
310
- and session.id = ${input.actor.sessionId}
311
- union all
312
- select parent.id, parent.parent_session_id, child.depth + 1,
313
- child.path || parent.id, parent.id = any(child.path)
314
- from caller_ancestry child
315
- join ${schema.sessions} parent
316
- on parent.workspace_id = ${input.workspaceId} and parent.id = child.parent_id
317
- where child.depth < ${SESSION_ANCESTRY_LIMIT} and not child.cycle
318
- )
319
- select
320
- coalesce(bool_or(id = ${input.targetSessionId}), false) as "containsCaller",
321
- coalesce(bool_or(cycle), false) or coalesce(max(depth), 0) >= ${SESSION_ANCESTRY_LIMIT}
322
- as invalid
323
- from caller_ancestry
324
- `);
325
- if (ancestry[0]?.invalid) {
326
- throw new SessionControlInvariantError("Caller ancestry is invalid");
327
- }
328
- if (ancestry[0]?.containsCaller) {
303
+ const targetsSelf = targetSession.id === callerSession.id;
304
+ const targetsDirectParent = callerSession.parentSessionId === targetSession.id;
305
+ const targetsDirectChild = targetSession.parentSessionId === callerSession.id;
306
+ const verticalTargetAllowed =
307
+ input.action === "goal"
308
+ ? targetsSelf
309
+ : input.action === "message"
310
+ ? targetsSelf || targetsDirectParent || targetsDirectChild
311
+ : targetsDirectChild;
312
+ if (!verticalTargetAllowed) {
329
313
  throw new AgentCommandAuthorityError(
330
- "SELF_OR_ANCESTOR_PAUSE",
331
- "An agent cannot pause its own session or an ancestor workstream",
314
+ "TARGET_NOT_VERTICAL",
315
+ input.action === "message"
316
+ ? "An agent may message only its own session, direct parent, or direct child"
317
+ : "An agent may control only a direct child session",
332
318
  );
333
319
  }
334
320
  }
@@ -430,7 +416,7 @@ export type SessionEventWriteLocks = {
430
416
  *
431
417
  * workspace_inference_controls (when control-aware)
432
418
  * -> actual workspaces row FOR KEY SHARE
433
- * -> session rows FOR UPDATE, UUID ordered
419
+ * -> session rows FOR NO KEY UPDATE, UUID ordered
434
420
  * -> exact turn rows FOR UPDATE, UUID ordered
435
421
  * -> exact attempt rows FOR UPDATE, UUID ordered
436
422
  *
@@ -439,6 +425,11 @@ export type SessionEventWriteLocks = {
439
425
  * `FOR UPDATE` lock serialized unrelated sessions and inverted the activity
440
426
  * path's session -> implicit workspace-FK edge.
441
427
  *
428
+ * `FOR NO KEY UPDATE` is equally deliberate for sessions. Session primary and
429
+ * composite identity keys are immutable, so writers need to exclude only concurrent
430
+ * non-key mutation. Keeping FK `FOR KEY SHARE` checks compatible prevents the
431
+ * activity finalizer's workspace counter from participating in a lock cycle.
432
+ *
442
433
  * Complex lifecycle transactions may acquire allocator/control locks before
443
434
  * this helper and may discover exact turn IDs only after locking the session.
444
435
  * They use the explicit `already_locked` stages, while retaining the same
@@ -478,7 +469,7 @@ export async function lockSessionEventWriteRows(
478
469
  ),
479
470
  )
480
471
  .orderBy(schema.sessions.id)
481
- .for("update")
472
+ .for("no key update")
482
473
  : [];
483
474
 
484
475
  const turnIds = [...new Set(input.turnIds ?? [])].sort();
@@ -529,14 +520,24 @@ export async function registerSessionTurnAttemptClaim(
529
520
  temporalWorkflowRunId: string;
530
521
  temporalActivityId: string;
531
522
  verifiedControlRevision: number;
523
+ authorityEpoch: number;
524
+ authorityVisibility: "user_private" | "workspace_shared";
525
+ authorityOwnerOrganizationMembershipId: string | null;
532
526
  mcpApprovalPolicies: Record<string, SessionMcpApprovalPolicy>;
533
527
  connectorActionPolicies: schema.ConnectorActionPolicySnapshotEntry[];
534
528
  },
535
529
  ): Promise<typeof schema.sessionTurnAttempts.$inferSelect> {
530
+ const authoritySnapshot = assertSessionAuthoritySnapshot({
531
+ attemptId: input.id,
532
+ authorityEpoch: input.authorityEpoch,
533
+ authorityVisibility: input.authorityVisibility,
534
+ authorityOwnerOrganizationMembershipId: input.authorityOwnerOrganizationMembershipId,
535
+ });
536
536
  const [inserted] = await db
537
537
  .insert(schema.sessionTurnAttempts)
538
538
  .values({
539
539
  ...input,
540
+ ...authoritySnapshot,
540
541
  state: "claimed",
541
542
  })
542
543
  // Only an idempotent replay of this exact preallocated attempt ID may
@@ -557,12 +558,22 @@ export async function registerSessionTurnAttemptClaim(
557
558
  )
558
559
  .for("update")
559
560
  .limit(1);
561
+ const existingAuthoritySnapshot = existing
562
+ ? assertSessionAuthoritySnapshot({
563
+ attemptId: existing.id,
564
+ authorityEpoch: existing.authorityEpoch,
565
+ authorityVisibility: existing.authorityVisibility,
566
+ authorityOwnerOrganizationMembershipId: existing.authorityOwnerOrganizationMembershipId,
567
+ })
568
+ : null;
560
569
  if (
561
570
  !existing ||
562
571
  existing.accountId !== input.accountId ||
563
572
  existing.sessionId !== input.sessionId ||
564
573
  existing.turnId !== input.turnId ||
565
574
  existing.executionGeneration !== input.executionGeneration ||
575
+ !existingAuthoritySnapshot ||
576
+ !sessionAuthoritySnapshotsEqual(existingAuthoritySnapshot, authoritySnapshot) ||
566
577
  existing.temporalWorkflowId !== input.temporalWorkflowId ||
567
578
  existing.temporalWorkflowRunId !== input.temporalWorkflowRunId ||
568
579
  existing.temporalActivityId !== input.temporalActivityId ||
@@ -577,6 +588,80 @@ export async function registerSessionTurnAttemptClaim(
577
588
  return existing;
578
589
  }
579
590
 
591
+ export type SessionAuthoritySnapshot = {
592
+ authorityEpoch: number;
593
+ authorityVisibility: "user_private" | "workspace_shared";
594
+ authorityOwnerOrganizationMembershipId: string | null;
595
+ };
596
+
597
+ /**
598
+ * Validate the complete accepted-attempt authority tuple. Missing or partial
599
+ * values are not legacy defaults: current claim writers must provide the
600
+ * exact locked session authority explicitly.
601
+ */
602
+ export function assertSessionAuthoritySnapshot(input: {
603
+ attemptId?: string;
604
+ authorityEpoch: unknown;
605
+ authorityVisibility: unknown;
606
+ authorityOwnerOrganizationMembershipId: unknown;
607
+ }): SessionAuthoritySnapshot {
608
+ const valid =
609
+ Number.isSafeInteger(input.authorityEpoch) &&
610
+ (input.authorityEpoch as number) > 0 &&
611
+ (input.authorityVisibility === "user_private" ||
612
+ input.authorityVisibility === "workspace_shared") &&
613
+ ((input.authorityVisibility === "user_private" &&
614
+ typeof input.authorityOwnerOrganizationMembershipId === "string") ||
615
+ (input.authorityVisibility === "workspace_shared" &&
616
+ (input.authorityOwnerOrganizationMembershipId === null ||
617
+ typeof input.authorityOwnerOrganizationMembershipId === "string")));
618
+ if (!valid) {
619
+ throw new SessionControlInvariantError(
620
+ `Attempt ${input.attemptId ?? "unknown"} has an invalid session authority snapshot`,
621
+ );
622
+ }
623
+ return {
624
+ authorityEpoch: input.authorityEpoch as number,
625
+ authorityVisibility:
626
+ input.authorityVisibility as SessionAuthoritySnapshot["authorityVisibility"],
627
+ authorityOwnerOrganizationMembershipId: input.authorityOwnerOrganizationMembershipId as
628
+ | string
629
+ | null,
630
+ };
631
+ }
632
+
633
+ export function sessionAuthoritySnapshotMatchesSession(
634
+ snapshot: SessionAuthoritySnapshot,
635
+ session: Pick<
636
+ typeof schema.sessions.$inferSelect,
637
+ "authorityEpoch" | "visibility" | "ownerOrganizationMembershipId"
638
+ >,
639
+ ): boolean {
640
+ return (
641
+ snapshot.authorityEpoch === session.authorityEpoch &&
642
+ snapshot.authorityVisibility === session.visibility &&
643
+ snapshot.authorityOwnerOrganizationMembershipId ===
644
+ (session.ownerOrganizationMembershipId ?? null)
645
+ );
646
+ }
647
+
648
+ export function sessionAuthoritySnapshotsEqual(
649
+ left: Pick<
650
+ SessionAuthoritySnapshot,
651
+ "authorityEpoch" | "authorityVisibility" | "authorityOwnerOrganizationMembershipId"
652
+ >,
653
+ right: Pick<
654
+ SessionAuthoritySnapshot,
655
+ "authorityEpoch" | "authorityVisibility" | "authorityOwnerOrganizationMembershipId"
656
+ >,
657
+ ): boolean {
658
+ return (
659
+ left.authorityEpoch === right.authorityEpoch &&
660
+ left.authorityVisibility === right.authorityVisibility &&
661
+ left.authorityOwnerOrganizationMembershipId === right.authorityOwnerOrganizationMembershipId
662
+ );
663
+ }
664
+
580
665
  /**
581
666
  * Close the exact first-class owner while the caller still holds the owning
582
667
  * session/turn locks. Every path that clears `session_turns.active_attempt_id`
@@ -1415,12 +1500,12 @@ export async function reserveSessionCommandReceipt(
1415
1500
  if (
1416
1501
  identityScope === "goal_operation" &&
1417
1502
  (input.actor.type !== "agent_attempt" ||
1418
- input.action !== "goal.update" ||
1503
+ !["goal.update", "goal.progress"].includes(input.action) ||
1419
1504
  input.targetSessionId === null ||
1420
1505
  input.targetTurnId !== null)
1421
1506
  ) {
1422
1507
  throw new SessionControlInvariantError(
1423
- "Target-scoped receipt identity is reserved for agent goal.update commands",
1508
+ "Target-scoped receipt identity is reserved for agent goal commands",
1424
1509
  );
1425
1510
  }
1426
1511
  const actorSubjectId = input.actor.type === "agent_attempt" ? null : input.actor.subjectId;
@@ -1,4 +1,6 @@
1
1
  import {
2
+ WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1,
3
+ XaiProviderAccountAuthoritySnapshotV1,
2
4
  DraftTimelineAnnotations,
3
5
  McpPersonalConnectionDelegations,
4
6
  TimelineAnnotations,
@@ -32,6 +34,7 @@ import {
32
34
  withLosslessContentWriteVersion,
33
35
  } from "./lossless-json";
34
36
  import { closePendingSessionToolCallsInTransaction } from "./session-tool-call-settlement";
37
+ import { cancelTurnInteractionInterventionsInTransaction } from "./browser-auth";
35
38
  import {
36
39
  assertAgentCommandAuthorityInTransaction,
37
40
  autoResumeSessionBranchInTransaction,
@@ -61,6 +64,7 @@ import {
61
64
  initiatorFromStorage,
62
65
  type FrozenTurnInitiator,
63
66
  } from "./turn-initiator";
67
+ import { resolveXaiProviderAccountAuthoritySnapshotForAcceptanceInTransaction } from "./xai-subscription";
64
68
 
65
69
  type SessionEventInsertWithPayload = typeof schema.sessionEvents.$inferInsert & {
66
70
  payload: unknown;
@@ -233,6 +237,43 @@ async function personalConnectionDelegationsForAgentActor(
233
237
  return parsed.data.map((delegation) => ({ ...delegation }));
234
238
  }
235
239
 
240
+ async function xaiAuthorityForAgentActor(
241
+ db: Database,
242
+ workspaceId: string,
243
+ actor: Extract<SessionCommandActor, { type: "agent_attempt" }>,
244
+ ): Promise<{
245
+ snapshot: ReturnType<typeof XaiProviderAccountAuthoritySnapshotV1.parse>;
246
+ subjectId: string | null;
247
+ }> {
248
+ const [row] = await db
249
+ .select({
250
+ snapshot: schema.sessionTurns.xaiProviderAccountAuthoritySnapshot,
251
+ initiatingHumanSubjectId: schema.sessionTurns.initiatingHumanSubjectId,
252
+ initiatorKind: schema.sessionTurns.initiatorKind,
253
+ initiatorSubjectId: schema.sessionTurns.initiatorSubjectId,
254
+ })
255
+ .from(schema.sessionTurns)
256
+ .where(
257
+ and(
258
+ eq(schema.sessionTurns.workspaceId, workspaceId),
259
+ eq(schema.sessionTurns.sessionId, actor.sessionId),
260
+ eq(schema.sessionTurns.id, actor.turnId),
261
+ ),
262
+ )
263
+ .limit(1);
264
+ if (!row) {
265
+ throw new SessionControlInvariantError(
266
+ `Agent xAI authority turn not found: ${actor.sessionId}/${actor.turnId}`,
267
+ );
268
+ }
269
+ return {
270
+ snapshot: XaiProviderAccountAuthoritySnapshotV1.parse(row.snapshot),
271
+ subjectId:
272
+ row.initiatingHumanSubjectId ??
273
+ (row.initiatorKind === "subject" ? row.initiatorSubjectId : null),
274
+ };
275
+ }
276
+
236
277
  async function lockSession(
237
278
  db: Database,
238
279
  workspaceId: string,
@@ -344,6 +385,12 @@ export async function supersedeSessionCurrentDirectionInTransaction(
344
385
  }
345
386
 
346
387
  const now = new Date();
388
+ await cancelTurnInteractionInterventionsInTransaction(db, {
389
+ accountId: input.accountId,
390
+ workspaceId: input.workspaceId,
391
+ sessionId: input.sessionId,
392
+ turnId: current.id,
393
+ });
347
394
  const closedTools = await closePendingSessionToolCallsInTransaction(db, {
348
395
  accountId: input.accountId,
349
396
  workspaceId: input.workspaceId,
@@ -392,7 +439,10 @@ export async function supersedeSessionCurrentDirectionInTransaction(
392
439
  turnId: current.id,
393
440
  turnGeneration: current.executionGeneration,
394
441
  turnAssociation: "current",
395
- payload: { requestId: request.id, response: { outcome: "cancelled" } },
442
+ payload: {
443
+ requestId: request.id,
444
+ response: { outcome: "cancelled" },
445
+ },
396
446
  occurredAt: now,
397
447
  })),
398
448
  "payload",
@@ -454,6 +504,17 @@ export async function supersedeSessionCurrentDirectionInTransaction(
454
504
  eq(schema.codexCapacityWaiters.status, "waiting"),
455
505
  ),
456
506
  );
507
+ await db
508
+ .update(schema.xaiCapacityWaiters)
509
+ .set({ status: "superseded", lastWakeReason: "steer", updatedAt: now })
510
+ .where(
511
+ and(
512
+ eq(schema.xaiCapacityWaiters.workspaceId, input.workspaceId),
513
+ eq(schema.xaiCapacityWaiters.sessionId, input.sessionId),
514
+ eq(schema.xaiCapacityWaiters.blockedTurnId, current.id),
515
+ eq(schema.xaiCapacityWaiters.status, "waiting"),
516
+ ),
517
+ );
457
518
  }
458
519
  return {
459
520
  interruptionCount: 0,
@@ -1652,6 +1713,15 @@ export async function submitHumanPromptInTransaction(
1652
1713
  input.subjectLabel,
1653
1714
  );
1654
1715
  }
1716
+ const xaiProviderAccountAuthoritySnapshot = editedSourceTurn
1717
+ ? XaiProviderAccountAuthoritySnapshotV1.parse(
1718
+ editedSourceTurn.xaiProviderAccountAuthoritySnapshot,
1719
+ )
1720
+ : input.actor.type === "human"
1721
+ ? await resolveXaiProviderAccountAuthoritySnapshotForAcceptanceInTransaction(db, {
1722
+ workspaceId: input.workspaceId,
1723
+ })
1724
+ : WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1;
1655
1725
  const acceptedEventId = crypto.randomUUID();
1656
1726
  const turnId = crypto.randomUUID();
1657
1727
  const workflowId = session.temporalWorkflowId ?? `session-${session.id}`;
@@ -1730,6 +1800,7 @@ export async function submitHumanPromptInTransaction(
1730
1800
  personalConnectionDelegations: editedSourceTurn
1731
1801
  ? editedSourceTurn.personalConnectionDelegations
1732
1802
  : (input.personalConnectionDelegations ?? []),
1803
+ xaiProviderAccountAuthoritySnapshot,
1733
1804
  createdAt: now,
1734
1805
  updatedAt: now,
1735
1806
  },
@@ -2083,6 +2154,7 @@ export async function sendAgentMessageInTransaction(
2083
2154
  input.workspaceId,
2084
2155
  input.actor,
2085
2156
  );
2157
+ const xaiAuthority = await xaiAuthorityForAgentActor(db, input.workspaceId, input.actor);
2086
2158
  const session = await lockSession(db, input.workspaceId, input.targetSessionId);
2087
2159
  if (session.status === "cancelled") {
2088
2160
  throw new QueueCommandConflictError(
@@ -2124,8 +2196,10 @@ export async function sendAgentMessageInTransaction(
2124
2196
  callerTurnId: input.actor.turnId,
2125
2197
  callerAttemptId: input.actor.attemptId,
2126
2198
  callerExecutionGeneration: input.actor.executionGeneration,
2199
+ ...(xaiAuthority.subjectId ? { xaiAuthoritySubjectId: xaiAuthority.subjectId } : {}),
2127
2200
  },
2128
2201
  personalConnectionDelegations,
2202
+ xaiProviderAccountAuthoritySnapshot: xaiAuthority.snapshot,
2129
2203
  state: "pending",
2130
2204
  },
2131
2205
  "summary",
@@ -2290,6 +2364,7 @@ export async function steerAgentSessionInTransaction(
2290
2364
  input.workspaceId,
2291
2365
  input.actor,
2292
2366
  );
2367
+ const xaiAuthority = await xaiAuthorityForAgentActor(db, input.workspaceId, input.actor);
2293
2368
  const resumed = await autoResumeSessionBranchInTransaction(db, {
2294
2369
  workspaceId: input.workspaceId,
2295
2370
  sessionId: input.targetSessionId,
@@ -2351,8 +2426,10 @@ export async function steerAgentSessionInTransaction(
2351
2426
  callerTurnId: input.actor.turnId,
2352
2427
  callerAttemptId: input.actor.attemptId,
2353
2428
  callerExecutionGeneration: input.actor.executionGeneration,
2429
+ ...(xaiAuthority.subjectId ? { xaiAuthoritySubjectId: xaiAuthority.subjectId } : {}),
2354
2430
  },
2355
2431
  personalConnectionDelegations,
2432
+ xaiProviderAccountAuthoritySnapshot: xaiAuthority.snapshot,
2356
2433
  state: "pending",
2357
2434
  },
2358
2435
  "summary",
@@ -0,0 +1,188 @@
1
+ import { createHash } from "node:crypto";
2
+ import { sql } from "drizzle-orm";
3
+ import {
4
+ rawRows,
5
+ type Database,
6
+ rlsContextForWorkspace,
7
+ withWorkspaceSubjectRls,
8
+ withWorkspaceSubjectSessionActivityRls,
9
+ } from "./database";
10
+
11
+ export type SessionTenancyVisibility = "user_private" | "workspace_shared";
12
+
13
+ export type TransitionSessionVisibilityInput = {
14
+ workspaceId: string;
15
+ sessionId: string;
16
+ actorSubjectId: string;
17
+ targetVisibility: SessionTenancyVisibility;
18
+ expectedAuthorityEpoch: number;
19
+ operationKey: string;
20
+ };
21
+
22
+ export type TransitionSessionVisibilityResult = {
23
+ operationId: string;
24
+ visibility: SessionTenancyVisibility;
25
+ authorityEpoch: number;
26
+ ownerOrganizationMembershipId: string | null;
27
+ changed: boolean;
28
+ replay: boolean;
29
+ interruptedAttemptCount: number;
30
+ cancelledTurnCount: number;
31
+ cancelledUpdateCount: number;
32
+ pausedGoalCount: number;
33
+ revokedGrantCount: number;
34
+ };
35
+
36
+ export type ForkSessionContentInput = {
37
+ sourceWorkspaceId: string;
38
+ sourceSessionId: string;
39
+ actorSubjectId: string;
40
+ destinationWorkspaceId: string;
41
+ destinationVisibility: SessionTenancyVisibility;
42
+ operationKey: string;
43
+ };
44
+
45
+ export type ForkSessionContentResult = {
46
+ operationId: string;
47
+ sessionId: string;
48
+ workspaceId: string;
49
+ visibility: SessionTenancyVisibility;
50
+ authorityEpoch: number;
51
+ copiedHistoryItemCount: number;
52
+ replay: boolean;
53
+ };
54
+
55
+ export function canonicalSessionVisibilityTransitionHash(
56
+ input: Pick<
57
+ TransitionSessionVisibilityInput,
58
+ "sessionId" | "targetVisibility" | "expectedAuthorityEpoch"
59
+ >,
60
+ ): string {
61
+ return createHash("sha256")
62
+ .update(
63
+ JSON.stringify({
64
+ version: 1,
65
+ sessionId: input.sessionId,
66
+ targetVisibility: input.targetVisibility,
67
+ expectedAuthorityEpoch: input.expectedAuthorityEpoch,
68
+ }),
69
+ )
70
+ .digest("hex");
71
+ }
72
+
73
+ export function canonicalSessionForkHash(
74
+ input: Pick<
75
+ ForkSessionContentInput,
76
+ "sourceSessionId" | "destinationWorkspaceId" | "destinationVisibility"
77
+ >,
78
+ ): string {
79
+ return createHash("sha256")
80
+ .update(
81
+ JSON.stringify({
82
+ version: 1,
83
+ sourceSessionId: input.sourceSessionId,
84
+ destinationWorkspaceId: input.destinationWorkspaceId,
85
+ destinationVisibility: input.destinationVisibility,
86
+ }),
87
+ )
88
+ .digest("hex");
89
+ }
90
+
91
+ export async function transitionSessionVisibility(
92
+ db: Database,
93
+ input: TransitionSessionVisibilityInput,
94
+ ): Promise<TransitionSessionVisibilityResult> {
95
+ if (!Number.isSafeInteger(input.expectedAuthorityEpoch) || input.expectedAuthorityEpoch < 1) {
96
+ throw new Error("expectedAuthorityEpoch must be a positive safe integer");
97
+ }
98
+ if (!input.operationKey.trim()) throw new Error("operationKey must not be empty");
99
+ const requestHash = canonicalSessionVisibilityTransitionHash(input);
100
+ const { accountId } = await rlsContextForWorkspace(db, input.workspaceId);
101
+ return await withWorkspaceSubjectSessionActivityRls(
102
+ db,
103
+ input.workspaceId,
104
+ input.actorSubjectId,
105
+ async (scopedDb) => {
106
+ const rows = await rawRows<{
107
+ operationId: string;
108
+ visibility: SessionTenancyVisibility;
109
+ authorityEpoch: number;
110
+ ownerOrganizationMembershipId: string | null;
111
+ changed: boolean;
112
+ replay: boolean;
113
+ interruptedAttemptCount: number;
114
+ cancelledTurnCount: number;
115
+ cancelledUpdateCount: number;
116
+ pausedGoalCount: number;
117
+ revokedGrantCount: number;
118
+ }>(
119
+ scopedDb,
120
+ sql`select
121
+ operation_id as "operationId",
122
+ visibility,
123
+ authority_epoch as "authorityEpoch",
124
+ owner_organization_membership_id as "ownerOrganizationMembershipId",
125
+ changed,
126
+ replay,
127
+ interrupted_attempt_count as "interruptedAttemptCount",
128
+ cancelled_turn_count as "cancelledTurnCount",
129
+ cancelled_update_count as "cancelledUpdateCount",
130
+ paused_goal_count as "pausedGoalCount",
131
+ revoked_grant_count as "revokedGrantCount"
132
+ from transition_session_visibility(
133
+ ${accountId}::uuid,
134
+ ${input.workspaceId}::uuid,
135
+ ${input.sessionId}::uuid,
136
+ ${input.actorSubjectId},
137
+ ${input.targetVisibility},
138
+ ${input.expectedAuthorityEpoch},
139
+ ${input.operationKey},
140
+ ${requestHash}
141
+ )`,
142
+ );
143
+ const result = rows[0];
144
+ if (!result) throw new Error("Session visibility transition returned no result");
145
+ return result;
146
+ },
147
+ );
148
+ }
149
+
150
+ export async function forkSessionContent(
151
+ db: Database,
152
+ input: ForkSessionContentInput,
153
+ ): Promise<ForkSessionContentResult> {
154
+ if (!input.operationKey.trim()) throw new Error("operationKey must not be empty");
155
+ const { accountId } = await rlsContextForWorkspace(db, input.sourceWorkspaceId);
156
+ const requestHash = canonicalSessionForkHash(input);
157
+ return await withWorkspaceSubjectRls(
158
+ db,
159
+ input.sourceWorkspaceId,
160
+ input.actorSubjectId,
161
+ async (scopedDb) => {
162
+ const rows = await rawRows<ForkSessionContentResult>(
163
+ scopedDb,
164
+ sql`select
165
+ operation_id as "operationId",
166
+ session_id as "sessionId",
167
+ workspace_id as "workspaceId",
168
+ visibility,
169
+ authority_epoch as "authorityEpoch",
170
+ copied_history_item_count as "copiedHistoryItemCount",
171
+ replay
172
+ from fork_session_content(
173
+ ${accountId}::uuid,
174
+ ${input.sourceWorkspaceId}::uuid,
175
+ ${input.sourceSessionId}::uuid,
176
+ ${input.actorSubjectId},
177
+ ${input.destinationWorkspaceId}::uuid,
178
+ ${input.destinationVisibility},
179
+ ${input.operationKey},
180
+ ${requestHash}
181
+ )`,
182
+ );
183
+ const result = rows[0];
184
+ if (!result) throw new Error("Session fork returned no result");
185
+ return result;
186
+ },
187
+ );
188
+ }
@@ -135,6 +135,10 @@ export async function closePendingSessionToolCallsInTransaction(
135
135
  row.resultItem === null
136
136
  ? null
137
137
  : fromPostgresLosslessJson(row.resultItem, row.resultItemCodecVersion),
138
+ eventOutput:
139
+ row.eventOutput === null
140
+ ? null
141
+ : fromPostgresLosslessJson(row.eventOutput, row.eventOutputCodecVersion),
138
142
  }));
139
143
  if (pending.length === 0) return { sequence: input.sequence, events: [], closed: 0 };
140
144
 
@@ -306,9 +310,11 @@ export async function closePendingSessionToolCallsInTransaction(
306
310
  },
307
311
  ],
308
312
  }
309
- : ((resolution.existingResult?.item ?? resolution.call.resultItem)?.output ??
310
- resolution.existingResult?.item ??
311
- resolution.call.resultItem),
313
+ : resolution.call.eventOutput !== null
314
+ ? resolution.call.eventOutput.value
315
+ : ((resolution.existingResult?.item ?? resolution.call.resultItem)?.output ??
316
+ resolution.existingResult?.item ??
317
+ resolution.call.resultItem),
312
318
  recovery: {
313
319
  interrupted: resolution.interrupted,
314
320
  outcome: resolution.interrupted ? "unknown" : "durable_result_found",