@opengeni/db 0.28.9 → 0.31.1

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 (163) hide show
  1. package/dist/attached-browser-devices.d.ts +42 -0
  2. package/dist/attempt-tool-catalogs.d.ts +16 -0
  3. package/dist/browser-auth.d.ts +91 -0
  4. package/dist/browser-identities.d.ts +128 -0
  5. package/dist/browser-sessions.d.ts +272 -0
  6. package/dist/browser-state-artifacts.d.ts +17 -0
  7. package/dist/capability-components.d.ts +37 -0
  8. package/dist/capability-integrations.d.ts +192 -0
  9. package/dist/chunk-F5FT7BEZ.js +3535 -0
  10. package/dist/chunk-F5FT7BEZ.js.map +1 -0
  11. package/dist/chunk-ISI2TIUG.js +639 -0
  12. package/dist/chunk-ISI2TIUG.js.map +1 -0
  13. package/dist/chunk-MWDVXQOL.js +11639 -0
  14. package/dist/chunk-MWDVXQOL.js.map +1 -0
  15. package/dist/chunk-NBE6CHRI.js +955 -0
  16. package/dist/chunk-NBE6CHRI.js.map +1 -0
  17. package/dist/{chunk-VHBFHMPC.js → chunk-YHZXQ7HP.js} +754 -11
  18. package/dist/chunk-YHZXQ7HP.js.map +1 -0
  19. package/dist/codemode-operations.d.ts +117 -0
  20. package/dist/codex-token-resolver.d.ts +2 -2
  21. package/dist/company-profile-schema.d.ts +798 -0
  22. package/dist/company-profile.d.ts +268 -0
  23. package/dist/computer-sessions.d.ts +109 -0
  24. package/dist/connection-token-resolver.d.ts +9 -4
  25. package/dist/database.d.ts +52 -3
  26. package/dist/editable-artifact-durable-export.d.ts +153 -0
  27. package/dist/editable-artifact-durable-export.js +788 -0
  28. package/dist/editable-artifact-durable-export.js.map +1 -0
  29. package/dist/editable-artifact-materialization.d.ts +123 -0
  30. package/dist/editable-artifacts-schema.d.ts +4152 -0
  31. package/dist/editable-artifacts.d.ts +610 -0
  32. package/dist/editable-artifacts.js +26 -0
  33. package/dist/editable-artifacts.js.map +1 -0
  34. package/dist/generated-images.d.ts +139 -0
  35. package/dist/index.d.ts +698 -111
  36. package/dist/index.js +27573 -10322
  37. package/dist/index.js.map +1 -1
  38. package/dist/insights.d.ts +51 -4
  39. package/dist/integration-bindings.d.ts +70 -0
  40. package/dist/integration-features.d.ts +99 -0
  41. package/dist/interaction-revisions.d.ts +3 -0
  42. package/dist/interaction-schema.d.ts +5372 -0
  43. package/dist/knowledge-source-sync-schema.d.ts +1679 -0
  44. package/dist/knowledge-source-sync.d.ts +274 -0
  45. package/dist/memory-governance.d.ts +2 -2
  46. package/dist/memory-slack-delivery.d.ts +101 -0
  47. package/dist/new-session-drafts.d.ts +2 -2
  48. package/dist/pack-components.d.ts +76 -0
  49. package/dist/pack-installations.d.ts +86 -0
  50. package/dist/persistence-errors.d.ts +3 -3
  51. package/dist/plugin-packages.d.ts +126 -0
  52. package/dist/preference-registry.d.ts +1 -1
  53. package/dist/provision-roles.d.ts +9 -1
  54. package/dist/provision-roles.js +1 -1
  55. package/dist/runtime-posture.d.ts +18 -6
  56. package/dist/schema.d.ts +18342 -9099
  57. package/dist/schema.js +165 -1
  58. package/dist/scoped-knowledge.d.ts +97 -1
  59. package/dist/session-control.d.ts +4 -4
  60. package/dist/session-queue-commands.d.ts +19 -11
  61. package/dist/session-realtime-context.d.ts +2 -2
  62. package/dist/session-realtime-ledger.d.ts +4 -4
  63. package/dist/session-realtime-mirror.d.ts +2 -2
  64. package/dist/session-realtime-state.d.ts +1 -1
  65. package/dist/session-realtime-terminal.d.ts +1 -1
  66. package/dist/session-realtime.d.ts +7 -7
  67. package/dist/session-tool-call-settlement.d.ts +1 -1
  68. package/dist/slack-user-link-access.d.ts +54 -0
  69. package/dist/transcription-recordings.d.ts +2 -2
  70. package/dist/turn-initiator.d.ts +2 -2
  71. package/dist/video-generation.d.ts +216 -0
  72. package/dist/video-generation.js +66 -0
  73. package/dist/video-generation.js.map +1 -0
  74. package/dist/workspace-artifacts.d.ts +1 -1
  75. package/dist/workspace-instruction-policies.d.ts +1 -1
  76. package/dist/workspace-learning-policy-schema.d.ts +932 -0
  77. package/dist/workspace-learning-policy.d.ts +161 -0
  78. package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
  79. package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
  80. package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
  81. package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
  82. package/drizzle/0187_generated_image_artifacts.sql +173 -0
  83. package/drizzle/0188_image_generation_retention_failure.sql +25 -0
  84. package/drizzle/0189_retained_session_image_formats.sql +13 -0
  85. package/drizzle/0190_timeline_annotations.sql +31 -0
  86. package/drizzle/0191_editable_artifact_engine.sql +3853 -0
  87. package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
  88. package/drizzle/0193_editable_artifact_authorization.sql +190 -0
  89. package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
  90. package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
  91. package/drizzle/0196_rig_provider_images.sql +152 -0
  92. package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
  93. package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
  94. package/drizzle/0199_workspace_learning_policy.sql +980 -0
  95. package/drizzle/0201_company_profile_authority.sql +849 -0
  96. package/drizzle/0202_document_index_checkpoints.sql +85 -0
  97. package/drizzle/0203_durable_video_generation.sql +353 -0
  98. package/drizzle/0204_video_generation_funding.sql +44 -0
  99. package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
  100. package/drizzle/0206_browser_sessions.sql +646 -0
  101. package/drizzle/0207_browser_identities.sql +379 -0
  102. package/drizzle/0208_attached_browser_devices.sql +165 -0
  103. package/drizzle/0209_computer_sessions.sql +575 -0
  104. package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
  105. package/drizzle/0211_editable_artifact_session_links.sql +58 -0
  106. package/drizzle/0212_slack_installation_bindings.sql +348 -0
  107. package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
  108. package/drizzle/0214_session_activity_commit_gate.sql +262 -0
  109. package/drizzle/0215_capabilities_platform.sql +980 -0
  110. package/drizzle/0216_pack_component_ownership.sql +235 -0
  111. package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
  112. package/package.json +18 -5
  113. package/src/attached-browser-devices.ts +371 -0
  114. package/src/attempt-tool-catalogs.ts +131 -0
  115. package/src/browser-auth.ts +1896 -0
  116. package/src/browser-identities.ts +1168 -0
  117. package/src/browser-sessions.ts +2005 -0
  118. package/src/browser-state-artifacts.ts +59 -0
  119. package/src/capability-components.ts +166 -0
  120. package/src/capability-integrations.ts +1663 -0
  121. package/src/codemode-operations.ts +674 -0
  122. package/src/company-profile-schema.ts +143 -0
  123. package/src/company-profile.ts +968 -0
  124. package/src/computer-sessions.ts +1130 -0
  125. package/src/connection-token-resolver.ts +270 -25
  126. package/src/database.ts +427 -23
  127. package/src/editable-artifact-durable-export.ts +1201 -0
  128. package/src/editable-artifact-materialization.ts +893 -0
  129. package/src/editable-artifacts-schema.ts +879 -0
  130. package/src/editable-artifacts.ts +5404 -0
  131. package/src/generated-images.ts +740 -0
  132. package/src/index.ts +5917 -1182
  133. package/src/insights.ts +160 -13
  134. package/src/integration-bindings.ts +455 -0
  135. package/src/integration-features.ts +859 -0
  136. package/src/interaction-revisions.ts +40 -0
  137. package/src/interaction-schema.ts +1303 -0
  138. package/src/knowledge-source-sync-schema.ts +192 -0
  139. package/src/knowledge-source-sync.ts +1922 -0
  140. package/src/memory-slack-delivery.ts +801 -0
  141. package/src/pack-components.ts +954 -0
  142. package/src/pack-installations.ts +929 -0
  143. package/src/persistence-errors.ts +4 -7
  144. package/src/plugin-packages.ts +1186 -0
  145. package/src/provision-roles.ts +302 -1
  146. package/src/runtime-posture-cli.ts +9 -0
  147. package/src/runtime-posture.ts +508 -5
  148. package/src/schema.ts +5117 -3119
  149. package/src/scoped-knowledge-schema.ts +1 -1
  150. package/src/scoped-knowledge.ts +655 -3
  151. package/src/session-control.ts +8 -5
  152. package/src/session-queue-commands.ts +187 -14
  153. package/src/session-realtime-context.ts +2 -2
  154. package/src/session-realtime-ledger.ts +3 -3
  155. package/src/session-realtime.ts +7 -7
  156. package/src/session-tool-call-settlement.ts +9 -0
  157. package/src/slack-user-link-access.ts +702 -0
  158. package/src/video-generation.ts +1452 -0
  159. package/src/workspace-learning-policy-schema.ts +139 -0
  160. package/src/workspace-learning-policy.ts +595 -0
  161. package/dist/chunk-P6CUHXQZ.js +0 -7248
  162. package/dist/chunk-P6CUHXQZ.js.map +0 -1
  163. package/dist/chunk-VHBFHMPC.js.map +0 -1
