@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.
- package/dist/{chunk-4LG5NBTC.js → chunk-VUKRIBO5.js} +577 -12
- package/dist/chunk-VUKRIBO5.js.map +1 -0
- package/dist/{chunk-KW526IJA.js → chunk-Y5WZZVQK.js} +80 -4
- package/dist/chunk-Y5WZZVQK.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6372 -2189
- package/dist/index.js.map +1 -1
- package/dist/migrate.d.ts +6 -3
- package/dist/migrate.js +1 -1
- package/dist/provision-roles.d.ts +1122 -91
- package/dist/{schema-CdPGTHlD.d.ts → schema-CnpD6BcX.d.ts} +5908 -3626
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +19 -1
- package/drizzle/0053_codex_credential_leases.sql +2 -2
- package/drizzle/0057_durable_queue_control.sql +1 -1
- package/drizzle/0061_session_workflow_wake_outbox.sql +1 -1
- package/drizzle/0062_session_list_snapshot_reaper.sql +1 -1
- package/drizzle/0063_session_control_mega_foundation.sql +1 -1
- package/drizzle/0064_rotation_strategy_sharded_backfill.sql +1 -1
- package/drizzle/0065_codex_subscription_overview.sql +168 -0
- package/drizzle/0065_session_tool_policy.sql +38 -0
- package/drizzle/0067_session_event_payload_bounds.sql +2 -2
- package/drizzle/0068_workspace_control_event_bounds.sql +2 -2
- package/drizzle/0069_session_event_history_backfill.sql +2 -2
- package/drizzle/0074_session_activity_revisions.sql +2 -2
- package/drizzle/0106_session_attempt_mcp_approval_policies.sql +29 -0
- package/drizzle/0107_host_export_lineage_contract.sql +381 -0
- package/drizzle/0108_fence_invalidated_warming_epochs.sql +76 -0
- package/drizzle/0109_nested_agent_depth_expand.sql +42 -0
- package/drizzle/0110_nested_agent_depth_boundary.sql +480 -0
- package/drizzle/0111_nested_agent_depth_backfill.sql +49 -0
- package/drizzle/0112_nested_agent_depth_contract.sql +38 -0
- package/drizzle/0113_nested_agent_depth_validate.sql +13 -0
- package/drizzle/0114_nested_agent_depth_contract.sql +49 -0
- package/drizzle/0115_nested_agent_depth_validate.sql +11 -0
- package/drizzle/0116_nested_agent_depth_index.sql +4 -0
- package/drizzle/0117_sandbox_recovery_generations.sql +699 -0
- package/drizzle/0118_new_session_drafts.sql +59 -0
- package/drizzle/0119_pending_tool_output_policy.sql +5 -0
- package/drizzle/0120_durable_goal_wake.sql +360 -0
- package/drizzle/0121_goal_update_idempotency.sql +11 -0
- package/package.json +5 -4
- package/src/codex-token-resolver.ts +175 -14
- package/src/connection-token-resolver.ts +143 -120
- package/src/event-payload-sanitizer.ts +32 -2
- package/src/index.ts +7734 -1330
- package/src/migrate.ts +131 -2
- package/src/new-session-drafts.ts +144 -0
- package/src/schema.ts +626 -16
- package/src/session-control.ts +44 -18
- package/src/session-queue-commands.ts +94 -21
- package/src/session-tool-call-settlement.ts +6 -1
- package/dist/chunk-4LG5NBTC.js.map +0 -1
- package/dist/chunk-KW526IJA.js.map +0 -1
package/src/session-control.ts
CHANGED
|
@@ -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
|
|
1351
|
-
.
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
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
|
|
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.
|
|
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:
|
|
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:
|
|
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(
|
|
258
|
+
item: sanitizeModelPayload(
|
|
259
|
+
boundModelToolOutputItem(
|
|
260
|
+
resolution.result,
|
|
261
|
+
resolution.call.modelToolOutputTruncationTokens ?? undefined,
|
|
262
|
+
),
|
|
263
|
+
),
|
|
259
264
|
active: true,
|
|
260
265
|
});
|
|
261
266
|
}
|