@opengeni/db 0.7.3 → 0.10.7
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-YFQ7SGE4.js → chunk-BMFDXFPA.js} +23 -1
- package/dist/chunk-BMFDXFPA.js.map +1 -0
- package/dist/chunk-KW526IJA.js +127 -0
- package/dist/chunk-KW526IJA.js.map +1 -0
- package/dist/{chunk-B22X3IEZ.js → chunk-P6PKXY5W.js} +604 -32
- package/dist/chunk-P6PKXY5W.js.map +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +7408 -2276
- package/dist/index.js.map +1 -1
- package/dist/migrate.d.ts +29 -4
- package/dist/migrate.js +3 -1
- package/dist/provision-roles.d.ts +1122 -73
- package/dist/provision-roles.js +1 -1
- package/dist/{schema-BN5mB9xZ.d.ts → schema-CqkzrBRS.d.ts} +6967 -4457
- 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 +15 -0
- package/drizzle/0065_codex_subscription_overview.sql +168 -0
- package/drizzle/0065_session_attempt_quiescence.sql +26 -0
- package/drizzle/0065_session_tool_policy.sql +38 -0
- package/drizzle/0066_session_interruption_attempt_lookup.sql +4 -0
- package/drizzle/0067_session_event_payload_bounds.sql +209 -0
- package/drizzle/0068_workspace_control_event_bounds.sql +134 -0
- package/drizzle/0069_session_event_history_backfill.sql +32 -0
- package/drizzle/0070_session_event_type_sequence_lookup.sql +4 -0
- package/drizzle/0071_session_event_monitoring_tail.sql +10 -0
- package/drizzle/0072_sessions_workspace_created_id_idx.sql +4 -0
- package/drizzle/0073_sessions_workspace_updated_id_idx.sql +4 -0
- package/drizzle/0074_session_activity_revisions.sql +72 -0
- package/drizzle/0075_sessions_workspace_activity_revision_idx.sql +4 -0
- package/drizzle/0076_session_workflow_wake_acl.sql +13 -0
- package/drizzle/0077_session_attempt_latest_lookup.sql +4 -0
- package/drizzle/0094_quarantine_credential_bearing_catalog_urls.sql +51 -0
- package/drizzle/0095_github_existing_installations.sql +84 -0
- package/drizzle/0096_session_turn_initiators.sql +97 -0
- package/drizzle/0097_host_export_outbox.sql +1220 -0
- package/drizzle/0098_usage_events_workspace_session_idx.sql +4 -0
- package/drizzle/0099_session_human_input_attempt_owner_index.sql +6 -0
- package/drizzle/0100_session_human_input_requests.sql +89 -0
- package/drizzle/0101_session_mcp_connection_refs.sql +30 -0
- package/drizzle/0102_session_command_receipt_service_actor.sql +16 -0
- package/drizzle/0103_host_export_root_session.sql +166 -0
- package/drizzle/0104_host_export_root_session_backfill.sql +27 -0
- package/drizzle/0105_session_turn_instructions.sql +9 -0
- 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/package.json +5 -4
- package/src/codex-token-resolver.ts +175 -14
- package/src/connection-token-resolver.ts +428 -119
- package/src/event-payload-sanitizer.ts +88 -20
- package/src/index.ts +7208 -1273
- package/src/memory-domain.ts +1 -1
- package/src/migrate.ts +86 -23
- package/src/persistence-errors.ts +252 -0
- package/src/provision-roles.ts +42 -0
- package/src/schema.ts +658 -34
- package/src/session-control.ts +520 -38
- package/src/session-queue-commands.ts +385 -61
- package/src/session-tool-call-settlement.ts +58 -7
- package/src/turn-initiator.ts +155 -0
- package/dist/chunk-7LDU7F5P.js +0 -80
- package/dist/chunk-7LDU7F5P.js.map +0 -1
- package/dist/chunk-B22X3IEZ.js.map +0 -1
- package/dist/chunk-YFQ7SGE4.js.map +0 -1
|
@@ -1,18 +1,24 @@
|
|
|
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";
|
|
14
|
+
import { sanitizeEventPayload } from "./event-payload-sanitizer";
|
|
10
15
|
import { closePendingSessionToolCallsInTransaction } from "./session-tool-call-settlement";
|
|
11
16
|
import {
|
|
12
17
|
assertAgentCommandAuthorityInTransaction,
|
|
13
18
|
autoResumeSessionBranchInTransaction,
|
|
14
19
|
canonicalSessionCommandHash,
|
|
15
20
|
evaluateSessionControl,
|
|
21
|
+
lockSessionEventWriteRows,
|
|
16
22
|
lockWorkspaceInferenceControl,
|
|
17
23
|
registerInternalUpdateWakeInTransaction,
|
|
18
24
|
reserveSessionCommandReceipt,
|
|
@@ -24,6 +30,12 @@ import {
|
|
|
24
30
|
updateSessionCommandReceiptResult,
|
|
25
31
|
} from "./session-control";
|
|
26
32
|
import * as schema from "./schema";
|
|
33
|
+
import {
|
|
34
|
+
frozenInitiatorForCommandActor,
|
|
35
|
+
initiatorColumns,
|
|
36
|
+
initiatorFromStorage,
|
|
37
|
+
type FrozenTurnInitiator,
|
|
38
|
+
} from "./turn-initiator";
|
|
27
39
|
|
|
28
40
|
export type QueueCommandConflictCode =
|
|
29
41
|
| "QUEUE_VERSION_CHANGED"
|
|
@@ -31,7 +43,8 @@ export type QueueCommandConflictCode =
|
|
|
31
43
|
| "QUEUE_ANCHOR_CHANGED"
|
|
32
44
|
| "PROMPT_CHANGED"
|
|
33
45
|
| "DRAFT_CHANGED"
|
|
34
|
-
| "DRAFT_NOT_EMPTY"
|
|
46
|
+
| "DRAFT_NOT_EMPTY"
|
|
47
|
+
| "EDIT_SOURCE_CHANGED";
|
|
35
48
|
|
|
36
49
|
export class QueueCommandConflictError extends Error {
|
|
37
50
|
readonly name = "QueueCommandConflictError";
|
|
@@ -99,12 +112,13 @@ async function lockSession(
|
|
|
99
112
|
workspaceId: string,
|
|
100
113
|
sessionId: string,
|
|
101
114
|
): Promise<typeof schema.sessions.$inferSelect> {
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
115
|
+
const locks = await lockSessionEventWriteRows(db, {
|
|
116
|
+
workspaceId,
|
|
117
|
+
controlLock: "already_locked",
|
|
118
|
+
workspaceLock: "already_locked",
|
|
119
|
+
sessionIds: [sessionId],
|
|
120
|
+
});
|
|
121
|
+
const session = locks.sessions[0];
|
|
108
122
|
if (!session) throw new Error(`Session not found: ${sessionId}`);
|
|
109
123
|
return session;
|
|
110
124
|
}
|
|
@@ -142,7 +156,7 @@ export async function supersedeSessionCurrentDirectionInTransaction(
|
|
|
142
156
|
lastSequence: input.lastSequence,
|
|
143
157
|
};
|
|
144
158
|
}
|
|
145
|
-
const [
|
|
159
|
+
const [preview] = await db
|
|
146
160
|
.select()
|
|
147
161
|
.from(schema.sessionTurns)
|
|
148
162
|
.where(
|
|
@@ -152,8 +166,15 @@ export async function supersedeSessionCurrentDirectionInTransaction(
|
|
|
152
166
|
eq(schema.sessionTurns.id, input.activeTurnId),
|
|
153
167
|
),
|
|
154
168
|
)
|
|
155
|
-
.for("update")
|
|
156
169
|
.limit(1);
|
|
170
|
+
const locks = await lockSessionEventWriteRows(db, {
|
|
171
|
+
workspaceId: input.workspaceId,
|
|
172
|
+
controlLock: "already_locked",
|
|
173
|
+
workspaceLock: "already_locked",
|
|
174
|
+
turnIds: [input.activeTurnId],
|
|
175
|
+
attemptIds: preview?.activeAttemptId ? [preview.activeAttemptId] : [],
|
|
176
|
+
});
|
|
177
|
+
const current = locks.turns[0];
|
|
157
178
|
if (!current) {
|
|
158
179
|
throw new SessionControlInvariantError(
|
|
159
180
|
`Session ${input.sessionId} points to missing active turn ${input.activeTurnId}`,
|
|
@@ -206,6 +227,44 @@ export async function supersedeSessionCurrentDirectionInTransaction(
|
|
|
206
227
|
sequence: input.lastSequence,
|
|
207
228
|
now,
|
|
208
229
|
});
|
|
230
|
+
const cancelledHumanInputs = await db
|
|
231
|
+
.update(schema.sessionHumanInputRequests)
|
|
232
|
+
.set({
|
|
233
|
+
status: "cancelled",
|
|
234
|
+
response: { outcome: "cancelled" },
|
|
235
|
+
respondedBy:
|
|
236
|
+
input.actor.type === "agent_attempt"
|
|
237
|
+
? `attempt:${input.actor.attemptId}`
|
|
238
|
+
: input.actor.subjectId,
|
|
239
|
+
respondedAt: now,
|
|
240
|
+
updatedAt: now,
|
|
241
|
+
})
|
|
242
|
+
.where(
|
|
243
|
+
and(
|
|
244
|
+
eq(schema.sessionHumanInputRequests.workspaceId, input.workspaceId),
|
|
245
|
+
eq(schema.sessionHumanInputRequests.sessionId, input.sessionId),
|
|
246
|
+
eq(schema.sessionHumanInputRequests.turnId, current.id),
|
|
247
|
+
eq(schema.sessionHumanInputRequests.status, "pending"),
|
|
248
|
+
),
|
|
249
|
+
)
|
|
250
|
+
.returning({ id: schema.sessionHumanInputRequests.id });
|
|
251
|
+
let lastSequence = closedTools.sequence;
|
|
252
|
+
if (cancelledHumanInputs.length > 0) {
|
|
253
|
+
await db.insert(schema.sessionEvents).values(
|
|
254
|
+
cancelledHumanInputs.map((request) => ({
|
|
255
|
+
accountId: input.accountId,
|
|
256
|
+
workspaceId: input.workspaceId,
|
|
257
|
+
sessionId: input.sessionId,
|
|
258
|
+
sequence: ++lastSequence,
|
|
259
|
+
type: "user.humanInputResponse",
|
|
260
|
+
turnId: current.id,
|
|
261
|
+
turnGeneration: current.executionGeneration,
|
|
262
|
+
turnAssociation: "current",
|
|
263
|
+
payload: { requestId: request.id, response: { outcome: "cancelled" } },
|
|
264
|
+
occurredAt: now,
|
|
265
|
+
})),
|
|
266
|
+
);
|
|
267
|
+
}
|
|
209
268
|
await db
|
|
210
269
|
.update(schema.sessionTurns)
|
|
211
270
|
.set({
|
|
@@ -248,7 +307,7 @@ export async function supersedeSessionCurrentDirectionInTransaction(
|
|
|
248
307
|
interruptionCount: 0,
|
|
249
308
|
replacedTurn: current,
|
|
250
309
|
liveCurrentTurnId: null,
|
|
251
|
-
lastSequence
|
|
310
|
+
lastSequence,
|
|
252
311
|
};
|
|
253
312
|
}
|
|
254
313
|
|
|
@@ -274,7 +333,16 @@ async function loadQueuedTurns(
|
|
|
274
333
|
asc(schema.sessionTurns.createdAt),
|
|
275
334
|
asc(schema.sessionTurns.id),
|
|
276
335
|
);
|
|
277
|
-
|
|
336
|
+
const rows = await query;
|
|
337
|
+
if (!lock || rows.length === 0) return rows;
|
|
338
|
+
const locks = await lockSessionEventWriteRows(db, {
|
|
339
|
+
workspaceId,
|
|
340
|
+
controlLock: "already_locked",
|
|
341
|
+
workspaceLock: "already_locked",
|
|
342
|
+
turnIds: rows.map((row) => row.id),
|
|
343
|
+
});
|
|
344
|
+
const byId = new Map(locks.turns.map((row) => [row.id, row]));
|
|
345
|
+
return rows.map((row) => byId.get(row.id)).filter((row): row is QueuedTurnRow => Boolean(row));
|
|
278
346
|
}
|
|
279
347
|
|
|
280
348
|
async function normalizeQueuePositions(
|
|
@@ -314,13 +382,19 @@ function draftIsNonEmpty(draft: ComposerDraftRow): boolean {
|
|
|
314
382
|
draft.text.length > 0 ||
|
|
315
383
|
draft.resources.length > 0 ||
|
|
316
384
|
draft.tools.length > 0 ||
|
|
385
|
+
draft.toolsProvided ||
|
|
317
386
|
draft.sourceTurnId !== null
|
|
318
387
|
);
|
|
319
388
|
}
|
|
320
389
|
|
|
321
390
|
export async function getComposerDraftInTransaction(
|
|
322
391
|
db: Database,
|
|
323
|
-
input: {
|
|
392
|
+
input: {
|
|
393
|
+
workspaceId: string;
|
|
394
|
+
sessionId: string;
|
|
395
|
+
subjectId: string;
|
|
396
|
+
lock?: boolean;
|
|
397
|
+
},
|
|
324
398
|
): Promise<ComposerDraftRow | null> {
|
|
325
399
|
const query = db
|
|
326
400
|
.select()
|
|
@@ -348,13 +422,21 @@ export async function saveComposerDraftInTransaction(
|
|
|
348
422
|
text: string;
|
|
349
423
|
resources: ResourceRef[];
|
|
350
424
|
tools: ToolRef[];
|
|
425
|
+
toolsProvided: boolean;
|
|
351
426
|
model: string;
|
|
352
427
|
reasoningEffort: ReasoningEffort;
|
|
353
428
|
},
|
|
354
429
|
): Promise<ComposerDraftRow> {
|
|
355
430
|
await lockWorkspaceInferenceControl(db, input.workspaceId, "share");
|
|
431
|
+
await lockSessionEventWriteRows(db, {
|
|
432
|
+
workspaceId: input.workspaceId,
|
|
433
|
+
controlLock: "already_locked",
|
|
434
|
+
});
|
|
356
435
|
await lockSession(db, input.workspaceId, input.sessionId);
|
|
357
|
-
const current = await getComposerDraftInTransaction(db, {
|
|
436
|
+
const current = await getComposerDraftInTransaction(db, {
|
|
437
|
+
...input,
|
|
438
|
+
lock: true,
|
|
439
|
+
});
|
|
358
440
|
const currentRevision = current?.revision ?? 0;
|
|
359
441
|
if (currentRevision !== input.expectedRevision) {
|
|
360
442
|
throw new QueueCommandConflictError("DRAFT_CHANGED", "Composer draft changed", {
|
|
@@ -372,10 +454,14 @@ export async function saveComposerDraftInTransaction(
|
|
|
372
454
|
text: input.text,
|
|
373
455
|
resources: input.resources,
|
|
374
456
|
tools: input.tools,
|
|
457
|
+
toolsProvided: input.toolsProvided,
|
|
375
458
|
model: input.model,
|
|
376
459
|
reasoningEffort: input.reasoningEffort,
|
|
377
|
-
|
|
378
|
-
|
|
460
|
+
// A queue edit is still the same accepted work item. Preserve its frozen
|
|
461
|
+
// initiator through arbitrary draft saves; only a genuinely new compose or
|
|
462
|
+
// Steer captures the submitting actor.
|
|
463
|
+
sourceTurnId: current?.sourceTurnId ?? null,
|
|
464
|
+
sourceTurnVersion: current?.sourceTurnVersion ?? null,
|
|
379
465
|
updatedAt: new Date(),
|
|
380
466
|
};
|
|
381
467
|
const [saved] = current
|
|
@@ -403,6 +489,10 @@ export async function moveQueuedTurnInTransaction(
|
|
|
403
489
|
},
|
|
404
490
|
): Promise<QueueCommandResult> {
|
|
405
491
|
await lockWorkspaceInferenceControl(db, input.workspaceId, "share");
|
|
492
|
+
await lockSessionEventWriteRows(db, {
|
|
493
|
+
workspaceId: input.workspaceId,
|
|
494
|
+
controlLock: "already_locked",
|
|
495
|
+
});
|
|
406
496
|
const session = await lockSession(db, input.workspaceId, input.sessionId);
|
|
407
497
|
const requestHash = canonicalSessionCommandHash({
|
|
408
498
|
beforeTurnId: input.beforeTurnId,
|
|
@@ -494,7 +584,11 @@ export async function moveQueuedTurnInTransaction(
|
|
|
494
584
|
eventIds.push(event.id);
|
|
495
585
|
await db
|
|
496
586
|
.update(schema.sessions)
|
|
497
|
-
.set({
|
|
587
|
+
.set({
|
|
588
|
+
queueVersion,
|
|
589
|
+
lastSequence: session.lastSequence + 1,
|
|
590
|
+
updatedAt: new Date(),
|
|
591
|
+
})
|
|
498
592
|
.where(eq(schema.sessions.id, input.sessionId));
|
|
499
593
|
}
|
|
500
594
|
const receipt = await updateSessionCommandReceiptResult(db, reserved.receipt.id, {
|
|
@@ -525,6 +619,10 @@ export async function deleteSessionQueueItemInTransaction(
|
|
|
525
619
|
},
|
|
526
620
|
): Promise<QueueCommandResult> {
|
|
527
621
|
await lockWorkspaceInferenceControl(db, input.workspaceId, "share");
|
|
622
|
+
await lockSessionEventWriteRows(db, {
|
|
623
|
+
workspaceId: input.workspaceId,
|
|
624
|
+
controlLock: "already_locked",
|
|
625
|
+
});
|
|
528
626
|
const session = await lockSession(db, input.workspaceId, input.sessionId);
|
|
529
627
|
const requestHash = canonicalSessionCommandHash({
|
|
530
628
|
expectedTurnVersion: input.expectedTurnVersion,
|
|
@@ -549,18 +647,14 @@ export async function deleteSessionQueueItemInTransaction(
|
|
|
549
647
|
replay: true,
|
|
550
648
|
};
|
|
551
649
|
}
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
),
|
|
561
|
-
)
|
|
562
|
-
.for("update")
|
|
563
|
-
.limit(1);
|
|
650
|
+
const turn = (
|
|
651
|
+
await lockSessionEventWriteRows(db, {
|
|
652
|
+
workspaceId: input.workspaceId,
|
|
653
|
+
controlLock: "already_locked",
|
|
654
|
+
workspaceLock: "already_locked",
|
|
655
|
+
turnIds: [input.turnId],
|
|
656
|
+
})
|
|
657
|
+
).turns[0];
|
|
564
658
|
if (!turn || turn.status !== "queued" || !["user", "api"].includes(turn.source)) {
|
|
565
659
|
throw new QueueCommandConflictError("QUEUE_PROMPT_STARTED", "Prompt is no longer waiting", {
|
|
566
660
|
queueVersion: session.queueVersion,
|
|
@@ -616,7 +710,11 @@ export async function deleteSessionQueueItemInTransaction(
|
|
|
616
710
|
if (!event) throw new Error("Queue delete event was not inserted");
|
|
617
711
|
await db
|
|
618
712
|
.update(schema.sessions)
|
|
619
|
-
.set({
|
|
713
|
+
.set({
|
|
714
|
+
queueVersion,
|
|
715
|
+
lastSequence: session.lastSequence + 1,
|
|
716
|
+
updatedAt: now,
|
|
717
|
+
})
|
|
620
718
|
.where(eq(schema.sessions.id, input.sessionId));
|
|
621
719
|
const receipt = await updateSessionCommandReceiptResult(db, reserved.receipt.id, {
|
|
622
720
|
queueVersion,
|
|
@@ -648,6 +746,10 @@ export async function editQueuedTurnInTransaction(
|
|
|
648
746
|
},
|
|
649
747
|
): Promise<EditQueueCommandResult> {
|
|
650
748
|
await lockWorkspaceInferenceControl(db, input.workspaceId, "share");
|
|
749
|
+
await lockSessionEventWriteRows(db, {
|
|
750
|
+
workspaceId: input.workspaceId,
|
|
751
|
+
controlLock: "already_locked",
|
|
752
|
+
});
|
|
651
753
|
const session = await lockSession(db, input.workspaceId, input.sessionId);
|
|
652
754
|
const requestHash = canonicalSessionCommandHash({
|
|
653
755
|
expectedTurnVersion: input.expectedTurnVersion,
|
|
@@ -664,7 +766,10 @@ export async function editQueuedTurnInTransaction(
|
|
|
664
766
|
operationKey: input.operationKey,
|
|
665
767
|
canonicalRequestHash: requestHash,
|
|
666
768
|
});
|
|
667
|
-
const existingDraft = await getComposerDraftInTransaction(db, {
|
|
769
|
+
const existingDraft = await getComposerDraftInTransaction(db, {
|
|
770
|
+
...input,
|
|
771
|
+
lock: true,
|
|
772
|
+
});
|
|
668
773
|
if (reserved.replay && reserved.receipt.appliedQueueVersion !== null) {
|
|
669
774
|
if (!existingDraft) throw new Error("Replayed queue Edit has no durable draft");
|
|
670
775
|
return {
|
|
@@ -689,18 +794,14 @@ export async function editQueuedTurnInTransaction(
|
|
|
689
794
|
draftRevision,
|
|
690
795
|
});
|
|
691
796
|
}
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
),
|
|
701
|
-
)
|
|
702
|
-
.for("update")
|
|
703
|
-
.limit(1);
|
|
797
|
+
const turn = (
|
|
798
|
+
await lockSessionEventWriteRows(db, {
|
|
799
|
+
workspaceId: input.workspaceId,
|
|
800
|
+
controlLock: "already_locked",
|
|
801
|
+
workspaceLock: "already_locked",
|
|
802
|
+
turnIds: [input.turnId],
|
|
803
|
+
})
|
|
804
|
+
).turns[0];
|
|
704
805
|
if (!turn || turn.status !== "queued" || !["user", "api"].includes(turn.source)) {
|
|
705
806
|
throw new QueueCommandConflictError("QUEUE_PROMPT_STARTED", "Prompt is no longer waiting", {
|
|
706
807
|
queueVersion: session.queueVersion,
|
|
@@ -725,6 +826,7 @@ export async function editQueuedTurnInTransaction(
|
|
|
725
826
|
text: turn.prompt,
|
|
726
827
|
resources: turn.resources,
|
|
727
828
|
tools: turn.tools,
|
|
829
|
+
toolsProvided: turn.toolsProvided,
|
|
728
830
|
model: turn.model,
|
|
729
831
|
reasoningEffort: turn.reasoningEffort,
|
|
730
832
|
sourceTurnId: turn.id,
|
|
@@ -780,7 +882,11 @@ export async function editQueuedTurnInTransaction(
|
|
|
780
882
|
if (!event) throw new Error("Queue edit event was not inserted");
|
|
781
883
|
await db
|
|
782
884
|
.update(schema.sessions)
|
|
783
|
-
.set({
|
|
885
|
+
.set({
|
|
886
|
+
queueVersion,
|
|
887
|
+
lastSequence: session.lastSequence + 1,
|
|
888
|
+
updatedAt: now,
|
|
889
|
+
})
|
|
784
890
|
.where(eq(schema.sessions.id, input.sessionId));
|
|
785
891
|
const receipt = await updateSessionCommandReceiptResult(db, reserved.receipt.id, {
|
|
786
892
|
queueVersion,
|
|
@@ -788,7 +894,14 @@ export async function editQueuedTurnInTransaction(
|
|
|
788
894
|
draftRevision: nextDraftRevision,
|
|
789
895
|
result: { sourceTurnId: turn.id },
|
|
790
896
|
});
|
|
791
|
-
return {
|
|
897
|
+
return {
|
|
898
|
+
receipt,
|
|
899
|
+
queueVersion,
|
|
900
|
+
items: remaining,
|
|
901
|
+
draft,
|
|
902
|
+
eventIds: [event.id],
|
|
903
|
+
replay: false,
|
|
904
|
+
};
|
|
792
905
|
}
|
|
793
906
|
|
|
794
907
|
export async function steerQueuedTurnInTransaction(
|
|
@@ -804,7 +917,18 @@ export async function steerQueuedTurnInTransaction(
|
|
|
804
917
|
operationKey: string;
|
|
805
918
|
},
|
|
806
919
|
): Promise<SteerQueueCommandResult> {
|
|
807
|
-
await lockWorkspaceInferenceControl(db, input.workspaceId, "update");
|
|
920
|
+
const workspaceControl = await lockWorkspaceInferenceControl(db, input.workspaceId, "update");
|
|
921
|
+
await lockSessionEventWriteRows(db, {
|
|
922
|
+
workspaceId: input.workspaceId,
|
|
923
|
+
controlLock: "already_locked",
|
|
924
|
+
sessionIds:
|
|
925
|
+
input.actor.type === "agent_attempt"
|
|
926
|
+
? [input.actor.sessionId, input.sessionId]
|
|
927
|
+
: [input.sessionId],
|
|
928
|
+
turnIds:
|
|
929
|
+
input.actor.type === "agent_attempt" ? [input.actor.turnId, input.turnId] : [input.turnId],
|
|
930
|
+
attemptIds: input.actor.type === "agent_attempt" ? [input.actor.attemptId] : [],
|
|
931
|
+
});
|
|
808
932
|
const requestHash = canonicalSessionCommandHash({
|
|
809
933
|
expectedTurnVersion: input.expectedTurnVersion,
|
|
810
934
|
controlEtag: input.controlEtag ?? null,
|
|
@@ -843,7 +967,7 @@ export async function steerQueuedTurnInTransaction(
|
|
|
843
967
|
});
|
|
844
968
|
}
|
|
845
969
|
const before = await evaluateSessionControl(db, input.workspaceId, input.sessionId, {
|
|
846
|
-
|
|
970
|
+
workspaceControl,
|
|
847
971
|
});
|
|
848
972
|
if (input.controlEtag && input.controlEtag !== before.controlEtag) {
|
|
849
973
|
throw new SessionControlConflictError();
|
|
@@ -885,6 +1009,7 @@ export async function steerQueuedTurnInTransaction(
|
|
|
885
1009
|
});
|
|
886
1010
|
const interruptionCount = supersession.interruptionCount;
|
|
887
1011
|
const supersededTurnId = supersession.replacedTurn?.id ?? null;
|
|
1012
|
+
const supersededAttemptId = supersession.replacedTurn?.activeAttemptId ?? null;
|
|
888
1013
|
const liveCurrentTurnId = supersession.liveCurrentTurnId;
|
|
889
1014
|
|
|
890
1015
|
const withoutTarget = rows.filter((row) => row.id !== target.id);
|
|
@@ -899,7 +1024,17 @@ export async function steerQueuedTurnInTransaction(
|
|
|
899
1024
|
const queueVersion = session.queueVersion + 1;
|
|
900
1025
|
await db
|
|
901
1026
|
.update(schema.sessionTurns)
|
|
902
|
-
.set({
|
|
1027
|
+
.set({
|
|
1028
|
+
version: target.version + 1,
|
|
1029
|
+
metadata: {
|
|
1030
|
+
...target.metadata,
|
|
1031
|
+
delivery: "steer",
|
|
1032
|
+
replacedTurnId: supersededTurnId,
|
|
1033
|
+
replacedAttemptId: supersededAttemptId,
|
|
1034
|
+
interruptionCount,
|
|
1035
|
+
},
|
|
1036
|
+
updatedAt: now,
|
|
1037
|
+
})
|
|
903
1038
|
.where(eq(schema.sessionTurns.id, target.id));
|
|
904
1039
|
let sequence = supersession.lastSequence;
|
|
905
1040
|
const actor =
|
|
@@ -998,33 +1133,62 @@ export async function submitHumanPromptInTransaction(
|
|
|
998
1133
|
workspaceId: string;
|
|
999
1134
|
sessionId: string;
|
|
1000
1135
|
subjectId: string;
|
|
1136
|
+
subjectLabel?: string;
|
|
1001
1137
|
actor: SessionCommandActor;
|
|
1002
1138
|
operationKey: string;
|
|
1003
1139
|
delivery: "send" | "steer";
|
|
1004
1140
|
controlEtag?: string | null;
|
|
1005
1141
|
expectedDraftRevision?: number | null;
|
|
1006
1142
|
text: string;
|
|
1143
|
+
turnInstructions?: string | null;
|
|
1007
1144
|
resources: ResourceRef[];
|
|
1008
1145
|
tools: ToolRef[];
|
|
1146
|
+
toolsProvided?: boolean;
|
|
1009
1147
|
model?: string | null;
|
|
1010
1148
|
reasoningEffort?: ReasoningEffort | null;
|
|
1011
1149
|
reasoningEffortFallback: ReasoningEffort;
|
|
1150
|
+
/** Trusted API/core admission snapshot. Omitted only by legacy low-level callers. */
|
|
1151
|
+
turnExecutionPolicy?: TurnExecutionPolicyV1;
|
|
1012
1152
|
source: "user" | "api";
|
|
1013
|
-
mcpCredentialUpdates?: Array<{
|
|
1153
|
+
mcpCredentialUpdates?: Array<{
|
|
1154
|
+
id: string;
|
|
1155
|
+
headersEncrypted: Record<string, string>;
|
|
1156
|
+
}>;
|
|
1014
1157
|
},
|
|
1015
1158
|
): Promise<SubmitHumanPromptResult> {
|
|
1016
|
-
await lockWorkspaceInferenceControl(db, input.workspaceId, "update");
|
|
1159
|
+
const workspaceControl = await lockWorkspaceInferenceControl(db, input.workspaceId, "update");
|
|
1160
|
+
await lockSessionEventWriteRows(db, {
|
|
1161
|
+
workspaceId: input.workspaceId,
|
|
1162
|
+
controlLock: "already_locked",
|
|
1163
|
+
sessionIds:
|
|
1164
|
+
input.actor.type === "agent_attempt"
|
|
1165
|
+
? [input.actor.sessionId, input.sessionId]
|
|
1166
|
+
: [input.sessionId],
|
|
1167
|
+
turnIds: input.actor.type === "agent_attempt" ? [input.actor.turnId] : [],
|
|
1168
|
+
attemptIds: input.actor.type === "agent_attempt" ? [input.actor.attemptId] : [],
|
|
1169
|
+
});
|
|
1017
1170
|
const requestHash = canonicalSessionCommandHash({
|
|
1018
1171
|
delivery: input.delivery,
|
|
1019
1172
|
controlEtag: input.controlEtag ?? null,
|
|
1020
1173
|
expectedDraftRevision: input.expectedDraftRevision ?? null,
|
|
1021
1174
|
text: input.text,
|
|
1175
|
+
turnInstructions: input.turnInstructions ?? null,
|
|
1022
1176
|
resources: input.resources,
|
|
1023
1177
|
tools: input.tools,
|
|
1178
|
+
toolsProvided: input.toolsProvided === true,
|
|
1024
1179
|
model: input.model ?? null,
|
|
1025
1180
|
reasoningEffort: input.reasoningEffort ?? null,
|
|
1026
1181
|
source: input.source,
|
|
1027
1182
|
mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
|
|
1183
|
+
...(input.actor.type === "service"
|
|
1184
|
+
? {
|
|
1185
|
+
serviceInitiator: {
|
|
1186
|
+
subjectId: input.actor.subjectId,
|
|
1187
|
+
subjectLabel: input.actor.subjectLabel ?? null,
|
|
1188
|
+
context: input.actor.context ?? {},
|
|
1189
|
+
},
|
|
1190
|
+
}
|
|
1191
|
+
: {}),
|
|
1028
1192
|
});
|
|
1029
1193
|
const reserved = await reserveSessionCommandReceipt(db, {
|
|
1030
1194
|
accountId: input.accountId,
|
|
@@ -1063,7 +1227,7 @@ export async function submitHumanPromptInTransaction(
|
|
|
1063
1227
|
}
|
|
1064
1228
|
|
|
1065
1229
|
const before = await evaluateSessionControl(db, input.workspaceId, input.sessionId, {
|
|
1066
|
-
|
|
1230
|
+
workspaceControl,
|
|
1067
1231
|
});
|
|
1068
1232
|
if (input.controlEtag && input.controlEtag !== before.controlEtag) {
|
|
1069
1233
|
throw new SessionControlConflictError();
|
|
@@ -1076,7 +1240,14 @@ export async function submitHumanPromptInTransaction(
|
|
|
1076
1240
|
input.actor.type === "agent_attempt"
|
|
1077
1241
|
? `attempt:${input.actor.attemptId}`
|
|
1078
1242
|
: input.actor.subjectId,
|
|
1079
|
-
reason:
|
|
1243
|
+
reason:
|
|
1244
|
+
input.actor.type === "service"
|
|
1245
|
+
? input.delivery === "steer"
|
|
1246
|
+
? "service_steer"
|
|
1247
|
+
: "service_send"
|
|
1248
|
+
: input.delivery === "steer"
|
|
1249
|
+
? "human_steer"
|
|
1250
|
+
: "human_send",
|
|
1080
1251
|
observedControlEtag: input.controlEtag ?? null,
|
|
1081
1252
|
});
|
|
1082
1253
|
const session = await lockSession(db, input.workspaceId, input.sessionId);
|
|
@@ -1113,6 +1284,7 @@ export async function submitHumanPromptInTransaction(
|
|
|
1113
1284
|
text: draft.text,
|
|
1114
1285
|
resources: draft.resources,
|
|
1115
1286
|
tools: draft.tools,
|
|
1287
|
+
toolsProvided: draft.toolsProvided,
|
|
1116
1288
|
model: draft.model,
|
|
1117
1289
|
reasoningEffort: draft.reasoningEffort,
|
|
1118
1290
|
}) !==
|
|
@@ -1120,6 +1292,7 @@ export async function submitHumanPromptInTransaction(
|
|
|
1120
1292
|
text: input.text,
|
|
1121
1293
|
resources: input.resources,
|
|
1122
1294
|
tools: input.tools,
|
|
1295
|
+
toolsProvided: input.toolsProvided === true,
|
|
1123
1296
|
model: input.model ?? session.model,
|
|
1124
1297
|
reasoningEffort: input.reasoningEffort ?? input.reasoningEffortFallback,
|
|
1125
1298
|
})
|
|
@@ -1135,6 +1308,52 @@ export async function submitHumanPromptInTransaction(
|
|
|
1135
1308
|
}
|
|
1136
1309
|
}
|
|
1137
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
|
+
|
|
1138
1357
|
for (const update of input.mcpCredentialUpdates ?? []) {
|
|
1139
1358
|
const [server] = await db
|
|
1140
1359
|
.update(schema.sessionMcpServers)
|
|
@@ -1155,6 +1374,28 @@ export async function submitHumanPromptInTransaction(
|
|
|
1155
1374
|
}
|
|
1156
1375
|
|
|
1157
1376
|
const now = new Date();
|
|
1377
|
+
let frozenInitiator: FrozenTurnInitiator;
|
|
1378
|
+
if (input.delivery === "send" && draft?.sourceTurnId) {
|
|
1379
|
+
const sourceTurn = editedSourceTurn;
|
|
1380
|
+
if (!sourceTurn) {
|
|
1381
|
+
throw new SessionControlInvariantError("Edited prompt source turn is missing");
|
|
1382
|
+
}
|
|
1383
|
+
frozenInitiator = {
|
|
1384
|
+
initiator: initiatorFromStorage(
|
|
1385
|
+
sourceTurn.initiatorKind,
|
|
1386
|
+
sourceTurn.initiatorSubjectId,
|
|
1387
|
+
sourceTurn.initiatorContext ?? {},
|
|
1388
|
+
),
|
|
1389
|
+
context: sourceTurn.initiatorContext ?? {},
|
|
1390
|
+
};
|
|
1391
|
+
} else {
|
|
1392
|
+
frozenInitiator = await frozenInitiatorForCommandActor(
|
|
1393
|
+
db,
|
|
1394
|
+
input.workspaceId,
|
|
1395
|
+
input.actor,
|
|
1396
|
+
input.subjectLabel,
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1158
1399
|
const acceptedEventId = crypto.randomUUID();
|
|
1159
1400
|
const turnId = crypto.randomUUID();
|
|
1160
1401
|
const workflowId = session.temporalWorkflowId ?? `session-${session.id}`;
|
|
@@ -1168,14 +1409,19 @@ export async function submitHumanPromptInTransaction(
|
|
|
1168
1409
|
sequence: ++sequence,
|
|
1169
1410
|
type: "user.message",
|
|
1170
1411
|
clientEventId: input.operationKey,
|
|
1171
|
-
payload: {
|
|
1412
|
+
payload: sanitizeEventPayload({
|
|
1172
1413
|
text: input.text,
|
|
1173
1414
|
...(input.resources.length ? { resources: input.resources } : {}),
|
|
1174
|
-
...(input.
|
|
1415
|
+
...(input.toolsProvided === true
|
|
1416
|
+
? { tools: input.tools }
|
|
1417
|
+
: input.tools.length
|
|
1418
|
+
? { tools: input.tools }
|
|
1419
|
+
: {}),
|
|
1175
1420
|
...(input.model ? { model: input.model } : {}),
|
|
1176
1421
|
...(input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {}),
|
|
1177
1422
|
delivery: input.delivery,
|
|
1178
|
-
|
|
1423
|
+
initiator: frozenInitiator.initiator,
|
|
1424
|
+
}),
|
|
1179
1425
|
occurredAt: now,
|
|
1180
1426
|
},
|
|
1181
1427
|
];
|
|
@@ -1193,13 +1439,21 @@ export async function submitHumanPromptInTransaction(
|
|
|
1193
1439
|
source: input.source,
|
|
1194
1440
|
position: input.delivery === "steer" ? 0 : existingQueued.length + 1,
|
|
1195
1441
|
prompt: input.text,
|
|
1442
|
+
turnInstructions:
|
|
1443
|
+
editedSourceTurnInstructions !== undefined
|
|
1444
|
+
? editedSourceTurnInstructions
|
|
1445
|
+
: (input.turnInstructions ?? null),
|
|
1196
1446
|
resources: input.resources,
|
|
1197
1447
|
tools: input.tools,
|
|
1448
|
+
toolsProvided: input.toolsProvided === true,
|
|
1198
1449
|
model: input.model ?? session.model,
|
|
1199
1450
|
reasoningEffort: input.reasoningEffort ?? input.reasoningEffortFallback,
|
|
1200
1451
|
sandboxBackend: session.sandboxBackend,
|
|
1201
|
-
metadata:
|
|
1452
|
+
metadata: input.turnExecutionPolicy
|
|
1453
|
+
? metadataWithTurnExecutionPolicyV1({}, input.turnExecutionPolicy)
|
|
1454
|
+
: {},
|
|
1202
1455
|
lineage: { actor: input.actor.type },
|
|
1456
|
+
...initiatorColumns(frozenInitiator),
|
|
1203
1457
|
})
|
|
1204
1458
|
.returning();
|
|
1205
1459
|
if (!turn) throw new SessionControlInvariantError("Prompt turn was not inserted");
|
|
@@ -1210,7 +1464,12 @@ export async function submitHumanPromptInTransaction(
|
|
|
1210
1464
|
sequence: ++sequence,
|
|
1211
1465
|
type: "turn.queued",
|
|
1212
1466
|
turnId,
|
|
1213
|
-
payload: {
|
|
1467
|
+
payload: {
|
|
1468
|
+
turnId,
|
|
1469
|
+
triggerEventId: acceptedEventId,
|
|
1470
|
+
source: input.source,
|
|
1471
|
+
initiator: frozenInitiator.initiator,
|
|
1472
|
+
},
|
|
1214
1473
|
occurredAt: now,
|
|
1215
1474
|
});
|
|
1216
1475
|
|
|
@@ -1238,7 +1497,36 @@ export async function submitHumanPromptInTransaction(
|
|
|
1238
1497
|
for (const event of eventValues) event.sequence = ++sequence;
|
|
1239
1498
|
const interruptionCount = supersession.interruptionCount;
|
|
1240
1499
|
const replacedTurnId = supersession.replacedTurn?.id ?? null;
|
|
1500
|
+
const replacedAttemptId = supersession.replacedTurn?.activeAttemptId ?? null;
|
|
1241
1501
|
const liveCurrentTurnId = supersession.liveCurrentTurnId;
|
|
1502
|
+
// Durable provenance for the queue projection. Until the superseded attempt
|
|
1503
|
+
// loses inference, user-visible output, and workspace-persistence authority,
|
|
1504
|
+
// its exact first-class attempt has no quiescence receipt. Pairing that
|
|
1505
|
+
// attempt with the replacement lets every client render "Stopping previous
|
|
1506
|
+
// attempt…" truthfully across refresh/reconnect, even though logical
|
|
1507
|
+
// settlement has already cleared sessions.active_turn_id. Do not infer this
|
|
1508
|
+
// state from a local request spinner or queue position.
|
|
1509
|
+
if (input.delivery === "steer") {
|
|
1510
|
+
await db
|
|
1511
|
+
.update(schema.sessionTurns)
|
|
1512
|
+
.set({
|
|
1513
|
+
metadata: {
|
|
1514
|
+
...turn.metadata,
|
|
1515
|
+
delivery: "steer",
|
|
1516
|
+
replacedTurnId,
|
|
1517
|
+
replacedAttemptId,
|
|
1518
|
+
interruptionCount,
|
|
1519
|
+
},
|
|
1520
|
+
updatedAt: now,
|
|
1521
|
+
})
|
|
1522
|
+
.where(
|
|
1523
|
+
and(
|
|
1524
|
+
eq(schema.sessionTurns.workspaceId, input.workspaceId),
|
|
1525
|
+
eq(schema.sessionTurns.sessionId, input.sessionId),
|
|
1526
|
+
eq(schema.sessionTurns.id, turnId),
|
|
1527
|
+
),
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1242
1530
|
if (supersession.replacedTurn) {
|
|
1243
1531
|
const current = supersession.replacedTurn;
|
|
1244
1532
|
if (!liveCurrentTurnId) {
|
|
@@ -1301,7 +1589,9 @@ export async function submitHumanPromptInTransaction(
|
|
|
1301
1589
|
.update(schema.sessions)
|
|
1302
1590
|
.set({
|
|
1303
1591
|
resources: mergeResourceRefs(session.resources as ResourceRef[], input.resources),
|
|
1304
|
-
tools:
|
|
1592
|
+
tools: sessionToolPolicyIsFixed(session.toolPolicy)
|
|
1593
|
+
? session.tools
|
|
1594
|
+
: mergeToolRefs(session.tools as ToolRef[], input.tools),
|
|
1305
1595
|
activeTurnId: input.delivery === "steer" ? liveCurrentTurnId : session.activeTurnId,
|
|
1306
1596
|
status: nextStatus,
|
|
1307
1597
|
queueVersion,
|
|
@@ -1331,7 +1621,14 @@ export async function submitHumanPromptInTransaction(
|
|
|
1331
1621
|
action: input.delivery === "steer" ? "session.prompt.steer" : "session.prompt.send",
|
|
1332
1622
|
targetType: "session_turn",
|
|
1333
1623
|
targetId: turnId,
|
|
1334
|
-
metadata: {
|
|
1624
|
+
metadata: {
|
|
1625
|
+
operationId: reserved.receipt.id,
|
|
1626
|
+
replacedTurnId,
|
|
1627
|
+
interruptionCount,
|
|
1628
|
+
...(input.turnExecutionPolicy
|
|
1629
|
+
? turnExecutionPolicyAuditMetadata(input.turnExecutionPolicy, turnId)
|
|
1630
|
+
: {}),
|
|
1631
|
+
},
|
|
1335
1632
|
});
|
|
1336
1633
|
const eventIds = eventRows.map((event) => event.id);
|
|
1337
1634
|
const receipt = await updateSessionCommandReceiptResult(db, reserved.receipt.id, {
|
|
@@ -1347,6 +1644,11 @@ export async function submitHumanPromptInTransaction(
|
|
|
1347
1644
|
interruptionCount,
|
|
1348
1645
|
replacedTurnId,
|
|
1349
1646
|
workspaceControlEventId: resumed.workspaceControlEventId,
|
|
1647
|
+
...(input.turnExecutionPolicy
|
|
1648
|
+
? {
|
|
1649
|
+
executionPolicy: turnExecutionPolicyAuditMetadata(input.turnExecutionPolicy, turnId),
|
|
1650
|
+
}
|
|
1651
|
+
: {}),
|
|
1350
1652
|
},
|
|
1351
1653
|
});
|
|
1352
1654
|
return {
|
|
@@ -1362,6 +1664,12 @@ export async function submitHumanPromptInTransaction(
|
|
|
1362
1664
|
};
|
|
1363
1665
|
}
|
|
1364
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
|
+
|
|
1365
1673
|
export async function sendAgentMessageInTransaction(
|
|
1366
1674
|
db: Database,
|
|
1367
1675
|
input: {
|
|
@@ -1373,7 +1681,14 @@ export async function sendAgentMessageInTransaction(
|
|
|
1373
1681
|
text: string;
|
|
1374
1682
|
},
|
|
1375
1683
|
): Promise<AgentInternalUpdateCommandResult> {
|
|
1376
|
-
await lockWorkspaceInferenceControl(db, input.workspaceId, "share");
|
|
1684
|
+
const workspaceControl = await lockWorkspaceInferenceControl(db, input.workspaceId, "share");
|
|
1685
|
+
await lockSessionEventWriteRows(db, {
|
|
1686
|
+
workspaceId: input.workspaceId,
|
|
1687
|
+
controlLock: "already_locked",
|
|
1688
|
+
sessionIds: [input.actor.sessionId, input.targetSessionId],
|
|
1689
|
+
turnIds: [input.actor.turnId],
|
|
1690
|
+
attemptIds: [input.actor.attemptId],
|
|
1691
|
+
});
|
|
1377
1692
|
const requestHash = canonicalSessionCommandHash({ text: input.text });
|
|
1378
1693
|
const reserved = await reserveSessionCommandReceipt(db, {
|
|
1379
1694
|
accountId: input.accountId,
|
|
@@ -1424,7 +1739,7 @@ export async function sendAgentMessageInTransaction(
|
|
|
1424
1739
|
);
|
|
1425
1740
|
}
|
|
1426
1741
|
const effective = await evaluateSessionControl(db, input.workspaceId, input.targetSessionId, {
|
|
1427
|
-
|
|
1742
|
+
workspaceControl,
|
|
1428
1743
|
});
|
|
1429
1744
|
const now = new Date();
|
|
1430
1745
|
const [update] = await db
|
|
@@ -1538,6 +1853,13 @@ export async function steerAgentSessionInTransaction(
|
|
|
1538
1853
|
},
|
|
1539
1854
|
): Promise<AgentInternalUpdateCommandResult> {
|
|
1540
1855
|
await lockWorkspaceInferenceControl(db, input.workspaceId, "update");
|
|
1856
|
+
await lockSessionEventWriteRows(db, {
|
|
1857
|
+
workspaceId: input.workspaceId,
|
|
1858
|
+
controlLock: "already_locked",
|
|
1859
|
+
sessionIds: [input.actor.sessionId, input.targetSessionId],
|
|
1860
|
+
turnIds: [input.actor.turnId],
|
|
1861
|
+
attemptIds: [input.actor.attemptId],
|
|
1862
|
+
});
|
|
1541
1863
|
const reserved = await reserveSessionCommandReceipt(db, {
|
|
1542
1864
|
accountId: input.accountId,
|
|
1543
1865
|
workspaceId: input.workspaceId,
|
|
@@ -1546,7 +1868,9 @@ export async function steerAgentSessionInTransaction(
|
|
|
1546
1868
|
targetSessionId: input.targetSessionId,
|
|
1547
1869
|
targetTurnId: null,
|
|
1548
1870
|
operationKey: input.operationKey,
|
|
1549
|
-
canonicalRequestHash: canonicalSessionCommandHash({
|
|
1871
|
+
canonicalRequestHash: canonicalSessionCommandHash({
|
|
1872
|
+
instruction: input.instruction,
|
|
1873
|
+
}),
|
|
1550
1874
|
});
|
|
1551
1875
|
if (reserved.replay) {
|
|
1552
1876
|
const updateId = String(reserved.receipt.result.updateId ?? "");
|