@opengeni/db 0.7.3 → 0.9.3
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-B22X3IEZ.js → chunk-4LG5NBTC.js} +512 -32
- package/dist/chunk-4LG5NBTC.js.map +1 -0
- 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/index.d.ts +3 -3
- package/dist/index.js +6068 -2090
- 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 +721 -46
- package/dist/provision-roles.js +1 -1
- package/dist/{schema-BN5mB9xZ.d.ts → schema-CdPGTHlD.d.ts} +2118 -119
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +17 -1
- package/drizzle/0064_rotation_strategy_sharded_backfill.sql +15 -0
- package/drizzle/0065_session_attempt_quiescence.sql +26 -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/package.json +4 -4
- package/src/connection-token-resolver.ts +287 -1
- package/src/event-payload-sanitizer.ts +57 -19
- package/src/index.ts +5377 -1125
- 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 +552 -34
- package/src/session-control.ts +518 -38
- package/src/session-queue-commands.ts +308 -57
- 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
|
@@ -7,12 +7,14 @@ import {
|
|
|
7
7
|
} from "@opengeni/contracts";
|
|
8
8
|
import { and, asc, eq, inArray, sql } from "drizzle-orm";
|
|
9
9
|
import type { Database } from "./index";
|
|
10
|
+
import { sanitizeEventPayload } from "./event-payload-sanitizer";
|
|
10
11
|
import { closePendingSessionToolCallsInTransaction } from "./session-tool-call-settlement";
|
|
11
12
|
import {
|
|
12
13
|
assertAgentCommandAuthorityInTransaction,
|
|
13
14
|
autoResumeSessionBranchInTransaction,
|
|
14
15
|
canonicalSessionCommandHash,
|
|
15
16
|
evaluateSessionControl,
|
|
17
|
+
lockSessionEventWriteRows,
|
|
16
18
|
lockWorkspaceInferenceControl,
|
|
17
19
|
registerInternalUpdateWakeInTransaction,
|
|
18
20
|
reserveSessionCommandReceipt,
|
|
@@ -24,6 +26,12 @@ import {
|
|
|
24
26
|
updateSessionCommandReceiptResult,
|
|
25
27
|
} from "./session-control";
|
|
26
28
|
import * as schema from "./schema";
|
|
29
|
+
import {
|
|
30
|
+
frozenInitiatorForCommandActor,
|
|
31
|
+
initiatorColumns,
|
|
32
|
+
initiatorFromStorage,
|
|
33
|
+
type FrozenTurnInitiator,
|
|
34
|
+
} from "./turn-initiator";
|
|
27
35
|
|
|
28
36
|
export type QueueCommandConflictCode =
|
|
29
37
|
| "QUEUE_VERSION_CHANGED"
|
|
@@ -99,12 +107,13 @@ async function lockSession(
|
|
|
99
107
|
workspaceId: string,
|
|
100
108
|
sessionId: string,
|
|
101
109
|
): Promise<typeof schema.sessions.$inferSelect> {
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
const locks = await lockSessionEventWriteRows(db, {
|
|
111
|
+
workspaceId,
|
|
112
|
+
controlLock: "already_locked",
|
|
113
|
+
workspaceLock: "already_locked",
|
|
114
|
+
sessionIds: [sessionId],
|
|
115
|
+
});
|
|
116
|
+
const session = locks.sessions[0];
|
|
108
117
|
if (!session) throw new Error(`Session not found: ${sessionId}`);
|
|
109
118
|
return session;
|
|
110
119
|
}
|
|
@@ -142,7 +151,7 @@ export async function supersedeSessionCurrentDirectionInTransaction(
|
|
|
142
151
|
lastSequence: input.lastSequence,
|
|
143
152
|
};
|
|
144
153
|
}
|
|
145
|
-
const [
|
|
154
|
+
const [preview] = await db
|
|
146
155
|
.select()
|
|
147
156
|
.from(schema.sessionTurns)
|
|
148
157
|
.where(
|
|
@@ -152,8 +161,15 @@ export async function supersedeSessionCurrentDirectionInTransaction(
|
|
|
152
161
|
eq(schema.sessionTurns.id, input.activeTurnId),
|
|
153
162
|
),
|
|
154
163
|
)
|
|
155
|
-
.for("update")
|
|
156
164
|
.limit(1);
|
|
165
|
+
const locks = await lockSessionEventWriteRows(db, {
|
|
166
|
+
workspaceId: input.workspaceId,
|
|
167
|
+
controlLock: "already_locked",
|
|
168
|
+
workspaceLock: "already_locked",
|
|
169
|
+
turnIds: [input.activeTurnId],
|
|
170
|
+
attemptIds: preview?.activeAttemptId ? [preview.activeAttemptId] : [],
|
|
171
|
+
});
|
|
172
|
+
const current = locks.turns[0];
|
|
157
173
|
if (!current) {
|
|
158
174
|
throw new SessionControlInvariantError(
|
|
159
175
|
`Session ${input.sessionId} points to missing active turn ${input.activeTurnId}`,
|
|
@@ -206,6 +222,44 @@ export async function supersedeSessionCurrentDirectionInTransaction(
|
|
|
206
222
|
sequence: input.lastSequence,
|
|
207
223
|
now,
|
|
208
224
|
});
|
|
225
|
+
const cancelledHumanInputs = await db
|
|
226
|
+
.update(schema.sessionHumanInputRequests)
|
|
227
|
+
.set({
|
|
228
|
+
status: "cancelled",
|
|
229
|
+
response: { outcome: "cancelled" },
|
|
230
|
+
respondedBy:
|
|
231
|
+
input.actor.type === "agent_attempt"
|
|
232
|
+
? `attempt:${input.actor.attemptId}`
|
|
233
|
+
: input.actor.subjectId,
|
|
234
|
+
respondedAt: now,
|
|
235
|
+
updatedAt: now,
|
|
236
|
+
})
|
|
237
|
+
.where(
|
|
238
|
+
and(
|
|
239
|
+
eq(schema.sessionHumanInputRequests.workspaceId, input.workspaceId),
|
|
240
|
+
eq(schema.sessionHumanInputRequests.sessionId, input.sessionId),
|
|
241
|
+
eq(schema.sessionHumanInputRequests.turnId, current.id),
|
|
242
|
+
eq(schema.sessionHumanInputRequests.status, "pending"),
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
.returning({ id: schema.sessionHumanInputRequests.id });
|
|
246
|
+
let lastSequence = closedTools.sequence;
|
|
247
|
+
if (cancelledHumanInputs.length > 0) {
|
|
248
|
+
await db.insert(schema.sessionEvents).values(
|
|
249
|
+
cancelledHumanInputs.map((request) => ({
|
|
250
|
+
accountId: input.accountId,
|
|
251
|
+
workspaceId: input.workspaceId,
|
|
252
|
+
sessionId: input.sessionId,
|
|
253
|
+
sequence: ++lastSequence,
|
|
254
|
+
type: "user.humanInputResponse",
|
|
255
|
+
turnId: current.id,
|
|
256
|
+
turnGeneration: current.executionGeneration,
|
|
257
|
+
turnAssociation: "current",
|
|
258
|
+
payload: { requestId: request.id, response: { outcome: "cancelled" } },
|
|
259
|
+
occurredAt: now,
|
|
260
|
+
})),
|
|
261
|
+
);
|
|
262
|
+
}
|
|
209
263
|
await db
|
|
210
264
|
.update(schema.sessionTurns)
|
|
211
265
|
.set({
|
|
@@ -248,7 +302,7 @@ export async function supersedeSessionCurrentDirectionInTransaction(
|
|
|
248
302
|
interruptionCount: 0,
|
|
249
303
|
replacedTurn: current,
|
|
250
304
|
liveCurrentTurnId: null,
|
|
251
|
-
lastSequence
|
|
305
|
+
lastSequence,
|
|
252
306
|
};
|
|
253
307
|
}
|
|
254
308
|
|
|
@@ -274,7 +328,16 @@ async function loadQueuedTurns(
|
|
|
274
328
|
asc(schema.sessionTurns.createdAt),
|
|
275
329
|
asc(schema.sessionTurns.id),
|
|
276
330
|
);
|
|
277
|
-
|
|
331
|
+
const rows = await query;
|
|
332
|
+
if (!lock || rows.length === 0) return rows;
|
|
333
|
+
const locks = await lockSessionEventWriteRows(db, {
|
|
334
|
+
workspaceId,
|
|
335
|
+
controlLock: "already_locked",
|
|
336
|
+
workspaceLock: "already_locked",
|
|
337
|
+
turnIds: rows.map((row) => row.id),
|
|
338
|
+
});
|
|
339
|
+
const byId = new Map(locks.turns.map((row) => [row.id, row]));
|
|
340
|
+
return rows.map((row) => byId.get(row.id)).filter((row): row is QueuedTurnRow => Boolean(row));
|
|
278
341
|
}
|
|
279
342
|
|
|
280
343
|
async function normalizeQueuePositions(
|
|
@@ -320,7 +383,12 @@ function draftIsNonEmpty(draft: ComposerDraftRow): boolean {
|
|
|
320
383
|
|
|
321
384
|
export async function getComposerDraftInTransaction(
|
|
322
385
|
db: Database,
|
|
323
|
-
input: {
|
|
386
|
+
input: {
|
|
387
|
+
workspaceId: string;
|
|
388
|
+
sessionId: string;
|
|
389
|
+
subjectId: string;
|
|
390
|
+
lock?: boolean;
|
|
391
|
+
},
|
|
324
392
|
): Promise<ComposerDraftRow | null> {
|
|
325
393
|
const query = db
|
|
326
394
|
.select()
|
|
@@ -353,8 +421,15 @@ export async function saveComposerDraftInTransaction(
|
|
|
353
421
|
},
|
|
354
422
|
): Promise<ComposerDraftRow> {
|
|
355
423
|
await lockWorkspaceInferenceControl(db, input.workspaceId, "share");
|
|
424
|
+
await lockSessionEventWriteRows(db, {
|
|
425
|
+
workspaceId: input.workspaceId,
|
|
426
|
+
controlLock: "already_locked",
|
|
427
|
+
});
|
|
356
428
|
await lockSession(db, input.workspaceId, input.sessionId);
|
|
357
|
-
const current = await getComposerDraftInTransaction(db, {
|
|
429
|
+
const current = await getComposerDraftInTransaction(db, {
|
|
430
|
+
...input,
|
|
431
|
+
lock: true,
|
|
432
|
+
});
|
|
358
433
|
const currentRevision = current?.revision ?? 0;
|
|
359
434
|
if (currentRevision !== input.expectedRevision) {
|
|
360
435
|
throw new QueueCommandConflictError("DRAFT_CHANGED", "Composer draft changed", {
|
|
@@ -374,8 +449,11 @@ export async function saveComposerDraftInTransaction(
|
|
|
374
449
|
tools: input.tools,
|
|
375
450
|
model: input.model,
|
|
376
451
|
reasoningEffort: input.reasoningEffort,
|
|
377
|
-
|
|
378
|
-
|
|
452
|
+
// A queue edit is still the same accepted work item. Preserve its frozen
|
|
453
|
+
// initiator through arbitrary draft saves; only a genuinely new compose or
|
|
454
|
+
// Steer captures the submitting actor.
|
|
455
|
+
sourceTurnId: current?.sourceTurnId ?? null,
|
|
456
|
+
sourceTurnVersion: current?.sourceTurnVersion ?? null,
|
|
379
457
|
updatedAt: new Date(),
|
|
380
458
|
};
|
|
381
459
|
const [saved] = current
|
|
@@ -403,6 +481,10 @@ export async function moveQueuedTurnInTransaction(
|
|
|
403
481
|
},
|
|
404
482
|
): Promise<QueueCommandResult> {
|
|
405
483
|
await lockWorkspaceInferenceControl(db, input.workspaceId, "share");
|
|
484
|
+
await lockSessionEventWriteRows(db, {
|
|
485
|
+
workspaceId: input.workspaceId,
|
|
486
|
+
controlLock: "already_locked",
|
|
487
|
+
});
|
|
406
488
|
const session = await lockSession(db, input.workspaceId, input.sessionId);
|
|
407
489
|
const requestHash = canonicalSessionCommandHash({
|
|
408
490
|
beforeTurnId: input.beforeTurnId,
|
|
@@ -494,7 +576,11 @@ export async function moveQueuedTurnInTransaction(
|
|
|
494
576
|
eventIds.push(event.id);
|
|
495
577
|
await db
|
|
496
578
|
.update(schema.sessions)
|
|
497
|
-
.set({
|
|
579
|
+
.set({
|
|
580
|
+
queueVersion,
|
|
581
|
+
lastSequence: session.lastSequence + 1,
|
|
582
|
+
updatedAt: new Date(),
|
|
583
|
+
})
|
|
498
584
|
.where(eq(schema.sessions.id, input.sessionId));
|
|
499
585
|
}
|
|
500
586
|
const receipt = await updateSessionCommandReceiptResult(db, reserved.receipt.id, {
|
|
@@ -525,6 +611,10 @@ export async function deleteSessionQueueItemInTransaction(
|
|
|
525
611
|
},
|
|
526
612
|
): Promise<QueueCommandResult> {
|
|
527
613
|
await lockWorkspaceInferenceControl(db, input.workspaceId, "share");
|
|
614
|
+
await lockSessionEventWriteRows(db, {
|
|
615
|
+
workspaceId: input.workspaceId,
|
|
616
|
+
controlLock: "already_locked",
|
|
617
|
+
});
|
|
528
618
|
const session = await lockSession(db, input.workspaceId, input.sessionId);
|
|
529
619
|
const requestHash = canonicalSessionCommandHash({
|
|
530
620
|
expectedTurnVersion: input.expectedTurnVersion,
|
|
@@ -549,18 +639,14 @@ export async function deleteSessionQueueItemInTransaction(
|
|
|
549
639
|
replay: true,
|
|
550
640
|
};
|
|
551
641
|
}
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
),
|
|
561
|
-
)
|
|
562
|
-
.for("update")
|
|
563
|
-
.limit(1);
|
|
642
|
+
const turn = (
|
|
643
|
+
await lockSessionEventWriteRows(db, {
|
|
644
|
+
workspaceId: input.workspaceId,
|
|
645
|
+
controlLock: "already_locked",
|
|
646
|
+
workspaceLock: "already_locked",
|
|
647
|
+
turnIds: [input.turnId],
|
|
648
|
+
})
|
|
649
|
+
).turns[0];
|
|
564
650
|
if (!turn || turn.status !== "queued" || !["user", "api"].includes(turn.source)) {
|
|
565
651
|
throw new QueueCommandConflictError("QUEUE_PROMPT_STARTED", "Prompt is no longer waiting", {
|
|
566
652
|
queueVersion: session.queueVersion,
|
|
@@ -616,7 +702,11 @@ export async function deleteSessionQueueItemInTransaction(
|
|
|
616
702
|
if (!event) throw new Error("Queue delete event was not inserted");
|
|
617
703
|
await db
|
|
618
704
|
.update(schema.sessions)
|
|
619
|
-
.set({
|
|
705
|
+
.set({
|
|
706
|
+
queueVersion,
|
|
707
|
+
lastSequence: session.lastSequence + 1,
|
|
708
|
+
updatedAt: now,
|
|
709
|
+
})
|
|
620
710
|
.where(eq(schema.sessions.id, input.sessionId));
|
|
621
711
|
const receipt = await updateSessionCommandReceiptResult(db, reserved.receipt.id, {
|
|
622
712
|
queueVersion,
|
|
@@ -648,6 +738,10 @@ export async function editQueuedTurnInTransaction(
|
|
|
648
738
|
},
|
|
649
739
|
): Promise<EditQueueCommandResult> {
|
|
650
740
|
await lockWorkspaceInferenceControl(db, input.workspaceId, "share");
|
|
741
|
+
await lockSessionEventWriteRows(db, {
|
|
742
|
+
workspaceId: input.workspaceId,
|
|
743
|
+
controlLock: "already_locked",
|
|
744
|
+
});
|
|
651
745
|
const session = await lockSession(db, input.workspaceId, input.sessionId);
|
|
652
746
|
const requestHash = canonicalSessionCommandHash({
|
|
653
747
|
expectedTurnVersion: input.expectedTurnVersion,
|
|
@@ -664,7 +758,10 @@ export async function editQueuedTurnInTransaction(
|
|
|
664
758
|
operationKey: input.operationKey,
|
|
665
759
|
canonicalRequestHash: requestHash,
|
|
666
760
|
});
|
|
667
|
-
const existingDraft = await getComposerDraftInTransaction(db, {
|
|
761
|
+
const existingDraft = await getComposerDraftInTransaction(db, {
|
|
762
|
+
...input,
|
|
763
|
+
lock: true,
|
|
764
|
+
});
|
|
668
765
|
if (reserved.replay && reserved.receipt.appliedQueueVersion !== null) {
|
|
669
766
|
if (!existingDraft) throw new Error("Replayed queue Edit has no durable draft");
|
|
670
767
|
return {
|
|
@@ -689,18 +786,14 @@ export async function editQueuedTurnInTransaction(
|
|
|
689
786
|
draftRevision,
|
|
690
787
|
});
|
|
691
788
|
}
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
),
|
|
701
|
-
)
|
|
702
|
-
.for("update")
|
|
703
|
-
.limit(1);
|
|
789
|
+
const turn = (
|
|
790
|
+
await lockSessionEventWriteRows(db, {
|
|
791
|
+
workspaceId: input.workspaceId,
|
|
792
|
+
controlLock: "already_locked",
|
|
793
|
+
workspaceLock: "already_locked",
|
|
794
|
+
turnIds: [input.turnId],
|
|
795
|
+
})
|
|
796
|
+
).turns[0];
|
|
704
797
|
if (!turn || turn.status !== "queued" || !["user", "api"].includes(turn.source)) {
|
|
705
798
|
throw new QueueCommandConflictError("QUEUE_PROMPT_STARTED", "Prompt is no longer waiting", {
|
|
706
799
|
queueVersion: session.queueVersion,
|
|
@@ -780,7 +873,11 @@ export async function editQueuedTurnInTransaction(
|
|
|
780
873
|
if (!event) throw new Error("Queue edit event was not inserted");
|
|
781
874
|
await db
|
|
782
875
|
.update(schema.sessions)
|
|
783
|
-
.set({
|
|
876
|
+
.set({
|
|
877
|
+
queueVersion,
|
|
878
|
+
lastSequence: session.lastSequence + 1,
|
|
879
|
+
updatedAt: now,
|
|
880
|
+
})
|
|
784
881
|
.where(eq(schema.sessions.id, input.sessionId));
|
|
785
882
|
const receipt = await updateSessionCommandReceiptResult(db, reserved.receipt.id, {
|
|
786
883
|
queueVersion,
|
|
@@ -788,7 +885,14 @@ export async function editQueuedTurnInTransaction(
|
|
|
788
885
|
draftRevision: nextDraftRevision,
|
|
789
886
|
result: { sourceTurnId: turn.id },
|
|
790
887
|
});
|
|
791
|
-
return {
|
|
888
|
+
return {
|
|
889
|
+
receipt,
|
|
890
|
+
queueVersion,
|
|
891
|
+
items: remaining,
|
|
892
|
+
draft,
|
|
893
|
+
eventIds: [event.id],
|
|
894
|
+
replay: false,
|
|
895
|
+
};
|
|
792
896
|
}
|
|
793
897
|
|
|
794
898
|
export async function steerQueuedTurnInTransaction(
|
|
@@ -804,7 +908,18 @@ export async function steerQueuedTurnInTransaction(
|
|
|
804
908
|
operationKey: string;
|
|
805
909
|
},
|
|
806
910
|
): Promise<SteerQueueCommandResult> {
|
|
807
|
-
await lockWorkspaceInferenceControl(db, input.workspaceId, "update");
|
|
911
|
+
const workspaceControl = await lockWorkspaceInferenceControl(db, input.workspaceId, "update");
|
|
912
|
+
await lockSessionEventWriteRows(db, {
|
|
913
|
+
workspaceId: input.workspaceId,
|
|
914
|
+
controlLock: "already_locked",
|
|
915
|
+
sessionIds:
|
|
916
|
+
input.actor.type === "agent_attempt"
|
|
917
|
+
? [input.actor.sessionId, input.sessionId]
|
|
918
|
+
: [input.sessionId],
|
|
919
|
+
turnIds:
|
|
920
|
+
input.actor.type === "agent_attempt" ? [input.actor.turnId, input.turnId] : [input.turnId],
|
|
921
|
+
attemptIds: input.actor.type === "agent_attempt" ? [input.actor.attemptId] : [],
|
|
922
|
+
});
|
|
808
923
|
const requestHash = canonicalSessionCommandHash({
|
|
809
924
|
expectedTurnVersion: input.expectedTurnVersion,
|
|
810
925
|
controlEtag: input.controlEtag ?? null,
|
|
@@ -843,7 +958,7 @@ export async function steerQueuedTurnInTransaction(
|
|
|
843
958
|
});
|
|
844
959
|
}
|
|
845
960
|
const before = await evaluateSessionControl(db, input.workspaceId, input.sessionId, {
|
|
846
|
-
|
|
961
|
+
workspaceControl,
|
|
847
962
|
});
|
|
848
963
|
if (input.controlEtag && input.controlEtag !== before.controlEtag) {
|
|
849
964
|
throw new SessionControlConflictError();
|
|
@@ -885,6 +1000,7 @@ export async function steerQueuedTurnInTransaction(
|
|
|
885
1000
|
});
|
|
886
1001
|
const interruptionCount = supersession.interruptionCount;
|
|
887
1002
|
const supersededTurnId = supersession.replacedTurn?.id ?? null;
|
|
1003
|
+
const supersededAttemptId = supersession.replacedTurn?.activeAttemptId ?? null;
|
|
888
1004
|
const liveCurrentTurnId = supersession.liveCurrentTurnId;
|
|
889
1005
|
|
|
890
1006
|
const withoutTarget = rows.filter((row) => row.id !== target.id);
|
|
@@ -899,7 +1015,17 @@ export async function steerQueuedTurnInTransaction(
|
|
|
899
1015
|
const queueVersion = session.queueVersion + 1;
|
|
900
1016
|
await db
|
|
901
1017
|
.update(schema.sessionTurns)
|
|
902
|
-
.set({
|
|
1018
|
+
.set({
|
|
1019
|
+
version: target.version + 1,
|
|
1020
|
+
metadata: {
|
|
1021
|
+
...target.metadata,
|
|
1022
|
+
delivery: "steer",
|
|
1023
|
+
replacedTurnId: supersededTurnId,
|
|
1024
|
+
replacedAttemptId: supersededAttemptId,
|
|
1025
|
+
interruptionCount,
|
|
1026
|
+
},
|
|
1027
|
+
updatedAt: now,
|
|
1028
|
+
})
|
|
903
1029
|
.where(eq(schema.sessionTurns.id, target.id));
|
|
904
1030
|
let sequence = supersession.lastSequence;
|
|
905
1031
|
const actor =
|
|
@@ -998,33 +1124,58 @@ export async function submitHumanPromptInTransaction(
|
|
|
998
1124
|
workspaceId: string;
|
|
999
1125
|
sessionId: string;
|
|
1000
1126
|
subjectId: string;
|
|
1127
|
+
subjectLabel?: string;
|
|
1001
1128
|
actor: SessionCommandActor;
|
|
1002
1129
|
operationKey: string;
|
|
1003
1130
|
delivery: "send" | "steer";
|
|
1004
1131
|
controlEtag?: string | null;
|
|
1005
1132
|
expectedDraftRevision?: number | null;
|
|
1006
1133
|
text: string;
|
|
1134
|
+
turnInstructions?: string | null;
|
|
1007
1135
|
resources: ResourceRef[];
|
|
1008
1136
|
tools: ToolRef[];
|
|
1009
1137
|
model?: string | null;
|
|
1010
1138
|
reasoningEffort?: ReasoningEffort | null;
|
|
1011
1139
|
reasoningEffortFallback: ReasoningEffort;
|
|
1012
1140
|
source: "user" | "api";
|
|
1013
|
-
mcpCredentialUpdates?: Array<{
|
|
1141
|
+
mcpCredentialUpdates?: Array<{
|
|
1142
|
+
id: string;
|
|
1143
|
+
headersEncrypted: Record<string, string>;
|
|
1144
|
+
}>;
|
|
1014
1145
|
},
|
|
1015
1146
|
): Promise<SubmitHumanPromptResult> {
|
|
1016
|
-
await lockWorkspaceInferenceControl(db, input.workspaceId, "update");
|
|
1147
|
+
const workspaceControl = await lockWorkspaceInferenceControl(db, input.workspaceId, "update");
|
|
1148
|
+
await lockSessionEventWriteRows(db, {
|
|
1149
|
+
workspaceId: input.workspaceId,
|
|
1150
|
+
controlLock: "already_locked",
|
|
1151
|
+
sessionIds:
|
|
1152
|
+
input.actor.type === "agent_attempt"
|
|
1153
|
+
? [input.actor.sessionId, input.sessionId]
|
|
1154
|
+
: [input.sessionId],
|
|
1155
|
+
turnIds: input.actor.type === "agent_attempt" ? [input.actor.turnId] : [],
|
|
1156
|
+
attemptIds: input.actor.type === "agent_attempt" ? [input.actor.attemptId] : [],
|
|
1157
|
+
});
|
|
1017
1158
|
const requestHash = canonicalSessionCommandHash({
|
|
1018
1159
|
delivery: input.delivery,
|
|
1019
1160
|
controlEtag: input.controlEtag ?? null,
|
|
1020
1161
|
expectedDraftRevision: input.expectedDraftRevision ?? null,
|
|
1021
1162
|
text: input.text,
|
|
1163
|
+
turnInstructions: input.turnInstructions ?? null,
|
|
1022
1164
|
resources: input.resources,
|
|
1023
1165
|
tools: input.tools,
|
|
1024
1166
|
model: input.model ?? null,
|
|
1025
1167
|
reasoningEffort: input.reasoningEffort ?? null,
|
|
1026
1168
|
source: input.source,
|
|
1027
1169
|
mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
|
|
1170
|
+
...(input.actor.type === "service"
|
|
1171
|
+
? {
|
|
1172
|
+
serviceInitiator: {
|
|
1173
|
+
subjectId: input.actor.subjectId,
|
|
1174
|
+
subjectLabel: input.actor.subjectLabel ?? null,
|
|
1175
|
+
context: input.actor.context ?? {},
|
|
1176
|
+
},
|
|
1177
|
+
}
|
|
1178
|
+
: {}),
|
|
1028
1179
|
});
|
|
1029
1180
|
const reserved = await reserveSessionCommandReceipt(db, {
|
|
1030
1181
|
accountId: input.accountId,
|
|
@@ -1063,7 +1214,7 @@ export async function submitHumanPromptInTransaction(
|
|
|
1063
1214
|
}
|
|
1064
1215
|
|
|
1065
1216
|
const before = await evaluateSessionControl(db, input.workspaceId, input.sessionId, {
|
|
1066
|
-
|
|
1217
|
+
workspaceControl,
|
|
1067
1218
|
});
|
|
1068
1219
|
if (input.controlEtag && input.controlEtag !== before.controlEtag) {
|
|
1069
1220
|
throw new SessionControlConflictError();
|
|
@@ -1076,7 +1227,14 @@ export async function submitHumanPromptInTransaction(
|
|
|
1076
1227
|
input.actor.type === "agent_attempt"
|
|
1077
1228
|
? `attempt:${input.actor.attemptId}`
|
|
1078
1229
|
: input.actor.subjectId,
|
|
1079
|
-
reason:
|
|
1230
|
+
reason:
|
|
1231
|
+
input.actor.type === "service"
|
|
1232
|
+
? input.delivery === "steer"
|
|
1233
|
+
? "service_steer"
|
|
1234
|
+
: "service_send"
|
|
1235
|
+
: input.delivery === "steer"
|
|
1236
|
+
? "human_steer"
|
|
1237
|
+
: "human_send",
|
|
1080
1238
|
observedControlEtag: input.controlEtag ?? null,
|
|
1081
1239
|
});
|
|
1082
1240
|
const session = await lockSession(db, input.workspaceId, input.sessionId);
|
|
@@ -1155,6 +1313,43 @@ export async function submitHumanPromptInTransaction(
|
|
|
1155
1313
|
}
|
|
1156
1314
|
|
|
1157
1315
|
const now = new Date();
|
|
1316
|
+
let frozenInitiator: FrozenTurnInitiator;
|
|
1317
|
+
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);
|
|
1334
|
+
if (!sourceTurn) {
|
|
1335
|
+
throw new SessionControlInvariantError("Edited prompt source turn is missing");
|
|
1336
|
+
}
|
|
1337
|
+
frozenInitiator = {
|
|
1338
|
+
initiator: initiatorFromStorage(
|
|
1339
|
+
sourceTurn.initiatorKind,
|
|
1340
|
+
sourceTurn.initiatorSubjectId,
|
|
1341
|
+
sourceTurn.initiatorContext ?? {},
|
|
1342
|
+
),
|
|
1343
|
+
context: sourceTurn.initiatorContext ?? {},
|
|
1344
|
+
};
|
|
1345
|
+
} else {
|
|
1346
|
+
frozenInitiator = await frozenInitiatorForCommandActor(
|
|
1347
|
+
db,
|
|
1348
|
+
input.workspaceId,
|
|
1349
|
+
input.actor,
|
|
1350
|
+
input.subjectLabel,
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1158
1353
|
const acceptedEventId = crypto.randomUUID();
|
|
1159
1354
|
const turnId = crypto.randomUUID();
|
|
1160
1355
|
const workflowId = session.temporalWorkflowId ?? `session-${session.id}`;
|
|
@@ -1168,14 +1363,15 @@ export async function submitHumanPromptInTransaction(
|
|
|
1168
1363
|
sequence: ++sequence,
|
|
1169
1364
|
type: "user.message",
|
|
1170
1365
|
clientEventId: input.operationKey,
|
|
1171
|
-
payload: {
|
|
1366
|
+
payload: sanitizeEventPayload({
|
|
1172
1367
|
text: input.text,
|
|
1173
1368
|
...(input.resources.length ? { resources: input.resources } : {}),
|
|
1174
1369
|
...(input.tools.length ? { tools: input.tools } : {}),
|
|
1175
1370
|
...(input.model ? { model: input.model } : {}),
|
|
1176
1371
|
...(input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {}),
|
|
1177
1372
|
delivery: input.delivery,
|
|
1178
|
-
|
|
1373
|
+
initiator: frozenInitiator.initiator,
|
|
1374
|
+
}),
|
|
1179
1375
|
occurredAt: now,
|
|
1180
1376
|
},
|
|
1181
1377
|
];
|
|
@@ -1193,6 +1389,7 @@ export async function submitHumanPromptInTransaction(
|
|
|
1193
1389
|
source: input.source,
|
|
1194
1390
|
position: input.delivery === "steer" ? 0 : existingQueued.length + 1,
|
|
1195
1391
|
prompt: input.text,
|
|
1392
|
+
turnInstructions: input.turnInstructions ?? null,
|
|
1196
1393
|
resources: input.resources,
|
|
1197
1394
|
tools: input.tools,
|
|
1198
1395
|
model: input.model ?? session.model,
|
|
@@ -1200,6 +1397,7 @@ export async function submitHumanPromptInTransaction(
|
|
|
1200
1397
|
sandboxBackend: session.sandboxBackend,
|
|
1201
1398
|
metadata: {},
|
|
1202
1399
|
lineage: { actor: input.actor.type },
|
|
1400
|
+
...initiatorColumns(frozenInitiator),
|
|
1203
1401
|
})
|
|
1204
1402
|
.returning();
|
|
1205
1403
|
if (!turn) throw new SessionControlInvariantError("Prompt turn was not inserted");
|
|
@@ -1210,7 +1408,12 @@ export async function submitHumanPromptInTransaction(
|
|
|
1210
1408
|
sequence: ++sequence,
|
|
1211
1409
|
type: "turn.queued",
|
|
1212
1410
|
turnId,
|
|
1213
|
-
payload: {
|
|
1411
|
+
payload: {
|
|
1412
|
+
turnId,
|
|
1413
|
+
triggerEventId: acceptedEventId,
|
|
1414
|
+
source: input.source,
|
|
1415
|
+
initiator: frozenInitiator.initiator,
|
|
1416
|
+
},
|
|
1214
1417
|
occurredAt: now,
|
|
1215
1418
|
});
|
|
1216
1419
|
|
|
@@ -1238,7 +1441,35 @@ export async function submitHumanPromptInTransaction(
|
|
|
1238
1441
|
for (const event of eventValues) event.sequence = ++sequence;
|
|
1239
1442
|
const interruptionCount = supersession.interruptionCount;
|
|
1240
1443
|
const replacedTurnId = supersession.replacedTurn?.id ?? null;
|
|
1444
|
+
const replacedAttemptId = supersession.replacedTurn?.activeAttemptId ?? null;
|
|
1241
1445
|
const liveCurrentTurnId = supersession.liveCurrentTurnId;
|
|
1446
|
+
// Durable provenance for the queue projection. Until the superseded attempt
|
|
1447
|
+
// loses inference, user-visible output, and workspace-persistence authority,
|
|
1448
|
+
// its exact first-class attempt has no quiescence receipt. Pairing that
|
|
1449
|
+
// attempt with the replacement lets every client render "Stopping previous
|
|
1450
|
+
// attempt…" truthfully across refresh/reconnect, even though logical
|
|
1451
|
+
// settlement has already cleared sessions.active_turn_id. Do not infer this
|
|
1452
|
+
// state from a local request spinner or queue position.
|
|
1453
|
+
if (input.delivery === "steer") {
|
|
1454
|
+
await db
|
|
1455
|
+
.update(schema.sessionTurns)
|
|
1456
|
+
.set({
|
|
1457
|
+
metadata: {
|
|
1458
|
+
delivery: "steer",
|
|
1459
|
+
replacedTurnId,
|
|
1460
|
+
replacedAttemptId,
|
|
1461
|
+
interruptionCount,
|
|
1462
|
+
},
|
|
1463
|
+
updatedAt: now,
|
|
1464
|
+
})
|
|
1465
|
+
.where(
|
|
1466
|
+
and(
|
|
1467
|
+
eq(schema.sessionTurns.workspaceId, input.workspaceId),
|
|
1468
|
+
eq(schema.sessionTurns.sessionId, input.sessionId),
|
|
1469
|
+
eq(schema.sessionTurns.id, turnId),
|
|
1470
|
+
),
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1242
1473
|
if (supersession.replacedTurn) {
|
|
1243
1474
|
const current = supersession.replacedTurn;
|
|
1244
1475
|
if (!liveCurrentTurnId) {
|
|
@@ -1331,7 +1562,11 @@ export async function submitHumanPromptInTransaction(
|
|
|
1331
1562
|
action: input.delivery === "steer" ? "session.prompt.steer" : "session.prompt.send",
|
|
1332
1563
|
targetType: "session_turn",
|
|
1333
1564
|
targetId: turnId,
|
|
1334
|
-
metadata: {
|
|
1565
|
+
metadata: {
|
|
1566
|
+
operationId: reserved.receipt.id,
|
|
1567
|
+
replacedTurnId,
|
|
1568
|
+
interruptionCount,
|
|
1569
|
+
},
|
|
1335
1570
|
});
|
|
1336
1571
|
const eventIds = eventRows.map((event) => event.id);
|
|
1337
1572
|
const receipt = await updateSessionCommandReceiptResult(db, reserved.receipt.id, {
|
|
@@ -1373,7 +1608,14 @@ export async function sendAgentMessageInTransaction(
|
|
|
1373
1608
|
text: string;
|
|
1374
1609
|
},
|
|
1375
1610
|
): Promise<AgentInternalUpdateCommandResult> {
|
|
1376
|
-
await lockWorkspaceInferenceControl(db, input.workspaceId, "share");
|
|
1611
|
+
const workspaceControl = await lockWorkspaceInferenceControl(db, input.workspaceId, "share");
|
|
1612
|
+
await lockSessionEventWriteRows(db, {
|
|
1613
|
+
workspaceId: input.workspaceId,
|
|
1614
|
+
controlLock: "already_locked",
|
|
1615
|
+
sessionIds: [input.actor.sessionId, input.targetSessionId],
|
|
1616
|
+
turnIds: [input.actor.turnId],
|
|
1617
|
+
attemptIds: [input.actor.attemptId],
|
|
1618
|
+
});
|
|
1377
1619
|
const requestHash = canonicalSessionCommandHash({ text: input.text });
|
|
1378
1620
|
const reserved = await reserveSessionCommandReceipt(db, {
|
|
1379
1621
|
accountId: input.accountId,
|
|
@@ -1424,7 +1666,7 @@ export async function sendAgentMessageInTransaction(
|
|
|
1424
1666
|
);
|
|
1425
1667
|
}
|
|
1426
1668
|
const effective = await evaluateSessionControl(db, input.workspaceId, input.targetSessionId, {
|
|
1427
|
-
|
|
1669
|
+
workspaceControl,
|
|
1428
1670
|
});
|
|
1429
1671
|
const now = new Date();
|
|
1430
1672
|
const [update] = await db
|
|
@@ -1538,6 +1780,13 @@ export async function steerAgentSessionInTransaction(
|
|
|
1538
1780
|
},
|
|
1539
1781
|
): Promise<AgentInternalUpdateCommandResult> {
|
|
1540
1782
|
await lockWorkspaceInferenceControl(db, input.workspaceId, "update");
|
|
1783
|
+
await lockSessionEventWriteRows(db, {
|
|
1784
|
+
workspaceId: input.workspaceId,
|
|
1785
|
+
controlLock: "already_locked",
|
|
1786
|
+
sessionIds: [input.actor.sessionId, input.targetSessionId],
|
|
1787
|
+
turnIds: [input.actor.turnId],
|
|
1788
|
+
attemptIds: [input.actor.attemptId],
|
|
1789
|
+
});
|
|
1541
1790
|
const reserved = await reserveSessionCommandReceipt(db, {
|
|
1542
1791
|
accountId: input.accountId,
|
|
1543
1792
|
workspaceId: input.workspaceId,
|
|
@@ -1546,7 +1795,9 @@ export async function steerAgentSessionInTransaction(
|
|
|
1546
1795
|
targetSessionId: input.targetSessionId,
|
|
1547
1796
|
targetTurnId: null,
|
|
1548
1797
|
operationKey: input.operationKey,
|
|
1549
|
-
canonicalRequestHash: canonicalSessionCommandHash({
|
|
1798
|
+
canonicalRequestHash: canonicalSessionCommandHash({
|
|
1799
|
+
instruction: input.instruction,
|
|
1800
|
+
}),
|
|
1550
1801
|
});
|
|
1551
1802
|
if (reserved.replay) {
|
|
1552
1803
|
const updateId = String(reserved.receipt.result.updateId ?? "");
|