@opengeni/db 0.9.3 → 0.12.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 (54) hide show
  1. package/dist/{chunk-4LG5NBTC.js → chunk-VUKRIBO5.js} +577 -12
  2. package/dist/chunk-VUKRIBO5.js.map +1 -0
  3. package/dist/{chunk-KW526IJA.js → chunk-Y5WZZVQK.js} +80 -4
  4. package/dist/chunk-Y5WZZVQK.js.map +1 -0
  5. package/dist/index.d.ts +4 -2
  6. package/dist/index.js +6372 -2189
  7. package/dist/index.js.map +1 -1
  8. package/dist/migrate.d.ts +6 -3
  9. package/dist/migrate.js +1 -1
  10. package/dist/provision-roles.d.ts +1122 -91
  11. package/dist/{schema-CdPGTHlD.d.ts → schema-CnpD6BcX.d.ts} +5908 -3626
  12. package/dist/schema.d.ts +1 -1
  13. package/dist/schema.js +19 -1
  14. package/drizzle/0053_codex_credential_leases.sql +2 -2
  15. package/drizzle/0057_durable_queue_control.sql +1 -1
  16. package/drizzle/0061_session_workflow_wake_outbox.sql +1 -1
  17. package/drizzle/0062_session_list_snapshot_reaper.sql +1 -1
  18. package/drizzle/0063_session_control_mega_foundation.sql +1 -1
  19. package/drizzle/0064_rotation_strategy_sharded_backfill.sql +1 -1
  20. package/drizzle/0065_codex_subscription_overview.sql +168 -0
  21. package/drizzle/0065_session_tool_policy.sql +38 -0
  22. package/drizzle/0067_session_event_payload_bounds.sql +2 -2
  23. package/drizzle/0068_workspace_control_event_bounds.sql +2 -2
  24. package/drizzle/0069_session_event_history_backfill.sql +2 -2
  25. package/drizzle/0074_session_activity_revisions.sql +2 -2
  26. package/drizzle/0106_session_attempt_mcp_approval_policies.sql +29 -0
  27. package/drizzle/0107_host_export_lineage_contract.sql +381 -0
  28. package/drizzle/0108_fence_invalidated_warming_epochs.sql +76 -0
  29. package/drizzle/0109_nested_agent_depth_expand.sql +42 -0
  30. package/drizzle/0110_nested_agent_depth_boundary.sql +480 -0
  31. package/drizzle/0111_nested_agent_depth_backfill.sql +49 -0
  32. package/drizzle/0112_nested_agent_depth_contract.sql +38 -0
  33. package/drizzle/0113_nested_agent_depth_validate.sql +13 -0
  34. package/drizzle/0114_nested_agent_depth_contract.sql +49 -0
  35. package/drizzle/0115_nested_agent_depth_validate.sql +11 -0
  36. package/drizzle/0116_nested_agent_depth_index.sql +4 -0
  37. package/drizzle/0117_sandbox_recovery_generations.sql +699 -0
  38. package/drizzle/0118_new_session_drafts.sql +59 -0
  39. package/drizzle/0119_pending_tool_output_policy.sql +5 -0
  40. package/drizzle/0120_durable_goal_wake.sql +360 -0
  41. package/drizzle/0121_goal_update_idempotency.sql +11 -0
  42. package/package.json +5 -4
  43. package/src/codex-token-resolver.ts +175 -14
  44. package/src/connection-token-resolver.ts +143 -120
  45. package/src/event-payload-sanitizer.ts +32 -2
  46. package/src/index.ts +7734 -1330
  47. package/src/migrate.ts +131 -2
  48. package/src/new-session-drafts.ts +144 -0
  49. package/src/schema.ts +626 -16
  50. package/src/session-control.ts +44 -18
  51. package/src/session-queue-commands.ts +94 -21
  52. package/src/session-tool-call-settlement.ts +6 -1
  53. package/dist/chunk-4LG5NBTC.js.map +0 -1
  54. package/dist/chunk-KW526IJA.js.map +0 -1
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import {
3
3
  boundWorkspaceControlEvent,
4
4
  workspaceControlUtf8Bytes,
5
+ type SessionMcpApprovalPolicy,
5
6
  type TurnInitiatorContext,
6
7
  } from "@opengeni/contracts";
7
8
  import { and, eq, inArray, sql } from "drizzle-orm";
