@opengeni/db 4.0.0-canary.0 → 4.0.0-canary.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/canonical-human-identities.js +3 -3
  2. package/dist/{chunk-2XEZK4LF.js → chunk-7YZP2CSL.js} +23 -1
  3. package/dist/chunk-7YZP2CSL.js.map +1 -0
  4. package/dist/{chunk-P6NSEG2S.js → chunk-B5Y3TCSP.js} +2 -2
  5. package/dist/{chunk-CJROYHSC.js → chunk-CQF6G4IF.js} +3 -3
  6. package/dist/chunk-DUB3LUVM.js +4145 -0
  7. package/dist/chunk-DUB3LUVM.js.map +1 -0
  8. package/dist/{chunk-3RI33YQN.js → chunk-FRIYCNK2.js} +3 -1
  9. package/dist/{chunk-3RI33YQN.js.map → chunk-FRIYCNK2.js.map} +1 -1
  10. package/dist/{chunk-RZNOSEMC.js → chunk-L2QFSPFP.js} +2 -2
  11. package/dist/{chunk-NLFHMPUB.js → chunk-NPKBYGAK.js} +2 -2
  12. package/dist/{chunk-QM6YOWHZ.js → chunk-PWJJKCTF.js} +85 -4
  13. package/dist/chunk-PWJJKCTF.js.map +1 -0
  14. package/dist/{chunk-MX6V5F65.js → chunk-VU2IK37Y.js} +2 -2
  15. package/dist/{chunk-UFAMOUWL.js → chunk-XIYAXH47.js} +2 -2
  16. package/dist/editable-artifact-durable-export.js +2 -2
  17. package/dist/editable-artifacts.js +3 -3
  18. package/dist/index.d.ts +113 -7
  19. package/dist/index.js +9392 -11506
  20. package/dist/index.js.map +1 -1
  21. package/dist/managed-auth-session-sets.js +3 -3
  22. package/dist/migrate.d.ts +6 -0
  23. package/dist/migrate.js +3 -1
  24. package/dist/migration-runner-settings.d.ts +10 -0
  25. package/dist/new-session-drafts.d.ts +2 -0
  26. package/dist/provision-roles.js +1 -1
  27. package/dist/runtime-posture.d.ts +2 -2
  28. package/dist/schema.d.ts +270 -9
  29. package/dist/schema.js +3 -1
  30. package/dist/session-background-commands.d.ts +83 -0
  31. package/dist/session-background-commands.js +18 -3
  32. package/dist/session-command-output.d.ts +17 -0
  33. package/dist/session-command-output.js +130 -0
  34. package/dist/session-command-output.js.map +1 -0
  35. package/dist/session-event-slices.d.ts +28 -0
  36. package/dist/session-event-slices.js +225 -0
  37. package/dist/session-event-slices.js.map +1 -0
  38. package/dist/session-mcp-progress.d.ts +12 -0
  39. package/dist/session-tenancy.js +3 -3
  40. package/dist/site-publication-request.d.ts +18 -0
  41. package/dist/video-generation.js +3 -3
  42. package/dist/workspace-artifacts.d.ts +34 -4
  43. package/dist/xai-subscription.d.ts +2 -0
  44. package/drizzle/0407_connected_command_tracking_retirement.sql +245 -0
  45. package/drizzle/0408_scheduled_session_target_index.sql +4 -0
  46. package/drizzle/0409_new_session_draft_project_provenance.sql +321 -0
  47. package/drizzle/0410_new_session_draft_project_provenance_index.sql +5 -0
  48. package/drizzle/0411_new_session_draft_project_provenance_backfill.sql +53 -0
  49. package/drizzle/0412_new_session_draft_project_provenance_validation.sql +30 -0
  50. package/drizzle/0413_session_filter_activity.sql +96 -0
  51. package/drizzle/0414_scheduled_generated_producer_materialization.sql +287 -0
  52. package/drizzle/0415_command_completion_observation.sql +9 -0
  53. package/drizzle/0416_scheduled_inherited_tool_admission.sql +27 -0
  54. package/drizzle/0417_command_runner_failure_metadata.sql +17 -0
  55. package/drizzle/0418_site_direct_uploads.sql +151 -0
  56. package/drizzle/0419_background_command_launch_authority.sql +56 -0
  57. package/package.json +14 -6
  58. package/src/index.ts +1691 -217
  59. package/src/migrate.ts +10 -2
  60. package/src/migration-runner-settings.ts +21 -0
  61. package/src/new-session-drafts.ts +105 -1
  62. package/src/runtime-posture.ts +2 -0
  63. package/src/schema.ts +97 -3
  64. package/src/session-background-commands.ts +320 -4
  65. package/src/session-command-output.ts +158 -0
  66. package/src/session-control.ts +53 -21
  67. package/src/session-event-slices.ts +310 -0
  68. package/src/session-mcp-progress.ts +67 -0
  69. package/src/site-publication-request.ts +32 -0
  70. package/src/workspace-artifacts.ts +95 -96
  71. package/src/xai-subscription.ts +8 -0
  72. package/dist/chunk-2XEZK4LF.js.map +0 -1
  73. package/dist/chunk-N5VXJ4I2.js +0 -589
  74. package/dist/chunk-N5VXJ4I2.js.map +0 -1
  75. package/dist/chunk-QM6YOWHZ.js.map +0 -1
  76. /package/dist/{chunk-P6NSEG2S.js.map → chunk-B5Y3TCSP.js.map} +0 -0
  77. /package/dist/{chunk-CJROYHSC.js.map → chunk-CQF6G4IF.js.map} +0 -0
  78. /package/dist/{chunk-RZNOSEMC.js.map → chunk-L2QFSPFP.js.map} +0 -0
  79. /package/dist/{chunk-NLFHMPUB.js.map → chunk-NPKBYGAK.js.map} +0 -0
  80. /package/dist/{chunk-MX6V5F65.js.map → chunk-VU2IK37Y.js.map} +0 -0
  81. /package/dist/{chunk-UFAMOUWL.js.map → chunk-XIYAXH47.js.map} +0 -0
