@opengeni/db 0.28.9 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attached-browser-devices.d.ts +42 -0
- package/dist/attempt-tool-catalogs.d.ts +16 -0
- package/dist/browser-auth.d.ts +299 -0
- package/dist/browser-downloads.d.ts +51 -0
- package/dist/browser-identities.d.ts +137 -0
- package/dist/browser-sessions.d.ts +354 -0
- package/dist/browser-state-artifacts.d.ts +91 -0
- package/dist/canonical-human-identities.d.ts +48 -0
- package/dist/canonical-human-identities.js +28 -0
- package/dist/canonical-human-identities.js.map +1 -0
- package/dist/capability-components.d.ts +37 -0
- package/dist/capability-integrations.d.ts +194 -0
- package/dist/chunk-5KTIEDX7.js +13200 -0
- package/dist/chunk-5KTIEDX7.js.map +1 -0
- package/dist/chunk-CQZ4QSP5.js +995 -0
- package/dist/chunk-CQZ4QSP5.js.map +1 -0
- package/dist/chunk-F32YT7MP.js +117 -0
- package/dist/chunk-F32YT7MP.js.map +1 -0
- package/dist/chunk-JC5QPLUM.js +666 -0
- package/dist/chunk-JC5QPLUM.js.map +1 -0
- package/dist/chunk-P3RE4D2B.js +3535 -0
- package/dist/chunk-P3RE4D2B.js.map +1 -0
- package/dist/chunk-TAUTWOS5.js +259 -0
- package/dist/chunk-TAUTWOS5.js.map +1 -0
- package/dist/chunk-VKLB2ZGI.js +2389 -0
- package/dist/chunk-VKLB2ZGI.js.map +1 -0
- package/dist/codemode-operations.d.ts +117 -0
- package/dist/codex-token-resolver.d.ts +2 -2
- package/dist/company-profile-schema.d.ts +798 -0
- package/dist/company-profile.d.ts +268 -0
- package/dist/computer-sessions.d.ts +109 -0
- package/dist/connection-token-resolver.d.ts +9 -4
- package/dist/database.d.ts +57 -3
- package/dist/editable-artifact-durable-export.d.ts +153 -0
- package/dist/editable-artifact-durable-export.js +788 -0
- package/dist/editable-artifact-durable-export.js.map +1 -0
- package/dist/editable-artifact-materialization.d.ts +123 -0
- package/dist/editable-artifacts-schema.d.ts +4152 -0
- package/dist/editable-artifacts.d.ts +610 -0
- package/dist/editable-artifacts.js +26 -0
- package/dist/editable-artifacts.js.map +1 -0
- package/dist/generated-images.d.ts +139 -0
- package/dist/index.d.ts +1122 -126
- package/dist/index.js +42329 -18407
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +73 -10
- package/dist/integration-bindings.d.ts +70 -0
- package/dist/integration-facets.d.ts +99 -0
- package/dist/interaction-revisions.d.ts +14 -0
- package/dist/interaction-schema.d.ts +6036 -0
- package/dist/knowledge-source-sync-schema.d.ts +1679 -0
- package/dist/knowledge-source-sync.d.ts +274 -0
- package/dist/memory-governance.d.ts +2 -2
- package/dist/memory-slack-delivery.d.ts +101 -0
- package/dist/new-session-drafts.d.ts +2 -2
- package/dist/pack-components.d.ts +76 -0
- package/dist/pack-installations.d.ts +86 -0
- package/dist/persistence-errors.d.ts +3 -3
- package/dist/plugin-packages.d.ts +126 -0
- package/dist/preference-registry.d.ts +1 -1
- package/dist/provision-roles.d.ts +9 -1
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +19 -6
- package/dist/schema.d.ts +22654 -9359
- package/dist/schema.js +209 -3
- package/dist/scoped-knowledge.d.ts +97 -1
- package/dist/session-control.d.ts +33 -7
- package/dist/session-queue-commands.d.ts +19 -11
- package/dist/session-realtime-context.d.ts +2 -2
- package/dist/session-realtime-ledger.d.ts +4 -4
- package/dist/session-realtime-mirror.d.ts +2 -2
- package/dist/session-realtime-state.d.ts +1 -1
- package/dist/session-realtime-terminal.d.ts +1 -1
- package/dist/session-realtime.d.ts +7 -7
- package/dist/session-tenancy.d.ts +44 -0
- package/dist/session-tenancy.js +16 -0
- package/dist/session-tenancy.js.map +1 -0
- package/dist/session-tool-call-settlement.d.ts +1 -1
- package/dist/slack-task-policy-schema.d.ts +847 -0
- package/dist/slack-task-policy.d.ts +65 -0
- package/dist/slack-user-link-access.d.ts +54 -0
- package/dist/task-notes-schema.d.ts +924 -0
- package/dist/task-notes.d.ts +85 -0
- package/dist/transcription-recordings.d.ts +2 -2
- package/dist/turn-initiator.d.ts +2 -2
- package/dist/video-generation.d.ts +225 -0
- package/dist/video-generation.js +68 -0
- package/dist/video-generation.js.map +1 -0
- package/dist/workspace-artifacts.d.ts +1 -1
- package/dist/workspace-instruction-policies.d.ts +1 -1
- package/dist/workspace-learning-policy-schema.d.ts +932 -0
- package/dist/workspace-learning-policy.d.ts +161 -0
- package/dist/xai-subscription.d.ts +326 -0
- package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
- package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
- package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
- package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
- package/drizzle/0187_generated_image_artifacts.sql +173 -0
- package/drizzle/0188_image_generation_retention_failure.sql +25 -0
- package/drizzle/0189_retained_session_image_formats.sql +13 -0
- package/drizzle/0190_timeline_annotations.sql +31 -0
- package/drizzle/0191_editable_artifact_engine.sql +3853 -0
- package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
- package/drizzle/0193_editable_artifact_authorization.sql +190 -0
- package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
- package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
- package/drizzle/0196_rig_provider_images.sql +152 -0
- package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
- package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
- package/drizzle/0199_workspace_learning_policy.sql +980 -0
- package/drizzle/0201_company_profile_authority.sql +849 -0
- package/drizzle/0202_document_index_checkpoints.sql +85 -0
- package/drizzle/0203_durable_video_generation.sql +353 -0
- package/drizzle/0204_video_generation_funding.sql +44 -0
- package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
- package/drizzle/0206_browser_sessions.sql +646 -0
- package/drizzle/0207_browser_identities.sql +379 -0
- package/drizzle/0208_attached_browser_devices.sql +165 -0
- package/drizzle/0209_computer_sessions.sql +575 -0
- package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
- package/drizzle/0211_editable_artifact_session_links.sql +58 -0
- package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
- package/drizzle/0212_slack_installation_bindings.sql +348 -0
- package/drizzle/0213_browser_interaction_authority.sql +67 -0
- package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
- package/drizzle/0214_browser_download_saves.sql +34 -0
- package/drizzle/0214_session_activity_commit_gate.sql +262 -0
- package/drizzle/0215_browser_controller_host.sql +385 -0
- package/drizzle/0215_capabilities_platform.sql +980 -0
- package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
- package/drizzle/0216_pack_component_ownership.sql +235 -0
- package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
- package/drizzle/0217_external_browser_auth_operations.sql +22 -0
- package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
- package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
- package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
- package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
- package/drizzle/0220_session_channels.sql +54 -0
- package/drizzle/0221_sessions_channel_index.sql +5 -0
- package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
- package/drizzle/0222_sessions_channel_fk.sql +89 -0
- package/drizzle/0223_pending_tool_event_output.sql +55 -0
- package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
- package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
- package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
- package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
- package/drizzle/0227_slack_native_actions.sql +145 -0
- package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
- package/drizzle/0228_slack_task_policy.sql +400 -0
- package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
- package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
- package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
- package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
- package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
- package/drizzle/0234_xai_subscription_authority.sql +1139 -0
- package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
- package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
- package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
- package/drizzle/0237_interaction_transition_reaper.sql +263 -0
- package/drizzle/0238_goal_persistence_policy.sql +471 -0
- package/drizzle/0238_supergrok_realtime_model.sql +29 -0
- package/drizzle/0239_supergrok_video_funding.sql +50 -0
- package/drizzle/0239_task_tree_notes.sql +990 -0
- package/package.json +26 -5
- package/src/attached-browser-devices.ts +371 -0
- package/src/attempt-tool-catalogs.ts +131 -0
- package/src/browser-auth.ts +4373 -0
- package/src/browser-downloads.ts +517 -0
- package/src/browser-identities.ts +1385 -0
- package/src/browser-sessions.ts +2294 -0
- package/src/browser-state-artifacts.ts +449 -0
- package/src/canonical-human-identities.ts +317 -0
- package/src/capability-components.ts +166 -0
- package/src/capability-integrations.ts +1571 -0
- package/src/codemode-operations.ts +674 -0
- package/src/company-profile-schema.ts +143 -0
- package/src/company-profile.ts +968 -0
- package/src/computer-sessions.ts +1130 -0
- package/src/connection-token-resolver.ts +270 -25
- package/src/database.ts +468 -24
- package/src/editable-artifact-durable-export.ts +1201 -0
- package/src/editable-artifact-materialization.ts +893 -0
- package/src/editable-artifacts-schema.ts +879 -0
- package/src/editable-artifacts.ts +5404 -0
- package/src/generated-images.ts +740 -0
- package/src/index.ts +9963 -1465
- package/src/insights.ts +251 -23
- package/src/integration-bindings.ts +455 -0
- package/src/integration-facets.ts +862 -0
- package/src/interaction-revisions.ts +70 -0
- package/src/interaction-schema.ts +1606 -0
- package/src/knowledge-source-sync-schema.ts +192 -0
- package/src/knowledge-source-sync.ts +1922 -0
- package/src/memory-slack-delivery.ts +801 -0
- package/src/pack-components.ts +954 -0
- package/src/pack-installations.ts +886 -0
- package/src/persistence-errors.ts +4 -7
- package/src/plugin-packages.ts +1186 -0
- package/src/provision-roles.ts +534 -1
- package/src/runtime-posture-cli.ts +9 -0
- package/src/runtime-posture.ts +721 -5
- package/src/schema.ts +6270 -3159
- package/src/scoped-knowledge-schema.ts +1 -1
- package/src/scoped-knowledge.ts +655 -3
- package/src/session-control.ts +130 -42
- package/src/session-queue-commands.ts +265 -15
- package/src/session-realtime-context.ts +2 -2
- package/src/session-realtime-ledger.ts +3 -3
- package/src/session-realtime.ts +7 -7
- package/src/session-tenancy.ts +188 -0
- package/src/session-tool-call-settlement.ts +18 -3
- package/src/slack-task-policy-schema.ts +130 -0
- package/src/slack-task-policy.ts +341 -0
- package/src/slack-user-link-access.ts +702 -0
- package/src/task-notes-schema.ts +135 -0
- package/src/task-notes.ts +178 -0
- package/src/video-generation.ts +1530 -0
- package/src/workspace-artifacts.ts +1 -1
- package/src/workspace-learning-policy-schema.ts +139 -0
- package/src/workspace-learning-policy.ts +595 -0
- package/src/xai-subscription.ts +1887 -0
- package/dist/chunk-P6CUHXQZ.js +0 -7248
- package/dist/chunk-P6CUHXQZ.js.map +0 -1
- package/dist/chunk-VHBFHMPC.js +0 -1219
- 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
|
+
}
|