@@ -10,7 +11,7 @@ import * as schema from "./schema";
10
11
 
11
12
  export const SESSION_ANCESTRY_LIMIT = 10_000;
12
13
 
13
- export type WorkspaceControlLockMode = "share" | "update";
14
+ export type WorkspaceControlLockMode = "none" | "share" | "update";
14
15
  export type EffectiveControlState = "active" | "paused";
15
16
  export type SessionCommandActor =
16
17
  | { type: "human" | "operator"; subjectId: string }
@@ -201,9 +202,12 @@ export async function assertAgentCommandAuthorityInTransaction(
201
202
  workspaceId: string;
202
203
  actor: Extract<SessionCommandActor, { type: "agent_attempt" }>;
203
204
  targetSessionId: string;
204
- action: "pause" | "resume" | "steer" | "message";
205
+ action: "pause" | "resume" | "steer" | "message" | "goal";
205
206
  },
206
207
  ): Promise<void> {
208
+ if (input.action === "goal" && input.targetSessionId !== input.actor.sessionId) {
209
+ throw new SessionControlInvariantError("An agent goal command must target its own session");
210
+ }
207
211
  // Every command caller establishes the control/workspace prefix first.
208
212
  // Reusing the event-write helper here keeps cross-session actor authority on
209
213
  // the same UUID-ordered session -> exact turn -> exact attempt suffix.
@@ -354,6 +358,7 @@ function controlEtag(value: unknown): string {
354
358
  }
355
359
 
356
360
  function lockClause(mode: WorkspaceControlLockMode) {
361
+ if (mode === "none") return sql.empty();
357
362
  return mode === "update" ? sql.raw("for update") : sql.raw("for share");
358
363
  }
359
364
 
@@ -512,6 +517,7 @@ export async function registerSessionTurnAttemptClaim(
512
517
  temporalWorkflowRunId: string;
513
518
  temporalActivityId: string;
514
519
  verifiedControlRevision: number;
520
+ mcpApprovalPolicies: Record<string, SessionMcpApprovalPolicy>;
515
521
  },
516
522
  ): Promise<typeof schema.sessionTurnAttempts.$inferSelect> {
517
523
  const [inserted] = await db
@@ -1343,26 +1349,32 @@ async function findCommandReceipt(
1343
1349
  targetSessionId: string | null;
1344
1350
  targetTurnId: string | null;
1345
1351
  operationKey: string;
1352
+ identityScope: "actor" | "goal_operation";
1346
1353
  },
1347
1354
  ): Promise<SessionCommandReceiptRow | null> {
1348
1355
  const actorSubjectId = input.actor.type === "agent_attempt" ? null : input.actor.subjectId;
1349
1356
  const actorAttemptId = input.actor.type === "agent_attempt" ? input.actor.attemptId : null;
1350
- const rows = await db
1351
- .select()
1352
- .from(schema.sessionCommandReceipts)
1353
- .where(
1354
- and(
1355
- eq(schema.sessionCommandReceipts.workspaceId, input.workspaceId),
1356
- eq(schema.sessionCommandReceipts.actorType, input.actor.type),
1357
- sql`${schema.sessionCommandReceipts.actorSubjectId} is not distinct from ${actorSubjectId}`,
1358
- sql`${schema.sessionCommandReceipts.actorAttemptId} is not distinct from ${actorAttemptId}::uuid`,
1359
- eq(schema.sessionCommandReceipts.action, input.action),
1360
- sql`${schema.sessionCommandReceipts.targetSessionId} is not distinct from ${input.targetSessionId}::uuid`,
1361
- sql`${schema.sessionCommandReceipts.targetTurnId} is not distinct from ${input.targetTurnId}::uuid`,
1362
- eq(schema.sessionCommandReceipts.operationKey, input.operationKey),
1363
- ),
1364
- )
1365
- .for("update");
1357
+ const identity =
1358
+ input.identityScope === "goal_operation"
1359
+ ? and(
1360
+ eq(schema.sessionCommandReceipts.workspaceId, input.workspaceId),
1361
+ eq(schema.sessionCommandReceipts.actorType, "agent_attempt"),
1362
+ eq(schema.sessionCommandReceipts.action, input.action),
1363
+ eq(schema.sessionCommandReceipts.targetSessionId, input.targetSessionId!),
1364
+ sql`${schema.sessionCommandReceipts.targetTurnId} is null`,
1365
+ eq(schema.sessionCommandReceipts.operationKey, input.operationKey),
1366
+ )
1367
+ : and(
1368
+ eq(schema.sessionCommandReceipts.workspaceId, input.workspaceId),
1369
+ eq(schema.sessionCommandReceipts.actorType, input.actor.type),
1370
+ sql`${schema.sessionCommandReceipts.actorSubjectId} is not distinct from ${actorSubjectId}`,
1371
+ sql`${schema.sessionCommandReceipts.actorAttemptId} is not distinct from ${actorAttemptId}::uuid`,
1372
+ eq(schema.sessionCommandReceipts.action, input.action),
1373
+ sql`${schema.sessionCommandReceipts.targetSessionId} is not distinct from ${input.targetSessionId}::uuid`,
1374
+ sql`${schema.sessionCommandReceipts.targetTurnId} is not distinct from ${input.targetTurnId}::uuid`,
1375
+ eq(schema.sessionCommandReceipts.operationKey, input.operationKey),
1376
+ );
1377
+ const rows = await db.select().from(schema.sessionCommandReceipts).where(identity).for("update");
1366
1378
  return rows[0] ?? null;
1367
1379
  }