@@ -0,0 +1,4145 @@
1
+ import {
2
+ cancelUnacceptedVideoGenerationsForToolCallsInTransaction
3
+ } from "./chunk-CQF6G4IF.js";
4
+ import {
5
+ nestedPostgresSqlState,
6
+ withRlsContext,
7
+ withSessionActivityRlsContext
8
+ } from "./chunk-XIYAXH47.js";
9
+ import {
10
+ LOSSLESS_CONTENT_CODEC_VERSION,
11
+ auditEvents,
12
+ codexCapacityWaiters,
13
+ fromPostgresLosslessJson,
14
+ sandboxRetainedProcesses,
15
+ sessionAttemptInterruptions,
16
+ sessionBackgroundCommands,
17
+ sessionCommandReceipts,
18
+ sessionEventCursors,
19
+ sessionEvents,
20
+ sessionHistoryItems,
21
+ sessionHumanInputRequests,
22
+ sessionPendingToolCalls,
23
+ sessionRealtimeEntries,
24
+ sessionRealtimeModes,
25
+ sessionSystemUpdateOutbox,
26
+ sessionSystemUpdates,
27
+ sessionTurnAttempts,
28
+ sessionTurns,
29
+ sessionWorkflowWakeOutbox,
30
+ sessions,
31
+ withLosslessContentWriteVersion,
32
+ workspaceControlEvents,
33
+ workspaceInferenceControls,
34
+ workspaces
35
+ } from "./chunk-PWJJKCTF.js";
36
+
37
+ // src/session-background-commands.ts
38
+ import { SessionCommandFailure } from "@opengeni/contracts";
39
+ import { isDeepStrictEqual } from "util";
40
+ import { and as and4, asc as asc2, desc as desc2, eq as eq4, gte, inArray as inArray3, sql as sql4 } from "drizzle-orm";
41
+
42
+ // src/session-control.ts
43
+ import { createHash as createHash2 } from "crypto";
44
+ import {
45
+ boundWorkspaceControlEvent,
46
+ childPausedClassification,
47
+ McpPersonalConnectionDelegations,
48
+ workspaceControlUtf8Bytes
49
+ } from "@opengeni/contracts";
50
+ import { and as and3, eq as eq3, inArray as inArray2, sql as sql3 } from "drizzle-orm";
51
+
52
+ // src/child-lifecycle-notices.ts
53
+ import {
54
+ CHILD_PAUSED_REASON_MAX_BYTES,
55
+ CHILD_PROGRESS_NOTE_MAX_BYTES,
56
+ CHILD_REQUIRES_ACTION_MAX_REQUESTS,
57
+ CHILD_REQUIRES_ACTION_PAYLOAD_MAX_BYTES,
58
+ CHILD_REQUIRES_ACTION_QUESTION_PREVIEW_MAX_BYTES,
59
+ approvalIdentifier
60
+ } from "@opengeni/contracts";
61
+ var childLifecycleNoticesEnabledFlag = false;
62
+ function configureChildLifecycleNotices(input) {
63
+ childLifecycleNoticesEnabledFlag = input.enabled === true;
64
+ }
65
+ function childLifecycleNoticesEnabled() {
66
+ return childLifecycleNoticesEnabledFlag;
67
+ }
68
+ function boundedChildNoticeText(value, maxBytes) {
69
+ const encoder = new TextEncoder();
70
+ const bytes = encoder.encode(value);
71
+ if (bytes.byteLength <= maxBytes) return { text: value, truncated: false };
72
+ const marker = "\u2026";
73
+ const budget = Math.max(0, maxBytes - encoder.encode(marker).byteLength);
74
+ let end = Math.min(budget, bytes.byteLength);
75
+ while (end > 0 && end < bytes.byteLength && (bytes[end] & 192) === 128) end -= 1;
76
+ return { text: `${new TextDecoder().decode(bytes.subarray(0, end))}${marker}`, truncated: true };
77
+ }
78
+ var CHILD_LIFECYCLE_DEDUPE_PREFIX = {
79
+ requiresAction: "child-requires-action",
80
+ requiresActionResolved: "child-requires-action-resolved",
81
+ paused: "child-paused",
82
+ waitingCapacity: "child-waiting-capacity",
83
+ progress: "child-progress"
84
+ };
85
+ function childRequiresActionDedupeKey(input) {
86
+ return `${CHILD_LIFECYCLE_DEDUPE_PREFIX.requiresAction}:${input.childSessionId}:${input.turnId}:${input.turnGeneration}`;
87
+ }
88
+ function childRequiresActionResolvedDedupeKey(input) {
89
+ return `${CHILD_LIFECYCLE_DEDUPE_PREFIX.requiresActionResolved}:${input.childSessionId}:${input.turnId}:${input.turnGeneration}:${input.requestId ?? input.approvalId ?? "unknown"}`;
90
+ }
91
+ function childPausedDedupeKey(input) {
92
+ return `${CHILD_LIFECYCLE_DEDUPE_PREFIX.paused}:${input.childSessionId}:${input.receiptId}`;
93
+ }
94
+ function childWaitingCapacityDedupeKey(input) {
95
+ return `${CHILD_LIFECYCLE_DEDUPE_PREFIX.waitingCapacity}:${input.childSessionId}:${input.waiterId}`;
96
+ }
97
+ function childProgressDedupeKey(input) {
98
+ return `${CHILD_LIFECYCLE_DEDUPE_PREFIX.progress}:${input.childSessionId}:${input.receiptId}`;
99
+ }
100
+ function approvalToolName(value) {
101
+ if (!value || typeof value !== "object") return null;
102
+ const approval = value;
103
+ const rawItem = approval.rawItem && typeof approval.rawItem === "object" ? approval.rawItem : null;
104
+ const candidate = rawItem?.name ?? approval.name;
105
+ return typeof candidate === "string" && candidate.length > 0 ? boundedChildNoticeText(candidate, 128).text : null;
106
+ }
107
+ function buildChildRequiresActionPayload(input) {
108
+ const requests = [];
109
+ for (const request of input.humanInputRequests) {
110
+ const first = request.questions[0];
111
+ requests.push({
112
+ kind: "human_input",
113
+ requestId: request.id,
114
+ questionCount: request.questions.length,
115
+ firstQuestion: boundedChildNoticeText(
116
+ typeof first?.prompt === "string" ? first.prompt : "",
117
+ CHILD_REQUIRES_ACTION_QUESTION_PREVIEW_MAX_BYTES
118
+ ).text,
119
+ allowSkip: request.allowSkip,
120
+ expiresAt: request.expiresAt ? request.expiresAt.toISOString() : null
121
+ });
122
+ }
123
+ const seenApprovals = /* @__PURE__ */ new Set();
124
+ for (const pending of input.pendingApprovals) {
125
+ const approvalId = approvalIdentifier(pending);
126
+ if (!approvalId || seenApprovals.has(approvalId)) continue;
127
+ seenApprovals.add(approvalId);
128
+ requests.push({
129
+ kind: "approval",
130
+ approvalId: boundedChildNoticeText(approvalId, 256).text,
131
+ toolName: approvalToolName(pending)
132
+ });
133
+ }
134
+ let truncated = requests.length > CHILD_REQUIRES_ACTION_MAX_REQUESTS;
135
+ let bounded = requests.slice(0, CHILD_REQUIRES_ACTION_MAX_REQUESTS);
136
+ const build = () => ({
137
+ type: "child_requires_action",
138
+ childSessionId: input.childSessionId,
139
+ childTurnId: input.childTurnId,
140
+ childTurnGeneration: input.childTurnGeneration,
141
+ requests: bounded,
142
+ truncated
143
+ });
144
+ let payload = build();
145
+ while (bounded.length > 0 && Buffer.byteLength(JSON.stringify(payload)) > CHILD_REQUIRES_ACTION_PAYLOAD_MAX_BYTES) {
146
+ bounded = bounded.slice(0, -1);
147
+ truncated = true;
148
+ payload = build();
149
+ }
150
+ return payload;
151
+ }
152
+ function childRequiresActionSummary(childSessionId, payload) {
153
+ const humanInputs = payload.requests.filter((request) => request.kind === "human_input");
154
+ const approvals = payload.requests.filter((request) => request.kind === "approval");
155
+ const first = humanInputs[0];
156
+ const lines = [
157
+ `Worker ${childSessionId} is blocked and needs input (turn ${payload.childTurnId}).`
158
+ ];
159
+ if (first && first.kind === "human_input") {
160
+ lines.push(
161
+ `It asked: ${first.firstQuestion}${first.questionCount > 1 ? ` (+${first.questionCount - 1} more question${first.questionCount > 2 ? "s" : ""})` : ""}${humanInputs.length > 1 ? ` [${humanInputs.length} open requests]` : ""}.`
162
+ );
163
+ }
164
+ if (approvals.length > 0) {
165
+ const names = approvals.map((request) => request.kind === "approval" ? request.toolName : null).filter((name) => name !== null);
166
+ lines.push(
167
+ `${approvals.length} tool approval${approvals.length > 1 ? "s are" : " is"} waiting for a human${names.length > 0 ? ` (${names.join(", ")})` : ""}.`
168
+ );
169
+ }
170
+ if (payload.truncated) lines.push("The request list was truncated.");
171
+ return lines.join(" ");
172
+ }
173
+ function childRequiresActionResolvedSummary(childSessionId, payload) {
174
+ const subject = payload.requestId ? "input request" : "approval";
175
+ return `Worker ${childSessionId}: its ${subject} was ${payload.outcome} (by ${payload.respondedByKind.replace("_", " ")}); the worker is no longer blocked on it.`;
176
+ }
177
+ function childPausedSummary(childSessionId, payload) {
178
+ const base = `Worker ${childSessionId} was paused by ${payload.actorKind === "agent" ? "an agent" : payload.actorKind === "api" ? "the API" : "a human"}.`;
179
+ return payload.reason ? `${base} Reason: ${payload.reason}` : base;
180
+ }
181
+ function childWaitingCapacitySummary(childSessionId, payload) {
182
+ return `Worker ${childSessionId} is waiting for ${payload.provider === "codex" ? "Codex" : "xAI"} provider capacity${payload.nextCheckAt ? ` (next check ${payload.nextCheckAt})` : ""}; its turn resumes automatically.`;
183
+ }
184
+ function childProgressSummary(childSessionId, payload) {
185
+ return `Worker ${childSessionId} progress: ${payload.progressNote}`;
186
+ }
187
+ function boundedChildPausedReason(reason) {
188
+ if (reason === null || reason === void 0 || reason.length === 0) return null;
189
+ return boundedChildNoticeText(reason, CHILD_PAUSED_REASON_MAX_BYTES).text;
190
+ }
191
+ function boundedChildProgressNote(note) {
192
+ return boundedChildNoticeText(note, CHILD_PROGRESS_NOTE_MAX_BYTES).text;
193
+ }
194
+
195
+ // src/session-tool-call-settlement.ts
196
+ import { canonicalizePersistedHistoryItem, omitOutputOnlyHistoryItemFields } from "@opengeni/codex";
197
+ import { and, asc, eq, inArray, sql } from "drizzle-orm";
198
+ var TOOL_RESULT_TYPE_BY_CALL_TYPE = {
199
+ function_call: "function_call_result",
200
+ computer_call: "computer_call_result",
201
+ shell_call: "shell_call_output",
202
+ apply_patch_call: "apply_patch_call_output",
203
+ tool_search_call: "tool_search_output"
204
+ };
205
+ function historyCallId(item) {
206
+ const providerData = item.providerData && typeof item.providerData === "object" && !Array.isArray(item.providerData) ? item.providerData : null;
207
+ const value = [
208
+ item.callId,
209
+ item.call_id,
210
+ providerData?.callId,
211
+ providerData?.call_id,
212
+ item.id
213
+ ].find((candidate) => typeof candidate === "string" && candidate.length > 0);
214
+ return value ?? null;
215
+ }
216
+ function historyItemType(item) {
217
+ return typeof item.type === "string" ? item.type : null;
218
+ }
219
+ function interruptedToolCallResult(input) {
220
+ const message = `Tool execution was interrupted by ${input.reason} before its result was durably recorded. The side-effect outcome is unknown; inspect actual state before repeating the call.`;
221
+ if (input.callType === "function_call") {
222
+ const name = typeof input.callItem.name === "string" ? input.callItem.name : "tool";
223
+ return {
224
+ type: "function_call_result",
225
+ name,
226
+ ...typeof input.callItem.namespace === "string" ? { namespace: input.callItem.namespace } : {},
227
+ callId: input.callId,
228
+ status: "incomplete",
229
+ output: { type: "text", text: message }
230
+ };
231
+ }
232
+ if (input.callType === "shell_call") {
233
+ return {
234
+ type: "shell_call_output",
235
+ callId: input.callId,
236
+ output: [{ stdout: "", stderr: message, outcome: { type: "exit", exitCode: null } }]
237
+ };
238
+ }
239
+ if (input.callType === "apply_patch_call") {
240
+ return {
241
+ type: "apply_patch_call_output",
242
+ callId: input.callId,
243
+ status: "failed",
244
+ output: message
245
+ };
246
+ }
247
+ if (input.callType === "computer_call") return null;
248
+ if (input.callType === "tool_search_call") {
249
+ const providerData = input.callItem.providerData && typeof input.callItem.providerData === "object" && !Array.isArray(input.callItem.providerData) ? input.callItem.providerData : null;
250
+ const execution = input.callItem.execution === "client" || input.callItem.execution === "server" ? input.callItem.execution : providerData?.execution === "client" || providerData?.execution === "server" ? providerData.execution : null;
251
+ const providerIdentity = typeof providerData?.call_id === "string" || typeof providerData?.callId === "string";
252
+ const snakeId = typeof input.callItem.call_id === "string";
253
+ return {
254
+ type: "tool_search_output",
255
+ ...providerIdentity ? {
256
+ providerData: {
257
+ call_id: input.callId,
258
+ ...execution ? { execution } : {}
259
+ }
260
+ } : snakeId ? { call_id: input.callId, ...execution ? { execution } : {} } : { callId: input.callId, ...execution ? { execution } : {} },
261
+ status: "incomplete",
262
+ tools: []
263
+ };
264
+ }
265
+ return null;
266
+ }
267
+ function mapEvent(row) {
268
+ return {
269
+ id: row.id,
270
+ workspaceId: row.workspaceId,
271
+ sessionId: row.sessionId,
272
+ sequence: row.sequence,
273
+ type: row.type,
274
+ payload: fromPostgresLosslessJson(row.payload, row.payloadCodecVersion),
275
+ occurredAt: row.occurredAt.toISOString(),
276
+ clientEventId: row.clientEventId,
277
+ turnId: row.turnId,
278
+ turnGeneration: row.turnGeneration,
279
+ turnAttemptId: row.turnAttemptId,
280
+ turnAssociation: row.turnAssociation,
281
+ duplicateOfEventId: row.duplicateOfEventId,
282
+ duplicateReason: row.duplicateReason
283
+ };
284
+ }
285
+ async function closePendingSessionToolCallsInTransaction(tx, input) {
286
+ const pendingRows = await tx.select().from(sessionPendingToolCalls).where(
287
+ and(
288
+ eq(sessionPendingToolCalls.workspaceId, input.workspaceId),
289
+ eq(sessionPendingToolCalls.sessionId, input.sessionId),
290
+ eq(sessionPendingToolCalls.turnId, input.turnId)
291
+ )
292
+ ).orderBy(asc(sessionPendingToolCalls.createdAt), asc(sessionPendingToolCalls.id)).for("update");
293
+ const pending = pendingRows.filter((row) => !(input.preserveInterruptionRows && row.interruptionKind !== null)).map((row) => ({
294
+ ...row,
295
+ callItem: fromPostgresLosslessJson(row.callItem, row.callItemCodecVersion),
296
+ resultItem: row.resultItem === null ? null : fromPostgresLosslessJson(row.resultItem, row.resultItemCodecVersion),
297
+ eventOutput: row.eventOutput === null ? null : fromPostgresLosslessJson(row.eventOutput, row.eventOutputCodecVersion)
298
+ }));
299
+ if (pending.length === 0) return { sequence: input.sequence, events: [], closed: 0 };
300
+ const history = await tx.select({
301
+ position: sessionHistoryItems.position,
302
+ item: sessionHistoryItems.item,
303
+ itemCodecVersion: sessionHistoryItems.itemCodecVersion,
304
+ active: sessionHistoryItems.active
305
+ }).from(sessionHistoryItems).where(
306
+ and(
307
+ eq(sessionHistoryItems.workspaceId, input.workspaceId),
308
+ eq(sessionHistoryItems.sessionId, input.sessionId),
309
+ eq(sessionHistoryItems.turnId, input.turnId)
310
+ )
311
+ ).orderBy(asc(sessionHistoryItems.position));
312
+ const decodedHistory = history.map((row) => ({
313
+ ...row,
314
+ item: fromPostgresLosslessJson(row.item, row.itemCodecVersion)
315
+ }));
316
+ const [{ maxPosition } = { maxPosition: -1 }] = await tx.select({ maxPosition: sql`coalesce(max(${sessionHistoryItems.position}), -1)` }).from(sessionHistoryItems).where(
317
+ and(
318
+ eq(sessionHistoryItems.workspaceId, input.workspaceId),
319
+ eq(sessionHistoryItems.sessionId, input.sessionId)
320
+ )
321
+ );
322
+ const existingOutputEvents = await tx.select({ callId: sql`${sessionEvents.payload} ->> 'id'` }).from(sessionEvents).where(
323
+ and(
324
+ eq(sessionEvents.workspaceId, input.workspaceId),
325
+ eq(sessionEvents.sessionId, input.sessionId),
326
+ eq(sessionEvents.turnId, input.turnId),
327
+ eq(sessionEvents.type, "agent.toolCall.output")
328
+ )
329
+ );
330
+ const projectedCallIds = new Set(
331
+ existingOutputEvents.flatMap(({ callId }) => callId ? [callId] : [])
332
+ );
333
+ let nextPosition = Math.floor(Number(maxPosition)) + 1;
334
+ let sequence = input.sequence;
335
+ const historyValues = [];
336
+ const eventValues = [];
337
+ const resolutions = pending.map((call) => {
338
+ const resultType = TOOL_RESULT_TYPE_BY_CALL_TYPE[call.callType];
339
+ const existingCall = decodedHistory.find(
340
+ ({ item }) => historyItemType(item) === call.callType && historyCallId(item) === call.callId
341
+ );
342
+ const existingResult = resultType ? decodedHistory.find(
343
+ ({ item, position }) => position > (existingCall?.position ?? Number.MAX_SAFE_INTEGER) && historyItemType(item) === resultType && historyCallId(item) === call.callId
344
+ ) : void 0;
345
+ const activeCall = decodedHistory.find(
346
+ ({ item, active }) => active && historyItemType(item) === call.callType && historyCallId(item) === call.callId
347
+ );
348
+ const activeResult = resultType ? decodedHistory.find(
349
+ ({ item, active, position }) => active && position > (activeCall?.position ?? Number.MAX_SAFE_INTEGER) && historyItemType(item) === resultType && historyCallId(item) === call.callId
350
+ ) : void 0;
351
+ const interruptedResult = interruptedToolCallResult({
352
+ callType: call.callType,
353
+ callId: call.callId,
354
+ callItem: call.callItem,
355
+ reason: input.reason
356
+ });
357
+ return {
358
+ call,
359
+ existingCall,
360
+ existingResult,
361
+ activeCall,
362
+ activeResult,
363
+ completeDurablePair: Boolean(existingCall && existingResult),
364
+ supersededDurablePair: Boolean(
365
+ existingCall && existingResult && (!existingCall.active || !existingResult.active)
366
+ ),
367
+ rawCallIsValid: historyItemType(call.callItem) === call.callType,
368
+ result: existingResult?.item ?? call.resultItem ?? interruptedResult,
369
+ interrupted: !existingResult && !call.resultItem
370
+ };
371
+ });
372
+ for (const resolution of resolutions) {
373
+ if (!resolution.completeDurablePair && !resolution.activeCall && resolution.result && resolution.rawCallIsValid) {
374
+ historyValues.push({
375
+ accountId: input.accountId,
376
+ workspaceId: input.workspaceId,
377
+ sessionId: input.sessionId,
378
+ turnId: input.turnId,
379
+ position: nextPosition++,
380
+ item: omitOutputOnlyHistoryItemFields(resolution.call.callItem),
381
+ itemCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
382
+ active: true
383
+ });
384
+ }
385
+ }
386
+ const orderedResults = [...resolutions].sort(
387
+ (left, right) => (left.call.resultRecordedAt?.getTime() ?? Number.MAX_SAFE_INTEGER) - (right.call.resultRecordedAt?.getTime() ?? Number.MAX_SAFE_INTEGER)
388
+ );
389
+ for (const resolution of orderedResults) {
390
+ if (!resolution.completeDurablePair && !resolution.activeResult && resolution.result && resolution.rawCallIsValid) {
391
+ historyValues.push({
392
+ accountId: input.accountId,
393
+ workspaceId: input.workspaceId,
394
+ sessionId: input.sessionId,
395
+ turnId: input.turnId,
396
+ position: nextPosition++,
397
+ item: canonicalizePersistedHistoryItem(
398
+ resolution.result,
399
+ resolution.call.modelToolOutputTruncationTokens ?? void 0
400
+ ),
401
+ itemCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
402
+ active: true
403
+ });
404
+ }
405
+ if (resolution.supersededDurablePair || projectedCallIds.has(resolution.call.callId)) continue;
406
+ eventValues.push({
407
+ accountId: input.accountId,
408
+ workspaceId: input.workspaceId,
409
+ sessionId: input.sessionId,
410
+ sequence: ++sequence,
411
+ type: "agent.toolCall.output",
412
+ turnId: input.turnId,
413
+ turnGeneration: resolution.call.executionGeneration,
414
+ turnAttemptId: resolution.call.attemptId,
415
+ turnAssociation: input.turnAssociation === void 0 ? "current" : input.turnAssociation,
416
+ payload: {
417
+ id: resolution.call.callId,
418
+ output: resolution.interrupted ? {
419
+ isError: true,
420
+ content: [
421
+ {
422
+ type: "text",
423
+ text: `Tool execution was interrupted by ${input.reason}; its side-effect outcome is unknown.`
424
+ }
425
+ ]
426
+ } : resolution.call.eventOutput !== null ? resolution.call.eventOutput.value : (resolution.existingResult?.item ?? resolution.call.resultItem)?.output ?? resolution.existingResult?.item ?? resolution.call.resultItem,
427
+ recovery: {
428
+ interrupted: resolution.interrupted,
429
+ outcome: resolution.interrupted ? "unknown" : "durable_result_found",
430
+ reason: input.reason,
431
+ unsupportedCallShape: resolution.interrupted && (!resolution.result || !resolution.rawCallIsValid)
432
+ }
433
+ },
434
+ payloadCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
435
+ occurredAt: input.now
436
+ });
437
+ }
438
+ if (historyValues.length > 0) await tx.insert(sessionHistoryItems).values(historyValues);
439
+ const inserted = eventValues.length > 0 ? await tx.insert(sessionEvents).values(eventValues).returning() : [];
440
+ await cancelUnacceptedVideoGenerationsForToolCallsInTransaction(tx, {
441
+ workspaceId: input.workspaceId,
442
+ sessionId: input.sessionId,
443
+ turnId: input.turnId,
444
+ toolCallIds: pending.map((call) => call.callId),
445
+ reason: input.reason,
446
+ now: input.now
447
+ });
448
+ await tx.delete(sessionPendingToolCalls).where(
449
+ and(
450
+ eq(sessionPendingToolCalls.workspaceId, input.workspaceId),
451
+ eq(sessionPendingToolCalls.sessionId, input.sessionId),
452
+ eq(sessionPendingToolCalls.turnId, input.turnId),
453
+ inArray(
454
+ sessionPendingToolCalls.id,
455
+ pending.map((call) => call.id)
456
+ )
457
+ )
458
+ );
459
+ return { sequence, events: inserted.map(mapEvent), closed: pending.length };
460
+ }
461
+
462
+ // src/session-realtime-mirror.ts
463
+ import { createHash } from "crypto";
464
+ import { and as and2, desc, eq as eq2, gt, sql as sql2 } from "drizzle-orm";
465
+ var SESSION_REALTIME_MIRROR_MAX_TEXT_BYTES = 131072;
466
+ var SESSION_REALTIME_MIRROR_MAX_PAYLOAD_BYTES = 131072;
467
+ var TRUNCATION_MARKER = "\n\u2026realtime context truncated\u2026";
468
+ function deterministicUuid(seed) {
469
+ const bytes = createHash("sha256").update(seed, "utf8").digest().subarray(0, 16);
470
+ bytes[6] = (bytes[6] ?? 0) & 15 | 80;
471
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
472
+ const hex = bytes.toString("hex");
473
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
474
+ }
475
+ function takeUtf8Head(value, maximumBytes) {
476
+ const bytes = Buffer.from(value, "utf8");
477
+ if (bytes.length <= maximumBytes) return value;
478
+ let end = maximumBytes;
479
+ while (end > 0 && (bytes[end] & 192) === 128) end -= 1;
480
+ return bytes.subarray(0, end).toString("utf8");
481
+ }
482
+ function boundedText(value) {
483
+ if (Buffer.byteLength(value, "utf8") <= SESSION_REALTIME_MIRROR_MAX_TEXT_BYTES) return value;
484
+ const markerBytes = Buffer.byteLength(TRUNCATION_MARKER, "utf8");
485
+ return `${takeUtf8Head(value, SESSION_REALTIME_MIRROR_MAX_TEXT_BYTES - markerBytes)}${TRUNCATION_MARKER}`;
486
+ }
487
+ function boundedPayload(value) {
488
+ const payload = value;
489
+ if (Buffer.byteLength(JSON.stringify(payload), "utf8") > SESSION_REALTIME_MIRROR_MAX_PAYLOAD_BYTES) {
490
+ throw new Error("Realtime mirror payload exceeds the durable ledger limit");
491
+ }
492
+ return payload;
493
+ }
494
+ async function mirrorSessionRealtimeContextInTransaction(db, input) {
495
+ const now = input.now ?? /* @__PURE__ */ new Date();
496
+ const modes = await db.select().from(sessionRealtimeModes).where(
497
+ and2(
498
+ eq2(sessionRealtimeModes.accountId, input.accountId),
499
+ eq2(sessionRealtimeModes.workspaceId, input.workspaceId),
500
+ eq2(sessionRealtimeModes.sessionId, input.sessionId),
501
+ eq2(sessionRealtimeModes.state, "active"),
502
+ gt(sessionRealtimeModes.leaseExpiresAt, now)
503
+ )
504
+ ).orderBy(desc(sessionRealtimeModes.startedAt)).for("update").limit(2);
505
+ if (modes.length === 0) return null;
506
+ if (modes.length !== 1) {
507
+ throw new Error(`Session ${input.sessionId} has multiple active realtime modes`);
508
+ }
509
+ const mode = modes[0];
510
+ const operationId = deterministicUuid(
511
+ `opengeni:session-realtime-mirror:${mode.id}:${input.sourceKind}:${input.sourceId}`
512
+ );
513
+ const [existing] = await db.select().from(sessionRealtimeEntries).where(
514
+ and2(
515
+ eq2(sessionRealtimeEntries.realtimeId, mode.id),
516
+ eq2(sessionRealtimeEntries.operationId, operationId)
517
+ )
518
+ ).limit(1);
519
+ if (existing) return { entry: existing, replay: true };
520
+ const [sequenceRow] = await db.select({ next: sql2`coalesce(max(${sessionRealtimeEntries.sequence}), 0) + 1` }).from(sessionRealtimeEntries).where(eq2(sessionRealtimeEntries.realtimeId, mode.id));
521
+ const payload = boundedPayload({
522
+ ...input.payload ?? {},
523
+ source: input.sourceKind,
524
+ sourceId: input.sourceId,
525
+ channel: input.channel,
526
+ ...input.turnId ? { sourceTurnId: input.turnId } : {}
527
+ });
528
+ const [entry] = await db.insert(sessionRealtimeEntries).values({
529
+ accountId: input.accountId,
530
+ workspaceId: input.workspaceId,
531
+ sessionId: input.sessionId,
532
+ realtimeId: mode.id,
533
+ operationId,
534
+ connectionEpoch: mode.connectionEpoch,
535
+ sequence: Number(sequenceRow?.next ?? 1),
536
+ direction: "provider_out",
537
+ kind: "session_update",
538
+ text: boundedText(input.text),
539
+ textCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
540
+ payload,
541
+ payloadCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
542
+ createdAt: now,
543
+ updatedAt: now
544
+ }).returning();
545
+ if (!entry) throw new Error("Failed to append realtime session context");
546
+ return { entry, replay: false };
547
+ }
548
+ function renderRealtimeHumanInputContext(input) {
549
+ return [
550
+ "<session_user_message>",
551
+ ` <status>${input.routing}</status>`,
552
+ ` <delivery>${input.delivery}</delivery>`,
553
+ ` <text>${escapeXmlText(input.text)}</text>`,
554
+ " <instruction>Already routed by OpenGeni; do not delegate this message again.</instruction>",
555
+ "</session_user_message>"
556
+ ].join("\n");
557
+ }
558
+ function renderRealtimeHumanInputRequestContext(input) {
559
+ const lines = ["<session_human_input_request>", " <status>waiting_for_user</status>"];
560
+ for (const request of input.requests) {
561
+ lines.push(
562
+ " <request>",
563
+ ` <id>${escapeXmlText(request.id)}</id>`,
564
+ ` <allow_skip>${String(request.allowSkip)}</allow_skip>`,
565
+ ` <expires_at>${escapeXmlText(renderExpiry(request.expiresAt))}</expires_at>`
566
+ );
567
+ for (const question of request.questions) {
568
+ lines.push(
569
+ " <question>",
570
+ ` <id>${escapeXmlText(question.id)}</id>`,
571
+ ` <kind>${escapeXmlText(question.kind)}</kind>`,
572
+ ` <required>${String(question.required)}</required>`
573
+ );
574
+ if (question.label) lines.push(` <label>${escapeXmlText(question.label)}</label>`);
575
+ lines.push(` <prompt>${escapeXmlText(question.prompt)}</prompt>`);
576
+ if (question.helpText) {
577
+ lines.push(` <help_text>${escapeXmlText(question.helpText)}</help_text>`);
578
+ }
579
+ if (question.options.length > 0) {
580
+ lines.push(" <options>");
581
+ for (const option of question.options) {
582
+ lines.push(
583
+ " <option>",
584
+ ` <id>${escapeXmlText(option.id)}</id>`,
585
+ ` <label>${escapeXmlText(option.label)}</label>`
586
+ );
587
+ if (option.description) {
588
+ lines.push(` <description>${escapeXmlText(option.description)}</description>`);
589
+ }
590
+ lines.push(" </option>");
591
+ }
592
+ lines.push(" </options>");
593
+ }
594
+ lines.push(
595
+ ` <allow_other>${String(question.kind !== "text")}</allow_other>`,
596
+ " </question>"
597
+ );
598
+ }
599
+ lines.push(" </request>");
600
+ }
601
+ lines.push(
602
+ " <instruction>The current work is waiting for the user's input. Ask the questions naturally, one at a time when useful, without changing their meaning or answering for the user. The user may answer in the visible form or answer conversationally here. If the user answers here, delegate exactly once with a complete message containing the relevant question and the user's answer. If the user changes direction instead, delegate that new direction normally. Do not claim the work resumed until a later session update confirms it.</instruction>",
603
+ "</session_human_input_request>"
604
+ );
605
+ return lines.join("\n");
606
+ }
607
+ function renderRealtimeHumanInputResponseContext(input) {
608
+ const lines = [
609
+ "<session_human_input_response>",
610
+ ` <request_id>${escapeXmlText(input.requestId)}</request_id>`,
611
+ ` <outcome>${escapeXmlText(input.response.outcome)}</outcome>`
612
+ ];
613
+ if (input.response.outcome === "answered") {
614
+ const questions = new Map(input.questions.map((question) => [question.id, question]));
615
+ lines.push(" <answers>");
616
+ for (const answer of input.response.answers) {
617
+ const question = questions.get(answer.questionId);
618
+ const optionLabels = new Map(
619
+ question?.options.map((option) => [option.id, option.label]) ?? []
620
+ );
621
+ lines.push(
622
+ " <answer>",
623
+ ` <question_id>${escapeXmlText(answer.questionId)}</question_id>`
624
+ );
625
+ if (question) lines.push(` <question>${escapeXmlText(question.prompt)}</question>`);
626
+ for (const value of answer.values) {
627
+ lines.push(` <value>${escapeXmlText(optionLabels.get(value) ?? value)}</value>`);
628
+ }
629
+ if (answer.other) lines.push(` <other>${escapeXmlText(answer.other)}</other>`);
630
+ lines.push(" </answer>");
631
+ }
632
+ lines.push(" </answers>");
633
+ }
634
+ lines.push(
635
+ input.response.outcome === "answered" || input.response.outcome === "skipped" ? " <instruction>This structured response was accepted through the session UI and the same work can resume. Treat it as authoritative user context, do not delegate it again, and acknowledge briefly only if useful.</instruction>" : " <instruction>This pending question is no longer active. Do not ask it again unless the user raises it.</instruction>",
636
+ "</session_human_input_response>"
637
+ );
638
+ return lines.join("\n");
639
+ }
640
+ function renderExpiry(value) {
641
+ if (value === null || value === void 0) return "none";
642
+ return value instanceof Date ? value.toISOString() : value;
643
+ }
644
+ function escapeXmlText(value) {
645
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
646
+ }
647
+
648
+ // src/session-control.ts
649
+ var SESSION_ANCESTRY_LIMIT = 1e4;
650
+ var NO_SETTLEMENT_ATTEMPTS = {
651
+ attemptCount: 0,
652
+ interruptionPendingCount: 0,
653
+ quiescencePendingCount: 0
654
+ };
655
+ var SESSION_DISCOVERY_CONTROL_TITLE_MAX_CHARS = 200;
656
+ var SESSION_DISCOVERY_CONTROL_TARGET_LIMIT = 100;
657
+ function serializeEffectiveSessionControl(control) {
658
+ const blocker = (value) => ({
659
+ ...value,
660
+ changedAt: value.changedAt?.toISOString() ?? null
661
+ });
662
+ return {
663
+ ...control,
664
+ primaryBlocker: control.primaryBlocker ? blocker(control.primaryBlocker) : null,
665
+ blockers: control.blockers.map(blocker),
666
+ resumeOptions: control.resumeOptions.map(({ remainingPrimaryBlocker, ...option }) => ({
667
+ ...option,
668
+ ...remainingPrimaryBlocker ? { remainingPrimaryBlocker: blocker(remainingPrimaryBlocker) } : {}
669
+ }))
670
+ };
671
+ }
672
+ var SessionControlInvariantError = class extends Error {
673
+ code = "SESSION_CONTROL_INVARIANT";
674
+ constructor(message) {
675
+ super(message);
676
+ this.name = "SessionControlInvariantError";
677
+ }
678
+ };
679
+ var SessionControlConflictError = class extends Error {
680
+ code = "CONTROL_CHANGED";
681
+ constructor(message = "The workstream control changed") {
682
+ super(message);
683
+ this.name = "SessionControlConflictError";
684
+ }
685
+ };
686
+ var SessionCommandIdempotencyError = class extends Error {
687
+ code = "IDEMPOTENCY_KEY_REUSED";
688
+ constructor() {
689
+ super("The operation key was already used with different input");
690
+ this.name = "SessionCommandIdempotencyError";
691
+ }
692
+ };
693
+ var AgentCommandAuthorityError = class extends Error {
694
+ constructor(code, message) {
695
+ super(message);
696
+ this.code = code;
697
+ this.name = "AgentCommandAuthorityError";
698
+ }
699
+ };
700
+ async function assertAgentCommandAuthorityInTransaction(db, input) {
701
+ if (["goal", "wait"].includes(input.action) && input.targetSessionId !== input.actor.sessionId) {
702
+ throw new SessionControlInvariantError("An agent self command must target its own session");
703
+ }
704
+ const authorityLocks = await lockSessionEventWriteRows(db, {
705
+ workspaceId: input.workspaceId,
706
+ controlLock: "already_locked",
707
+ workspaceLock: "already_locked",
708
+ sessionIds: [input.actor.sessionId, input.targetSessionId],
709
+ turnIds: [input.actor.turnId],
710
+ attemptIds: [input.actor.attemptId]
711
+ });
712
+ const lockedSessions = authorityLocks.sessions;
713
+ const callerSession = lockedSessions.find((row) => row.id === input.actor.sessionId);
714
+ if (!callerSession) {
715
+ throw new AgentCommandAuthorityError("CALLER_STALE", "The calling session no longer exists");
716
+ }
717
+ const targetSession = lockedSessions.find((row) => row.id === input.targetSessionId);
718
+ if (!targetSession) {
719
+ throw new SessionControlInvariantError(`Target session not found: ${input.targetSessionId}`);
720
+ }
721
+ const rows = await db.execute(sql3`
722
+ select
723
+ attempt.id as "attemptId",
724
+ attempt.state as "attemptState",
725
+ attempt.session_id as "attemptSessionId",
726
+ attempt.turn_id as "attemptTurnId",
727
+ attempt.execution_generation as "executionGeneration",
728
+ turn.active_attempt_id as "activeAttemptId",
729
+ turn.status as "turnStatus",
730
+ session.active_turn_id as "activeTurnId",
731
+ exists (
732
+ select 1
733
+ from ${sessionAttemptInterruptions} interruption
734
+ where interruption.workspace_id = ${input.workspaceId}
735
+ and interruption.attempt_id = attempt.id
736
+ and interruption.state in ('pending', 'delivered', 'acknowledged')
737
+ ) as interrupted
738
+ from ${sessionTurnAttempts} attempt
739
+ join ${sessionTurns} turn
740
+ on turn.workspace_id = attempt.workspace_id and turn.id = attempt.turn_id
741
+ join ${sessions} session
742
+ on session.workspace_id = attempt.workspace_id and session.id = attempt.session_id
743
+ where attempt.workspace_id = ${input.workspaceId}
744
+ and attempt.id = ${input.actor.attemptId}
745
+ `);
746
+ const caller = rows[0];
747
+ if (!caller || !["claimed", "running"].includes(caller.attemptState) || caller.attemptSessionId !== input.actor.sessionId || caller.attemptTurnId !== input.actor.turnId || Number(caller.executionGeneration) !== input.actor.executionGeneration || caller.activeAttemptId !== input.actor.attemptId || caller.activeTurnId !== input.actor.turnId || !["running", "requires_action", "recovering", "waiting_capacity"].includes(caller.turnStatus)) {
748
+ throw new AgentCommandAuthorityError(
749
+ "CALLER_STALE",
750
+ "The calling agent attempt no longer owns its turn"
751
+ );
752
+ }
753
+ if (caller.interrupted) {
754
+ throw new AgentCommandAuthorityError(
755
+ "CALLER_INTERRUPTED",
756
+ "The calling agent attempt is being interrupted"
757
+ );
758
+ }
759
+ if (input.action === "steer" && input.targetSessionId === input.actor.sessionId) {
760
+ throw new AgentCommandAuthorityError("SELF_STEER", "An agent cannot steer its own session");
761
+ }
762
+ }
763
+ function asSafeRevision(value, label) {
764
+ if (value === null) return null;
765
+ const revision = Number(value);
766
+ if (!Number.isSafeInteger(revision) || revision < 0) {
767
+ throw new SessionControlInvariantError(`${label} is not a safe non-negative revision`);
768
+ }
769
+ return revision;
770
+ }
771
+ function asDate(value) {
772
+ if (value === null) return null;
773
+ return value instanceof Date ? value : new Date(value);
774
+ }
775
+ function canonicalize(value) {
776
+ if (Array.isArray(value)) return value.map(canonicalize);
777
+ if (value && typeof value === "object") {
778
+ return Object.fromEntries(
779
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonicalize(item)])
780
+ );
781
+ }
782
+ return value;
783
+ }
784
+ function canonicalSessionCommandHash(value) {
785
+ return createHash2("sha256").update(JSON.stringify(canonicalize(value))).digest("hex");
786
+ }
787
+ function controlEtag(value) {
788
+ return `sc1:${canonicalSessionCommandHash(value)}`;
789
+ }
790
+ function lockClause(mode) {
791
+ if (mode === "none") return sql3.empty();
792
+ return mode === "update" ? sql3.raw("for update") : sql3.raw("for share");
793
+ }
794
+ var WORKSPACE_CONTROL_REQUEST_LOCK_TIMEOUT_MS = 2e4;
795
+ var configuredRequestLockTimeoutMs = WORKSPACE_CONTROL_REQUEST_LOCK_TIMEOUT_MS;
796
+ function configureWorkspaceControlRequestLockTimeoutMs(lockTimeoutMs) {
797
+ if (!Number.isSafeInteger(lockTimeoutMs) || lockTimeoutMs <= 0) {
798
+ throw new Error("Workspace control request lock timeout must be a positive integer (ms)");
799
+ }
800
+ configuredRequestLockTimeoutMs = lockTimeoutMs;
801
+ }
802
+ function workspaceControlRequestLockTimeoutMs() {
803
+ return configuredRequestLockTimeoutMs;
804
+ }
805
+ var WorkspaceControlBusyError = class extends Error {
806
+ constructor(workspaceId, lockTimeoutMs) {
807
+ super("The workspace control row is busy; retry shortly");
808
+ this.workspaceId = workspaceId;
809
+ this.lockTimeoutMs = lockTimeoutMs;
810
+ this.name = "WorkspaceControlBusyError";
811
+ }
812
+ code = "WORKSPACE_CONTROL_BUSY";
813
+ };
814
+ function workspaceControlLockBudget(lockTimeoutMs) {
815
+ if (!Number.isFinite(lockTimeoutMs) || lockTimeoutMs <= 0) {
816
+ throw new Error("Workspace control lockTimeoutMs must be a positive number");
817
+ }
818
+ const totalMs = Math.max(1, Math.floor(lockTimeoutMs));
819
+ return { totalMs, deadlineAt: Date.now() + totalMs };
820
+ }
821
+ function advisoryLockFunction(mode) {
822
+ return mode === "update" ? sql3.raw("pg_advisory_xact_lock") : sql3.raw("pg_advisory_xact_lock_shared");
823
+ }
824
+ function lockBudgetFor(options) {
825
+ if (options.budget) return options.budget;
826
+ if (options.lockTimeoutMs === void 0) return null;
827
+ return workspaceControlLockBudget(options.lockTimeoutMs);
828
+ }
829
+ async function boundedLockStep(db, workspaceId, budget, step) {
830
+ if (!budget) return await step();
831
+ const remainingMs = budget.deadlineAt - Date.now();
832
+ if (remainingMs <= 0) throw new WorkspaceControlBusyError(workspaceId, budget.totalMs);
833
+ await db.execute(
834
+ sql3`select set_config('lock_timeout', ${`${Math.max(1, Math.floor(remainingMs))}ms`}, true)`
835
+ );
836
+ try {
837
+ return await step();
838
+ } catch (error) {
839
+ if (nestedPostgresSqlState(error) === "55P03") {
840
+ throw new WorkspaceControlBusyError(workspaceId, budget.totalMs);
841
+ }
842
+ throw error;
843
+ }
844
+ }
845
+ async function lockWorkspaceInferenceControl(db, workspaceId, mode, options = {}) {
846
+ const budget = lockBudgetFor(options);
847
+ const [prior] = budget ? await db.execute(sql3`select current_setting('lock_timeout') as value`) : [];
848
+ if (mode !== "none") {
849
+ await boundedLockStep(
850
+ db,
851
+ workspaceId,
852
+ budget,
853
+ () => db.execute(
854
+ sql3`select ${advisoryLockFunction(mode)}(hashtextextended(${`workspace-control:${workspaceId}`}, 0))`
855
+ )
856
+ );
857
+ }
858
+ const rows = await boundedLockStep(
859
+ db,
860
+ workspaceId,
861
+ budget,
862
+ () => db.execute(sql3`
863
+ select
864
+ workspace_id as "workspaceId",
865
+ account_id as "accountId",
866
+ revision,
867
+ workspace_state as "workspaceState",
868
+ workspace_pause_revision as "workspacePauseRevision",
869
+ reason,
870
+ changed_by as "changedBy",
871
+ changed_at as "changedAt"
872
+ from ${workspaceInferenceControls}
873
+ where workspace_id = ${workspaceId}
874
+ ${lockClause(mode)}
875
+ `)
876
+ );
877
+ if (budget) {
878
+ await db.execute(sql3`select set_config('lock_timeout', ${prior?.value ?? "0"}, true)`);
879
+ }
880
+ const row = rows[0];
881
+ if (!row) {
882
+ throw new SessionControlInvariantError(
883
+ `Workspace ${workspaceId} has no mandatory inference-control row`
884
+ );
885
+ }
886
+ return row;
887
+ }
888
+ var WORKSPACE_CONTROL_ADMISSION_SAVEPOINT = "opengeni_workspace_control_admission";
889
+ async function lockWorkspaceInferenceControlForAdmission(db, input) {
890
+ const lockOptions = input.lockTimeoutMs === void 0 ? {} : { budget: workspaceControlLockBudget(input.lockTimeoutMs) };
891
+ await db.execute(sql3.raw(`savepoint ${WORKSPACE_CONTROL_ADMISSION_SAVEPOINT}`));
892
+ const shared = await lockWorkspaceInferenceControl(db, input.workspaceId, "share", lockOptions);
893
+ if (!input.resumePausedBranch) {
894
+ await db.execute(sql3.raw(`release savepoint ${WORKSPACE_CONTROL_ADMISSION_SAVEPOINT}`));
895
+ return { control: shared, mode: "share" };
896
+ }
897
+ const effective = await evaluateSessionControl(db, input.workspaceId, input.sessionId, {
898
+ workspaceControl: shared
899
+ });
900
+ if (effective.state === "active") {
901
+ await db.execute(sql3.raw(`release savepoint ${WORKSPACE_CONTROL_ADMISSION_SAVEPOINT}`));
902
+ return { control: shared, mode: "share" };
903
+ }
904
+ await db.execute(sql3.raw(`rollback to savepoint ${WORKSPACE_CONTROL_ADMISSION_SAVEPOINT}`));
905
+ await db.execute(sql3.raw(`release savepoint ${WORKSPACE_CONTROL_ADMISSION_SAVEPOINT}`));
906
+ return {
907
+ control: await lockWorkspaceInferenceControl(db, input.workspaceId, "update", lockOptions),
908
+ mode: "update"
909
+ };
910
+ }
911
+ async function lockSessionEventWriteRows(db, input) {
912
+ const controlLock = input.controlLock;
913
+ const control = controlLock === "share" || controlLock === "update" ? await lockWorkspaceInferenceControl(db, input.workspaceId, controlLock) : null;
914
+ let workspace = null;
915
+ if ((input.workspaceLock ?? "key_share") === "key_share") {
916
+ const [lockedWorkspace] = await db.select().from(workspaces).where(eq3(workspaces.id, input.workspaceId)).for("key share").limit(1);
917
+ workspace = lockedWorkspace ?? null;
918
+ }
919
+ const sessionIds = [...new Set(input.sessionIds ?? [])].sort();
920
+ const sessionLock = input.sessionLock ?? "no_key_update";
921
+ let sessions2 = sessionIds.length > 0 ? await db.select().from(sessions).where(
922
+ and3(
923
+ eq3(sessions.workspaceId, input.workspaceId),
924
+ inArray2(sessions.id, sessionIds)
925
+ )
926
+ ).orderBy(sessions.id).for(sessionLock === "key_share" ? "key share" : "no key update") : [];
927
+ const cursors = sessions2.length > 0 ? await db.select().from(sessionEventCursors).where(
928
+ and3(
929
+ eq3(sessionEventCursors.workspaceId, input.workspaceId),
930
+ inArray2(
931
+ sessionEventCursors.sessionId,
932
+ sessions2.map((session) => session.id)
933
+ )
934
+ )
935
+ ).orderBy(sessionEventCursors.sessionId).for("update") : [];
936
+ if (cursors.length !== sessions2.length) {
937
+ throw new SessionControlInvariantError(
938
+ `Session event cursor lock set was incomplete for workspace ${input.workspaceId}`
939
+ );
940
+ }
941
+ if (sessionLock === "key_share" && sessions2.length > 0) {
942
+ sessions2 = await db.select().from(sessions).where(
943
+ and3(
944
+ eq3(sessions.workspaceId, input.workspaceId),
945
+ inArray2(sessions.id, sessionIds)
946
+ )
947
+ ).orderBy(sessions.id).for("key share");
948
+ }
949
+ const canonicalSessions = sessions2.map((session, index) => {
950
+ const cursor = cursors[index];
951
+ if (cursor.sessionId !== session.id || cursor.accountId !== session.accountId || cursor.workspaceId !== session.workspaceId) {
952
+ throw new SessionControlInvariantError(
953
+ `Session event cursor identity failed for session ${session.id}`
954
+ );
955
+ }
956
+ if (cursor.lastSequence < session.lastSequence) {
957
+ throw new SessionControlInvariantError(
958
+ `Session event cursor is behind session projection for session ${session.id}`
959
+ );
960
+ }
961
+ return { ...session, lastSequence: cursor.lastSequence };
962
+ });
963
+ const turnIds = [...new Set(input.turnIds ?? [])].sort();
964
+ const turns = turnIds.length > 0 ? await db.select().from(sessionTurns).where(
965
+ and3(
966
+ eq3(sessionTurns.workspaceId, input.workspaceId),
967
+ inArray2(sessionTurns.id, turnIds)
968
+ )
969
+ ).orderBy(sessionTurns.id).for("update") : [];
970
+ const attemptIds = [...new Set(input.attemptIds ?? [])].sort();
971
+ const attempts = attemptIds.length > 0 ? await db.select().from(sessionTurnAttempts).where(
972
+ and3(
973
+ eq3(sessionTurnAttempts.workspaceId, input.workspaceId),
974
+ inArray2(sessionTurnAttempts.id, attemptIds)
975
+ )
976
+ ).orderBy(sessionTurnAttempts.id).for("update") : [];
977
+ return { control, workspace, sessions: canonicalSessions, cursors, turns, attempts };
978
+ }
979
+ async function registerSessionTurnAttemptClaim(db, input) {
980
+ const authoritySnapshot = assertSessionAuthoritySnapshot({
981
+ attemptId: input.id,
982
+ authorityEpoch: input.authorityEpoch,
983
+ authorityVisibility: input.authorityVisibility,
984
+ authorityOwnerOrganizationMembershipId: input.authorityOwnerOrganizationMembershipId
985
+ });
986
+ const [inserted] = await db.insert(sessionTurnAttempts).values({
987
+ ...input,
988
+ ...authoritySnapshot,
989
+ state: "claimed"
990
+ }).onConflictDoNothing({ target: sessionTurnAttempts.id }).returning();
991
+ if (inserted) return inserted;
992
+ const [existing] = await db.select().from(sessionTurnAttempts).where(
993
+ and3(
994
+ eq3(sessionTurnAttempts.workspaceId, input.workspaceId),
995
+ eq3(sessionTurnAttempts.id, input.id)
996
+ )
997
+ ).for("update").limit(1);
998
+ const existingAuthoritySnapshot = existing ? assertSessionAuthoritySnapshot({
999
+ attemptId: existing.id,
1000
+ authorityEpoch: existing.authorityEpoch,
1001
+ authorityVisibility: existing.authorityVisibility,
1002
+ authorityOwnerOrganizationMembershipId: existing.authorityOwnerOrganizationMembershipId
1003
+ }) : null;
1004
+ if (!existing || existing.accountId !== input.accountId || existing.sessionId !== input.sessionId || existing.turnId !== input.turnId || existing.executionGeneration !== input.executionGeneration || !existingAuthoritySnapshot || !sessionAuthoritySnapshotsEqual(existingAuthoritySnapshot, authoritySnapshot) || existing.temporalWorkflowId !== input.temporalWorkflowId || existing.temporalWorkflowRunId !== input.temporalWorkflowRunId || existing.temporalActivityId !== input.temporalActivityId || existing.personalResourceProtocolVersion !== input.personalResourceProtocolVersion || JSON.stringify(existing.connectorActionPolicies) !== JSON.stringify(input.connectorActionPolicies) || existing.state === "closed") {
1005
+ throw new SessionControlInvariantError(
1006
+ `Attempt ${input.id} conflicts with a different or closed ownership chain`
1007
+ );
1008
+ }
1009
+ return existing;
1010
+ }
1011
+ function assertSessionAuthoritySnapshot(input) {
1012
+ const valid = Number.isSafeInteger(input.authorityEpoch) && input.authorityEpoch > 0 && (input.authorityVisibility === "user_private" || input.authorityVisibility === "workspace_shared") && (input.authorityVisibility === "user_private" && typeof input.authorityOwnerOrganizationMembershipId === "string" || input.authorityVisibility === "workspace_shared" && (input.authorityOwnerOrganizationMembershipId === null || typeof input.authorityOwnerOrganizationMembershipId === "string"));
1013
+ if (!valid) {
1014
+ throw new SessionControlInvariantError(
1015
+ `Attempt ${input.attemptId ?? "unknown"} has an invalid session authority snapshot`
1016
+ );
1017
+ }
1018
+ return {
1019
+ authorityEpoch: input.authorityEpoch,
1020
+ authorityVisibility: input.authorityVisibility,
1021
+ authorityOwnerOrganizationMembershipId: input.authorityOwnerOrganizationMembershipId
1022
+ };
1023
+ }
1024
+ function sessionAuthoritySnapshotMatchesSession(snapshot, session) {
1025
+ return snapshot.authorityEpoch === session.authorityEpoch && snapshot.authorityVisibility === session.visibility && snapshot.authorityOwnerOrganizationMembershipId === (session.ownerOrganizationMembershipId ?? null);
1026
+ }
1027
+ function sessionAuthoritySnapshotsEqual(left, right) {
1028
+ return left.authorityEpoch === right.authorityEpoch && left.authorityVisibility === right.authorityVisibility && left.authorityOwnerOrganizationMembershipId === right.authorityOwnerOrganizationMembershipId;
1029
+ }
1030
+ async function closeSessionTurnAttemptInTransaction(db, input) {
1031
+ const [attempt] = await db.select().from(sessionTurnAttempts).where(
1032
+ and3(
1033
+ eq3(sessionTurnAttempts.workspaceId, input.workspaceId),
1034
+ eq3(sessionTurnAttempts.id, input.id)
1035
+ )
1036
+ ).for("update").limit(1);
1037
+ if (!attempt || attempt.accountId !== input.accountId || attempt.sessionId !== input.sessionId || attempt.turnId !== input.turnId || attempt.executionGeneration !== input.executionGeneration) {
1038
+ throw new SessionControlInvariantError(
1039
+ `Attempt ${input.id} does not own the expected session turn generation`
1040
+ );
1041
+ }
1042
+ if (attempt.state === "closed") {
1043
+ if (attempt.outcome !== input.outcome) {
1044
+ throw new SessionControlInvariantError(
1045
+ `Attempt ${input.id} is already closed as ${attempt.outcome ?? "unknown"}`
1046
+ );
1047
+ }
1048
+ return { action: "already_closed" };
1049
+ }
1050
+ if (attempt.state !== "claimed" && attempt.state !== "running") {
1051
+ throw new SessionControlInvariantError(
1052
+ `Attempt ${input.id} has invalid live state ${attempt.state}`
1053
+ );
1054
+ }
1055
+ const now = input.closedAt ?? /* @__PURE__ */ new Date();
1056
+ const [closed] = await db.update(sessionTurnAttempts).set({
1057
+ state: "closed",
1058
+ outcome: input.outcome,
1059
+ workerId: null,
1060
+ leaseId: null,
1061
+ leaseExpiresAt: null,
1062
+ closedAt: now,
1063
+ updatedAt: now
1064
+ }).where(
1065
+ and3(
1066
+ eq3(sessionTurnAttempts.workspaceId, input.workspaceId),
1067
+ eq3(sessionTurnAttempts.id, input.id),
1068
+ sql3`${sessionTurnAttempts.state} in ('claimed', 'running')`
1069
+ )
1070
+ ).returning({ id: sessionTurnAttempts.id });
1071
+ if (!closed) {
1072
+ throw new SessionControlInvariantError(`Attempt ${input.id} changed while locked`);
1073
+ }
1074
+ return { action: "closed" };
1075
+ }
1076
+ function targetValues(sessionIds) {
1077
+ if (sessionIds.length === 0) {
1078
+ throw new SessionControlInvariantError("At least one target session is required");
1079
+ }
1080
+ return sql3.join(
1081
+ sessionIds.map((id) => sql3`(${id}::uuid)`),
1082
+ sql3`, `
1083
+ );
1084
+ }
1085
+ var TARGET_PATH_PROJECTION_LIMIT = 128;
1086
+ async function loadTargetAncestryRows(db, workspaceId, sessionIds) {
1087
+ return await db.execute(sql3`
1088
+ with recursive targets(id) as (values ${targetValues(sessionIds)}),
1089
+ ancestry as (
1090
+ select
1091
+ target.id as target_id,
1092
+ session.id as session_id,
1093
+ session.parent_session_id,
1094
+ session.title,
1095
+ session.direct_control_state,
1096
+ session.direct_pause_revision,
1097
+ session.subtree_run_override_revision,
1098
+ session.control_version,
1099
+ session.direct_control_changed_by,
1100
+ session.direct_control_reason,
1101
+ session.direct_control_changed_at,
1102
+ 0::integer as depth,
1103
+ array[session.id]::uuid[] as path,
1104
+ false as cycle
1105
+ from targets target
1106
+ join ${sessions} session
1107
+ on session.workspace_id = ${workspaceId} and session.id = target.id
1108
+ union all
1109
+ select
1110
+ child.target_id,
1111
+ parent.id,
1112
+ parent.parent_session_id,
1113
+ parent.title,
1114
+ parent.direct_control_state,
1115
+ parent.direct_pause_revision,
1116
+ parent.subtree_run_override_revision,
1117
+ parent.control_version,
1118
+ parent.direct_control_changed_by,
1119
+ parent.direct_control_reason,
1120
+ parent.direct_control_changed_at,
1121
+ child.depth + 1,
1122
+ child.path || parent.id,
1123
+ parent.id = any(child.path)
1124
+ from ancestry child
1125
+ join ${sessions} parent
1126
+ on parent.workspace_id = ${workspaceId} and parent.id = child.parent_session_id
1127
+ where child.parent_session_id is not null
1128
+ and not child.cycle
1129
+ and child.depth < ${SESSION_ANCESTRY_LIMIT}
1130
+ )
1131
+ select
1132
+ target_id as "targetId",
1133
+ session_id as "sessionId",
1134
+ parent_session_id as "parentSessionId",
1135
+ title,
1136
+ direct_control_state as "directState",
1137
+ direct_pause_revision as "directPauseRevision",
1138
+ subtree_run_override_revision as "subtreeRunOverrideRevision",
1139
+ control_version as "controlVersion",
1140
+ direct_control_changed_by as "directControlChangedBy",
1141
+ direct_control_reason as "directControlReason",
1142
+ direct_control_changed_at as "directControlChangedAt",
1143
+ depth,
1144
+ cycle
1145
+ from ancestry
1146
+ order by target_id, depth
1147
+ `);
1148
+ }
1149
+ async function loadAncestryNodes(db, workspaceId, sessionIds) {
1150
+ const rows = await db.execute(sql3`
1151
+ with recursive targets(id) as (values ${targetValues(sessionIds)}),
1152
+ ancestry_ids(id) as (
1153
+ select session.id
1154
+ from targets target
1155
+ join ${sessions} session
1156
+ on session.workspace_id = ${workspaceId} and session.id = target.id
1157
+ union
1158
+ select parent.id
1159
+ from ancestry_ids child
1160
+ join ${sessions} current
1161
+ on current.workspace_id = ${workspaceId} and current.id = child.id
1162
+ join ${sessions} parent
1163
+ on parent.workspace_id = current.workspace_id
1164
+ and parent.id = current.parent_session_id
1165
+ )
1166
+ select
1167
+ session.id as "sessionId",
1168
+ session.parent_session_id as "parentSessionId",
1169
+ session.title,
1170
+ session.direct_control_state as "directState",
1171
+ session.direct_pause_revision as "directPauseRevision",
1172
+ session.subtree_run_override_revision as "subtreeRunOverrideRevision",
1173
+ session.control_version as "controlVersion",
1174
+ session.direct_control_changed_by as "directControlChangedBy",
1175
+ session.direct_control_reason as "directControlReason",
1176
+ session.direct_control_changed_at as "directControlChangedAt"
1177
+ from ancestry_ids ancestry
1178
+ join ${sessions} session
1179
+ on session.workspace_id = ${workspaceId} and session.id = ancestry.id
1180
+ `);
1181
+ return new Map(rows.map((row) => [row.sessionId, row]));
1182
+ }
1183
+ function ancestryRowsForTarget(targetId, nodes) {
1184
+ const rows = [];
1185
+ const path = /* @__PURE__ */ new Set();
1186
+ let sessionId = targetId;
1187
+ for (let depth = 0; sessionId !== null; depth += 1) {
1188
+ const node = nodes.get(sessionId);
1189
+ if (!node) break;
1190
+ const cycle = path.has(sessionId);
1191
+ rows.push({ ...node, targetId, depth, cycle });
1192
+ if (cycle || depth >= SESSION_ANCESTRY_LIMIT) break;
1193
+ path.add(sessionId);
1194
+ sessionId = node.parentSessionId;
1195
+ }
1196
+ return rows;
1197
+ }
1198
+ function assertCompleteAncestry(sessionId, rows) {
1199
+ if (rows.length === 0) {
1200
+ throw new SessionControlInvariantError(`Session ${sessionId} does not exist in its workspace`);
1201
+ }
1202
+ if (rows.some((row) => row.cycle)) {
1203
+ throw new SessionControlInvariantError(`Session ${sessionId} has cyclic ancestry`);
1204
+ }
1205
+ const last = rows.at(-1);
1206
+ const depth = Number(last.depth);
1207
+ if (last.parentSessionId !== null) {
1208
+ throw new SessionControlInvariantError(
1209
+ depth >= SESSION_ANCESTRY_LIMIT ? `Session ${sessionId} ancestry exceeds ${SESSION_ANCESTRY_LIMIT}` : `Session ${sessionId} has a missing ancestor ${last.parentSessionId}`
1210
+ );
1211
+ }
1212
+ }
1213
+ function blockerForSession(row, revision) {
1214
+ return {
1215
+ kind: "session",
1216
+ sessionId: row.sessionId,
1217
+ displayName: row.title?.trim() || "Untitled session",
1218
+ actor: row.directControlChangedBy,
1219
+ reason: row.directControlReason,
1220
+ changedAt: asDate(row.directControlChangedAt),
1221
+ revision
1222
+ };
1223
+ }
1224
+ function projectEffectiveControl(workspace, targetId, rows, settlementAttempts, stoppingBackgroundCommandCount) {
1225
+ assertCompleteAncestry(targetId, rows);
1226
+ const workspaceRevision = asSafeRevision(workspace.revision, "workspace control revision");
1227
+ const path = rows.map((row) => ({
1228
+ row,
1229
+ depth: Number(row.depth),
1230
+ pauseRevision: asSafeRevision(row.directPauseRevision, "direct pause revision"),
1231
+ overrideRevision: asSafeRevision(
1232
+ row.subtreeRunOverrideRevision,
1233
+ "subtree run override revision"
1234
+ ),
1235
+ controlVersion: asSafeRevision(row.controlVersion, "session control version")
1236
+ }));
1237
+ const undefeated = [];
1238
+ for (const candidate of path) {
1239
+ if (candidate.row.directState !== "paused" || candidate.pauseRevision === null) continue;
1240
+ const defeated = path.some(
1241
+ (possibleOverride) => possibleOverride.depth < candidate.depth && possibleOverride.overrideRevision !== null && possibleOverride.overrideRevision > candidate.pauseRevision
1242
+ );
1243
+ if (!defeated) {
1244
+ undefeated.push({
1245
+ blocker: blockerForSession(candidate.row, candidate.pauseRevision),
1246
+ depth: candidate.depth
1247
+ });
1248
+ }
1249
+ }
1250
+ const workspacePauseRevision = asSafeRevision(
1251
+ workspace.workspacePauseRevision,
1252
+ "workspace pause revision"
1253
+ );
1254
+ if (workspace.workspaceState === "paused") {
1255
+ if (workspacePauseRevision === null) {
1256
+ throw new SessionControlInvariantError("Paused workspace is missing its pause revision");
1257
+ }
1258
+ const defeated = path.some(
1259
+ (candidate) => candidate.overrideRevision !== null && candidate.overrideRevision > workspacePauseRevision
1260
+ );
1261
+ if (!defeated) {
1262
+ undefeated.push({
1263
+ blocker: {
1264
+ kind: "workspace",
1265
+ displayName: "Workspace",
1266
+ actor: workspace.changedBy,
1267
+ reason: workspace.reason,
1268
+ changedAt: asDate(workspace.changedAt),
1269
+ revision: workspacePauseRevision
1270
+ },
1271
+ depth: Number.POSITIVE_INFINITY
1272
+ });
1273
+ }
1274
+ }
1275
+ undefeated.sort((left, right) => left.depth - right.depth);
1276
+ const blockers = undefeated.map(({ blocker }) => blocker);
1277
+ const primaryBlocker = blockers[0] ?? null;
1278
+ const target = path[0];
1279
+ const override = path.filter((candidate) => candidate.overrideRevision !== null).sort((left, right) => right.overrideRevision - left.overrideRevision)[0];
1280
+ const options = [];
1281
+ if (blockers.length > 0) {
1282
+ options.push({
1283
+ scope: "selected",
1284
+ targetId,
1285
+ selectedStateAfter: "active",
1286
+ impactCopy: "Resume this session and its descendants without changing sibling workstreams."
1287
+ });
1288
+ for (const entry of undefeated) {
1289
+ if (entry.blocker.kind !== "session" || entry.blocker.sessionId === targetId) continue;
1290
+ const remaining = undefeated.find(
1291
+ (candidate) => candidate.depth < entry.depth && candidate.blocker.sessionId !== entry.blocker.sessionId
1292
+ )?.blocker;
1293
+ options.push({
1294
+ scope: "session",
1295
+ targetId: entry.blocker.sessionId,
1296
+ selectedStateAfter: remaining ? "paused" : "active",
1297
+ ...remaining ? { remainingPrimaryBlocker: remaining } : {},
1298
+ impactCopy: `Resume the workstream rooted at ${entry.blocker.displayName}.`
1299
+ });
1300
+ }
1301
+ if (workspace.workspaceState === "paused") {
1302
+ const remaining = undefeated.find((entry) => entry.blocker.kind === "session")?.blocker;
1303
+ options.push({
1304
+ scope: "workspace",
1305
+ selectedStateAfter: remaining ? "paused" : "active",
1306
+ ...remaining ? { remainingPrimaryBlocker: remaining } : {},
1307
+ impactCopy: "Resume the entire workspace; narrower session pauses remain in force."
1308
+ });
1309
+ }
1310
+ }
1311
+ const etagFacts = {
1312
+ workspace: {
1313
+ state: workspace.workspaceState,
1314
+ pauseRevision: workspacePauseRevision
1315
+ },
1316
+ path: path.map((candidate) => ({
1317
+ sessionId: candidate.row.sessionId,
1318
+ directState: candidate.row.directState,
1319
+ pauseRevision: candidate.pauseRevision,
1320
+ overrideRevision: candidate.overrideRevision
1321
+ }))
1322
+ };
1323
+ return {
1324
+ state: blockers.length > 0 ? "paused" : "active",
1325
+ controlVersion: Math.max(
1326
+ workspaceRevision,
1327
+ ...path.map((candidate) => candidate.controlVersion)
1328
+ ),
1329
+ controlEtag: controlEtag(etagFacts),
1330
+ directState: target.row.directState === "paused" ? "paused" : "active",
1331
+ primaryBlocker,
1332
+ additionalBlockerCount: Math.max(0, blockers.length - 1),
1333
+ blockers,
1334
+ resumeOptions: options,
1335
+ override: override?.overrideRevision === null || !override ? null : {
1336
+ rootSessionId: override.row.sessionId,
1337
+ revision: override.overrideRevision
1338
+ },
1339
+ settlement: settlementAttempts.attemptCount > 0 ? { state: "stopping", ...settlementAttempts } : null,
1340
+ backgroundCommandSettlement: stoppingBackgroundCommandCount > 0 ? { state: "stopping", commandCount: stoppingBackgroundCommandCount } : null
1341
+ };
1342
+ }
1343
+ async function stoppingBackgroundCommandCounts(db, workspaceId, sessionIds) {
1344
+ const rows = await db.execute(sql3`
1345
+ with recursive targets(id) as (values ${targetValues(sessionIds)}),
1346
+ descendants(target_id, session_id, depth, path) as (
1347
+ select target.id, target.id, 0::integer, array[target.id]::uuid[] from targets target
1348
+ union all
1349
+ select parent.target_id, child.id, parent.depth + 1, parent.path || child.id
1350
+ from descendants parent
1351
+ join ${sessions} child
1352
+ on child.workspace_id = ${workspaceId} and child.parent_session_id = parent.session_id
1353
+ where not child.id = any(parent.path) and parent.depth < ${SESSION_ANCESTRY_LIMIT}
1354
+ )
1355
+ select descendant.target_id as "sessionId", count(distinct command.id)::integer as "commandCount"
1356
+ from descendants descendant
1357
+ join ${sessionBackgroundCommands} command
1358
+ on command.workspace_id = ${workspaceId} and command.session_id = descendant.session_id
1359
+ and command.state = 'stopping'
1360
+ group by descendant.target_id
1361
+ `);
1362
+ return new Map(
1363
+ rows.map((row) => [
1364
+ row.sessionId,
1365
+ Number(row.commandCount)
1366
+ ])
1367
+ );
1368
+ }
1369
+ async function settlementAttemptCounts(db, workspaceId, sessionIds) {
1370
+ const rows = await db.execute(sql3`
1371
+ with recursive targets(id) as (values ${targetValues(sessionIds)}),
1372
+ descendant_sessions(target_id, session_id, depth, path) as (
1373
+ select target.id, target.id, 0::integer, array[target.id]::uuid[]
1374
+ from targets target
1375
+ union all
1376
+ select
1377
+ descendant.target_id,
1378
+ child.id,
1379
+ descendant.depth + 1,
1380
+ descendant.path || child.id
1381
+ from descendant_sessions descendant
1382
+ join ${sessions} child
1383
+ on child.workspace_id = ${workspaceId}
1384
+ and child.parent_session_id = descendant.session_id
1385
+ where not child.id = any(descendant.path)
1386
+ and descendant.depth < ${SESSION_ANCESTRY_LIMIT}
1387
+ ), interruptions as (
1388
+ select
1389
+ descendant.target_id,
1390
+ interruption.attempt_id,
1391
+ bool_or(interruption.state in ('pending', 'delivered', 'acknowledged'))
1392
+ as interruption_pending,
1393
+ bool_or(
1394
+ interruption.state in ('settled', 'rejected_stale')
1395
+ and attempt.quiesced_at is null
1396
+ ) as quiescence_pending
1397
+ from descendant_sessions descendant
1398
+ join ${sessionAttemptInterruptions} interruption
1399
+ on interruption.workspace_id = ${workspaceId}
1400
+ and interruption.session_id = descendant.session_id
1401
+ join ${sessionTurnAttempts} attempt
1402
+ on attempt.workspace_id = interruption.workspace_id
1403
+ and attempt.id = interruption.attempt_id
1404
+ where (
1405
+ interruption.state in ('pending', 'delivered', 'acknowledged')
1406
+ or (
1407
+ interruption.state in ('settled', 'rejected_stale')
1408
+ and attempt.quiesced_at is null
1409
+ )
1410
+ )
1411
+ group by descendant.target_id, interruption.attempt_id
1412
+ )
1413
+ select
1414
+ target.id as "sessionId",
1415
+ count(interruption.attempt_id)::integer as "attemptCount",
1416
+ count(interruption.attempt_id)
1417
+ filter (where interruption.interruption_pending)::integer as "interruptionPendingCount",
1418
+ count(interruption.attempt_id)
1419
+ filter (where interruption.quiescence_pending)::integer as "quiescencePendingCount"
1420
+ from targets target
1421
+ join interruptions interruption on interruption.target_id = target.id
1422
+ group by target.id
1423
+ `);
1424
+ return new Map(
1425
+ rows.map(
1426
+ (row) => [
1427
+ row.sessionId,
1428
+ {
1429
+ attemptCount: Number(row.attemptCount),
1430
+ interruptionPendingCount: Number(row.interruptionPendingCount),
1431
+ quiescencePendingCount: Number(row.quiescencePendingCount)
1432
+ }
1433
+ ]
1434
+ )
1435
+ );
1436
+ }
1437
+ async function evaluateSessionControls(db, workspaceId, sessionIds, options = {}) {
1438
+ const uniqueIds = [...new Set(sessionIds)];
1439
+ if (uniqueIds.length === 0) {
1440
+ return /* @__PURE__ */ new Map();
1441
+ }
1442
+ if (options.workspaceControl && options.workspaceControl.workspaceId !== workspaceId) {
1443
+ throw new SessionControlInvariantError(
1444
+ `Locked workspace control ${options.workspaceControl.workspaceId} does not match ${workspaceId}`
1445
+ );
1446
+ }
1447
+ const workspace = options.workspaceControl ?? await lockWorkspaceInferenceControl(db, workspaceId, options.lock ?? "share");
1448
+ const stopping = await settlementAttemptCounts(db, workspaceId, uniqueIds);
1449
+ const stoppingCommands = await stoppingBackgroundCommandCounts(db, workspaceId, uniqueIds);
1450
+ const result = /* @__PURE__ */ new Map();
1451
+ if (uniqueIds.length <= TARGET_PATH_PROJECTION_LIMIT) {
1452
+ const ancestry2 = await loadTargetAncestryRows(db, workspaceId, uniqueIds);
1453
+ const ancestryByTarget = /* @__PURE__ */ new Map();
1454
+ for (const row of ancestry2) {
1455
+ const rows = ancestryByTarget.get(row.targetId);
1456
+ if (rows) rows.push(row);
1457
+ else ancestryByTarget.set(row.targetId, [row]);
1458
+ }
1459
+ for (const sessionId of uniqueIds) {
1460
+ result.set(
1461
+ sessionId,
1462
+ projectEffectiveControl(
1463
+ workspace,
1464
+ sessionId,
1465
+ ancestryByTarget.get(sessionId) ?? [],
1466
+ stopping.get(sessionId) ?? NO_SETTLEMENT_ATTEMPTS,
1467
+ stoppingCommands.get(sessionId) ?? 0
1468
+ )
1469
+ );
1470
+ }
1471
+ return result;
1472
+ }
1473
+ const ancestry = await loadAncestryNodes(db, workspaceId, uniqueIds);
1474
+ for (const sessionId of uniqueIds) {
1475
+ result.set(
1476
+ sessionId,
1477
+ projectEffectiveControl(
1478
+ workspace,
1479
+ sessionId,
1480
+ ancestryRowsForTarget(sessionId, ancestry),
1481
+ stopping.get(sessionId) ?? NO_SETTLEMENT_ATTEMPTS,
1482
+ stoppingCommands.get(sessionId) ?? 0
1483
+ )
1484
+ );
1485
+ }
1486
+ return result;
1487
+ }
1488
+ async function evaluateSessionWriteAdmissionControl(db, workspaceId, sessionId, options = {}) {
1489
+ if (options.workspaceControl && options.workspaceControl.workspaceId !== workspaceId) {
1490
+ throw new SessionControlInvariantError(
1491
+ `Locked workspace control ${options.workspaceControl.workspaceId} does not match ${workspaceId}`
1492
+ );
1493
+ }
1494
+ const workspace = options.workspaceControl ?? await lockWorkspaceInferenceControl(db, workspaceId, options.lock ?? "share");
1495
+ const projected = projectEffectiveControl(
1496
+ workspace,
1497
+ sessionId,
1498
+ await loadTargetAncestryRows(db, workspaceId, [sessionId]),
1499
+ NO_SETTLEMENT_ATTEMPTS,
1500
+ 0
1501
+ );
1502
+ return {
1503
+ state: projected.state,
1504
+ controlVersion: projected.controlVersion,
1505
+ primaryBlockerKind: projected.primaryBlocker?.kind ?? null
1506
+ };
1507
+ }
1508
+ async function evaluateSessionDiscoveryControls(db, workspaceId, sessionIds) {
1509
+ const uniqueIds = [...new Set(sessionIds)];
1510
+ if (uniqueIds.length === 0) return /* @__PURE__ */ new Map();
1511
+ if (uniqueIds.length > SESSION_DISCOVERY_CONTROL_TARGET_LIMIT) {
1512
+ throw new SessionControlInvariantError(
1513
+ `Session discovery control projection exceeds ${SESSION_DISCOVERY_CONTROL_TARGET_LIMIT} targets`
1514
+ );
1515
+ }
1516
+ const workspace = await lockWorkspaceInferenceControl(db, workspaceId, "none");
1517
+ const workspacePauseRevision = asSafeRevision(
1518
+ workspace.workspacePauseRevision,
1519
+ "workspace pause revision"
1520
+ );
1521
+ if (workspace.workspaceState === "paused" && workspacePauseRevision === null) {
1522
+ throw new SessionControlInvariantError("Paused workspace is missing its pause revision");
1523
+ }
1524
+ const rows = await db.execute(sql3`
1525
+ with recursive targets(id) as (values ${targetValues(uniqueIds)}),
1526
+ ancestry as (
1527
+ select
1528
+ target.id as target_id,
1529
+ session.id as session_id,
1530
+ session.parent_session_id,
1531
+ left(coalesce(nullif(btrim(session.title), ''), 'Untitled session'), ${SESSION_DISCOVERY_CONTROL_TITLE_MAX_CHARS}) as display_name,
1532
+ char_length(coalesce(nullif(btrim(session.title), ''), 'Untitled session'))::integer as display_name_original_chars,
1533
+ session.direct_control_state,
1534
+ session.direct_pause_revision,
1535
+ session.subtree_run_override_revision,
1536
+ 0::integer as depth
1537
+ from targets target
1538
+ join ${sessions} session
1539
+ on session.workspace_id = ${workspaceId} and session.id = target.id
1540
+ union all
1541
+ select
1542
+ child.target_id,
1543
+ parent.id,
1544
+ parent.parent_session_id,
1545
+ left(coalesce(nullif(btrim(parent.title), ''), 'Untitled session'), ${SESSION_DISCOVERY_CONTROL_TITLE_MAX_CHARS}),
1546
+ char_length(coalesce(nullif(btrim(parent.title), ''), 'Untitled session'))::integer,
1547
+ parent.direct_control_state,
1548
+ parent.direct_pause_revision,
1549
+ parent.subtree_run_override_revision,
1550
+ child.depth + 1
1551
+ from ancestry child
1552
+ join ${sessions} parent
1553
+ on parent.workspace_id = ${workspaceId} and parent.id = child.parent_session_id
1554
+ where child.parent_session_id is not null
1555
+ and child.depth < ${SESSION_ANCESTRY_LIMIT}
1556
+ ),
1557
+ path as (
1558
+ select
1559
+ ancestry.*,
1560
+ max(ancestry.subtree_run_override_revision) over (
1561
+ partition by ancestry.target_id
1562
+ order by ancestry.depth
1563
+ rows between unbounded preceding and 1 preceding
1564
+ ) as descendant_override_revision
1565
+ from ancestry
1566
+ ),
1567
+ session_blockers as (
1568
+ select
1569
+ path.target_id,
1570
+ 'session'::text as kind,
1571
+ path.session_id,
1572
+ path.display_name,
1573
+ path.display_name_original_chars,
1574
+ path.depth
1575
+ from path
1576
+ where path.direct_control_state = 'paused'
1577
+ and path.direct_pause_revision is not null
1578
+ and (
1579
+ path.descendant_override_revision is null
1580
+ or path.descendant_override_revision <= path.direct_pause_revision
1581
+ )
1582
+ ),
1583
+ workspace_blockers as (
1584
+ select
1585
+ target.id as target_id,
1586
+ 'workspace'::text as kind,
1587
+ null::uuid as session_id,
1588
+ 'Workspace'::text as display_name,
1589
+ 9::integer as display_name_original_chars,
1590
+ ${SESSION_ANCESTRY_LIMIT + 1}::integer as depth
1591
+ from targets target
1592
+ where ${workspace.workspaceState === "paused"}
1593
+ and not exists (
1594
+ select 1
1595
+ from path
1596
+ where path.target_id = target.id
1597
+ and path.subtree_run_override_revision is not null
1598
+ and path.subtree_run_override_revision > ${workspacePauseRevision}
1599
+ )
1600
+ ),
1601
+ blockers as (
1602
+ select * from session_blockers
1603
+ union all
1604
+ select * from workspace_blockers
1605
+ ),
1606
+ primary_blockers as (
1607
+ select distinct on (blockers.target_id)
1608
+ blockers.target_id,
1609
+ blockers.kind,
1610
+ blockers.session_id,
1611
+ blockers.display_name,
1612
+ blockers.display_name_original_chars
1613
+ from blockers
1614
+ order by blockers.target_id, blockers.depth
1615
+ ),
1616
+ blocker_counts as (
1617
+ select blockers.target_id, count(*)::integer as blocker_count
1618
+ from blockers
1619
+ group by blockers.target_id
1620
+ ),
1621
+ ancestry_metadata as (
1622
+ select
1623
+ ancestry.target_id,
1624
+ count(*)::integer as ancestry_count,
1625
+ bool_or(ancestry.parent_session_id is null) as reached_root,
1626
+ max(ancestry.depth)::integer as max_depth
1627
+ from ancestry
1628
+ group by ancestry.target_id
1629
+ )
1630
+ select
1631
+ target.id as "targetId",
1632
+ coalesce(metadata.ancestry_count, 0)::integer as "ancestryCount",
1633
+ coalesce(metadata.reached_root, false) as "reachedRoot",
1634
+ metadata.max_depth as "maxDepth",
1635
+ coalesce(counts.blocker_count, 0)::integer as "blockerCount",
1636
+ primary_blocker.kind as "primaryKind",
1637
+ primary_blocker.session_id as "primarySessionId",
1638
+ primary_blocker.display_name as "primaryDisplayName",
1639
+ primary_blocker.display_name_original_chars as "primaryDisplayNameOriginalChars"
1640
+ from targets target
1641
+ left join ancestry_metadata metadata on metadata.target_id = target.id
1642
+ left join blocker_counts counts on counts.target_id = target.id
1643
+ left join primary_blockers primary_blocker on primary_blocker.target_id = target.id
1644
+ order by target.id
1645
+ `);
1646
+ const result = /* @__PURE__ */ new Map();
1647
+ for (const row of rows) {
1648
+ const ancestryCount = Number(row.ancestryCount);
1649
+ const maxDepth = row.maxDepth === null ? null : Number(row.maxDepth);
1650
+ if (ancestryCount === 0) {
1651
+ throw new SessionControlInvariantError(
1652
+ `Session ${row.targetId} does not exist in its workspace`
1653
+ );
1654
+ }
1655
+ if (!row.reachedRoot) {
1656
+ throw new SessionControlInvariantError(
1657
+ maxDepth !== null && maxDepth >= SESSION_ANCESTRY_LIMIT ? `Session ${row.targetId} ancestry exceeds ${SESSION_ANCESTRY_LIMIT}` : `Session ${row.targetId} has a missing ancestor`
1658
+ );
1659
+ }
1660
+ const blockerCount = Number(row.blockerCount);
1661
+ const primaryBlocker = row.primaryKind ? {
1662
+ kind: row.primaryKind,
1663
+ ...row.primarySessionId ? { sessionId: row.primarySessionId } : {},
1664
+ displayName: row.primaryDisplayName ?? (row.primaryKind === "workspace" ? "Workspace" : "Untitled session"),
1665
+ displayNameOriginalChars: Number(
1666
+ row.primaryDisplayNameOriginalChars ?? (row.primaryKind === "workspace" ? 9 : "Untitled session".length)
1667
+ )
1668
+ } : null;
1669
+ if (blockerCount > 0 !== (primaryBlocker !== null)) {
1670
+ throw new SessionControlInvariantError(
1671
+ `Session ${row.targetId} discovery blocker aggregate is inconsistent`
1672
+ );
1673
+ }
1674
+ result.set(row.targetId, {
1675
+ state: blockerCount > 0 ? "paused" : "active",
1676
+ primaryBlocker,
1677
+ additionalBlockerCount: Math.max(0, blockerCount - 1)
1678
+ });
1679
+ }
1680
+ if (result.size !== uniqueIds.length) {
1681
+ throw new SessionControlInvariantError("Session discovery control projection is incomplete");
1682
+ }
1683
+ return result;
1684
+ }
1685
+ async function evaluateSessionControl(db, workspaceId, sessionId, options = {}) {
1686
+ return (await evaluateSessionControls(db, workspaceId, [sessionId], options)).get(sessionId);
1687
+ }
1688
+ async function findCommandReceipt(db, input) {
1689
+ const actorSubjectId = input.actor.type === "agent_attempt" ? null : input.actor.subjectId;
1690
+ const actorAttemptId = input.actor.type === "agent_attempt" ? input.actor.attemptId : null;
1691
+ const identity = input.identityScope === "target_operation" ? and3(
1692
+ eq3(sessionCommandReceipts.workspaceId, input.workspaceId),
1693
+ eq3(sessionCommandReceipts.actorType, "agent_attempt"),
1694
+ eq3(sessionCommandReceipts.action, input.action),
1695
+ eq3(sessionCommandReceipts.targetSessionId, input.targetSessionId),
1696
+ sql3`${sessionCommandReceipts.targetTurnId} is null`,
1697
+ eq3(sessionCommandReceipts.operationKey, input.operationKey)
1698
+ ) : and3(
1699
+ eq3(sessionCommandReceipts.workspaceId, input.workspaceId),
1700
+ eq3(sessionCommandReceipts.actorType, input.actor.type),
1701
+ sql3`${sessionCommandReceipts.actorSubjectId} is not distinct from ${actorSubjectId}`,
1702
+ sql3`${sessionCommandReceipts.actorAttemptId} is not distinct from ${actorAttemptId}::uuid`,
1703
+ eq3(sessionCommandReceipts.action, input.action),
1704
+ sql3`${sessionCommandReceipts.targetSessionId} is not distinct from ${input.targetSessionId}::uuid`,
1705
+ sql3`${sessionCommandReceipts.targetTurnId} is not distinct from ${input.targetTurnId}::uuid`,
1706
+ eq3(sessionCommandReceipts.operationKey, input.operationKey)
1707
+ );
1708
+ const rows = await db.select().from(sessionCommandReceipts).where(identity).for("update");
1709
+ return rows[0] ?? null;
1710
+ }
1711
+ async function findPromptCommandReceipts(db, input) {
1712
+ const actorSubjectId = input.actor.type === "agent_attempt" ? null : input.actor.subjectId;
1713
+ const actorAttemptId = input.actor.type === "agent_attempt" ? input.actor.attemptId : null;
1714
+ const query = db.select().from(sessionCommandReceipts).where(
1715
+ and3(
1716
+ eq3(sessionCommandReceipts.workspaceId, input.workspaceId),
1717
+ eq3(sessionCommandReceipts.actorType, input.actor.type),
1718
+ sql3`${sessionCommandReceipts.actorSubjectId} is not distinct from ${actorSubjectId}`,
1719
+ sql3`${sessionCommandReceipts.actorAttemptId} is not distinct from ${actorAttemptId}::uuid`,
1720
+ inArray2(sessionCommandReceipts.action, ["prompt.send", "prompt.steer"]),
1721
+ eq3(sessionCommandReceipts.operationKey, input.operationKey)
1722
+ )
1723
+ );
1724
+ const rows = input.lock === "update" ? await query.for("update") : await query;
1725
+ return rows;
1726
+ }
1727
+ function fingerprintedPromptCommandReceipt(receipts) {
1728
+ const fingerprinted = receipts.filter(
1729
+ (receipt) => typeof receipt.result.boundaryRequestHash === "string"
1730
+ );
1731
+ if (fingerprinted.length > 1) {
1732
+ throw new SessionControlInvariantError(
1733
+ "Prompt operation key resolved to multiple fingerprinted receipts"
1734
+ );
1735
+ }
1736
+ return fingerprinted[0] ?? null;
1737
+ }
1738
+ function assertPromptCommandReceiptIdentity(receipt, input) {
1739
+ if (receipt.action !== input.action || receipt.targetSessionId !== input.targetSessionId || receipt.targetTurnId !== null || typeof receipt.result.boundaryRequestHash === "string" && receipt.result.boundaryRequestHash !== input.boundaryRequestHash) {
1740
+ throw new SessionCommandIdempotencyError();
1741
+ }
1742
+ }
1743
+ async function reserveSessionPromptCommandReceipt(db, input) {
1744
+ await lockSessionPromptCommandOperation(db, input);
1745
+ const existingReceipts = await findPromptCommandReceipts(db, {
1746
+ workspaceId: input.workspaceId,
1747
+ actor: input.actor,
1748
+ operationKey: input.operationKey,
1749
+ lock: "update"
1750
+ });
1751
+ const fingerprinted = fingerprintedPromptCommandReceipt(existingReceipts);
1752
+ if (fingerprinted) {
1753
+ assertPromptCommandReceiptIdentity(fingerprinted, input);
1754
+ return { receipt: fingerprinted, replay: true };
1755
+ }
1756
+ const legacyExact = existingReceipts.find(
1757
+ (receipt) => receipt.action === input.action && receipt.targetSessionId === input.targetSessionId && receipt.targetTurnId === null
1758
+ );
1759
+ if (legacyExact) {
1760
+ if (legacyExact.canonicalRequestHash !== input.canonicalRequestHash) {
1761
+ throw new SessionCommandIdempotencyError();
1762
+ }
1763
+ return { receipt: legacyExact, replay: true };
1764
+ }
1765
+ return await reserveSessionCommandReceipt(db, {
1766
+ accountId: input.accountId,
1767
+ workspaceId: input.workspaceId,
1768
+ actor: input.actor,
1769
+ action: input.action,
1770
+ targetSessionId: input.targetSessionId,
1771
+ targetTurnId: null,
1772
+ operationKey: input.operationKey,
1773
+ canonicalRequestHash: input.canonicalRequestHash,
1774
+ ...existingReceipts.length === 0 ? { initialResult: { boundaryRequestHash: input.boundaryRequestHash } } : {}
1775
+ });
1776
+ }
1777
+ async function lockSessionPromptCommandOperation(db, input) {
1778
+ const actorIdentity = input.actor.type === "agent_attempt" ? `attempt:${input.actor.attemptId}` : `${input.actor.type}:${input.actor.subjectId}`;
1779
+ await db.execute(
1780
+ sql3`select pg_advisory_xact_lock(hashtextextended(${`session-prompt:${input.workspaceId}:${actorIdentity}:${input.operationKey}`}, 0))`
1781
+ );
1782
+ }
1783
+ async function getCompletedSessionPromptCommandReceipt(db, input) {
1784
+ const receipt = fingerprintedPromptCommandReceipt(
1785
+ await findPromptCommandReceipts(db, {
1786
+ workspaceId: input.workspaceId,
1787
+ actor: input.actor,
1788
+ operationKey: input.operationKey
1789
+ })
1790
+ );
1791
+ if (!receipt) return null;
1792
+ assertPromptCommandReceiptIdentity(receipt, input);
1793
+ if (typeof receipt.result.boundaryRequestHash !== "string" || receipt.appliedQueueVersion === null) {
1794
+ return null;
1795
+ }
1796
+ return receipt;
1797
+ }
1798
+ async function reserveSessionCommandReceipt(db, input) {
1799
+ if (!input.operationKey.trim()) throw new Error("operationKey must not be empty");
1800
+ const identityScope = input.identityScope ?? "actor";
1801
+ if (identityScope === "target_operation" && (input.actor.type !== "agent_attempt" || !["goal.update", "goal.progress", "session.wait_for_input"].includes(input.action) || input.targetSessionId === null || input.targetTurnId !== null)) {
1802
+ throw new SessionControlInvariantError(
1803
+ "Target-scoped receipt identity is reserved for agent self commands"
1804
+ );
1805
+ }
1806
+ const actorSubjectId = input.actor.type === "agent_attempt" ? null : input.actor.subjectId;
1807
+ const actorAttemptId = input.actor.type === "agent_attempt" ? input.actor.attemptId : null;
1808
+ const [inserted] = await db.insert(sessionCommandReceipts).values({
1809
+ accountId: input.accountId,
1810
+ workspaceId: input.workspaceId,
1811
+ actorType: input.actor.type,
1812
+ actorSubjectId,
1813
+ actorAttemptId,
1814
+ action: input.action,
1815
+ targetSessionId: input.targetSessionId,
1816
+ targetTurnId: input.targetTurnId,
1817
+ operationKey: input.operationKey,
1818
+ canonicalRequestHash: input.canonicalRequestHash,
1819
+ result: input.initialResult ?? {}
1820
+ }).onConflictDoNothing().returning();
1821
+ const receipt = inserted ?? await findCommandReceipt(db, {
1822
+ workspaceId: input.workspaceId,
1823
+ actor: input.actor,
1824
+ action: input.action,
1825
+ targetSessionId: input.targetSessionId,
1826
+ targetTurnId: input.targetTurnId,
1827
+ operationKey: input.operationKey,
1828
+ identityScope
1829
+ });
1830
+ if (!receipt) throw new SessionControlInvariantError("Command receipt conflict was not readable");
1831
+ if (receipt.canonicalRequestHash !== input.canonicalRequestHash) {
1832
+ throw new SessionCommandIdempotencyError();
1833
+ }
1834
+ return { receipt, replay: !inserted };
1835
+ }
1836
+ function nextRevision(workspace) {
1837
+ const current = asSafeRevision(workspace.revision, "workspace control revision");
1838
+ if (current >= Number.MAX_SAFE_INTEGER) {
1839
+ throw new SessionControlInvariantError("Workspace control revision is exhausted");
1840
+ }
1841
+ return current + 1;
1842
+ }
1843
+ async function advanceWorkspaceRevision(db, workspaceId, revision) {
1844
+ const rows = await db.update(workspaceInferenceControls).set({ revision, updatedAt: /* @__PURE__ */ new Date() }).where(
1845
+ and3(
1846
+ eq3(workspaceInferenceControls.workspaceId, workspaceId),
1847
+ eq3(workspaceInferenceControls.revision, revision - 1)
1848
+ )
1849
+ ).returning({ workspaceId: workspaceInferenceControls.workspaceId });
1850
+ if (rows.length !== 1) {
1851
+ throw new SessionControlInvariantError(
1852
+ "Workspace control revision did not advance exactly once"
1853
+ );
1854
+ }
1855
+ }
1856
+ async function registerContinuableWakes(db, input) {
1857
+ const rows = await db.execute(sql3`
1858
+ with eligible as (
1859
+ select *
1860
+ from opengeni_private.list_continuable_sessions(
1861
+ ${input.workspaceId}::uuid,
1862
+ ${input.rootSessionId}::uuid
1863
+ )
1864
+ ), upserted as (
1865
+ insert into ${sessionWorkflowWakeOutbox} (
1866
+ session_id, account_id, workspace_id, temporal_workflow_id, reason
1867
+ )
1868
+ select session_id, account_id, workspace_id, temporal_workflow_id, ${input.reason}
1869
+ from eligible
1870
+ on conflict (session_id) do update set
1871
+ wake_revision = ${sessionWorkflowWakeOutbox}.wake_revision + 1,
1872
+ temporal_workflow_id = excluded.temporal_workflow_id,
1873
+ reason = excluded.reason,
1874
+ attempts = 0,
1875
+ next_attempt_at = now(),
1876
+ last_error = null,
1877
+ updated_at = now()
1878
+ returning session_id
1879
+ )
1880
+ select count(*)::bigint as "wakeCount" from upserted
1881
+ `);
1882
+ return Number(rows[0]?.wakeCount ?? 0);
1883
+ }
1884
+ async function registerDescendantWakes(db, input) {
1885
+ return await registerContinuableWakes(db, {
1886
+ workspaceId: input.workspaceId,
1887
+ rootSessionId: input.sessionId,
1888
+ reason: input.reason
1889
+ });
1890
+ }
1891
+ async function registerWorkspaceWakes(db, input) {
1892
+ return await registerContinuableWakes(db, {
1893
+ workspaceId: input.workspaceId,
1894
+ rootSessionId: null,
1895
+ reason: input.reason
1896
+ });
1897
+ }
1898
+ async function registerInterruptionWakes(db, input) {
1899
+ const rows = await db.execute(sql3`
1900
+ with eligible as (
1901
+ select distinct
1902
+ session.id as session_id,
1903
+ session.account_id,
1904
+ session.workspace_id,
1905
+ coalesce(session.temporal_workflow_id, 'session-' || session.id::text)
1906
+ as temporal_workflow_id
1907
+ from ${sessionAttemptInterruptions} interruption
1908
+ join ${sessions} session
1909
+ on session.workspace_id = interruption.workspace_id
1910
+ and session.id = interruption.session_id
1911
+ where interruption.operation_id = ${input.operationId}::uuid
1912
+ and interruption.state in ('pending', 'delivered', 'acknowledged')
1913
+ ), upserted as (
1914
+ insert into ${sessionWorkflowWakeOutbox} (
1915
+ session_id, account_id, workspace_id, temporal_workflow_id, reason
1916
+ )
1917
+ select session_id, account_id, workspace_id, temporal_workflow_id, ${input.reason}
1918
+ from eligible
1919
+ on conflict (session_id) do update set
1920
+ wake_revision = ${sessionWorkflowWakeOutbox}.wake_revision + 1,
1921
+ temporal_workflow_id = excluded.temporal_workflow_id,
1922
+ reason = excluded.reason,
1923
+ attempts = 0,
1924
+ next_attempt_at = now(),
1925
+ last_error = null,
1926
+ updated_at = now()
1927
+ returning session_id
1928
+ )
1929
+ select count(*)::bigint as "wakeCount" from upserted
1930
+ `);
1931
+ return Number(rows[0]?.wakeCount ?? 0);
1932
+ }
1933
+ async function registerCancellationWakes(db, input) {
1934
+ if (input.sessionIds.length === 0) return 0;
1935
+ const sessionIds = sql3.join(
1936
+ input.sessionIds.map((id) => sql3`${id}::uuid`),
1937
+ sql3`, `
1938
+ );
1939
+ const rows = await db.execute(sql3`
1940
+ with eligible as (
1941
+ select
1942
+ session.id as session_id,
1943
+ session.account_id,
1944
+ session.workspace_id,
1945
+ coalesce(session.temporal_workflow_id, 'session-' || session.id::text)
1946
+ as temporal_workflow_id
1947
+ from ${sessions} session
1948
+ where session.workspace_id = ${input.workspaceId}
1949
+ and session.id in (${sessionIds})
1950
+ ), upserted as (
1951
+ insert into ${sessionWorkflowWakeOutbox} (
1952
+ session_id, account_id, workspace_id, temporal_workflow_id, reason, control_revision
1953
+ )
1954
+ select session_id, account_id, workspace_id, temporal_workflow_id, 'session_cancelled', 1
1955
+ from eligible
1956
+ on conflict (session_id) do update set
1957
+ wake_revision = ${sessionWorkflowWakeOutbox}.wake_revision + 1,
1958
+ control_revision = ${sessionWorkflowWakeOutbox}.wake_revision + 1,
1959
+ temporal_workflow_id = excluded.temporal_workflow_id,
1960
+ reason = excluded.reason,
1961
+ attempts = 0,
1962
+ next_attempt_at = now(),
1963
+ last_error = null,
1964
+ updated_at = now()
1965
+ returning session_id
1966
+ )
1967
+ select count(*)::bigint as "wakeCount" from upserted
1968
+ `);
1969
+ return Number(rows[0]?.wakeCount ?? 0);
1970
+ }
1971
+ async function registerSessionWorkflowWakeInTransaction(db, input) {
1972
+ const [row] = await db.insert(sessionWorkflowWakeOutbox).values({
1973
+ accountId: input.accountId,
1974
+ workspaceId: input.workspaceId,
1975
+ sessionId: input.sessionId,
1976
+ temporalWorkflowId: input.temporalWorkflowId,
1977
+ controlRevision: input.controlRequested ? 1 : 0,
1978
+ reason: input.reason
1979
+ }).onConflictDoUpdate({
1980
+ target: sessionWorkflowWakeOutbox.sessionId,
1981
+ set: {
1982
+ temporalWorkflowId: input.temporalWorkflowId,
1983
+ wakeRevision: sql3`${sessionWorkflowWakeOutbox.wakeRevision} + 1`,
1984
+ controlRevision: input.controlRequested ? sql3`${sessionWorkflowWakeOutbox.wakeRevision} + 1` : sql3`${sessionWorkflowWakeOutbox.controlRevision}`,
1985
+ reason: input.reason,
1986
+ attempts: 0,
1987
+ nextAttemptAt: /* @__PURE__ */ new Date(),
1988
+ lastError: null,
1989
+ updatedAt: /* @__PURE__ */ new Date()
1990
+ }
1991
+ }).returning({ wakeRevision: sessionWorkflowWakeOutbox.wakeRevision });
1992
+ if (!row) {
1993
+ throw new SessionControlInvariantError(`Failed to register wake for ${input.sessionId}`);
1994
+ }
1995
+ return Number(row.wakeRevision);
1996
+ }
1997
+ async function registerInternalUpdateWakeInTransaction(db, input) {
1998
+ const [existing] = await db.select().from(sessionWorkflowWakeOutbox).where(
1999
+ and3(
2000
+ eq3(sessionWorkflowWakeOutbox.workspaceId, input.workspaceId),
2001
+ eq3(sessionWorkflowWakeOutbox.sessionId, input.sessionId)
2002
+ )
2003
+ ).for("update").limit(1);
2004
+ if (existing && existing.wakeRevision > existing.deliveredRevision) {
2005
+ await db.update(sessionWorkflowWakeOutbox).set({
2006
+ temporalWorkflowId: input.temporalWorkflowId,
2007
+ reason: "internal_update_batch",
2008
+ nextAttemptAt: /* @__PURE__ */ new Date(),
2009
+ lastError: null,
2010
+ updatedAt: /* @__PURE__ */ new Date()
2011
+ }).where(eq3(sessionWorkflowWakeOutbox.sessionId, input.sessionId));
2012
+ return { wakeRevision: existing.wakeRevision, shouldSignal: false };
2013
+ }
2014
+ return {
2015
+ wakeRevision: await registerSessionWorkflowWakeInTransaction(db, {
2016
+ ...input,
2017
+ reason: "internal_update_batch"
2018
+ }),
2019
+ shouldSignal: true
2020
+ };
2021
+ }
2022
+ async function interruptDescendantAttempts(db, input) {
2023
+ const rows = await db.execute(sql3`
2024
+ with recursive live_attempts as (
2025
+ select attempt.id, attempt.account_id, attempt.workspace_id, attempt.session_id
2026
+ from ${sessionTurnAttempts} attempt
2027
+ where attempt.workspace_id = ${input.workspaceId}
2028
+ and attempt.state in ('claimed', 'running')
2029
+ ), attempt_ancestry(attempt_id, account_id, workspace_id, session_id, ancestor_id, depth, path) as (
2030
+ select
2031
+ attempt.id,
2032
+ attempt.account_id,
2033
+ attempt.workspace_id,
2034
+ attempt.session_id,
2035
+ attempt.session_id,
2036
+ 0::integer,
2037
+ array[attempt.session_id]::uuid[]
2038
+ from live_attempts attempt
2039
+ union all
2040
+ select
2041
+ ancestry.attempt_id,
2042
+ ancestry.account_id,
2043
+ ancestry.workspace_id,
2044
+ ancestry.session_id,
2045
+ current.parent_session_id,
2046
+ ancestry.depth + 1,
2047
+ ancestry.path || current.parent_session_id
2048
+ from attempt_ancestry ancestry
2049
+ join ${sessions} current
2050
+ on current.workspace_id = ${input.workspaceId} and current.id = ancestry.ancestor_id
2051
+ where current.parent_session_id is not null
2052
+ and not current.parent_session_id = any(ancestry.path)
2053
+ and ancestry.depth < ${SESSION_ANCESTRY_LIMIT}
2054
+ ), inserted as (
2055
+ insert into ${sessionAttemptInterruptions} (
2056
+ account_id, workspace_id, session_id, operation_id, attempt_id,
2057
+ kind, control_revision
2058
+ )
2059
+ select ancestry.account_id, ancestry.workspace_id, ancestry.session_id,
2060
+ ${input.operationId}::uuid, ancestry.attempt_id, ${input.kind}, ${input.controlRevision}
2061
+ from attempt_ancestry ancestry
2062
+ where ancestry.ancestor_id = ${input.sessionId}
2063
+ on conflict (operation_id, attempt_id) do nothing
2064
+ returning id
2065
+ )
2066
+ select count(*)::integer as count from inserted
2067
+ `);
2068
+ return Number(rows[0]?.count ?? 0);
2069
+ }
2070
+ async function interruptWorkspaceAttempts(db, input) {
2071
+ const rows = await db.execute(sql3`
2072
+ with inserted as (
2073
+ insert into ${sessionAttemptInterruptions} (
2074
+ account_id, workspace_id, session_id, operation_id, attempt_id,
2075
+ kind, control_revision
2076
+ )
2077
+ select attempt.account_id, attempt.workspace_id, attempt.session_id,
2078
+ ${input.operationId}::uuid, attempt.id, 'workspace_pause',
2079
+ ${input.controlRevision}
2080
+ from ${sessionTurnAttempts} attempt
2081
+ where attempt.workspace_id = ${input.workspaceId}
2082
+ and attempt.state in ('claimed', 'running')
2083
+ on conflict (operation_id, attempt_id) do nothing
2084
+ returning id
2085
+ )
2086
+ select count(*)::integer as count from inserted
2087
+ `);
2088
+ return Number(rows[0]?.count ?? 0);
2089
+ }
2090
+ async function updateSessionCommandReceiptResult(db, receiptId, input) {
2091
+ const [receipt] = await db.update(sessionCommandReceipts).set({
2092
+ ...input.controlRevision !== void 0 ? { appliedControlRevision: input.controlRevision } : {},
2093
+ ...input.queueVersion !== void 0 ? { appliedQueueVersion: input.queueVersion } : {},
2094
+ ...input.turnVersion !== void 0 ? { appliedTurnVersion: input.turnVersion } : {},
2095
+ ...input.draftRevision !== void 0 ? { appliedDraftRevision: input.draftRevision } : {},
2096
+ result: input.result,
2097
+ updatedAt: /* @__PURE__ */ new Date()
2098
+ }).where(eq3(sessionCommandReceipts.id, receiptId)).returning();
2099
+ if (!receipt) throw new SessionControlInvariantError("Command receipt disappeared");
2100
+ return receipt;
2101
+ }
2102
+ async function pendingSessionControlWorkflowWake(db, workspaceId, sessionId, wakeCount) {
2103
+ if (wakeCount === 0) return null;
2104
+ const [wake] = await db.select({
2105
+ accountId: sessionWorkflowWakeOutbox.accountId,
2106
+ workspaceId: sessionWorkflowWakeOutbox.workspaceId,
2107
+ sessionId: sessionWorkflowWakeOutbox.sessionId,
2108
+ temporalWorkflowId: sessionWorkflowWakeOutbox.temporalWorkflowId,
2109
+ wakeRevision: sessionWorkflowWakeOutbox.wakeRevision,
2110
+ deliveredRevision: sessionWorkflowWakeOutbox.deliveredRevision
2111
+ }).from(sessionWorkflowWakeOutbox).where(
2112
+ and3(
2113
+ eq3(sessionWorkflowWakeOutbox.workspaceId, workspaceId),
2114
+ eq3(sessionWorkflowWakeOutbox.sessionId, sessionId)
2115
+ )
2116
+ ).limit(1);
2117
+ if (!wake || wake.wakeRevision <= wake.deliveredRevision) return null;
2118
+ return {
2119
+ accountId: wake.accountId,
2120
+ workspaceId: wake.workspaceId,
2121
+ sessionId: wake.sessionId,
2122
+ temporalWorkflowId: wake.temporalWorkflowId,
2123
+ wakeRevision: wake.wakeRevision
2124
+ };
2125
+ }
2126
+ async function sessionPauseEffectsNeeded(db, input) {
2127
+ const rows = await db.execute(sql3`
2128
+ with recursive subtree(id, path, cycle) as (
2129
+ select session.id, array[session.id]::uuid[], false
2130
+ from ${sessions} session
2131
+ where session.workspace_id = ${input.workspaceId}
2132
+ and session.id = ${input.sessionId}
2133
+ union all
2134
+ select child.id, parent.path || child.id, child.id = any(parent.path)
2135
+ from subtree parent
2136
+ join ${sessions} child
2137
+ on child.workspace_id = ${input.workspaceId}
2138
+ and child.parent_session_id = parent.id
2139
+ where not parent.cycle
2140
+ and cardinality(parent.path) <= ${SESSION_ANCESTRY_LIMIT}
2141
+ ), scoped_sessions as (
2142
+ select session.id, session.subtree_run_override_revision
2143
+ from subtree
2144
+ join ${sessions} session
2145
+ on session.workspace_id = ${input.workspaceId}
2146
+ and session.id = subtree.id
2147
+ where not subtree.cycle
2148
+ )
2149
+ select exists (
2150
+ select 1
2151
+ from scoped_sessions session
2152
+ where session.subtree_run_override_revision > ${input.directPauseRevision}
2153
+ union all
2154
+ select 1
2155
+ from ${sessionTurnAttempts} attempt
2156
+ join scoped_sessions session on session.id = attempt.session_id
2157
+ where attempt.workspace_id = ${input.workspaceId}
2158
+ and attempt.state in ('claimed', 'running')
2159
+ and not exists (
2160
+ select 1
2161
+ from ${sessionAttemptInterruptions} interruption
2162
+ where interruption.workspace_id = attempt.workspace_id
2163
+ and interruption.attempt_id = attempt.id
2164
+ and interruption.kind in ('session_pause', 'workspace_pause')
2165
+ and interruption.state in ('pending', 'delivered', 'acknowledged')
2166
+ )
2167
+ union all
2168
+ select 1
2169
+ from ${sessionBackgroundCommands} command
2170
+ join scoped_sessions session on session.id = command.session_id
2171
+ where command.workspace_id = ${input.workspaceId}
2172
+ and command.state = 'running'
2173
+ ) as needed
2174
+ `);
2175
+ return rows[0]?.needed ?? false;
2176
+ }
2177
+ async function workspacePauseEffectsNeeded(db, input) {
2178
+ const rows = await db.execute(sql3`
2179
+ select exists (
2180
+ select 1
2181
+ from ${sessions} session
2182
+ where session.workspace_id = ${input.workspaceId}
2183
+ and session.subtree_run_override_revision > ${input.workspacePauseRevision}
2184
+ union all
2185
+ select 1
2186
+ from ${sessionTurnAttempts} attempt
2187
+ where attempt.workspace_id = ${input.workspaceId}
2188
+ and attempt.state in ('claimed', 'running')
2189
+ and not exists (
2190
+ select 1
2191
+ from ${sessionAttemptInterruptions} interruption
2192
+ where interruption.workspace_id = attempt.workspace_id
2193
+ and interruption.attempt_id = attempt.id
2194
+ and interruption.kind in ('session_pause', 'workspace_pause')
2195
+ and interruption.state in ('pending', 'delivered', 'acknowledged')
2196
+ )
2197
+ union all
2198
+ select 1
2199
+ from ${sessionBackgroundCommands} command
2200
+ where command.workspace_id = ${input.workspaceId}
2201
+ and command.state = 'running'
2202
+ ) as needed
2203
+ `);
2204
+ return rows[0]?.needed ?? false;
2205
+ }
2206
+ async function continuableWakeRepairNeeded(db, input) {
2207
+ const rows = await db.execute(sql3`
2208
+ select exists (
2209
+ select 1
2210
+ from opengeni_private.list_continuable_sessions(
2211
+ ${input.workspaceId}::uuid,
2212
+ ${input.rootSessionId}::uuid
2213
+ ) eligible
2214
+ left join ${sessionWorkflowWakeOutbox} wake
2215
+ on wake.workspace_id = eligible.workspace_id
2216
+ and wake.session_id = eligible.session_id
2217
+ where wake.session_id is null
2218
+ or wake.wake_revision <= wake.delivered_revision
2219
+ ) as needed
2220
+ `);
2221
+ return rows[0]?.needed ?? false;
2222
+ }
2223
+ async function loadSessionSubtreeIds(db, workspaceId, rootSessionId) {
2224
+ const rows = await db.execute(sql3`
2225
+ with recursive subtree(id, root_parent_session_id, depth, path, cycle) as (
2226
+ select session.id, session.parent_session_id, 0::integer, array[session.id]::uuid[], false
2227
+ from ${sessions} session
2228
+ where session.workspace_id = ${workspaceId} and session.id = ${rootSessionId}
2229
+ union all
2230
+ select child.id, parent.root_parent_session_id, parent.depth + 1,
2231
+ parent.path || child.id, child.id = any(parent.path)
2232
+ from subtree parent
2233
+ join ${sessions} child
2234
+ on child.workspace_id = ${workspaceId} and child.parent_session_id = parent.id
2235
+ where parent.depth < ${SESSION_ANCESTRY_LIMIT} and not parent.cycle
2236
+ )
2237
+ select id, root_parent_session_id as "rootParentSessionId", depth, cycle
2238
+ from subtree
2239
+ order by id
2240
+ `);
2241
+ if (rows.length === 0) {
2242
+ throw new SessionControlInvariantError(`Session ${rootSessionId} does not exist`);
2243
+ }
2244
+ if (rows.some((row) => row.cycle || Number(row.depth) >= SESSION_ANCESTRY_LIMIT)) {
2245
+ throw new SessionControlInvariantError(`Session ${rootSessionId} subtree is invalid`);
2246
+ }
2247
+ return {
2248
+ sessionIds: rows.map((row) => row.id),
2249
+ rootParentSessionId: rows[0]?.rootParentSessionId ?? null
2250
+ };
2251
+ }
2252
+ var TERMINAL_CANCELLATION_LIVE_TURN_STATUSES = [
2253
+ "queued",
2254
+ "running",
2255
+ "requires_action",
2256
+ "recovering",
2257
+ "waiting_capacity"
2258
+ ];
2259
+ async function loadCancellationTurnIds(db, workspaceId, sessionIds) {
2260
+ const rows = await db.select({ id: sessionTurns.id }).from(sessionTurns).where(
2261
+ and3(
2262
+ eq3(sessionTurns.workspaceId, workspaceId),
2263
+ inArray2(sessionTurns.sessionId, sessionIds),
2264
+ inArray2(sessionTurns.status, TERMINAL_CANCELLATION_LIVE_TURN_STATUSES)
2265
+ )
2266
+ ).orderBy(sessionTurns.id);
2267
+ return rows.map((row) => row.id);
2268
+ }
2269
+ async function loadParentSessionId(db, workspaceId, sessionId) {
2270
+ const [row] = await db.select({ parentSessionId: sessions.parentSessionId }).from(sessions).where(and3(eq3(sessions.workspaceId, workspaceId), eq3(sessions.id, sessionId))).limit(1);
2271
+ return row?.parentSessionId ?? null;
2272
+ }
2273
+ async function insertChildOutboxRowInTransaction(db, input) {
2274
+ const parentSessionId = input.childSession.parentSessionId;
2275
+ if (!parentSessionId) return;
2276
+ if (!input.parentSession || input.parentSession.id !== parentSessionId) {
2277
+ throw new SessionControlInvariantError(
2278
+ `Parent session ${parentSessionId} was not locked with child ${input.childSession.id}`
2279
+ );
2280
+ }
2281
+ if (input.parentSession.status === "cancelled") return;
2282
+ if (input.payload.type !== input.kind) {
2283
+ throw new SessionControlInvariantError(
2284
+ `Child outbox ${input.kind} payload discriminator mismatch`
2285
+ );
2286
+ }
2287
+ let personalConnectionDelegations = [];
2288
+ if (input.childSession.parentTurnId) {
2289
+ const [parentTurn] = await db.select({
2290
+ delegations: sessionTurns.personalConnectionDelegations
2291
+ }).from(sessionTurns).where(
2292
+ and3(
2293
+ eq3(sessionTurns.workspaceId, input.workspaceId),
2294
+ eq3(sessionTurns.sessionId, parentSessionId),
2295
+ eq3(sessionTurns.id, input.childSession.parentTurnId)
2296
+ )
2297
+ ).limit(1);
2298
+ if (parentTurn) {
2299
+ const parsed = McpPersonalConnectionDelegations.safeParse(parentTurn.delegations);
2300
+ if (!parsed.success) {
2301
+ throw new SessionControlInvariantError(
2302
+ `Invalid personal MCP delegation snapshot at session_turns:${input.workspaceId}:${parentSessionId}:${input.childSession.parentTurnId}`
2303
+ );
2304
+ }
2305
+ personalConnectionDelegations = parsed.data.map((delegation) => ({
2306
+ ...delegation
2307
+ }));
2308
+ }
2309
+ }
2310
+ await db.insert(sessionSystemUpdateOutbox).values(
2311
+ withLosslessContentWriteVersion(
2312
+ withLosslessContentWriteVersion(
2313
+ {
2314
+ accountId: input.childSession.accountId,
2315
+ workspaceId: input.workspaceId,
2316
+ sourceSessionId: input.childSession.id,
2317
+ targetSessionId: parentSessionId,
2318
+ dedupeKey: input.dedupeKey,
2319
+ kind: input.kind,
2320
+ classification: input.classification,
2321
+ sourceId: input.childSession.id,
2322
+ summary: input.summary,
2323
+ payload: input.payload,
2324
+ lineage: {
2325
+ childSessionId: input.childSession.id,
2326
+ parentSessionId,
2327
+ ...input.childSession.parentTurnId ? { parentTurnId: input.childSession.parentTurnId } : {},
2328
+ ...input.lineage ?? {}
2329
+ },
2330
+ personalConnectionDelegations
2331
+ },
2332
+ "summary",
2333
+ "summaryCodecVersion"
2334
+ ),
2335
+ "payload",
2336
+ "payloadCodecVersion"
2337
+ )
2338
+ ).onConflictDoNothing({
2339
+ target: [
2340
+ sessionSystemUpdateOutbox.workspaceId,
2341
+ sessionSystemUpdateOutbox.dedupeKey
2342
+ ]
2343
+ });
2344
+ }
2345
+ async function enqueueCancelledChildOutboxInTransaction(db, input) {
2346
+ await insertChildOutboxRowInTransaction(db, {
2347
+ workspaceId: input.workspaceId,
2348
+ childSession: input.rootSession,
2349
+ parentSession: input.parentSession,
2350
+ dedupeKey: `child-completion:${input.rootSession.id}:cancelled`,
2351
+ kind: "child_terminal_result",
2352
+ classification: "info",
2353
+ summary: "Child session was terminally cancelled.",
2354
+ payload: {
2355
+ type: "child_terminal_result",
2356
+ childSessionId: input.rootSession.id,
2357
+ status: "cancelled"
2358
+ }
2359
+ });
2360
+ }
2361
+ async function enqueueChildPausedOutboxInTransaction(db, input) {
2362
+ if (!childLifecycleNoticesEnabled()) return;
2363
+ const payload = {
2364
+ type: "child_paused",
2365
+ childSessionId: input.childSession.id,
2366
+ operationId: input.operationId,
2367
+ actorKind: input.actorKind,
2368
+ reason: boundedChildPausedReason(input.reason)
2369
+ };
2370
+ await insertChildOutboxRowInTransaction(db, {
2371
+ workspaceId: input.workspaceId,
2372
+ childSession: input.childSession,
2373
+ parentSession: input.parentSession,
2374
+ dedupeKey: childPausedDedupeKey({
2375
+ childSessionId: input.childSession.id,
2376
+ receiptId: input.operationId
2377
+ }),
2378
+ kind: "child_paused",
2379
+ classification: childPausedClassification(input.actorKind),
2380
+ summary: childPausedSummary(input.childSession.id, payload),
2381
+ payload
2382
+ });
2383
+ }
2384
+ async function enqueueCancelledChildRequiresActionResolvedOutboxInTransaction(db, input) {
2385
+ if (!childLifecycleNoticesEnabled() || !input.parentSession) return;
2386
+ for (const humanInput of input.cancelledHumanInputs) {
2387
+ if (humanInput.sessionId !== input.rootSession.id) continue;
2388
+ const payload = {
2389
+ type: "child_requires_action_resolved",
2390
+ childSessionId: input.rootSession.id,
2391
+ childTurnId: humanInput.turnId,
2392
+ childTurnGeneration: humanInput.turnGeneration,
2393
+ requestId: humanInput.id,
2394
+ approvalId: null,
2395
+ outcome: "cancelled",
2396
+ respondedByKind: "system"
2397
+ };
2398
+ await insertChildOutboxRowInTransaction(db, {
2399
+ workspaceId: input.workspaceId,
2400
+ childSession: input.rootSession,
2401
+ parentSession: input.parentSession,
2402
+ dedupeKey: childRequiresActionResolvedDedupeKey({
2403
+ childSessionId: input.rootSession.id,
2404
+ turnId: humanInput.turnId,
2405
+ turnGeneration: humanInput.turnGeneration,
2406
+ requestId: humanInput.id,
2407
+ approvalId: null
2408
+ }),
2409
+ kind: "child_requires_action_resolved",
2410
+ classification: "info",
2411
+ summary: childRequiresActionResolvedSummary(input.rootSession.id, payload),
2412
+ payload,
2413
+ lineage: { turnId: humanInput.turnId }
2414
+ });
2415
+ }
2416
+ }
2417
+ async function assertSessionBranchIsNotCancelled(db, workspaceId, sessionId) {
2418
+ const rows = await db.execute(sql3`
2419
+ with recursive ancestry(id, parent_id, status, depth, path, cycle) as (
2420
+ select session.id, session.parent_session_id, session.status, 0::integer,
2421
+ array[session.id]::uuid[], false
2422
+ from ${sessions} session
2423
+ where session.workspace_id = ${workspaceId} and session.id = ${sessionId}
2424
+ union all
2425
+ select parent.id, parent.parent_session_id, parent.status, child.depth + 1,
2426
+ child.path || parent.id, parent.id = any(child.path)
2427
+ from ancestry child
2428
+ join ${sessions} parent
2429
+ on parent.workspace_id = ${workspaceId} and parent.id = child.parent_id
2430
+ where child.depth < ${SESSION_ANCESTRY_LIMIT} and not child.cycle
2431
+ )
2432
+ select
2433
+ coalesce(bool_or(status = 'cancelled'), false) as cancelled,
2434
+ count(*) = 0 or coalesce(bool_or(cycle), false)
2435
+ or coalesce(max(depth), 0) >= ${SESSION_ANCESTRY_LIMIT} as invalid
2436
+ from ancestry
2437
+ `);
2438
+ if (rows[0]?.invalid) {
2439
+ throw new SessionControlInvariantError(`Session ${sessionId} ancestry is invalid`);
2440
+ }
2441
+ if (rows[0]?.cancelled) {
2442
+ throw new SessionControlConflictError("Cancelled session subtree cannot accept work");
2443
+ }
2444
+ }
2445
+ async function cancelSessionSubtreeInTransaction(db, input) {
2446
+ const candidateTurnIdSet = new Set(input.candidateTurnIds);
2447
+ const candidateTurns = input.lockedTurns.filter((turn) => candidateTurnIdSet.has(turn.id));
2448
+ if (candidateTurns.length !== candidateTurnIdSet.size) {
2449
+ throw new SessionControlInvariantError("Cancellation candidate turns changed while locking");
2450
+ }
2451
+ const suffixLocks = await lockSessionEventWriteRows(db, {
2452
+ workspaceId: input.workspaceId,
2453
+ controlLock: "already_locked",
2454
+ workspaceLock: "already_locked",
2455
+ sessionIds: input.lockedSessions.map((session) => session.id),
2456
+ turnIds: input.candidateTurnIds
2457
+ });
2458
+ if (suffixLocks.sessions.length !== input.lockedSessions.length || suffixLocks.turns.length !== candidateTurnIdSet.size) {
2459
+ throw new SessionControlInvariantError("Cancellation rows changed under canonical locks");
2460
+ }
2461
+ const sessionIdSet = new Set(input.sessionIds);
2462
+ const rootSession = input.lockedSessions.find((session) => session.id === input.rootSessionId);
2463
+ if (!rootSession) {
2464
+ throw new SessionControlInvariantError(
2465
+ `Cancellation root ${input.rootSessionId} was not locked with its subtree`
2466
+ );
2467
+ }
2468
+ const rootParentSession = rootSession.parentSessionId ? input.lockedSessions.find((session) => session.id === rootSession.parentSessionId) ?? null : null;
2469
+ const candidateTurnById = new Map(candidateTurns.map((turn) => [turn.id, turn]));
2470
+ const liveTurnIds = new Set(
2471
+ input.lockedSessions.filter((session) => session.activeTurnId !== null).flatMap((session) => {
2472
+ const turn = session.activeTurnId ? candidateTurnById.get(session.activeTurnId) : void 0;
2473
+ if (turn?.activeAttemptId === null) return [];
2474
+ return turn ? [turn.id] : [];
2475
+ })
2476
+ );
2477
+ const immediatelyCancelledTurns = candidateTurns.filter((turn) => !liveTurnIds.has(turn.id));
2478
+ const cancelledTurnsBySession = /* @__PURE__ */ new Map();
2479
+ for (const turn of immediatelyCancelledTurns) {
2480
+ const turns = cancelledTurnsBySession.get(turn.sessionId);
2481
+ if (turns) turns.push(turn);
2482
+ else cancelledTurnsBySession.set(turn.sessionId, [turn]);
2483
+ }
2484
+ const immediatelyCancelledTurnIds = immediatelyCancelledTurns.map((turn) => turn.id);
2485
+ const now = /* @__PURE__ */ new Date();
2486
+ let cancelledHumanInputs = [];
2487
+ if (immediatelyCancelledTurnIds.length > 0) {
2488
+ await db.update(sessionTurns).set({
2489
+ status: "cancelled",
2490
+ activeAttemptId: null,
2491
+ cancelledBy: input.actor,
2492
+ cancelReason: input.reason ?? "session_cancelled",
2493
+ version: sql3`${sessionTurns.version} + 1`,
2494
+ finishedAt: now,
2495
+ updatedAt: now
2496
+ }).where(inArray2(sessionTurns.id, immediatelyCancelledTurnIds));
2497
+ cancelledHumanInputs = await db.update(sessionHumanInputRequests).set({
2498
+ status: "cancelled",
2499
+ response: { outcome: "cancelled" },
2500
+ respondedBy: input.actor,
2501
+ respondedAt: now,
2502
+ updatedAt: now
2503
+ }).where(
2504
+ and3(
2505
+ eq3(sessionHumanInputRequests.workspaceId, input.workspaceId),
2506
+ inArray2(sessionHumanInputRequests.turnId, immediatelyCancelledTurnIds),
2507
+ eq3(sessionHumanInputRequests.status, "pending")
2508
+ )
2509
+ ).returning({
2510
+ id: sessionHumanInputRequests.id,
2511
+ sessionId: sessionHumanInputRequests.sessionId,
2512
+ turnId: sessionHumanInputRequests.turnId,
2513
+ turnGeneration: sessionHumanInputRequests.turnGeneration,
2514
+ questions: sessionHumanInputRequests.questions
2515
+ });
2516
+ await db.update(codexCapacityWaiters).set({
2517
+ status: "superseded",
2518
+ lastWakeReason: "session_cancelled",
2519
+ updatedAt: now
2520
+ }).where(
2521
+ and3(
2522
+ eq3(codexCapacityWaiters.workspaceId, input.workspaceId),
2523
+ inArray2(codexCapacityWaiters.blockedTurnId, immediatelyCancelledTurnIds),
2524
+ eq3(codexCapacityWaiters.status, "waiting")
2525
+ )
2526
+ );
2527
+ }
2528
+ const cancelledSystemUpdates = await db.update(sessionSystemUpdates).set({ state: "cancelled" }).where(
2529
+ and3(
2530
+ eq3(sessionSystemUpdates.workspaceId, input.workspaceId),
2531
+ inArray2(sessionSystemUpdates.sessionId, input.sessionIds),
2532
+ eq3(sessionSystemUpdates.state, "pending")
2533
+ )
2534
+ ).returning({
2535
+ id: sessionSystemUpdates.id,
2536
+ sessionId: sessionSystemUpdates.sessionId
2537
+ });
2538
+ const humanInputsByTurn = /* @__PURE__ */ new Map();
2539
+ for (const humanInput of cancelledHumanInputs) {
2540
+ const inputs = humanInputsByTurn.get(humanInput.turnId);
2541
+ if (inputs) inputs.push(humanInput);
2542
+ else humanInputsByTurn.set(humanInput.turnId, [humanInput]);
2543
+ }
2544
+ const systemUpdatesBySession = /* @__PURE__ */ new Map();
2545
+ for (const update of cancelledSystemUpdates) {
2546
+ const updates = systemUpdatesBySession.get(update.sessionId);
2547
+ if (updates) updates.push(update);
2548
+ else systemUpdatesBySession.set(update.sessionId, [update]);
2549
+ }
2550
+ const affectedSessionEvents = [];
2551
+ for (const session of input.lockedSessions) {
2552
+ if (!sessionIdSet.has(session.id)) continue;
2553
+ let sequence = session.lastSequence + (session.id === input.rootSessionId ? 1 : 0);
2554
+ const cancelledTurns = cancelledTurnsBySession.get(session.id) ?? [];
2555
+ const preinsertedEvents = [];
2556
+ const eventValues = [];
2557
+ for (const update of (systemUpdatesBySession.get(session.id) ?? []).sort(
2558
+ (left, right) => left.id.localeCompare(right.id)
2559
+ )) {
2560
+ eventValues.push({
2561
+ accountId: input.accountId,
2562
+ workspaceId: input.workspaceId,
2563
+ sessionId: session.id,
2564
+ sequence: ++sequence,
2565
+ type: "system.update.cancelled",
2566
+ payload: { updateId: update.id, reason: "session_cancelled" },
2567
+ occurredAt: now
2568
+ });
2569
+ }
2570
+ for (const turn of cancelledTurns) {
2571
+ const closedTools = await closePendingSessionToolCallsInTransaction(db, {
2572
+ accountId: input.accountId,
2573
+ workspaceId: input.workspaceId,
2574
+ sessionId: session.id,
2575
+ turnId: turn.id,
2576
+ reason: "session_cancelled",
2577
+ sequence,
2578
+ now
2579
+ });
2580
+ sequence = closedTools.sequence;
2581
+ preinsertedEvents.push(
2582
+ ...closedTools.events.map((event) => ({
2583
+ id: event.id,
2584
+ sequence: event.sequence
2585
+ }))
2586
+ );
2587
+ for (const humanInput of (humanInputsByTurn.get(turn.id) ?? []).sort(
2588
+ (left, right) => left.id.localeCompare(right.id)
2589
+ )) {
2590
+ eventValues.push({
2591
+ accountId: input.accountId,
2592
+ workspaceId: input.workspaceId,
2593
+ sessionId: session.id,
2594
+ sequence: ++sequence,
2595
+ type: "user.humanInputResponse",
2596
+ turnId: turn.id,
2597
+ turnGeneration: turn.executionGeneration,
2598
+ ...turn.id === session.activeTurnId ? { turnAssociation: "current" } : {},
2599
+ payload: {
2600
+ requestId: humanInput.id,
2601
+ response: { outcome: "cancelled" }
2602
+ },
2603
+ occurredAt: now
2604
+ });
2605
+ }
2606
+ eventValues.push({
2607
+ accountId: input.accountId,
2608
+ workspaceId: input.workspaceId,
2609
+ sessionId: session.id,
2610
+ sequence: ++sequence,
2611
+ type: "turn.cancelled",
2612
+ turnId: turn.id,
2613
+ turnGeneration: turn.executionGeneration,
2614
+ ...turn.id === session.activeTurnId ? { turnAssociation: "current" } : {},
2615
+ payload: {
2616
+ reason: input.reason ?? "session_cancelled",
2617
+ operationId: input.operationId
2618
+ },
2619
+ occurredAt: now
2620
+ });
2621
+ }
2622
+ if (session.status !== "cancelled") {
2623
+ eventValues.push({
2624
+ accountId: input.accountId,
2625
+ workspaceId: input.workspaceId,
2626
+ sessionId: session.id,
2627
+ sequence: ++sequence,
2628
+ type: "session.status.changed",
2629
+ payload: {
2630
+ status: "cancelled",
2631
+ reason: input.reason ?? "session_cancelled",
2632
+ operationId: input.operationId
2633
+ },
2634
+ occurredAt: now
2635
+ });
2636
+ }
2637
+ const inserted = eventValues.length > 0 ? await db.insert(sessionEvents).values(withLosslessContentWriteVersion(eventValues, "payload", "payloadCodecVersion")).returning({
2638
+ id: sessionEvents.id,
2639
+ sequence: sessionEvents.sequence,
2640
+ type: sessionEvents.type,
2641
+ turnId: sessionEvents.turnId,
2642
+ payload: sessionEvents.payload
2643
+ }) : [];
2644
+ const cancelledHumanInputsById = new Map(
2645
+ cancelledHumanInputs.filter((request) => request.sessionId === session.id).map((request) => [request.id, request])
2646
+ );
2647
+ for (const event of inserted) {
2648
+ if (event.type !== "user.humanInputResponse") continue;
2649
+ const payload = event.payload;
2650
+ const request = typeof payload.requestId === "string" ? cancelledHumanInputsById.get(payload.requestId) : null;
2651
+ if (!request) continue;
2652
+ await mirrorSessionRealtimeContextInTransaction(db, {
2653
+ accountId: input.accountId,
2654
+ workspaceId: input.workspaceId,
2655
+ sessionId: session.id,
2656
+ sourceKind: "human_input_response",
2657
+ sourceId: event.id,
2658
+ turnId: event.turnId,
2659
+ channel: null,
2660
+ text: renderRealtimeHumanInputResponseContext({
2661
+ requestId: request.id,
2662
+ questions: request.questions,
2663
+ response: { outcome: "cancelled" }
2664
+ }),
2665
+ payload: {
2666
+ requestId: request.id,
2667
+ outcome: "cancelled",
2668
+ sourceEventId: event.id
2669
+ },
2670
+ now
2671
+ });
2672
+ }
2673
+ const liveTurnId = session.activeTurnId && liveTurnIds.has(session.activeTurnId) ? session.activeTurnId : null;
2674
+ await db.update(sessions).set({
2675
+ status: "cancelled",
2676
+ activeTurnId: liveTurnId,
2677
+ queueVersion: sql3`${sessions.queueVersion} + 1`,
2678
+ queueHeadPosition: 0,
2679
+ queueTailPosition: 0,
2680
+ lastSequence: sequence,
2681
+ updatedAt: now
2682
+ }).where(
2683
+ and3(eq3(sessions.workspaceId, input.workspaceId), eq3(sessions.id, session.id))
2684
+ );
2685
+ const eventIds = [...preinsertedEvents, ...inserted].sort((left, right) => left.sequence - right.sequence).map((event) => event.id);
2686
+ if (session.id === input.rootSessionId) eventIds.unshift(input.rootControlEventId);
2687
+ if (eventIds.length > 0) affectedSessionEvents.push({ sessionId: session.id, eventIds });
2688
+ }
2689
+ await enqueueCancelledChildRequiresActionResolvedOutboxInTransaction(db, {
2690
+ workspaceId: input.workspaceId,
2691
+ rootSession,
2692
+ parentSession: rootParentSession,
2693
+ cancelledHumanInputs
2694
+ });
2695
+ await enqueueCancelledChildOutboxInTransaction(db, {
2696
+ workspaceId: input.workspaceId,
2697
+ rootSession,
2698
+ parentSession: rootParentSession
2699
+ });
2700
+ return {
2701
+ cancelledSessionCount: input.sessionIds.length,
2702
+ cancelledTurnCount: candidateTurns.length,
2703
+ affectedSessionEvents
2704
+ };
2705
+ }
2706
+ async function mutateSessionControlInTransaction(db, input) {
2707
+ const workspace = await lockWorkspaceInferenceControl(db, input.workspaceId, "update", {
2708
+ ...input.controlLockTimeoutMs !== void 0 ? { lockTimeoutMs: input.controlLockTimeoutMs } : {}
2709
+ });
2710
+ const cancellationSubtree = input.action === "cancel" ? await loadSessionSubtreeIds(db, input.workspaceId, input.sessionId) : { sessionIds: [input.sessionId], rootParentSessionId: null };
2711
+ const cancellationSessionIds = cancellationSubtree.sessionIds;
2712
+ const cancellationTurnIds = input.action === "cancel" ? await loadCancellationTurnIds(db, input.workspaceId, cancellationSessionIds) : [];
2713
+ const pauseParentSessionId = input.action === "pause" && childLifecycleNoticesEnabled() ? await loadParentSessionId(db, input.workspaceId, input.sessionId) : null;
2714
+ const lineageParentSessionId = cancellationSubtree.rootParentSessionId ?? pauseParentSessionId;
2715
+ const locks = await lockSessionEventWriteRows(db, {
2716
+ workspaceId: input.workspaceId,
2717
+ controlLock: "already_locked",
2718
+ sessionIds: input.actor.type === "agent_attempt" ? [
2719
+ input.actor.sessionId,
2720
+ ...cancellationSessionIds,
2721
+ ...lineageParentSessionId ? [lineageParentSessionId] : []
2722
+ ] : [...cancellationSessionIds, ...lineageParentSessionId ? [lineageParentSessionId] : []],
2723
+ turnIds: input.actor.type === "agent_attempt" ? [input.actor.turnId, ...cancellationTurnIds] : cancellationTurnIds,
2724
+ attemptIds: input.actor.type === "agent_attempt" ? [input.actor.attemptId] : []
2725
+ });
2726
+ if (input.action === "cancel" || pauseParentSessionId !== null) {
2727
+ const rootSession = locks.sessions.find((session) => session.id === input.sessionId);
2728
+ if (!rootSession || rootSession.parentSessionId !== lineageParentSessionId) {
2729
+ throw new SessionControlInvariantError(
2730
+ `Session ${input.sessionId} parent changed while establishing ${input.action} locks`
2731
+ );
2732
+ }
2733
+ }
2734
+ const hash = canonicalSessionCommandHash({
2735
+ action: input.action,
2736
+ reason: input.reason ?? null,
2737
+ expectedControlEtag: input.expectedControlEtag ?? null
2738
+ });
2739
+ const reserved = await reserveSessionCommandReceipt(db, {
2740
+ accountId: input.accountId,
2741
+ workspaceId: input.workspaceId,
2742
+ actor: input.actor,
2743
+ action: `session.${input.action}`,
2744
+ targetSessionId: input.sessionId,
2745
+ targetTurnId: null,
2746
+ operationKey: input.operationKey,
2747
+ canonicalRequestHash: hash
2748
+ });
2749
+ if (reserved.replay) {
2750
+ const storedOutcome = reserved.receipt.result.outcome;
2751
+ const unchanged = storedOutcome === "unchanged" && reserved.receipt.appliedControlRevision === null;
2752
+ if (!unchanged && reserved.receipt.appliedControlRevision === null) {
2753
+ throw new SessionControlInvariantError(
2754
+ "Replayed session control receipt has no applied control revision"
2755
+ );
2756
+ }
2757
+ const workspaceControlEventId2 = unchanged ? null : String(reserved.receipt.result.workspaceControlEventId ?? "");
2758
+ if (!unchanged && !workspaceControlEventId2) {
2759
+ throw new SessionControlInvariantError("Replayed session control receipt has no event");
2760
+ }
2761
+ const sessionControlEventId = unchanged ? null : String(reserved.receipt.result.eventId ?? "");
2762
+ if (!unchanged && !sessionControlEventId) {
2763
+ throw new SessionControlInvariantError(
2764
+ "Replayed session control receipt has no session event"
2765
+ );
2766
+ }
2767
+ const wakeCount2 = Number(reserved.receipt.result.wakeCount ?? 0);
2768
+ return {
2769
+ receipt: reserved.receipt,
2770
+ control: await evaluateSessionControl(db, input.workspaceId, input.sessionId, {
2771
+ workspaceControl: workspace
2772
+ }),
2773
+ sessionControlEventId,
2774
+ workspaceControlEventId: workspaceControlEventId2,
2775
+ interruptionCount: Number(reserved.receipt.result.interruptionCount ?? 0),
2776
+ backgroundCommandCount: Number(reserved.receipt.result.backgroundCommandCount ?? 0),
2777
+ wakeCount: wakeCount2,
2778
+ cancelledSessionCount: Number(reserved.receipt.result.cancelledSessionCount ?? 0),
2779
+ cancelledTurnCount: Number(reserved.receipt.result.cancelledTurnCount ?? 0),
2780
+ affectedSessionEvents: Array.isArray(reserved.receipt.result.affectedSessionEvents) ? reserved.receipt.result.affectedSessionEvents : sessionControlEventId ? [{ sessionId: input.sessionId, eventIds: [sessionControlEventId] }] : [],
2781
+ workflowWake: await pendingSessionControlWorkflowWake(
2782
+ db,
2783
+ input.workspaceId,
2784
+ input.sessionId,
2785
+ wakeCount2
2786
+ ),
2787
+ outcome: "replayed",
2788
+ replay: true
2789
+ };
2790
+ }
2791
+ if (input.actor.type === "agent_attempt") {
2792
+ await assertAgentCommandAuthorityInTransaction(db, {
2793
+ workspaceId: input.workspaceId,
2794
+ actor: input.actor,
2795
+ targetSessionId: input.sessionId,
2796
+ action: input.action === "cancel" ? "pause" : input.action
2797
+ });
2798
+ }
2799
+ if (input.action === "resume") {
2800
+ await assertSessionBranchIsNotCancelled(db, input.workspaceId, input.sessionId);
2801
+ }
2802
+ const before = await evaluateSessionControl(db, input.workspaceId, input.sessionId, {
2803
+ workspaceControl: workspace
2804
+ });
2805
+ if (input.expectedControlEtag && input.expectedControlEtag !== before.controlEtag) {
2806
+ throw new SessionControlConflictError();
2807
+ }
2808
+ if (input.action !== "cancel") {
2809
+ const targetSession2 = locks.sessions.find((session) => session.id === input.sessionId);
2810
+ if (!targetSession2) {
2811
+ throw new SessionControlInvariantError(`Session ${input.sessionId} disappeared`);
2812
+ }
2813
+ const directPauseRevision = asSafeRevision(
2814
+ targetSession2.directPauseRevision,
2815
+ "direct pause revision"
2816
+ );
2817
+ const changed = input.action === "pause" ? targetSession2.directControlState !== "paused" || directPauseRevision === null || await sessionPauseEffectsNeeded(db, {
2818
+ workspaceId: input.workspaceId,
2819
+ sessionId: input.sessionId,
2820
+ directPauseRevision
2821
+ }) : before.state === "paused" || await continuableWakeRepairNeeded(db, {
2822
+ workspaceId: input.workspaceId,
2823
+ rootSessionId: input.sessionId
2824
+ });
2825
+ if (!changed) {
2826
+ const [quiescence] = input.action === "pause" ? await db.execute(sql3`
2827
+ select exists (
2828
+ select 1 from ${sessionTurnAttempts} attempt
2829
+ where attempt.workspace_id = ${input.workspaceId}
2830
+ and attempt.session_id = ${input.sessionId}
2831
+ and attempt.state = 'closed'
2832
+ and attempt.quiesced_at is null
2833
+ and exists (
2834
+ select 1 from ${sessionAttemptInterruptions} interruption
2835
+ where interruption.workspace_id = attempt.workspace_id
2836
+ and interruption.session_id = attempt.session_id
2837
+ and interruption.attempt_id = attempt.id
2838
+ and interruption.state in ('settled', 'rejected_stale')
2839
+ )
2840
+ ) as needed
2841
+ `) : [];
2842
+ const wakeCount2 = quiescence?.needed ? 1 : 0;
2843
+ if (wakeCount2 > 0) {
2844
+ await registerSessionWorkflowWakeInTransaction(db, {
2845
+ accountId: input.accountId,
2846
+ workspaceId: input.workspaceId,
2847
+ sessionId: input.sessionId,
2848
+ temporalWorkflowId: targetSession2.temporalWorkflowId ?? `session-${input.sessionId}`,
2849
+ reason: "session_pause_quiescence_reconciliation"
2850
+ });
2851
+ }
2852
+ const receipt2 = await updateSessionCommandReceiptResult(db, reserved.receipt.id, {
2853
+ result: {
2854
+ outcome: "unchanged",
2855
+ interruptionCount: 0,
2856
+ backgroundCommandCount: 0,
2857
+ wakeCount: wakeCount2,
2858
+ cancelledSessionCount: 0,
2859
+ cancelledTurnCount: 0,
2860
+ affectedSessionEvents: []
2861
+ }
2862
+ });
2863
+ return {
2864
+ receipt: receipt2,
2865
+ control: before,
2866
+ sessionControlEventId: null,
2867
+ workspaceControlEventId: null,
2868
+ interruptionCount: 0,
2869
+ backgroundCommandCount: 0,
2870
+ wakeCount: wakeCount2,
2871
+ cancelledSessionCount: 0,
2872
+ cancelledTurnCount: 0,
2873
+ affectedSessionEvents: [],
2874
+ workflowWake: await pendingSessionControlWorkflowWake(
2875
+ db,
2876
+ input.workspaceId,
2877
+ input.sessionId,
2878
+ wakeCount2
2879
+ ),
2880
+ outcome: "unchanged",
2881
+ replay: false
2882
+ };
2883
+ }
2884
+ }
2885
+ const targetSession = locks.sessions.find((session) => session.id === input.sessionId);
2886
+ if (!targetSession) {
2887
+ throw new SessionControlInvariantError(`Session ${input.sessionId} disappeared`);
2888
+ }
2889
+ const revision = nextRevision(workspace);
2890
+ await advanceWorkspaceRevision(db, input.workspaceId, revision);
2891
+ const updatedRows = await db.update(sessions).set(
2892
+ input.action === "pause" || input.action === "cancel" ? {
2893
+ directControlState: "paused",
2894
+ directPauseRevision: revision,
2895
+ controlVersion: revision,
2896
+ directControlReason: input.reason ?? null,
2897
+ directControlChangedBy: input.actor.type === "agent_attempt" ? `attempt:${input.actor.attemptId}` : input.actor.subjectId,
2898
+ directControlChangedAt: /* @__PURE__ */ new Date(),
2899
+ updatedAt: /* @__PURE__ */ new Date()
2900
+ } : {
2901
+ directControlState: "active",
2902
+ directPauseRevision: null,
2903
+ subtreeRunOverrideRevision: revision,
2904
+ controlVersion: revision,
2905
+ directControlReason: input.reason ?? null,
2906
+ directControlChangedBy: input.actor.type === "agent_attempt" ? `attempt:${input.actor.attemptId}` : input.actor.subjectId,
2907
+ directControlChangedAt: /* @__PURE__ */ new Date(),
2908
+ updatedAt: /* @__PURE__ */ new Date()
2909
+ }
2910
+ ).where(
2911
+ and3(
2912
+ eq3(sessions.workspaceId, input.workspaceId),
2913
+ input.action === "cancel" ? inArray2(sessions.id, cancellationSessionIds) : eq3(sessions.id, input.sessionId)
2914
+ )
2915
+ ).returning({ id: sessions.id });
2916
+ const updated = updatedRows.find((row) => row.id === input.sessionId);
2917
+ if (!updated) throw new SessionControlInvariantError(`Session ${input.sessionId} disappeared`);
2918
+ const actor = input.actor.type === "agent_attempt" ? `attempt:${input.actor.attemptId}` : input.actor.subjectId;
2919
+ const backgroundCommandCount = input.action === "pause" || input.action === "cancel" ? await requestSessionBackgroundCommandCancellationsInTransaction(db, {
2920
+ workspaceId: input.workspaceId,
2921
+ rootSessionIds: [input.sessionId],
2922
+ subjectId: actor
2923
+ }) : 0;
2924
+ const workspaceControlEventId = await insertWorkspaceControlEventInTransaction(db, {
2925
+ accountId: input.accountId,
2926
+ workspaceId: input.workspaceId,
2927
+ revision,
2928
+ scope: "session",
2929
+ rootSessionId: input.sessionId,
2930
+ action: input.action === "cancel" ? "pause" : input.action,
2931
+ automatic: false,
2932
+ reason: input.reason ?? null,
2933
+ actor
2934
+ });
2935
+ const interruptionCount = input.action === "pause" || input.action === "cancel" ? await interruptDescendantAttempts(db, {
2936
+ accountId: input.accountId,
2937
+ workspaceId: input.workspaceId,
2938
+ sessionId: input.sessionId,
2939
+ operationId: reserved.receipt.id,
2940
+ kind: "session_pause",
2941
+ controlRevision: revision
2942
+ }) : 0;
2943
+ const wakeCount = input.action === "cancel" ? await registerCancellationWakes(db, {
2944
+ workspaceId: input.workspaceId,
2945
+ sessionIds: cancellationSessionIds
2946
+ }) : input.action === "pause" ? await registerInterruptionWakes(db, {
2947
+ operationId: reserved.receipt.id,
2948
+ reason: "session_pause_interruption"
2949
+ }) : await registerDescendantWakes(db, {
2950
+ workspaceId: input.workspaceId,
2951
+ sessionId: input.sessionId,
2952
+ reason: "session_resume"
2953
+ });
2954
+ const [controlEvent] = await db.insert(sessionEvents).values(
2955
+ withLosslessContentWriteVersion(
2956
+ {
2957
+ accountId: input.accountId,
2958
+ workspaceId: input.workspaceId,
2959
+ sessionId: input.sessionId,
2960
+ sequence: targetSession.lastSequence + 1,
2961
+ type: input.action === "pause" || input.action === "cancel" ? "session.control.paused" : "session.control.resumed",
2962
+ payload: {
2963
+ operationId: reserved.receipt.id,
2964
+ revision,
2965
+ actor,
2966
+ ...input.reason ? { reason: input.reason } : {},
2967
+ ...input.action === "cancel" ? { terminal: true } : {},
2968
+ interruptionCount
2969
+ },
2970
+ occurredAt: /* @__PURE__ */ new Date()
2971
+ },
2972
+ "payload",
2973
+ "payloadCodecVersion"
2974
+ )
2975
+ ).returning({ id: sessionEvents.id });
2976
+ if (!controlEvent)
2977
+ throw new SessionControlInvariantError("Session control event was not inserted");
2978
+ await db.update(sessions).set({ lastSequence: targetSession.lastSequence + 1, updatedAt: /* @__PURE__ */ new Date() }).where(eq3(sessions.id, input.sessionId));
2979
+ await db.insert(auditEvents).values(
2980
+ withLosslessContentWriteVersion(
2981
+ {
2982
+ accountId: input.accountId,
2983
+ workspaceId: input.workspaceId,
2984
+ subjectId: actor,
2985
+ action: `session.control.${input.action}`,
2986
+ targetType: "session",
2987
+ targetId: input.sessionId,
2988
+ metadata: {
2989
+ operationId: reserved.receipt.id,
2990
+ revision,
2991
+ interruptionCount,
2992
+ ...input.actor.type === "agent_attempt" ? {
2993
+ callerSessionId: input.actor.sessionId,
2994
+ callerTurnId: input.actor.turnId,
2995
+ callerAttemptId: input.actor.attemptId,
2996
+ callerExecutionGeneration: input.actor.executionGeneration
2997
+ } : {}
2998
+ }
2999
+ },
3000
+ "metadata",
3001
+ "metadataCodecVersion"
3002
+ )
3003
+ );
3004
+ const cancellation = input.action === "cancel" ? await cancelSessionSubtreeInTransaction(db, {
3005
+ accountId: input.accountId,
3006
+ workspaceId: input.workspaceId,
3007
+ rootSessionId: input.sessionId,
3008
+ sessionIds: cancellationSessionIds,
3009
+ lockedSessions: locks.sessions,
3010
+ candidateTurnIds: cancellationTurnIds,
3011
+ lockedTurns: locks.turns,
3012
+ actor,
3013
+ reason: input.reason ?? null,
3014
+ operationId: reserved.receipt.id,
3015
+ rootControlEventId: controlEvent.id
3016
+ }) : {
3017
+ cancelledSessionCount: 0,
3018
+ cancelledTurnCount: 0,
3019
+ affectedSessionEvents: [{ sessionId: input.sessionId, eventIds: [controlEvent.id] }]
3020
+ };
3021
+ if (input.action === "pause" && pauseParentSessionId !== null) {
3022
+ const pausedSession = locks.sessions.find((session) => session.id === input.sessionId);
3023
+ const parentSession = locks.sessions.find((session) => session.id === pauseParentSessionId);
3024
+ const issuedByParentAttempt = input.actor.type === "agent_attempt" && input.actor.sessionId === pauseParentSessionId;
3025
+ if (pausedSession && parentSession && !issuedByParentAttempt) {
3026
+ await enqueueChildPausedOutboxInTransaction(db, {
3027
+ workspaceId: input.workspaceId,
3028
+ childSession: pausedSession,
3029
+ parentSession,
3030
+ operationId: reserved.receipt.id,
3031
+ actorKind: input.actor.type === "agent_attempt" ? "agent" : input.actor.type === "service" ? "api" : "human",
3032
+ reason: input.reason ?? null
3033
+ });
3034
+ }
3035
+ }
3036
+ const receipt = await updateSessionCommandReceiptResult(db, reserved.receipt.id, {
3037
+ controlRevision: revision,
3038
+ result: {
3039
+ outcome: "changed",
3040
+ interruptionCount,
3041
+ backgroundCommandCount,
3042
+ wakeCount,
3043
+ eventId: controlEvent.id,
3044
+ workspaceControlEventId,
3045
+ cancelledSessionCount: cancellation.cancelledSessionCount,
3046
+ cancelledTurnCount: cancellation.cancelledTurnCount,
3047
+ affectedSessionEvents: cancellation.affectedSessionEvents
3048
+ }
3049
+ });
3050
+ const control = await evaluateSessionControl(db, input.workspaceId, input.sessionId, {
3051
+ lock: "share"
3052
+ });
3053
+ return {
3054
+ receipt,
3055
+ control,
3056
+ sessionControlEventId: controlEvent.id,
3057
+ workspaceControlEventId,
3058
+ interruptionCount,
3059
+ backgroundCommandCount,
3060
+ wakeCount,
3061
+ cancelledSessionCount: cancellation.cancelledSessionCount,
3062
+ cancelledTurnCount: cancellation.cancelledTurnCount,
3063
+ affectedSessionEvents: cancellation.affectedSessionEvents,
3064
+ workflowWake: await pendingSessionControlWorkflowWake(
3065
+ db,
3066
+ input.workspaceId,
3067
+ input.sessionId,
3068
+ wakeCount
3069
+ ),
3070
+ outcome: "changed",
3071
+ replay: false
3072
+ };
3073
+ }
3074
+ async function autoResumeSessionBranchInTransaction(db, input) {
3075
+ const workspace = input.admission?.control ?? await lockWorkspaceInferenceControl(db, input.workspaceId, "update");
3076
+ await assertSessionBranchIsNotCancelled(db, input.workspaceId, input.sessionId);
3077
+ const before = await evaluateSessionControl(db, input.workspaceId, input.sessionId, {
3078
+ workspaceControl: workspace
3079
+ });
3080
+ if (input.observedControlEtag && input.observedControlEtag !== before.controlEtag) {
3081
+ throw new SessionControlConflictError();
3082
+ }
3083
+ if (before.state === "active") {
3084
+ return {
3085
+ revision: asSafeRevision(workspace.revision, "workspace control revision"),
3086
+ control: before,
3087
+ changed: false,
3088
+ workspaceControlEventId: null
3089
+ };
3090
+ }
3091
+ if (input.admission?.mode === "share") {
3092
+ throw new SessionControlInvariantError(
3093
+ `Session ${input.sessionId} became paused under a shared workspace control lock`
3094
+ );
3095
+ }
3096
+ const revision = nextRevision(workspace);
3097
+ await advanceWorkspaceRevision(db, input.workspaceId, revision);
3098
+ await db.update(sessions).set({
3099
+ directControlState: "active",
3100
+ directPauseRevision: null,
3101
+ subtreeRunOverrideRevision: revision,
3102
+ controlVersion: revision,
3103
+ directControlReason: input.reason,
3104
+ directControlChangedBy: input.actor,
3105
+ directControlChangedAt: /* @__PURE__ */ new Date(),
3106
+ updatedAt: /* @__PURE__ */ new Date()
3107
+ }).where(
3108
+ and3(
3109
+ eq3(sessions.workspaceId, input.workspaceId),
3110
+ eq3(sessions.id, input.sessionId)
3111
+ )
3112
+ );
3113
+ const workspaceControlEventId = await insertWorkspaceControlEventInTransaction(db, {
3114
+ accountId: workspace.accountId,
3115
+ workspaceId: input.workspaceId,
3116
+ revision,
3117
+ scope: "session",
3118
+ rootSessionId: input.sessionId,
3119
+ action: "resume",
3120
+ automatic: true,
3121
+ reason: input.reason,
3122
+ actor: input.actor
3123
+ });
3124
+ return {
3125
+ revision,
3126
+ control: await evaluateSessionControl(db, input.workspaceId, input.sessionId, {
3127
+ lock: "share"
3128
+ }),
3129
+ changed: true,
3130
+ workspaceControlEventId
3131
+ };
3132
+ }
3133
+ async function mutateWorkspaceControlInTransaction(db, input) {
3134
+ const workspace = await lockWorkspaceInferenceControl(db, input.workspaceId, "update", {
3135
+ ...input.controlLockTimeoutMs !== void 0 ? { lockTimeoutMs: input.controlLockTimeoutMs } : {}
3136
+ });
3137
+ const currentRevision = asSafeRevision(workspace.revision, "workspace control revision");
3138
+ const hash = canonicalSessionCommandHash({
3139
+ action: input.action,
3140
+ reason: input.reason ?? null,
3141
+ expectedRevision: input.expectedRevision ?? null
3142
+ });
3143
+ const reserved = await reserveSessionCommandReceipt(db, {
3144
+ accountId: input.accountId,
3145
+ workspaceId: input.workspaceId,
3146
+ actor: input.actor,
3147
+ action: `workspace.${input.action}`,
3148
+ targetSessionId: null,
3149
+ targetTurnId: null,
3150
+ operationKey: input.operationKey,
3151
+ canonicalRequestHash: hash
3152
+ });
3153
+ if (reserved.replay) {
3154
+ const storedOutcome = reserved.receipt.result.outcome;
3155
+ const unchanged = storedOutcome === "unchanged" && reserved.receipt.appliedControlRevision === null;
3156
+ if (!unchanged && reserved.receipt.appliedControlRevision === null) {
3157
+ throw new SessionControlInvariantError(
3158
+ "Replayed workspace control receipt has no applied control revision"
3159
+ );
3160
+ }
3161
+ const workspaceControlEventId2 = unchanged ? null : String(reserved.receipt.result.workspaceControlEventId ?? "");
3162
+ if (!unchanged && !workspaceControlEventId2) {
3163
+ throw new SessionControlInvariantError("Replayed workspace control receipt has no event");
3164
+ }
3165
+ return {
3166
+ receipt: reserved.receipt,
3167
+ revision: reserved.receipt.appliedControlRevision === null ? currentRevision : Number(reserved.receipt.appliedControlRevision),
3168
+ workspaceControlEventId: workspaceControlEventId2 || null,
3169
+ workspaceState: workspace.workspaceState === "paused" ? "paused" : "active",
3170
+ interruptionCount: Number(reserved.receipt.result.interruptionCount ?? 0),
3171
+ backgroundCommandCount: Number(reserved.receipt.result.backgroundCommandCount ?? 0),
3172
+ wakeCount: Number(reserved.receipt.result.wakeCount ?? 0),
3173
+ outcome: "replayed",
3174
+ replay: true
3175
+ };
3176
+ }
3177
+ if (input.expectedRevision !== null && input.expectedRevision !== void 0) {
3178
+ if (input.expectedRevision !== currentRevision) throw new SessionControlConflictError();
3179
+ }
3180
+ const workspacePauseRevision = asSafeRevision(
3181
+ workspace.workspacePauseRevision,
3182
+ "workspace pause revision"
3183
+ );
3184
+ const changed = input.action === "pause" ? workspace.workspaceState !== "paused" || workspacePauseRevision === null || await workspacePauseEffectsNeeded(db, {
3185
+ workspaceId: input.workspaceId,
3186
+ workspacePauseRevision
3187
+ }) : workspace.workspaceState !== "active" || await continuableWakeRepairNeeded(db, {
3188
+ workspaceId: input.workspaceId,
3189
+ rootSessionId: null
3190
+ });
3191
+ if (!changed) {
3192
+ const receipt2 = await updateSessionCommandReceiptResult(db, reserved.receipt.id, {
3193
+ result: {
3194
+ outcome: "unchanged",
3195
+ interruptionCount: 0,
3196
+ backgroundCommandCount: 0,
3197
+ wakeCount: 0
3198
+ }
3199
+ });
3200
+ return {
3201
+ receipt: receipt2,
3202
+ revision: currentRevision,
3203
+ workspaceControlEventId: null,
3204
+ workspaceState: workspace.workspaceState === "paused" ? "paused" : "active",
3205
+ interruptionCount: 0,
3206
+ backgroundCommandCount: 0,
3207
+ wakeCount: 0,
3208
+ outcome: "unchanged",
3209
+ replay: false
3210
+ };
3211
+ }
3212
+ const revision = nextRevision(workspace);
3213
+ const actor = input.actor.type === "agent_attempt" ? `attempt:${input.actor.attemptId}` : input.actor.subjectId;
3214
+ const backgroundCommandCount = input.action === "pause" ? await requestWorkspaceBackgroundCommandCancellationsInTransaction(db, {
3215
+ workspaceId: input.workspaceId,
3216
+ subjectId: actor
3217
+ }) : 0;
3218
+ const [updated] = await db.update(workspaceInferenceControls).set({
3219
+ revision,
3220
+ workspaceState: input.action === "pause" ? "paused" : "active",
3221
+ workspacePauseRevision: input.action === "pause" ? revision : null,
3222
+ reason: input.reason ?? null,
3223
+ changedBy: actor,
3224
+ changedAt: /* @__PURE__ */ new Date(),
3225
+ updatedAt: /* @__PURE__ */ new Date()
3226
+ }).where(
3227
+ and3(
3228
+ eq3(workspaceInferenceControls.workspaceId, input.workspaceId),
3229
+ eq3(workspaceInferenceControls.revision, currentRevision)
3230
+ )
3231
+ ).returning({ workspaceId: workspaceInferenceControls.workspaceId });
3232
+ if (!updated) {
3233
+ throw new SessionControlInvariantError("Workspace control did not mutate exactly once");
3234
+ }
3235
+ const workspaceControlEventId = await insertWorkspaceControlEventInTransaction(db, {
3236
+ accountId: input.accountId,
3237
+ workspaceId: input.workspaceId,
3238
+ revision,
3239
+ scope: "workspace",
3240
+ rootSessionId: null,
3241
+ action: input.action,
3242
+ automatic: false,
3243
+ reason: input.reason ?? null,
3244
+ actor
3245
+ });
3246
+ const interruptionCount = input.action === "pause" ? await interruptWorkspaceAttempts(db, {
3247
+ workspaceId: input.workspaceId,
3248
+ operationId: reserved.receipt.id,
3249
+ controlRevision: revision
3250
+ }) : 0;
3251
+ const wakeCount = input.action === "pause" ? await registerInterruptionWakes(db, {
3252
+ operationId: reserved.receipt.id,
3253
+ reason: "workspace_pause_interruption"
3254
+ }) : await registerWorkspaceWakes(db, {
3255
+ workspaceId: input.workspaceId,
3256
+ reason: "workspace_resume"
3257
+ });
3258
+ const receipt = await updateSessionCommandReceiptResult(db, reserved.receipt.id, {
3259
+ controlRevision: revision,
3260
+ result: {
3261
+ outcome: "changed",
3262
+ interruptionCount,
3263
+ backgroundCommandCount,
3264
+ wakeCount,
3265
+ workspaceControlEventId
3266
+ }
3267
+ });
3268
+ return {
3269
+ receipt,
3270
+ revision,
3271
+ workspaceControlEventId,
3272
+ workspaceState: input.action === "pause" ? "paused" : "active",
3273
+ interruptionCount,
3274
+ backgroundCommandCount,
3275
+ wakeCount,
3276
+ outcome: "changed",
3277
+ replay: false
3278
+ };
3279
+ }
3280
+ async function insertWorkspaceControlEventInTransaction(db, input) {
3281
+ const projected = boundWorkspaceControlEvent(
3282
+ {
3283
+ id: crypto.randomUUID(),
3284
+ workspaceId: input.workspaceId,
3285
+ sequence: input.revision,
3286
+ revision: input.revision,
3287
+ type: "workspace.control.changed",
3288
+ scope: input.scope,
3289
+ rootSessionId: input.rootSessionId,
3290
+ action: input.action,
3291
+ automatic: input.automatic,
3292
+ reason: input.reason,
3293
+ actor: input.actor,
3294
+ occurredAt: (/* @__PURE__ */ new Date(0)).toISOString()
3295
+ },
3296
+ { surface: "durable_control" }
3297
+ );
3298
+ const field = (name) => projected.truncation?.fields.find((candidate) => candidate.field === name);
3299
+ const [event] = await db.insert(workspaceControlEvents).values({
3300
+ ...input,
3301
+ reason: projected.reason,
3302
+ reasonOriginalBytes: input.reason === null ? null : field("reason")?.originalBytes ?? workspaceControlUtf8Bytes(projected.reason),
3303
+ actor: projected.actor,
3304
+ actorOriginalBytes: field("actor")?.originalBytes ?? workspaceControlUtf8Bytes(projected.actor)
3305
+ }).returning({ id: workspaceControlEvents.id });
3306
+ if (!event) {
3307
+ throw new SessionControlInvariantError("Workspace control event was not inserted");
3308
+ }
3309
+ return event.id;
3310
+ }
3311
+
3312
+ // src/session-background-commands.ts
3313
+ var SESSION_BACKGROUND_COMMAND_REASON_MAX_BYTES = 512;
3314
+ function boundedSessionBackgroundCommandReason(value, label) {
3315
+ const normalized = value.trim();
3316
+ let bounded = "";
3317
+ let bytes = 0;
3318
+ for (const character of normalized) {
3319
+ const characterBytes = Buffer.byteLength(character, "utf8");
3320
+ if (bytes + characterBytes > SESSION_BACKGROUND_COMMAND_REASON_MAX_BYTES) break;
3321
+ bounded += character;
3322
+ bytes += characterBytes;
3323
+ }
3324
+ if (!bounded) throw new Error(`${label} must not be empty`);
3325
+ return bounded;
3326
+ }
3327
+ function commandPreview(value) {
3328
+ const normalized = value.replace(/\s+/g, " ").trim();
3329
+ return Array.from(normalized).slice(0, 512).join("");
3330
+ }
3331
+ function mapCommand(row) {
3332
+ const terminal = row.state === "exited" || row.state === "lost";
3333
+ const legacyFailureCode = row.provider === "connected_machine" ? /^op_failure_([A-Za-z0-9_-]{1,128})$/.exec(row.settlementReason ?? "")?.[1] : void 0;
3334
+ const storedFailure = row.runnerFailure ? SessionCommandFailure.safeParse(row.runnerFailure) : null;
3335
+ const failure = !terminal ? void 0 : row.runnerFailure ? storedFailure?.success ? storedFailure.data : {
3336
+ code: /^[A-Za-z0-9_-]{1,128}$/.test(row.runnerFailure.code) ? row.runnerFailure.code : "INVALID_RUNNER_FAILURE",
3337
+ detail: {
3338
+ metadata_error: "Stored runner failure details do not match the retained contract."
3339
+ },
3340
+ retryable: false
3341
+ } : legacyFailureCode ? { code: legacyFailureCode, retryable: false } : void 0;
3342
+ return {
3343
+ id: row.id,
3344
+ workspaceId: row.workspaceId,
3345
+ sessionId: row.sessionId,
3346
+ provider: row.provider,
3347
+ state: row.state,
3348
+ commandPreview: row.commandPreview,
3349
+ cancelRequestedAt: row.cancelRequestedAt?.toISOString() ?? null,
3350
+ exitCode: row.exitCode ?? null,
3351
+ settlementReason: row.settlementReason ?? null,
3352
+ ...failure ? { failure } : {},
3353
+ startedAt: row.startedAt.toISOString(),
3354
+ settledAt: row.settledAt?.toISOString() ?? null,
3355
+ completionObservedAt: row.completionObservedAt?.toISOString() ?? null,
3356
+ updatedAt: row.updatedAt.toISOString()
3357
+ };
3358
+ }
3359
+ async function backgroundCommandActivityForSessions(db, input) {
3360
+ if (input.sessionIds.length === 0) return /* @__PURE__ */ new Map();
3361
+ return await withRlsContext(
3362
+ db,
3363
+ { accountId: input.accountId, workspaceId: input.workspaceId },
3364
+ async (scopedDb) => {
3365
+ const rows = await scopedDb.select({
3366
+ sessionId: sessionBackgroundCommands.sessionId,
3367
+ count: sql4`count(*)::int`,
3368
+ stoppingCount: sql4`count(*) filter (where ${sessionBackgroundCommands.state} = 'stopping')::int`
3369
+ }).from(sessionBackgroundCommands).where(
3370
+ and4(
3371
+ eq4(sessionBackgroundCommands.workspaceId, input.workspaceId),
3372
+ inArray3(sessionBackgroundCommands.sessionId, input.sessionIds),
3373
+ inArray3(sessionBackgroundCommands.state, ["running", "stopping"])
3374
+ )
3375
+ ).groupBy(sessionBackgroundCommands.sessionId);
3376
+ return new Map(
3377
+ rows.map((row) => [
3378
+ row.sessionId,
3379
+ {
3380
+ state: Number(row.stoppingCount) > 0 ? "stopping" : "running",
3381
+ count: Number(row.count)
3382
+ }
3383
+ ])
3384
+ );
3385
+ }
3386
+ );
3387
+ }
3388
+ async function listSessionBackgroundCommands(db, input) {
3389
+ return await withRlsContext(
3390
+ db,
3391
+ { accountId: input.accountId, workspaceId: input.workspaceId },
3392
+ async (scopedDb) => {
3393
+ const rows = await scopedDb.select().from(sessionBackgroundCommands).where(
3394
+ and4(
3395
+ eq4(sessionBackgroundCommands.workspaceId, input.workspaceId),
3396
+ eq4(sessionBackgroundCommands.sessionId, input.sessionId),
3397
+ ...input.activeOnly ? [inArray3(sessionBackgroundCommands.state, ["running", "stopping"])] : []
3398
+ )
3399
+ ).orderBy(
3400
+ desc2(sessionBackgroundCommands.startedAt),
3401
+ desc2(sessionBackgroundCommands.id)
3402
+ ).limit(1e3);
3403
+ return rows.map(mapCommand);
3404
+ }
3405
+ );
3406
+ }
3407
+ async function insertManagedSessionBackgroundCommandInTransaction(db, input) {
3408
+ const [process] = await db.select({
3409
+ accountId: sandboxRetainedProcesses.accountId,
3410
+ workspaceId: sandboxRetainedProcesses.workspaceId,
3411
+ sessionId: sandboxRetainedProcesses.sessionId,
3412
+ state: sandboxRetainedProcesses.state,
3413
+ ownerActorKind: sandboxRetainedProcesses.ownerActorKind,
3414
+ ownerActorId: sandboxRetainedProcesses.ownerActorId,
3415
+ ownerTurnId: sandboxRetainedProcesses.ownerTurnId,
3416
+ ownerAttemptId: sandboxRetainedProcesses.ownerAttemptId,
3417
+ ownerExecutionGeneration: sandboxRetainedProcesses.ownerExecutionGeneration
3418
+ }).from(sandboxRetainedProcesses).where(eq4(sandboxRetainedProcesses.id, input.retainedProcessId)).for("update").limit(1);
3419
+ if (!process || process.accountId !== input.accountId || process.workspaceId !== input.workspaceId || process.sessionId !== input.sessionId || process.state !== "active") {
3420
+ throw new Error("Managed background command requires its exact active retained process");
3421
+ }
3422
+ const [row] = await db.insert(sessionBackgroundCommands).values({
3423
+ id: input.commandId,
3424
+ accountId: input.accountId,
3425
+ workspaceId: input.workspaceId,
3426
+ sessionId: input.sessionId,
3427
+ provider: "managed",
3428
+ state: "running",
3429
+ retainedProcessId: input.retainedProcessId,
3430
+ commandPreview: commandPreview(input.command),
3431
+ launchTurnId: input.turnId ?? null,
3432
+ launchAttemptId: input.attemptId ?? null,
3433
+ launchExecutionGeneration: input.executionGeneration ?? null
3434
+ }).onConflictDoUpdate({
3435
+ target: sessionBackgroundCommands.retainedProcessId,
3436
+ targetWhere: sql4`${sessionBackgroundCommands.retainedProcessId} is not null`,
3437
+ set: { updatedAt: /* @__PURE__ */ new Date() }
3438
+ }).returning();
3439
+ if (!row) throw new Error("Managed background command adoption returned no row");
3440
+ const legacyReplayMatches = row.launchTurnId === null && row.launchAttemptId === null && row.launchExecutionGeneration === null && process.ownerActorKind === "turn" && process.ownerActorId === input.attemptId && process.ownerTurnId === input.turnId && process.ownerAttemptId === input.attemptId && process.ownerExecutionGeneration === input.executionGeneration;
3441
+ if (row.accountId !== input.accountId || row.workspaceId !== input.workspaceId || row.sessionId !== input.sessionId || !legacyReplayMatches && (row.launchTurnId !== (input.turnId ?? null) || row.launchAttemptId !== (input.attemptId ?? null) || row.launchExecutionGeneration !== (input.executionGeneration ?? null)) || row.provider !== "managed" || row.retainedProcessId !== input.retainedProcessId) {
3442
+ throw new Error("Managed background command adoption conflicted with another identity");
3443
+ }
3444
+ return mapCommand(row);
3445
+ }
3446
+ async function insertConnectedMachineSessionBackgroundCommandInTransaction(db, input) {
3447
+ const [row] = await db.insert(sessionBackgroundCommands).values({
3448
+ id: input.commandId,
3449
+ accountId: input.accountId,
3450
+ workspaceId: input.workspaceId,
3451
+ sessionId: input.sessionId,
3452
+ provider: "connected_machine",
3453
+ state: "running",
3454
+ controlWorkspaceId: input.controlWorkspaceId,
3455
+ enrollmentId: input.enrollmentId,
3456
+ connectionInstanceId: input.connectionInstanceId,
3457
+ opId: input.opId,
3458
+ commandPreview: commandPreview(input.command),
3459
+ launchTurnId: input.turnId ?? null,
3460
+ launchAttemptId: input.attemptId ?? null,
3461
+ launchExecutionGeneration: input.executionGeneration ?? null
3462
+ }).onConflictDoUpdate({
3463
+ target: [
3464
+ sessionBackgroundCommands.controlWorkspaceId,
3465
+ sessionBackgroundCommands.enrollmentId,
3466
+ sessionBackgroundCommands.connectionInstanceId,
3467
+ sessionBackgroundCommands.opId
3468
+ ],
3469
+ targetWhere: sql4`${sessionBackgroundCommands.provider} = 'connected_machine'`,
3470
+ set: { updatedAt: /* @__PURE__ */ new Date() }
3471
+ }).returning();
3472
+ if (!row) throw new Error("Connected Machine background command adoption returned no row");
3473
+ if (row.accountId !== input.accountId || row.workspaceId !== input.workspaceId || row.sessionId !== input.sessionId || row.launchTurnId !== (input.turnId ?? null) || row.launchAttemptId !== (input.attemptId ?? null) || row.launchExecutionGeneration !== (input.executionGeneration ?? null) || row.provider !== "connected_machine" || row.controlWorkspaceId !== input.controlWorkspaceId || row.enrollmentId !== input.enrollmentId || row.connectionInstanceId !== input.connectionInstanceId || row.opId !== input.opId) {
3474
+ throw new Error(
3475
+ "Connected Machine background command adoption conflicted with another identity"
3476
+ );
3477
+ }
3478
+ return mapCommand(row);
3479
+ }
3480
+ async function claimConnectedMachineSessionBackgroundCommands(db, input) {
3481
+ if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 100) {
3482
+ throw new Error("Connected command reconciliation limit must be between 1 and 100");
3483
+ }
3484
+ if (!Number.isSafeInteger(input.claimTtlMs) || input.claimTtlMs < 0 || input.claimTtlMs > 36e5) {
3485
+ throw new Error("Connected command reconciliation claim TTL is invalid");
3486
+ }
3487
+ const rows = await db.execute(sql4`
3488
+ select account_id as "accountId",
3489
+ workspace_id as "workspaceId",
3490
+ session_id as "sessionId",
3491
+ command_id as "commandId",
3492
+ claim_id as "claimId",
3493
+ command_state as "commandState",
3494
+ control_workspace_id as "controlWorkspaceId",
3495
+ enrollment_id as "enrollmentId",
3496
+ connection_instance_id as "connectionInstanceId",
3497
+ op_id as "opId",
3498
+ reconcile_attempts as "reconcileAttempts",
3499
+ reconcile_proof_outcome as "reconcileProofOutcome",
3500
+ reconcile_proof_exit_code as "reconcileProofExitCode",
3501
+ reconcile_proof_reason as "reconcileProofReason",
3502
+ reconcile_proof_observed_at as "reconcileProofObservedAt"
3503
+ from opengeni_private.claim_connected_machine_background_commands(
3504
+ ${input.claimId}::uuid, ${input.limit}::integer, ${input.claimTtlMs}::bigint,
3505
+ ${(input.dueBefore ?? /* @__PURE__ */ new Date()).toISOString()}::timestamptz
3506
+ )
3507
+ `);
3508
+ return rows.map((row) => {
3509
+ if (row.commandState !== "running" && row.commandState !== "stopping") {
3510
+ throw new Error(`Connected command claim returned invalid state ${row.commandState}`);
3511
+ }
3512
+ const proof = row.reconcileProofOutcome === null ? null : row.reconcileProofOutcome === "exited" || row.reconcileProofOutcome === "lost" ? {
3513
+ outcome: row.reconcileProofOutcome,
3514
+ exitCode: row.reconcileProofExitCode === null ? null : Number(row.reconcileProofExitCode),
3515
+ reason: row.reconcileProofReason,
3516
+ observedAt: row.reconcileProofObservedAt instanceof Date ? row.reconcileProofObservedAt : new Date(row.reconcileProofObservedAt)
3517
+ } : (() => {
3518
+ throw new Error(
3519
+ `Connected command claim returned invalid proof ${row.reconcileProofOutcome}`
3520
+ );
3521
+ })();
3522
+ return {
3523
+ commandId: row.commandId,
3524
+ accountId: row.accountId,
3525
+ workspaceId: row.workspaceId,
3526
+ sessionId: row.sessionId,
3527
+ claimId: row.claimId,
3528
+ state: row.commandState,
3529
+ controlWorkspaceId: row.controlWorkspaceId,
3530
+ enrollmentId: row.enrollmentId,
3531
+ connectionInstanceId: row.connectionInstanceId,
3532
+ opId: row.opId,
3533
+ reconcileAttempts: Number(row.reconcileAttempts),
3534
+ proof
3535
+ };
3536
+ });
3537
+ }
3538
+ function connectedClaimIdentityWhere(claim) {
3539
+ return and4(
3540
+ eq4(sessionBackgroundCommands.id, claim.commandId),
3541
+ eq4(sessionBackgroundCommands.accountId, claim.accountId),
3542
+ eq4(sessionBackgroundCommands.workspaceId, claim.workspaceId),
3543
+ eq4(sessionBackgroundCommands.sessionId, claim.sessionId),
3544
+ eq4(sessionBackgroundCommands.provider, "connected_machine"),
3545
+ eq4(sessionBackgroundCommands.controlWorkspaceId, claim.controlWorkspaceId),
3546
+ eq4(sessionBackgroundCommands.enrollmentId, claim.enrollmentId),
3547
+ eq4(sessionBackgroundCommands.connectionInstanceId, claim.connectionInstanceId),
3548
+ eq4(sessionBackgroundCommands.opId, claim.opId),
3549
+ eq4(sessionBackgroundCommands.reconcileClaimId, claim.claimId),
3550
+ inArray3(sessionBackgroundCommands.state, ["running", "stopping"])
3551
+ );
3552
+ }
3553
+ async function recordConnectedMachineBackgroundCommandProof(db, input) {
3554
+ const failure = input.proof.failure ? SessionCommandFailure.parse(input.proof.failure) : null;
3555
+ const reason = boundedSessionBackgroundCommandReason(
3556
+ input.proof.reason,
3557
+ "Connected command proof reason"
3558
+ );
3559
+ if (failure && reason !== `op_failure_${failure.code}`)
3560
+ throw new Error("Connected command failure code conflicts with proof reason");
3561
+ if (input.proof.outcome === "exited" && input.proof.exitCode === null) {
3562
+ throw new Error("Connected command exit proof requires an exit code");
3563
+ }
3564
+ if (input.proof.outcome === "lost" && input.proof.exitCode !== null) {
3565
+ throw new Error("Connected command loss proof cannot carry an exit code");
3566
+ }
3567
+ await withRlsContext(
3568
+ db,
3569
+ { accountId: input.claim.accountId, workspaceId: input.claim.workspaceId },
3570
+ async (scopedDb) => {
3571
+ const [current] = await scopedDb.select().from(sessionBackgroundCommands).where(connectedClaimIdentityWhere(input.claim)).for("update").limit(1);
3572
+ if (!current) throw new Error("Connected command proof was fenced by newer lifecycle state");
3573
+ if (current.reconcileProofOutcome !== null) {
3574
+ const observedAt = current.reconcileProofObservedAt?.getTime() ?? null;
3575
+ if (current.reconcileProofOutcome !== input.proof.outcome || current.reconcileProofExitCode !== input.proof.exitCode || current.reconcileProofReason !== reason || !isDeepStrictEqual(current.runnerFailure, failure) || observedAt !== input.proof.observedAt.getTime()) {
3576
+ throw new Error("Connected command reconciliation proof conflicts with durable proof");
3577
+ }
3578
+ return;
3579
+ }
3580
+ const rows = await scopedDb.update(sessionBackgroundCommands).set({
3581
+ reconcileProofOutcome: input.proof.outcome,
3582
+ reconcileProofExitCode: input.proof.exitCode,
3583
+ reconcileProofReason: reason,
3584
+ runnerFailure: failure,
3585
+ reconcileProofObservedAt: input.proof.observedAt,
3586
+ lastReconcileOutcome: `proof_${input.proof.outcome}`,
3587
+ updatedAt: /* @__PURE__ */ new Date()
3588
+ }).where(connectedClaimIdentityWhere(input.claim)).returning({ id: sessionBackgroundCommands.id });
3589
+ if (rows.length !== 1) {
3590
+ throw new Error("Connected command proof lost its exact claim during checkpoint");
3591
+ }
3592
+ }
3593
+ );
3594
+ }
3595
+ async function deferConnectedMachineBackgroundCommandReconciliation(db, input) {
3596
+ if (!Number.isSafeInteger(input.retryAfterMs) || input.retryAfterMs < 0) {
3597
+ throw new Error("Connected command reconciliation retry delay is invalid");
3598
+ }
3599
+ const outcome = input.outcome.trim().slice(0, 64);
3600
+ if (!outcome) throw new Error("Connected command reconciliation outcome must not be empty");
3601
+ return await withRlsContext(
3602
+ db,
3603
+ { accountId: input.claim.accountId, workspaceId: input.claim.workspaceId },
3604
+ async (scopedDb) => {
3605
+ const rows = await scopedDb.update(sessionBackgroundCommands).set({
3606
+ reconcileAfter: new Date(Date.now() + input.retryAfterMs),
3607
+ reconcileClaimId: null,
3608
+ reconcileClaimedAt: null,
3609
+ lastReconcileOutcome: outcome,
3610
+ updatedAt: /* @__PURE__ */ new Date()
3611
+ }).where(connectedClaimIdentityWhere(input.claim)).returning({ id: sessionBackgroundCommands.id });
3612
+ return rows.length === 1;
3613
+ }
3614
+ );
3615
+ }
3616
+ async function settleClaimedConnectedMachineBackgroundCommandWithMutation(db, input, mutateTerminal) {
3617
+ return await withSessionActivityRlsContext(
3618
+ db,
3619
+ { accountId: input.claim.accountId, workspaceId: input.claim.workspaceId },
3620
+ async (tx) => {
3621
+ await mutateTerminal.prepare(tx);
3622
+ const rows = await tx.execute(sql4`
3623
+ update ${sessionBackgroundCommands} command set
3624
+ state = command.reconcile_proof_outcome,
3625
+ exit_code = case
3626
+ when command.reconcile_proof_outcome = 'exited'
3627
+ then command.reconcile_proof_exit_code
3628
+ else null
3629
+ end,
3630
+ settlement_reason = command.reconcile_proof_reason,
3631
+ settled_at = command.reconcile_proof_observed_at,
3632
+ reconcile_claim_id = null,
3633
+ reconcile_claimed_at = null,
3634
+ last_reconcile_outcome = 'settled_' || command.reconcile_proof_outcome,
3635
+ updated_at = clock_timestamp()
3636
+ where command.id = ${input.claim.commandId}
3637
+ and command.account_id = ${input.claim.accountId}
3638
+ and command.workspace_id = ${input.claim.workspaceId}
3639
+ and command.session_id = ${input.claim.sessionId}
3640
+ and command.provider = 'connected_machine'
3641
+ and command.control_workspace_id = ${input.claim.controlWorkspaceId}
3642
+ and command.enrollment_id = ${input.claim.enrollmentId}
3643
+ and command.connection_instance_id = ${input.claim.connectionInstanceId}
3644
+ and command.op_id = ${input.claim.opId}
3645
+ and command.reconcile_claim_id = ${input.claim.claimId}
3646
+ and command.state in ('running', 'stopping')
3647
+ and command.reconcile_proof_outcome in ('exited', 'lost')
3648
+ and command.reconcile_proof_observed_at is not null
3649
+ returning command.id
3650
+ `);
3651
+ if (rows.length !== 1) return false;
3652
+ const [row] = await tx.select().from(sessionBackgroundCommands).where(eq4(sessionBackgroundCommands.id, input.claim.commandId)).limit(1);
3653
+ if (!row) throw new Error("Settled Connected Machine command disappeared");
3654
+ await mutateTerminal.commit(tx, mapCommand(row));
3655
+ return true;
3656
+ }
3657
+ );
3658
+ }
3659
+ async function requestSessionBackgroundCommandCancellation(db, input) {
3660
+ return await withRlsContext(
3661
+ db,
3662
+ { accountId: input.accountId, workspaceId: input.workspaceId },
3663
+ async (scopedDb) => await scopedDb.transaction(async (tx) => {
3664
+ const [current] = await tx.select().from(sessionBackgroundCommands).where(
3665
+ and4(
3666
+ eq4(sessionBackgroundCommands.workspaceId, input.workspaceId),
3667
+ eq4(sessionBackgroundCommands.sessionId, input.sessionId),
3668
+ eq4(sessionBackgroundCommands.id, input.commandId)
3669
+ )
3670
+ ).for("update").limit(1);
3671
+ if (!current) return { command: null, accepted: false };
3672
+ if (current.state !== "running") {
3673
+ return { command: mapCommand(current), accepted: false };
3674
+ }
3675
+ const [updated] = await tx.update(sessionBackgroundCommands).set({
3676
+ state: "stopping",
3677
+ cancelRequestedAt: /* @__PURE__ */ new Date(),
3678
+ cancelRequestedBy: input.subjectId.slice(0, 1024),
3679
+ reconcileAfter: /* @__PURE__ */ new Date(),
3680
+ reconcileClaimId: null,
3681
+ reconcileClaimedAt: null,
3682
+ updatedAt: /* @__PURE__ */ new Date()
3683
+ }).where(eq4(sessionBackgroundCommands.id, current.id)).returning();
3684
+ if (current.retainedProcessId) {
3685
+ await tx.update(sandboxRetainedProcesses).set({ reconcileAfter: /* @__PURE__ */ new Date(), lastReconcileOutcome: "cancel_requested" }).where(
3686
+ and4(
3687
+ eq4(sandboxRetainedProcesses.id, current.retainedProcessId),
3688
+ eq4(sandboxRetainedProcesses.state, "active"),
3689
+ sql4`${sandboxRetainedProcesses.reconcileClaimId} is null`
3690
+ )
3691
+ );
3692
+ }
3693
+ return { command: mapCommand(updated), accepted: true };
3694
+ })
3695
+ );
3696
+ }
3697
+ async function requestSessionBackgroundCommandCancellationsInTransaction(db, input) {
3698
+ const rootSessionIds = [...new Set(input.rootSessionIds)];
3699
+ if (rootSessionIds.length === 0) return 0;
3700
+ const rows = await db.execute(sql4`
3701
+ with recursive roots(id) as (
3702
+ values ${sql4.join(
3703
+ rootSessionIds.map((sessionId) => sql4`(${sessionId}::uuid)`),
3704
+ sql4`, `
3705
+ )}
3706
+ ), subtree(id, path, cycle) as (
3707
+ select session.id, array[session.id]::uuid[], false
3708
+ from roots root
3709
+ join ${sessions} session
3710
+ on session.workspace_id = ${input.workspaceId}
3711
+ and session.id = root.id
3712
+ union all
3713
+ select child.id, parent.path || child.id, child.id = any(parent.path)
3714
+ from subtree parent
3715
+ join ${sessions} child
3716
+ on child.workspace_id = ${input.workspaceId}
3717
+ and child.parent_session_id = parent.id
3718
+ where not parent.cycle
3719
+ ), stopped as (
3720
+ update ${sessionBackgroundCommands} command set
3721
+ state = 'stopping',
3722
+ cancel_requested_at = clock_timestamp(),
3723
+ cancel_requested_by = left(${input.subjectId}, 1024),
3724
+ reconcile_after = clock_timestamp(),
3725
+ reconcile_claim_id = null,
3726
+ reconcile_claimed_at = null,
3727
+ updated_at = clock_timestamp()
3728
+ where command.workspace_id = ${input.workspaceId}
3729
+ and command.session_id in (select id from subtree where not cycle)
3730
+ and command.state = 'running'
3731
+ returning command.retained_process_id
3732
+ ), nudged as (
3733
+ update ${sandboxRetainedProcesses} process set
3734
+ reconcile_after = clock_timestamp(),
3735
+ reconcile_claim_id = null,
3736
+ reconcile_claimed_at = null,
3737
+ last_reconcile_outcome = 'cancel_requested'
3738
+ where process.id in (
3739
+ select stopped.retained_process_id
3740
+ from stopped
3741
+ where stopped.retained_process_id is not null
3742
+ )
3743
+ and process.state = 'active'
3744
+ returning process.id
3745
+ )
3746
+ select count(*)::integer as "commandCount" from stopped
3747
+ `);
3748
+ return Number(rows[0]?.commandCount ?? 0);
3749
+ }
3750
+ async function requestWorkspaceBackgroundCommandCancellationsInTransaction(db, input) {
3751
+ const rows = await db.execute(sql4`
3752
+ with stopped as (
3753
+ update ${sessionBackgroundCommands} command set
3754
+ state = 'stopping',
3755
+ cancel_requested_at = clock_timestamp(),
3756
+ cancel_requested_by = left(${input.subjectId}, 1024),
3757
+ reconcile_after = clock_timestamp(),
3758
+ reconcile_claim_id = null,
3759
+ reconcile_claimed_at = null,
3760
+ updated_at = clock_timestamp()
3761
+ where command.workspace_id = ${input.workspaceId}
3762
+ and command.state = 'running'
3763
+ returning command.retained_process_id
3764
+ ), nudged as (
3765
+ update ${sandboxRetainedProcesses} process set
3766
+ reconcile_after = clock_timestamp(),
3767
+ reconcile_claim_id = null,
3768
+ reconcile_claimed_at = null,
3769
+ last_reconcile_outcome = 'cancel_requested'
3770
+ where process.id in (
3771
+ select stopped.retained_process_id
3772
+ from stopped
3773
+ where stopped.retained_process_id is not null
3774
+ )
3775
+ and process.state = 'active'
3776
+ returning process.id
3777
+ )
3778
+ select count(*)::integer as "commandCount" from stopped
3779
+ `);
3780
+ return Number(rows[0]?.commandCount ?? 0);
3781
+ }
3782
+ async function settleSessionBackgroundCommandForRetainedProcessInTransaction(tx, input, mutateTerminal) {
3783
+ const reason = boundedSessionBackgroundCommandReason(
3784
+ input.reason,
3785
+ "Managed command settlement reason"
3786
+ );
3787
+ await mutateTerminal.prepare(tx);
3788
+ const [updatedRow] = await tx.update(sessionBackgroundCommands).set({
3789
+ state: input.outcome,
3790
+ exitCode: input.outcome === "exited" ? input.exitCode : null,
3791
+ settlementReason: reason,
3792
+ settledAt: /* @__PURE__ */ new Date(),
3793
+ reconcileClaimId: null,
3794
+ reconcileClaimedAt: null,
3795
+ lastReconcileOutcome: `settled_${input.outcome}`,
3796
+ updatedAt: /* @__PURE__ */ new Date()
3797
+ }).where(
3798
+ and4(
3799
+ eq4(sessionBackgroundCommands.workspaceId, input.workspaceId),
3800
+ eq4(sessionBackgroundCommands.sessionId, input.sessionId),
3801
+ eq4(sessionBackgroundCommands.retainedProcessId, input.retainedProcessId),
3802
+ inArray3(sessionBackgroundCommands.state, ["running", "stopping"])
3803
+ )
3804
+ ).returning();
3805
+ let row = updatedRow;
3806
+ if (!row) {
3807
+ const [current] = await tx.select().from(sessionBackgroundCommands).where(
3808
+ and4(
3809
+ eq4(sessionBackgroundCommands.workspaceId, input.workspaceId),
3810
+ eq4(sessionBackgroundCommands.sessionId, input.sessionId),
3811
+ eq4(sessionBackgroundCommands.retainedProcessId, input.retainedProcessId)
3812
+ )
3813
+ ).for("update").limit(1);
3814
+ if (!current) return null;
3815
+ if (current.state !== input.outcome || current.exitCode !== (input.outcome === "exited" ? input.exitCode : null)) {
3816
+ throw new Error("Managed background command terminal state conflicts with process proof");
3817
+ }
3818
+ const [existingEvent] = await tx.select({ id: sessionEvents.id }).from(sessionEvents).where(
3819
+ and4(
3820
+ eq4(sessionEvents.workspaceId, input.workspaceId),
3821
+ eq4(sessionEvents.sessionId, input.sessionId),
3822
+ eq4(sessionEvents.type, "session.command.finished"),
3823
+ sql4`${sessionEvents.payload} ->> 'commandId' = ${current.id}`
3824
+ )
3825
+ ).limit(1);
3826
+ if (existingEvent) return mapCommand(current);
3827
+ row = current;
3828
+ }
3829
+ const command = mapCommand(row);
3830
+ await mutateTerminal.commit(tx, command);
3831
+ return command;
3832
+ }
3833
+ async function settleConnectedMachineSessionBackgroundCommandWithMutation(db, input, mutateTerminal) {
3834
+ const failure = input.failure ? SessionCommandFailure.parse(input.failure) : null;
3835
+ const reason = boundedSessionBackgroundCommandReason(
3836
+ input.reason,
3837
+ "Connected command settlement reason"
3838
+ );
3839
+ if (failure && reason !== `op_failure_${failure.code}`)
3840
+ throw new Error("Connected command failure code conflicts with settlement reason");
3841
+ if (input.outcome === "exited" && input.exitCode === null) {
3842
+ throw new Error("Connected command exit settlement requires an exit code");
3843
+ }
3844
+ if (input.outcome === "lost" && input.exitCode !== null) {
3845
+ throw new Error("Connected command loss settlement cannot carry an exit code");
3846
+ }
3847
+ return await withSessionActivityRlsContext(
3848
+ db,
3849
+ { accountId: input.accountId, workspaceId: input.workspaceId },
3850
+ async (tx) => {
3851
+ await mutateTerminal.prepare(tx);
3852
+ const settledAt = /* @__PURE__ */ new Date();
3853
+ const [row] = await tx.update(sessionBackgroundCommands).set({
3854
+ state: input.outcome,
3855
+ exitCode: input.outcome === "exited" ? input.exitCode : null,
3856
+ settlementReason: reason,
3857
+ runnerFailure: failure,
3858
+ settledAt,
3859
+ reconcileClaimId: null,
3860
+ reconcileClaimedAt: null,
3861
+ reconcileProofOutcome: input.outcome,
3862
+ reconcileProofExitCode: input.outcome === "exited" ? input.exitCode : null,
3863
+ reconcileProofReason: reason,
3864
+ reconcileProofObservedAt: settledAt,
3865
+ lastReconcileOutcome: `settled_${input.outcome}`,
3866
+ updatedAt: /* @__PURE__ */ new Date()
3867
+ }).where(
3868
+ and4(
3869
+ eq4(sessionBackgroundCommands.workspaceId, input.workspaceId),
3870
+ eq4(sessionBackgroundCommands.sessionId, input.sessionId),
3871
+ eq4(sessionBackgroundCommands.id, input.commandId),
3872
+ eq4(sessionBackgroundCommands.provider, "connected_machine"),
3873
+ eq4(sessionBackgroundCommands.controlWorkspaceId, input.controlWorkspaceId),
3874
+ eq4(sessionBackgroundCommands.enrollmentId, input.enrollmentId),
3875
+ eq4(sessionBackgroundCommands.connectionInstanceId, input.connectionInstanceId),
3876
+ eq4(sessionBackgroundCommands.opId, input.opId),
3877
+ // A fast owner settlement cannot erase or replace metadata already
3878
+ // checkpointed by the reconciler under the exact command identity.
3879
+ sql4`(${sessionBackgroundCommands.runnerFailure} is null or
3880
+ ${sessionBackgroundCommands.runnerFailure} = ${failure ? JSON.stringify(failure) : null}::jsonb)`,
3881
+ inArray3(sessionBackgroundCommands.state, ["running", "stopping"])
3882
+ )
3883
+ ).returning();
3884
+ if (!row) return null;
3885
+ const command = mapCommand(row);
3886
+ await mutateTerminal.commit(tx, command);
3887
+ return command;
3888
+ }
3889
+ );
3890
+ }
3891
+ async function getSessionBackgroundCommand(db, input) {
3892
+ return await withRlsContext(
3893
+ db,
3894
+ { accountId: input.accountId, workspaceId: input.workspaceId },
3895
+ async (scopedDb) => {
3896
+ const [row] = await scopedDb.select().from(sessionBackgroundCommands).where(
3897
+ and4(
3898
+ eq4(sessionBackgroundCommands.workspaceId, input.workspaceId),
3899
+ eq4(sessionBackgroundCommands.sessionId, input.sessionId),
3900
+ eq4(sessionBackgroundCommands.id, input.commandId)
3901
+ )
3902
+ ).limit(1);
3903
+ return row ? mapCommand(row) : null;
3904
+ }
3905
+ );
3906
+ }
3907
+ async function observeSessionBackgroundCommandCompletion(db, input) {
3908
+ return await withSessionActivityRlsContext(db, input, async (tx) => {
3909
+ const locks = await lockSessionEventWriteRows(tx, {
3910
+ workspaceId: input.workspaceId,
3911
+ controlLock: "share",
3912
+ sessionIds: [input.sessionId]
3913
+ });
3914
+ if (locks.sessions[0]?.accountId !== input.accountId) return null;
3915
+ const identity = and4(
3916
+ eq4(sessionBackgroundCommands.accountId, input.accountId),
3917
+ eq4(sessionBackgroundCommands.workspaceId, input.workspaceId),
3918
+ eq4(sessionBackgroundCommands.sessionId, input.sessionId),
3919
+ eq4(sessionBackgroundCommands.id, input.commandId)
3920
+ );
3921
+ const [command] = await tx.select().from(sessionBackgroundCommands).where(identity).for("update").limit(1);
3922
+ if (!command) return null;
3923
+ if (command.state !== "exited" && command.state !== "lost") return mapCommand(command);
3924
+ const observedAt = command.completionObservedAt ?? /* @__PURE__ */ new Date();
3925
+ await tx.update(sessionBackgroundCommands).set({ completionObservedAt: observedAt }).where(identity);
3926
+ await tx.update(sessionSystemUpdates).set({ state: "superseded" }).where(
3927
+ and4(
3928
+ eq4(sessionSystemUpdates.accountId, input.accountId),
3929
+ eq4(sessionSystemUpdates.workspaceId, input.workspaceId),
3930
+ eq4(sessionSystemUpdates.sessionId, input.sessionId),
3931
+ eq4(sessionSystemUpdates.kind, "background_command_result"),
3932
+ eq4(sessionSystemUpdates.sourceId, input.commandId),
3933
+ eq4(sessionSystemUpdates.dedupeKey, `background-command-result:${input.commandId}`),
3934
+ eq4(sessionSystemUpdates.state, "pending")
3935
+ )
3936
+ );
3937
+ return mapCommand({ ...command, completionObservedAt: observedAt });
3938
+ });
3939
+ }
3940
+ var COMMAND_OUTPUT_DEFAULT_BYTES = 16384;
3941
+ var COMMAND_OUTPUT_MAX_BYTES = 65536;
3942
+ var COMMAND_OUTPUT_PAGE_ROWS = 64;
3943
+ function parseCommandOutputCursor(cursor, commandId) {
3944
+ if (cursor === void 0) return { sequence: 0, offset: 0 };
3945
+ if (cursor.length > 128) throw new Error("Invalid command output cursor");
3946
+ const parts = cursor.split(":");
3947
+ if (parts.length !== 3 || parts[0] !== commandId || !/^\d+$/.test(parts[1]) || !/^\d+$/.test(parts[2])) {
3948
+ throw new Error("Invalid command output cursor");
3949
+ }
3950
+ const sequence = Number(parts[1]);
3951
+ const offset = Number(parts[2]);
3952
+ if (!Number.isSafeInteger(sequence) || !Number.isSafeInteger(offset))
3953
+ throw new Error("Invalid command output cursor");
3954
+ return { sequence, offset };
3955
+ }
3956
+ function projectCommandOutputPage(input) {
3957
+ const start = parseCommandOutputCursor(input.cursor, input.commandId);
3958
+ const budget = input.maxOutputBytes ?? COMMAND_OUTPUT_DEFAULT_BYTES;
3959
+ if (!Number.isSafeInteger(budget) || budget < 4 || budget > COMMAND_OUTPUT_MAX_BYTES)
3960
+ throw new Error("maxOutputBytes must be between 4 and 65536");
3961
+ let remaining = budget;
3962
+ let sequence = start.sequence;
3963
+ let offset = start.offset;
3964
+ const chunks = [];
3965
+ const gaps = [];
3966
+ if (start.offset > 0 && input.rows[0]?.sequence !== start.sequence)
3967
+ gaps.push("cursor_output_no_longer_retained");
3968
+ let consumed = 0;
3969
+ for (const row of input.rows.slice(0, COMMAND_OUTPUT_PAGE_ROWS)) {
3970
+ const payload = fromPostgresLosslessJson(row.payload, row.payloadCodecVersion);
3971
+ const text = typeof payload?.chunk === "string" ? payload.chunk : "";
3972
+ if (typeof payload?.chunk !== "string") gaps.push("retained_event_has_no_output_chunk");
3973
+ if (payload?.commandReadProjectionGap === true) gaps.push("retained_event_exceeds_read_limit");
3974
+ if (payload?.truncation && typeof payload.truncation === "object")
3975
+ gaps.push("output_truncated_at_retention_boundary");
3976
+ const begin = row.sequence === start.sequence ? start.offset : 0;
3977
+ if (begin > 0 && begin < text.length && text.charCodeAt(begin - 1) >= 55296 && text.charCodeAt(begin - 1) <= 56319 && text.charCodeAt(begin) >= 56320 && text.charCodeAt(begin) <= 57343) {
3978
+ throw new Error("Invalid command output cursor: offset splits a Unicode code point");
3979
+ }
3980
+ if (begin > text.length) gaps.push("cursor_output_no_longer_retained");
3981
+ let end = Math.min(begin, text.length);
3982
+ for (const character of text.slice(end)) {
3983
+ const bytes = Buffer.byteLength(character, "utf8");
3984
+ if (bytes > remaining) break;
3985
+ remaining -= bytes;
3986
+ end += character.length;
3987
+ }
3988
+ if (end > begin)
3989
+ chunks.push({
3990
+ sequence: row.sequence,
3991
+ stream: payload.stream === "stderr" ? "stderr" : "stdout",
3992
+ streamFidelity: payload.streamFidelity === "merged" ? "merged" : payload.streamFidelity === "separate" ? "separate" : "unknown",
3993
+ chunk: text.slice(begin, end)
3994
+ });
3995
+ if (end < text.length) {
3996
+ sequence = row.sequence;
3997
+ offset = end;
3998
+ break;
3999
+ }
4000
+ sequence = row.sequence + 1;
4001
+ offset = 0;
4002
+ consumed++;
4003
+ }
4004
+ return {
4005
+ chunks,
4006
+ nextCursor: `${input.commandId}:${sequence}:${offset}`,
4007
+ hasMore: consumed < input.rows.length,
4008
+ retention: {
4009
+ source: "retained_session_events",
4010
+ completeness: "unknown",
4011
+ gaps: [...new Set(gaps)]
4012
+ }
4013
+ };
4014
+ }
4015
+ async function readSessionBackgroundCommandOutput(db, input) {
4016
+ const cursor = parseCommandOutputCursor(input.cursor, input.commandId);
4017
+ projectCommandOutputPage({ ...input, rows: [] });
4018
+ const command = await getSessionBackgroundCommand(db, input);
4019
+ if (!command) throw new Error("Background command not found in this session");
4020
+ const rows = await withRlsContext(
4021
+ db,
4022
+ input,
4023
+ async (tx) => await tx.select({
4024
+ sequence: sessionEvents.sequence,
4025
+ // Bound legacy payloads in SQL too: LIMIT alone cannot bound one old event.
4026
+ // Oversized historical rows carry explicit loss rather than allocating an
4027
+ // unbounded JSON value in the API process.
4028
+ payload: sql4`case when octet_length(${sessionEvents.payload}::text) <= 262144
4029
+ then ${sessionEvents.payload}
4030
+ else jsonb_build_object('commandReadProjectionGap', true) end`,
4031
+ payloadCodecVersion: sessionEvents.payloadCodecVersion
4032
+ }).from(sessionEvents).where(
4033
+ and4(
4034
+ eq4(sessionEvents.workspaceId, input.workspaceId),
4035
+ eq4(sessionEvents.sessionId, input.sessionId),
4036
+ eq4(sessionEvents.type, "sandbox.command.output.delta"),
4037
+ sql4`${sessionEvents.payload} ->> 'commandId' = ${input.commandId}`,
4038
+ gte(sessionEvents.sequence, cursor.sequence)
4039
+ )
4040
+ ).orderBy(asc2(sessionEvents.sequence)).limit(COMMAND_OUTPUT_PAGE_ROWS + 1)
4041
+ );
4042
+ const page = projectCommandOutputPage({ ...input, rows });
4043
+ const terminal = command.state === "exited" || command.state === "lost";
4044
+ const observed = terminal ? await observeSessionBackgroundCommandCompletion(db, input) : command;
4045
+ return {
4046
+ commandId: command.id,
4047
+ state: command.state,
4048
+ exitCode: command.exitCode,
4049
+ settlementReason: command.settlementReason,
4050
+ ...command.failure ? { failure: command.failure } : {},
4051
+ terminal,
4052
+ completionObservedAt: observed?.completionObservedAt ?? null,
4053
+ ...page
4054
+ };
4055
+ }
4056
+
4057
+ export {
4058
+ TOOL_RESULT_TYPE_BY_CALL_TYPE,
4059
+ historyCallId,
4060
+ historyItemType,
4061
+ interruptedToolCallResult,
4062
+ closePendingSessionToolCallsInTransaction,
4063
+ configureChildLifecycleNotices,
4064
+ childLifecycleNoticesEnabled,
4065
+ boundedChildNoticeText,
4066
+ CHILD_LIFECYCLE_DEDUPE_PREFIX,
4067
+ childRequiresActionDedupeKey,
4068
+ childRequiresActionResolvedDedupeKey,
4069
+ childPausedDedupeKey,
4070
+ childWaitingCapacityDedupeKey,
4071
+ childProgressDedupeKey,
4072
+ buildChildRequiresActionPayload,
4073
+ childRequiresActionSummary,
4074
+ childRequiresActionResolvedSummary,
4075
+ childPausedSummary,
4076
+ childWaitingCapacitySummary,
4077
+ childProgressSummary,
4078
+ boundedChildPausedReason,
4079
+ boundedChildProgressNote,
4080
+ mirrorSessionRealtimeContextInTransaction,
4081
+ renderRealtimeHumanInputContext,
4082
+ renderRealtimeHumanInputRequestContext,
4083
+ renderRealtimeHumanInputResponseContext,
4084
+ SESSION_BACKGROUND_COMMAND_REASON_MAX_BYTES,
4085
+ boundedSessionBackgroundCommandReason,
4086
+ backgroundCommandActivityForSessions,
4087
+ listSessionBackgroundCommands,
4088
+ insertManagedSessionBackgroundCommandInTransaction,
4089
+ insertConnectedMachineSessionBackgroundCommandInTransaction,
4090
+ claimConnectedMachineSessionBackgroundCommands,
4091
+ recordConnectedMachineBackgroundCommandProof,
4092
+ deferConnectedMachineBackgroundCommandReconciliation,
4093
+ settleClaimedConnectedMachineBackgroundCommandWithMutation,
4094
+ requestSessionBackgroundCommandCancellation,
4095
+ requestSessionBackgroundCommandCancellationsInTransaction,
4096
+ requestWorkspaceBackgroundCommandCancellationsInTransaction,
4097
+ settleSessionBackgroundCommandForRetainedProcessInTransaction,
4098
+ settleConnectedMachineSessionBackgroundCommandWithMutation,
4099
+ getSessionBackgroundCommand,
4100
+ observeSessionBackgroundCommandCompletion,
4101
+ COMMAND_OUTPUT_DEFAULT_BYTES,
4102
+ COMMAND_OUTPUT_MAX_BYTES,
4103
+ COMMAND_OUTPUT_PAGE_ROWS,
4104
+ parseCommandOutputCursor,
4105
+ projectCommandOutputPage,
4106
+ readSessionBackgroundCommandOutput,
4107
+ SESSION_ANCESTRY_LIMIT,
4108
+ SESSION_DISCOVERY_CONTROL_TITLE_MAX_CHARS,
4109
+ SESSION_DISCOVERY_CONTROL_TARGET_LIMIT,
4110
+ serializeEffectiveSessionControl,
4111
+ SessionControlInvariantError,
4112
+ SessionControlConflictError,
4113
+ SessionCommandIdempotencyError,
4114
+ AgentCommandAuthorityError,
4115
+ assertAgentCommandAuthorityInTransaction,
4116
+ canonicalSessionCommandHash,
4117
+ WORKSPACE_CONTROL_REQUEST_LOCK_TIMEOUT_MS,
4118
+ configureWorkspaceControlRequestLockTimeoutMs,
4119
+ workspaceControlRequestLockTimeoutMs,
4120
+ WorkspaceControlBusyError,
4121
+ workspaceControlLockBudget,
4122
+ lockWorkspaceInferenceControl,
4123
+ lockWorkspaceInferenceControlForAdmission,
4124
+ lockSessionEventWriteRows,
4125
+ registerSessionTurnAttemptClaim,
4126
+ assertSessionAuthoritySnapshot,
4127
+ sessionAuthoritySnapshotMatchesSession,
4128
+ sessionAuthoritySnapshotsEqual,
4129
+ closeSessionTurnAttemptInTransaction,
4130
+ evaluateSessionControls,
4131
+ evaluateSessionWriteAdmissionControl,
4132
+ evaluateSessionDiscoveryControls,
4133
+ evaluateSessionControl,
4134
+ reserveSessionPromptCommandReceipt,
4135
+ lockSessionPromptCommandOperation,
4136
+ getCompletedSessionPromptCommandReceipt,
4137
+ reserveSessionCommandReceipt,
4138
+ registerSessionWorkflowWakeInTransaction,
4139
+ registerInternalUpdateWakeInTransaction,
4140
+ updateSessionCommandReceiptResult,
4141
+ mutateSessionControlInTransaction,
4142
+ autoResumeSessionBranchInTransaction,
4143
+ mutateWorkspaceControlInTransaction
4144
+ };
4145
+ //# sourceMappingURL=chunk-DUB3LUVM.js.map