@@ -0,0 +1,674 @@
1
+ import {
2
+ AttemptToolCall,
3
+ AttemptToolResult,
4
+ CODEMODE_ARGUMENTS_MAX_BYTES,
5
+ CODEMODE_CLAIM_LEASE_MS,
6
+ CODEMODE_RESULT_MAX_BYTES,
7
+ CODEMODE_OPERATION_VERSION,
8
+ CodemodeOperation,
9
+ type AttemptToolCall as AttemptToolCallValue,
10
+ type AttemptToolResult as AttemptToolResultValue,
11
+ type CodemodeOperation as CodemodeOperationValue,
12
+ } from "@opengeni/contracts";
13
+ import {
14
+ digestCodemodeOperationRequest,
15
+ parseVerifiedAttemptToolCatalog,
16
+ } from "@opengeni/codemode";
17
+ import { and, eq, sql } from "drizzle-orm";
18
+ import type { Database } from "./database";
19
+ import { withRlsContext } from "./database";
20
+ import * as schema from "./schema";
21
+
22
+ export class CodemodeOperationConflictError extends Error {
23
+ readonly code = "codemode_operation_conflict";
24
+
25
+ constructor() {
26
+ super("Codemode operation id is already bound to a different request");
27
+ this.name = "CodemodeOperationConflictError";
28
+ }
29
+ }
30
+
31
+ export class CodemodeOperationNotExecutableError extends Error {
32
+ readonly code = "codemode_attempt_not_executable";
33
+
34
+ constructor() {
35
+ super("Codemode operation does not belong to the active running execution attempt");
36
+ this.name = "CodemodeOperationNotExecutableError";
37
+ }
38
+ }
39
+
40
+ export class CodemodeToolNotInCatalogError extends Error {
41
+ readonly code = "codemode_tool_not_in_catalog";
42
+
43
+ constructor() {
44
+ super("Tool is not present in the exact execution attempt catalog");
45
+ this.name = "CodemodeToolNotInCatalogError";
46
+ }
47
+ }
48
+
49
+ export class CodemodeToolApprovalRequiredError extends Error {
50
+ readonly code = "codemode_tool_approval_required";
51
+
52
+ constructor() {
53
+ super("Tool requires human approval and must be invoked through the agent");
54
+ this.name = "CodemodeToolApprovalRequiredError";
55
+ }
56
+ }
57
+
58
+ export class CodemodeCallBudgetExceededError extends Error {
59
+ readonly code = "codemode_call_budget_exhausted";
60
+
61
+ constructor() {
62
+ super("Codemode call budget is exhausted for this turn");
63
+ this.name = "CodemodeCallBudgetExceededError";
64
+ }
65
+ }
66
+
67
+ export class CodemodePayloadTooLargeError extends Error {
68
+ readonly code = "codemode_payload_too_large";
69
+
70
+ constructor(kind: "arguments" | "result") {
71
+ super(`Codemode ${kind} exceed the maximum serialized size`);
72
+ this.name = "CodemodePayloadTooLargeError";
73
+ }
74
+ }
75
+
76
+ export type SubmitCodemodeOperationInput = {
77
+ accountId: string;
78
+ workspaceId: string;
79
+ sessionId: string;
80
+ turnId: string;
81
+ attemptId: string;
82
+ executionGeneration: number;
83
+ call: AttemptToolCallValue;
84
+ callLimit: number;
85
+ };
86
+
87
+ /**
88
+ * Bind one caller-provided operation id to one exact catalog call. Duplicate
89
+ * submission of identical bytes is free and returns the same row; repurposing
90
+ * the id fails. Admission, budget reservation and insert share one turn lock.
91
+ */
92
+ export async function submitCodemodeOperation(
93
+ db: Database,
94
+ input: SubmitCodemodeOperationInput,
95
+ ): Promise<{ operation: CodemodeOperationValue; created: boolean }> {
96
+ const call = AttemptToolCall.parse(input.call);
97
+ if (call.caller.kind !== "codemode") throw new CodemodeOperationNotExecutableError();
98
+ assertJsonBytes(call.arguments, CODEMODE_ARGUMENTS_MAX_BYTES, "arguments");
99
+ const requestDigest = digestCodemodeOperationRequest(call);
100
+ return await withRlsContext(
101
+ db,
102
+ { accountId: input.accountId, workspaceId: input.workspaceId },
103
+ async (scopedDb) =>
104
+ await scopedDb.transaction(async (tx) => {
105
+ const [existing] = await tx
106
+ .select()
107
+ .from(schema.sessionAttemptCodemodeCalls)
108
+ .where(eq(schema.sessionAttemptCodemodeCalls.operationId, call.operationId))
109
+ .for("update")
110
+ .limit(1);
111
+ if (existing) {
112
+ assertExistingRequest(existing, input, requestDigest);
113
+ return { operation: mapOperation(existing), created: false };
114
+ }
115
+
116
+ const [turn] = await tx
117
+ .select({
118
+ accountId: schema.sessionTurns.accountId,
119
+ sessionId: schema.sessionTurns.sessionId,
120
+ status: schema.sessionTurns.status,
121
+ activeAttemptId: schema.sessionTurns.activeAttemptId,
122
+ executionGeneration: schema.sessionTurns.executionGeneration,
123
+ callCount: schema.sessionTurns.codemodeCallCount,
124
+ })
125
+ .from(schema.sessionTurns)
126
+ .where(
127
+ and(
128
+ eq(schema.sessionTurns.workspaceId, input.workspaceId),
129
+ eq(schema.sessionTurns.id, input.turnId),
130
+ ),
131
+ )
132
+ .for("update")
133
+ .limit(1);
134
+ if (
135
+ !turn ||
136
+ turn.accountId !== input.accountId ||
137
+ turn.sessionId !== input.sessionId ||
138
+ turn.status !== "running" ||
139
+ turn.activeAttemptId !== input.attemptId ||
140
+ turn.executionGeneration !== input.executionGeneration
141
+ ) {
142
+ throw new CodemodeOperationNotExecutableError();
143
+ }
144
+ if (!Number.isInteger(input.callLimit) || input.callLimit < 1) {
145
+ throw new CodemodeCallBudgetExceededError();
146
+ }
147
+ if (turn.callCount >= input.callLimit) throw new CodemodeCallBudgetExceededError();
148
+
149
+ const [catalogRow] = await tx
150
+ .select({ catalog: schema.sessionAttemptToolCatalogs.catalog })
151
+ .from(schema.sessionAttemptToolCatalogs)
152
+ .where(
153
+ and(
154
+ eq(schema.sessionAttemptToolCatalogs.attemptId, input.attemptId),
155
+ eq(schema.sessionAttemptToolCatalogs.digest, call.catalogDigest),
156
+ ),
157
+ )
158
+ .limit(1);
159
+ if (!catalogRow) throw new CodemodeOperationNotExecutableError();
160
+ const catalog = parseVerifiedAttemptToolCatalog(catalogRow.catalog);
161
+ if (
162
+ catalog.accountId !== input.accountId ||
163
+ catalog.workspaceId !== input.workspaceId ||
164
+ catalog.sessionId !== input.sessionId ||
165
+ catalog.turnId !== input.turnId ||
166
+ catalog.attemptId !== input.attemptId ||
167
+ catalog.executionGeneration !== input.executionGeneration
168
+ ) {
169
+ throw new CodemodeOperationNotExecutableError();
170
+ }
171
+ const catalogEntry = catalog.entries.find(
172
+ (entry) =>
173
+ entry.identity.serverId === call.identity.serverId &&
174
+ entry.identity.toolName === call.identity.toolName,
175
+ );
176
+ if (!catalogEntry) {
177
+ throw new CodemodeToolNotInCatalogError();
178
+ }
179
+ if (catalogEntry.approval === "human") {
180
+ throw new CodemodeToolApprovalRequiredError();
181
+ }
182
+
183
+ const [counter] = await tx
184
+ .update(schema.sessionTurns)
185
+ .set({
186
+ codemodeCallCount: sql`${schema.sessionTurns.codemodeCallCount} + 1`,
187
+ updatedAt: new Date(),
188
+ })
189
+ .where(
190
+ and(
191
+ eq(schema.sessionTurns.workspaceId, input.workspaceId),
192
+ eq(schema.sessionTurns.id, input.turnId),
193
+ eq(schema.sessionTurns.status, "running"),
194
+ eq(schema.sessionTurns.activeAttemptId, input.attemptId),
195
+ eq(schema.sessionTurns.executionGeneration, input.executionGeneration),
196
+ sql`${schema.sessionTurns.codemodeCallCount} < ${input.callLimit}`,
197
+ ),
198
+ )
199
+ .returning({ count: schema.sessionTurns.codemodeCallCount });
200
+ if (!counter) throw new CodemodeCallBudgetExceededError();
201
+
202
+ const [created] = await tx
203
+ .insert(schema.sessionAttemptCodemodeCalls)
204
+ .values({
205
+ operationId: call.operationId,
206
+ accountId: input.accountId,
207
+ workspaceId: input.workspaceId,
208
+ sessionId: input.sessionId,
209
+ turnId: input.turnId,
210
+ attemptId: input.attemptId,
211
+ executionGeneration: input.executionGeneration,
212
+ catalogDigest: call.catalogDigest,
213
+ requestDigest,
214
+ serverId: call.identity.serverId,
215
+ toolName: call.identity.toolName,
216
+ arguments: call.arguments,
217
+ callerSubjectId: call.caller.subjectId,
218
+ })
219
+ .returning();
220
+ if (!created) throw new Error("Failed to create Codemode operation");
221
+ return { operation: mapOperation(created), created: true };
222
+ }),
223
+ );
224
+ }
225
+
226
+ export async function getCodemodeOperation(
227
+ db: Database,
228
+ input: { accountId: string; workspaceId: string; attemptId: string; operationId: string },
229
+ ): Promise<CodemodeOperationValue | null> {
230
+ return await withRlsContext(
231
+ db,
232
+ { accountId: input.accountId, workspaceId: input.workspaceId },
233
+ async (scopedDb) => {
234
+ const [row] = await scopedDb
235
+ .select()
236
+ .from(schema.sessionAttemptCodemodeCalls)
237
+ .where(
238
+ and(
239
+ eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId),
240
+ eq(schema.sessionAttemptCodemodeCalls.attemptId, input.attemptId),
241
+ ),
242
+ )
243
+ .limit(1);
244
+ return row ? mapOperation(row) : null;
245
+ },
246
+ );
247
+ }
248
+
249
+ export type ClaimCodemodeOperationResult =
250
+ | { status: "claimed"; operation: CodemodeOperationValue; claimId: string }
251
+ | { status: "already_running" | "terminal"; operation: CodemodeOperationValue }
252
+ | { status: "rejected"; operation: CodemodeOperationValue | null };
253
+
254
+ /** Exactly one worker may cross the side-effect boundary for an operation. */
255
+ export async function claimCodemodeOperation(
256
+ db: Database,
257
+ input: {
258
+ accountId: string;
259
+ workspaceId: string;
260
+ sessionId: string;
261
+ turnId: string;
262
+ attemptId: string;
263
+ executionGeneration: number;
264
+ catalogDigest: string;
265
+ operationId: string;
266
+ claimId: string;
267
+ now?: Date;
268
+ claimLeaseMs?: number;
269
+ },
270
+ ): Promise<ClaimCodemodeOperationResult> {
271
+ return await withRlsContext(
272
+ db,
273
+ { accountId: input.accountId, workspaceId: input.workspaceId },
274
+ async (scopedDb) =>
275
+ await scopedDb.transaction(async (tx) => {
276
+ let [row] = await tx
277
+ .select()
278
+ .from(schema.sessionAttemptCodemodeCalls)
279
+ .where(eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId))
280
+ .for("update")
281
+ .limit(1);
282
+ if (!row || !operationMatchesAuthority(row, input)) {
283
+ return { status: "rejected", operation: row ? mapOperation(row) : null };
284
+ }
285
+ const now = input.now ?? new Date();
286
+ const claimExpiresAt = new Date(now.getTime() + boundedClaimLeaseMs(input.claimLeaseMs));
287
+ if (row.state === "running") {
288
+ if (!row.claimExpiresAt || row.claimExpiresAt.getTime() > now.getTime()) {
289
+ return { status: "already_running", operation: mapOperation(row) };
290
+ }
291
+ if (row.executionStartedAt) {
292
+ const [unknown] = await tx
293
+ .update(schema.sessionAttemptCodemodeCalls)
294
+ .set({
295
+ state: "outcome_unknown",
296
+ errorCode: "worker_lost_during_execution",
297
+ errorMessage:
298
+ "The execution owner disappeared after the tool call began. Inspect actual state before retrying.",
299
+ completedAt: now,
300
+ updatedAt: now,
301
+ })
302
+ .where(
303
+ and(
304
+ eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId),
305
+ eq(schema.sessionAttemptCodemodeCalls.state, "running"),
306
+ eq(schema.sessionAttemptCodemodeCalls.claimId, row.claimId!),
307
+ ),
308
+ )
309
+ .returning();
310
+ return { status: "terminal", operation: mapOperation(unknown ?? row) };
311
+ }
312
+ const [requeued] = await tx
313
+ .update(schema.sessionAttemptCodemodeCalls)
314
+ .set({
315
+ state: "queued",
316
+ claimId: null,
317
+ claimedAt: null,
318
+ claimExpiresAt: null,
319
+ updatedAt: now,
320
+ })
321
+ .where(
322
+ and(
323
+ eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId),
324
+ eq(schema.sessionAttemptCodemodeCalls.state, "running"),
325
+ eq(schema.sessionAttemptCodemodeCalls.claimId, row.claimId!),
326
+ ),
327
+ )
328
+ .returning();
329
+ if (!requeued) throw new Error("Codemode expired claim recovery lost its row lock");
330
+ row = requeued;
331
+ }
332
+ if (isTerminalState(row.state)) return { status: "terminal", operation: mapOperation(row) };
333
+
334
+ const [turn] = await tx
335
+ .select({
336
+ status: schema.sessionTurns.status,
337
+ activeAttemptId: schema.sessionTurns.activeAttemptId,
338
+ executionGeneration: schema.sessionTurns.executionGeneration,
339
+ })
340
+ .from(schema.sessionTurns)
341
+ .where(
342
+ and(
343
+ eq(schema.sessionTurns.workspaceId, input.workspaceId),
344
+ eq(schema.sessionTurns.id, input.turnId),
345
+ ),
346
+ )
347
+ .for("update")
348
+ .limit(1);
349
+ if (
350
+ !turn ||
351
+ turn.status !== "running" ||
352
+ turn.activeAttemptId !== input.attemptId ||
353
+ turn.executionGeneration !== input.executionGeneration
354
+ ) {
355
+ const [cancelled] = await tx
356
+ .update(schema.sessionAttemptCodemodeCalls)
357
+ .set({
358
+ state: "cancelled",
359
+ errorCode: "attempt_not_executable",
360
+ errorMessage: "Execution attempt ended before the Codemode call started",
361
+ completedAt: now,
362
+ updatedAt: now,
363
+ })
364
+ .where(
365
+ and(
366
+ eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId),
367
+ eq(schema.sessionAttemptCodemodeCalls.state, "queued"),
368
+ ),
369
+ )
370
+ .returning();
371
+ return { status: "terminal", operation: mapOperation(cancelled ?? row) };
372
+ }
373
+ const [claimed] = await tx
374
+ .update(schema.sessionAttemptCodemodeCalls)
375
+ .set({
376
+ state: "running",
377
+ claimId: input.claimId,
378
+ claimedAt: now,
379
+ claimExpiresAt,
380
+ updatedAt: now,
381
+ })
382
+ .where(
383
+ and(
384
+ eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId),
385
+ eq(schema.sessionAttemptCodemodeCalls.state, "queued"),
386
+ ),
387
+ )
388
+ .returning();
389
+ if (!claimed) throw new Error("Codemode operation claim lost its row lock");
390
+ return { status: "claimed", operation: mapOperation(claimed), claimId: input.claimId };
391
+ }),
392
+ );
393
+ }
394
+
395
+ /** Durable side-effect boundary. A stale claim before this marker may be retried. */
396
+ export async function markCodemodeOperationExecutionStarted(
397
+ db: Database,
398
+ input: CodemodeCompletionAuthority & { now?: Date; claimLeaseMs?: number },
399
+ ): Promise<boolean> {
400
+ return await withRlsContext(
401
+ db,
402
+ { accountId: input.accountId, workspaceId: input.workspaceId },
403
+ async (scopedDb) => {
404
+ const now = input.now ?? new Date();
405
+ const rows = await scopedDb
406
+ .update(schema.sessionAttemptCodemodeCalls)
407
+ .set({
408
+ executionStartedAt: sql`coalesce(${schema.sessionAttemptCodemodeCalls.executionStartedAt}, now())`,
409
+ claimExpiresAt: new Date(now.getTime() + boundedClaimLeaseMs(input.claimLeaseMs)),
410
+ updatedAt: now,
411
+ })
412
+ .where(
413
+ and(
414
+ eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId),
415
+ eq(schema.sessionAttemptCodemodeCalls.attemptId, input.attemptId),
416
+ eq(schema.sessionAttemptCodemodeCalls.state, "running"),
417
+ eq(schema.sessionAttemptCodemodeCalls.claimId, input.claimId),
418
+ ),
419
+ )
420
+ .returning({ id: schema.sessionAttemptCodemodeCalls.operationId });
421
+ return rows.length === 1;
422
+ },
423
+ );
424
+ }
425
+
426
+ export async function renewCodemodeOperationClaim(
427
+ db: Database,
428
+ input: CodemodeCompletionAuthority & { now?: Date; claimLeaseMs?: number },
429
+ ): Promise<boolean> {
430
+ return await withRlsContext(
431
+ db,
432
+ { accountId: input.accountId, workspaceId: input.workspaceId },
433
+ async (scopedDb) => {
434
+ const now = input.now ?? new Date();
435
+ const rows = await scopedDb
436
+ .update(schema.sessionAttemptCodemodeCalls)
437
+ .set({
438
+ claimExpiresAt: new Date(now.getTime() + boundedClaimLeaseMs(input.claimLeaseMs)),
439
+ updatedAt: now,
440
+ })
441
+ .where(
442
+ and(
443
+ eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId),
444
+ eq(schema.sessionAttemptCodemodeCalls.attemptId, input.attemptId),
445
+ eq(schema.sessionAttemptCodemodeCalls.state, "running"),
446
+ eq(schema.sessionAttemptCodemodeCalls.claimId, input.claimId),
447
+ ),
448
+ )
449
+ .returning({ id: schema.sessionAttemptCodemodeCalls.operationId });
450
+ return rows.length === 1;
451
+ },
452
+ );
453
+ }
454
+
455
+ export async function completeCodemodeOperation(
456
+ db: Database,
457
+ input: CodemodeCompletionAuthority & { result: AttemptToolResultValue },
458
+ ): Promise<boolean> {
459
+ return await withRlsContext(
460
+ db,
461
+ { accountId: input.accountId, workspaceId: input.workspaceId },
462
+ async (scopedDb) => await completeCodemodeOperationInTransaction(scopedDb, input),
463
+ );
464
+ }
465
+
466
+ /** Internal transaction primitive used when the terminal timeline event must commit atomically. */
467
+ export async function completeCodemodeOperationInTransaction(
468
+ tx: Database,
469
+ input: CodemodeCompletionAuthority & { result: AttemptToolResultValue },
470
+ ): Promise<boolean> {
471
+ const result = AttemptToolResult.parse(input.result);
472
+ assertJsonBytes(result, CODEMODE_RESULT_MAX_BYTES, "result");
473
+ return await settleClaimedCodemodeOperationInTransaction(tx, input, {
474
+ state: "completed",
475
+ result,
476
+ errorCode: null,
477
+ errorMessage: null,
478
+ });
479
+ }
480
+
481
+ export async function failCodemodeOperation(
482
+ db: Database,
483
+ input: CodemodeCompletionAuthority & {
484
+ state: "failed" | "outcome_unknown";
485
+ errorCode: string;
486
+ errorMessage: string;
487
+ },
488
+ ): Promise<boolean> {
489
+ return await withRlsContext(
490
+ db,
491
+ { accountId: input.accountId, workspaceId: input.workspaceId },
492
+ async (scopedDb) => await failCodemodeOperationInTransaction(scopedDb, input),
493
+ );
494
+ }
495
+
496
+ /** Internal transaction primitive used when the terminal timeline event must commit atomically. */
497
+ export async function failCodemodeOperationInTransaction(
498
+ tx: Database,
499
+ input: CodemodeCompletionAuthority & {
500
+ state: "failed" | "outcome_unknown";
501
+ errorCode: string;
502
+ errorMessage: string;
503
+ },
504
+ ): Promise<boolean> {
505
+ return await settleClaimedCodemodeOperationInTransaction(tx, input, {
506
+ state: input.state,
507
+ result: null,
508
+ errorCode: boundedText(input.errorCode, 128),
509
+ errorMessage: boundedText(input.errorMessage, 4_096),
510
+ });
511
+ }
512
+
513
+ export async function cancelQueuedCodemodeOperationsForAttempt(
514
+ db: Database,
515
+ input: {
516
+ accountId: string;
517
+ workspaceId: string;
518
+ attemptId: string;
519
+ reason: string;
520
+ },
521
+ ): Promise<number> {
522
+ return await withRlsContext(
523
+ db,
524
+ { accountId: input.accountId, workspaceId: input.workspaceId },
525
+ async (scopedDb) => {
526
+ const now = new Date();
527
+ const rows = await scopedDb
528
+ .update(schema.sessionAttemptCodemodeCalls)
529
+ .set({
530
+ state: "cancelled",
531
+ errorCode: "attempt_ended",
532
+ errorMessage: boundedText(input.reason, 4_096),
533
+ completedAt: now,
534
+ updatedAt: now,
535
+ })
536
+ .where(
537
+ and(
538
+ eq(schema.sessionAttemptCodemodeCalls.attemptId, input.attemptId),
539
+ eq(schema.sessionAttemptCodemodeCalls.state, "queued"),
540
+ ),
541
+ )
542
+ .returning({ id: schema.sessionAttemptCodemodeCalls.operationId });
543
+ return rows.length;
544
+ },
545
+ );
546
+ }
547
+
548
+ type CodemodeCompletionAuthority = {
549
+ accountId: string;
550
+ workspaceId: string;
551
+ attemptId: string;
552
+ operationId: string;
553
+ claimId: string;
554
+ };
555
+
556
+ async function settleClaimedCodemodeOperationInTransaction(
557
+ tx: Database,
558
+ input: CodemodeCompletionAuthority,
559
+ settlement: {
560
+ state: "completed" | "failed" | "outcome_unknown";
561
+ result: AttemptToolResultValue | null;
562
+ errorCode: string | null;
563
+ errorMessage: string | null;
564
+ },
565
+ ): Promise<boolean> {
566
+ const now = new Date();
567
+ const rows = await tx
568
+ .update(schema.sessionAttemptCodemodeCalls)
569
+ .set({ ...settlement, completedAt: now, updatedAt: now })
570
+ .where(
571
+ and(
572
+ eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId),
573
+ eq(schema.sessionAttemptCodemodeCalls.attemptId, input.attemptId),
574
+ eq(schema.sessionAttemptCodemodeCalls.state, "running"),
575
+ eq(schema.sessionAttemptCodemodeCalls.claimId, input.claimId),
576
+ ),
577
+ )
578
+ .returning({ id: schema.sessionAttemptCodemodeCalls.operationId });
579
+ return rows.length === 1;
580
+ }
581
+
582
+ function assertExistingRequest(
583
+ row: typeof schema.sessionAttemptCodemodeCalls.$inferSelect,
584
+ input: SubmitCodemodeOperationInput,
585
+ requestDigest: string,
586
+ ): void {
587
+ if (
588
+ row.accountId !== input.accountId ||
589
+ row.workspaceId !== input.workspaceId ||
590
+ row.sessionId !== input.sessionId ||
591
+ row.turnId !== input.turnId ||
592
+ row.attemptId !== input.attemptId ||
593
+ row.executionGeneration !== input.executionGeneration ||
594
+ row.catalogDigest !== input.call.catalogDigest ||
595
+ row.requestDigest !== requestDigest
596
+ ) {
597
+ throw new CodemodeOperationConflictError();
598
+ }
599
+ }
600
+
601
+ function operationMatchesAuthority(
602
+ row: typeof schema.sessionAttemptCodemodeCalls.$inferSelect,
603
+ input: {
604
+ accountId: string;
605
+ workspaceId: string;
606
+ sessionId: string;
607
+ turnId: string;
608
+ attemptId: string;
609
+ executionGeneration: number;
610
+ catalogDigest: string;
611
+ },
612
+ ): boolean {
613
+ return (
614
+ row.accountId === input.accountId &&
615
+ row.workspaceId === input.workspaceId &&
616
+ row.sessionId === input.sessionId &&
617
+ row.turnId === input.turnId &&
618
+ row.attemptId === input.attemptId &&
619
+ row.executionGeneration === input.executionGeneration &&
620
+ row.catalogDigest === input.catalogDigest
621
+ );
622
+ }
623
+
624
+ function mapOperation(
625
+ row: typeof schema.sessionAttemptCodemodeCalls.$inferSelect,
626
+ ): CodemodeOperationValue {
627
+ return CodemodeOperation.parse({
628
+ version: CODEMODE_OPERATION_VERSION,
629
+ operationId: row.operationId,
630
+ accountId: row.accountId,
631
+ workspaceId: row.workspaceId,
632
+ sessionId: row.sessionId,
633
+ turnId: row.turnId,
634
+ attemptId: row.attemptId,
635
+ executionGeneration: row.executionGeneration,
636
+ catalogDigest: row.catalogDigest,
637
+ requestDigest: row.requestDigest,
638
+ identity: { serverId: row.serverId, toolName: row.toolName },
639
+ arguments: row.arguments,
640
+ caller: { kind: "codemode", subjectId: row.callerSubjectId },
641
+ state: row.state,
642
+ result: row.result,
643
+ errorCode: row.errorCode,
644
+ errorMessage: row.errorMessage,
645
+ createdAt: row.createdAt.toISOString(),
646
+ claimedAt: row.claimedAt?.toISOString() ?? null,
647
+ executionStartedAt: row.executionStartedAt?.toISOString() ?? null,
648
+ completedAt: row.completedAt?.toISOString() ?? null,
649
+ updatedAt: row.updatedAt.toISOString(),
650
+ });
651
+ }
652
+
653
+ function isTerminalState(state: string): boolean {
654
+ return ["completed", "failed", "outcome_unknown", "cancelled"].includes(state);
655
+ }
656
+
657
+ function assertJsonBytes(value: unknown, maximum: number, kind: "arguments" | "result"): void {
658
+ if (new TextEncoder().encode(JSON.stringify(value)).byteLength > maximum) {
659
+ throw new CodemodePayloadTooLargeError(kind);
660
+ }
661
+ }
662
+
663
+ function boundedText(value: string, maximum: number): string {
664
+ const normalized = value.trim() || "Codemode operation failed";
665
+ return normalized.length <= maximum ? normalized : normalized.slice(0, maximum);
666
+ }
667
+
668
+ function boundedClaimLeaseMs(value: number | undefined): number {
669
+ const candidate = value ?? CODEMODE_CLAIM_LEASE_MS;
670
+ if (!Number.isSafeInteger(candidate) || candidate < 1_000 || candidate > 10 * 60_000) {
671
+ throw new Error("Codemode claim lease must be between 1 second and 10 minutes");
672
+ }
673
+ return candidate;
674
+ }