1368
1380
 
@@ -1377,9 +1389,22 @@ export async function reserveSessionCommandReceipt(
1377
1389
  targetTurnId: string | null;
1378
1390
  operationKey: string;
1379
1391
  canonicalRequestHash: string;
1392
+ identityScope?: "actor" | "goal_operation";
1380
1393
  },
1381
1394
  ): Promise<{ receipt: SessionCommandReceiptRow; replay: boolean }> {
1382
1395
  if (!input.operationKey.trim()) throw new Error("operationKey must not be empty");
1396
+ const identityScope = input.identityScope ?? "actor";
1397
+ if (
1398
+ identityScope === "goal_operation" &&
1399
+ (input.actor.type !== "agent_attempt" ||
1400
+ input.action !== "goal.update" ||
1401
+ input.targetSessionId === null ||
1402
+ input.targetTurnId !== null)
1403
+ ) {
1404
+ throw new SessionControlInvariantError(
1405
+ "Target-scoped receipt identity is reserved for agent goal.update commands",
1406
+ );
1407
+ }
1383
1408
  const actorSubjectId = input.actor.type === "agent_attempt" ? null : input.actor.subjectId;
1384
1409
  const actorAttemptId = input.actor.type === "agent_attempt" ? input.actor.attemptId : null;
1385
1410
  const [inserted] = await db
@@ -1407,6 +1432,7 @@ export async function reserveSessionCommandReceipt(
1407
1432
  targetSessionId: input.targetSessionId,
1408
1433
  targetTurnId: input.targetTurnId,
1409
1434
  operationKey: input.operationKey,
1435
+ identityScope,
1410
1436
  }));
1411
1437
  if (!receipt) throw new SessionControlInvariantError("Command receipt conflict was not readable");
1412
1438
  if (receipt.canonicalRequestHash !== input.canonicalRequestHash) {
@@ -1,9 +1,13 @@
1
1
  import {
2
+ metadataWithTurnExecutionPolicyV1,
2
3
  mergeResourceRefs,
3
4
  mergeToolRefs,
5
+ turnExecutionPolicyAuditMetadata,
4
6
  type ReasoningEffort,
5
7
  type ResourceRef,
8
+ type SessionToolPolicy,
6
9
  type ToolRef,
10
+ type TurnExecutionPolicyV1,
7
11
  } from "@opengeni/contracts";
8
12
  import { and, asc, eq, inArray, sql } from "drizzle-orm";
9
13
  import type { Database } from "./index";
@@ -39,7 +43,8 @@ export type QueueCommandConflictCode =
39
43
  | "QUEUE_ANCHOR_CHANGED"
40
44
  | "PROMPT_CHANGED"
41
45
  | "DRAFT_CHANGED"
42
- | "DRAFT_NOT_EMPTY";
46
+ | "DRAFT_NOT_EMPTY"
47
+ | "EDIT_SOURCE_CHANGED";
43
48
 
44
49
  export class QueueCommandConflictError extends Error {
45
50
  readonly name = "QueueCommandConflictError";
@@ -377,6 +382,7 @@ function draftIsNonEmpty(draft: ComposerDraftRow): boolean {
377
382
  draft.text.length > 0 ||
378
383
  draft.resources.length > 0 ||
379
384
  draft.tools.length > 0 ||
385
+ draft.toolsProvided ||
380
386
  draft.sourceTurnId !== null
381
387
  );
382
388
  }
@@ -416,6 +422,7 @@ export async function saveComposerDraftInTransaction(
416
422
  text: string;
417
423
  resources: ResourceRef[];
418
424
  tools: ToolRef[];
425
+ toolsProvided: boolean;
419
426
  model: string;
420
427
  reasoningEffort: ReasoningEffort;
421
428
  },
@@ -447,6 +454,7 @@ export async function saveComposerDraftInTransaction(
447
454
  text: input.text,
448
455
  resources: input.resources,
449
456
  tools: input.tools,
457
+ toolsProvided: input.toolsProvided,
450
458
  model: input.model,
451
459
  reasoningEffort: input.reasoningEffort,
452
460
  // A queue edit is still the same accepted work item. Preserve its frozen
@@ -818,6 +826,7 @@ export async function editQueuedTurnInTransaction(
818
826
  text: turn.prompt,
819
827
  resources: turn.resources,
820
828
  tools: turn.tools,
829
+ toolsProvided: turn.toolsProvided,
821
830
  model: turn.model,
822
831
  reasoningEffort: turn.reasoningEffort,
823
832
  sourceTurnId: turn.id,
@@ -1134,9 +1143,12 @@ export async function submitHumanPromptInTransaction(
1134
1143
  turnInstructions?: string | null;
1135
1144
  resources: ResourceRef[];
1136
1145
  tools: ToolRef[];
1146
+ toolsProvided?: boolean;
1137
1147
  model?: string | null;
1138
1148
  reasoningEffort?: ReasoningEffort | null;
1139
1149
  reasoningEffortFallback: ReasoningEffort;
1150
+ /** Trusted API/core admission snapshot. Omitted only by legacy low-level callers. */
1151
+ turnExecutionPolicy?: TurnExecutionPolicyV1;
1140
1152
  source: "user" | "api";
1141
1153
  mcpCredentialUpdates?: Array<{
1142
1154
  id: string;
@@ -1163,6 +1175,7 @@ export async function submitHumanPromptInTransaction(
1163
1175
  turnInstructions: input.turnInstructions ?? null,
1164
1176
  resources: input.resources,
1165
1177
  tools: input.tools,
1178
+ toolsProvided: input.toolsProvided === true,
1166
1179
  model: input.model ?? null,
1167
1180
  reasoningEffort: input.reasoningEffort ?? null,
1168
1181
  source: input.source,
@@ -1271,6 +1284,7 @@ export async function submitHumanPromptInTransaction(
1271
1284
  text: draft.text,
1272
1285
  resources: draft.resources,
1273
1286
  tools: draft.tools,
1287
+ toolsProvided: draft.toolsProvided,
1274
1288
  model: draft.model,
1275
1289
  reasoningEffort: draft.reasoningEffort,
1276
1290
  }) !==
@@ -1278,6 +1292,7 @@ export async function submitHumanPromptInTransaction(
1278
1292
  text: input.text,
1279
1293
  resources: input.resources,
1280
1294
  tools: input.tools,
1295
+ toolsProvided: input.toolsProvided === true,
1281
1296
  model: input.model ?? session.model,
1282
1297
  reasoningEffort: input.reasoningEffort ?? input.reasoningEffortFallback,
1283
1298
  })
@@ -1293,6 +1308,52 @@ export async function submitHumanPromptInTransaction(
1293
1308
  }
1294
1309
  }
1295
1310
 
1311
+ let editedSourceTurn: QueuedTurnRow | undefined;
1312
+ let editedSourceTurnInstructions: string | null | undefined;
1313
+ if (draft?.sourceTurnId) {
1314
+ const sourceLocks = await lockSessionEventWriteRows(db, {
1315
+ workspaceId: input.workspaceId,
1316
+ controlLock: "already_locked",
1317
+ workspaceLock: "already_locked",
1318
+ turnIds: [draft.sourceTurnId],
1319
+ });
1320
+ const sourceTurn = sourceLocks.turns[0];
1321
+ const sourceTurnVersion = draft.sourceTurnVersion;
1322
+ const sourceMetadata = sourceTurn?.metadata ?? {};
1323
+ const sourceIsExactWithdrawnRevision =
1324
+ sourceTurn !== undefined &&
1325
+ sourceTurn.accountId === input.accountId &&
1326
+ sourceTurn.workspaceId === input.workspaceId &&
1327
+ sourceTurn.sessionId === input.sessionId &&
1328
+ (sourceTurn.source === "user" || sourceTurn.source === "api") &&
1329
+ sourceTurn.status === "withdrawn_for_edit" &&
1330
+ sourceTurnVersion !== null &&
1331
+ sourceTurn.version === sourceTurnVersion + 1 &&
1332
+ sourceTurn.cancelledBy === input.subjectId &&
1333
+ sourceTurn.cancelReason === "withdrawn_for_edit" &&
1334
+ sourceTurn.activeAttemptId === null &&
1335
+ sourceMetadata.delivery !== "steer";
1336
+ if (!sourceIsExactWithdrawnRevision) {
1337
+ throw new QueueCommandConflictError(
1338
+ "EDIT_SOURCE_CHANGED",
1339
+ "Edited prompt source changed or is no longer withdrawn for edit",
1340
+ {
1341
+ queueVersion: session.queueVersion,
1342
+ draftRevision: draft.revision,
1343
+ ...(sourceTurn ? { turnVersion: sourceTurn.version } : {}),
1344
+ },
1345
+ );
1346
+ }
1347
+ // This is the sole private-instruction source for an edited replacement.
1348
+ // It is deliberately held separately from the public draft and event
1349
+ // projections below. The public draft is expected to differ after editing;
1350
+ // source identity is fenced by its exact withdrawn row version, not by
1351
+ // comparing the replacement content with the old prompt. A client-supplied
1352
+ // instruction value must never override the private source value.
1353
+ editedSourceTurn = sourceTurn;
1354
+ editedSourceTurnInstructions = sourceTurn.turnInstructions ?? null;
1355
+ }
1356
+
1296
1357
  for (const update of input.mcpCredentialUpdates ?? []) {
1297
1358
  const [server] = await db
1298
1359
  .update(schema.sessionMcpServers)
@@ -1315,22 +1376,7 @@ export async function submitHumanPromptInTransaction(
1315
1376
  const now = new Date();
1316
1377
  let frozenInitiator: FrozenTurnInitiator;
1317
1378
  if (input.delivery === "send" && draft?.sourceTurnId) {
1318
- const [sourceTurn] = await db
1319
- .select({
1320
- sessionId: schema.sessionTurns.sessionId,
1321
- initiatorKind: schema.sessionTurns.initiatorKind,
1322
- initiatorSubjectId: schema.sessionTurns.initiatorSubjectId,
1323
- initiatorContext: schema.sessionTurns.initiatorContext,
1324
- })
1325
- .from(schema.sessionTurns)
1326
- .where(
1327
- and(
1328
- eq(schema.sessionTurns.workspaceId, input.workspaceId),
1329
- eq(schema.sessionTurns.sessionId, input.sessionId),
1330
- eq(schema.sessionTurns.id, draft.sourceTurnId),
1331
- ),
1332
- )
1333
- .limit(1);
1379
+ const sourceTurn = editedSourceTurn;
1334
1380
  if (!sourceTurn) {
1335
1381
  throw new SessionControlInvariantError("Edited prompt source turn is missing");
1336
1382
  }
@@ -1366,7 +1412,11 @@ export async function submitHumanPromptInTransaction(
1366
1412
  payload: sanitizeEventPayload({
1367
1413
  text: input.text,
1368
1414
  ...(input.resources.length ? { resources: input.resources } : {}),
1369
- ...(input.tools.length ? { tools: input.tools } : {}),
1415
+ ...(input.toolsProvided === true
1416
+ ? { tools: input.tools }
1417
+ : input.tools.length
1418
+ ? { tools: input.tools }
1419
+ : {}),
1370
1420
  ...(input.model ? { model: input.model } : {}),
1371
1421
  ...(input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {}),
1372
1422
  delivery: input.delivery,
@@ -1389,13 +1439,19 @@ export async function submitHumanPromptInTransaction(
1389
1439
  source: input.source,
1390
1440
  position: input.delivery === "steer" ? 0 : existingQueued.length + 1,
1391
1441
  prompt: input.text,
1392
- turnInstructions: input.turnInstructions ?? null,
1442
+ turnInstructions:
1443
+ editedSourceTurnInstructions !== undefined
1444
+ ? editedSourceTurnInstructions
1445
+ : (input.turnInstructions ?? null),
1393
1446
  resources: input.resources,
1394
1447
  tools: input.tools,
1448
+ toolsProvided: input.toolsProvided === true,
1395
1449
  model: input.model ?? session.model,
1396
1450
  reasoningEffort: input.reasoningEffort ?? input.reasoningEffortFallback,
1397
1451
  sandboxBackend: session.sandboxBackend,
1398
- metadata: {},
1452
+ metadata: input.turnExecutionPolicy
1453
+ ? metadataWithTurnExecutionPolicyV1({}, input.turnExecutionPolicy)
1454
+ : {},
1399
1455
  lineage: { actor: input.actor.type },
1400
1456
  ...initiatorColumns(frozenInitiator),
1401
1457
  })
@@ -1455,6 +1511,7 @@ export async function submitHumanPromptInTransaction(
1455
1511
  .update(schema.sessionTurns)
1456
1512
  .set({
1457
1513
  metadata: {
1514
+ ...turn.metadata,
1458
1515
  delivery: "steer",
1459
1516
  replacedTurnId,
1460
1517
  replacedAttemptId,
@@ -1532,7 +1589,9 @@ export async function submitHumanPromptInTransaction(
1532
1589
  .update(schema.sessions)
1533
1590
  .set({
1534
1591
  resources: mergeResourceRefs(session.resources as ResourceRef[], input.resources),
1535
- tools: mergeToolRefs(session.tools as ToolRef[], input.tools),
1592
+ tools: sessionToolPolicyIsFixed(session.toolPolicy)
1593
+ ? session.tools
1594
+ : mergeToolRefs(session.tools as ToolRef[], input.tools),
1536
1595
  activeTurnId: input.delivery === "steer" ? liveCurrentTurnId : session.activeTurnId,
1537
1596
  status: nextStatus,
1538
1597
  queueVersion,
@@ -1566,6 +1625,9 @@ export async function submitHumanPromptInTransaction(
1566
1625
  operationId: reserved.receipt.id,
1567
1626
  replacedTurnId,
1568
1627
  interruptionCount,
1628
+ ...(input.turnExecutionPolicy
1629
+ ? turnExecutionPolicyAuditMetadata(input.turnExecutionPolicy, turnId)
1630
+ : {}),
1569
1631
  },
1570
1632
  });
1571
1633
  const eventIds = eventRows.map((event) => event.id);
@@ -1582,6 +1644,11 @@ export async function submitHumanPromptInTransaction(
1582
1644
  interruptionCount,
1583
1645
  replacedTurnId,
1584
1646
  workspaceControlEventId: resumed.workspaceControlEventId,
1647
+ ...(input.turnExecutionPolicy
1648
+ ? {
1649
+ executionPolicy: turnExecutionPolicyAuditMetadata(input.turnExecutionPolicy, turnId),
1650
+ }
1651
+ : {}),
1585
1652
  },
1586
1653
  });
1587
1654
  return {
@@ -1597,6 +1664,12 @@ export async function submitHumanPromptInTransaction(
1597
1664
  };
1598
1665
  }
1599
1666
 
1667
+ function sessionToolPolicyIsFixed(value: unknown): boolean {
1668
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1669
+ const mode = (value as Partial<SessionToolPolicy>).mode;
1670
+ return mode === "workspace_default" || mode === "explicit" || mode === "inherited";
1671
+ }
1672
+
1600
1673
  export async function sendAgentMessageInTransaction(
1601
1674
  db: Database,
1602
1675
  input: {
@@ -255,7 +255,12 @@ export async function closePendingSessionToolCallsInTransaction(
255
255
  sessionId: input.sessionId,
256
256
  turnId: input.turnId,
257
257
  position: nextPosition++,
258
- item: sanitizeModelPayload(boundModelToolOutputItem(resolution.result)),
258
+ item: sanitizeModelPayload(
259
+ boundModelToolOutputItem(
260
+ resolution.result,
261
+ resolution.call.modelToolOutputTruncationTokens ?? undefined,
262
+ ),
263
+ ),
259
264
  active: true,
260
265
  });
261
266
  }