@xenosystem/agent-interface-host 0.1.7 → 0.1.9

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 (84) hide show
  1. package/dist/acp/AcpAgentRegistryService.d.ts.map +1 -1
  2. package/dist/acp/AcpAgentRegistryService.js +40 -18
  3. package/dist/acp/AcpAgentRegistryService.js.map +1 -1
  4. package/dist/acp/AcpAgentTurnExecutionAdapter.d.ts.map +1 -1
  5. package/dist/acp/AcpAgentTurnExecutionAdapter.js +11 -0
  6. package/dist/acp/AcpAgentTurnExecutionAdapter.js.map +1 -1
  7. package/dist/acp/AcpNativeProviderLauncherResolver.d.ts +26 -0
  8. package/dist/acp/AcpNativeProviderLauncherResolver.d.ts.map +1 -0
  9. package/dist/acp/AcpNativeProviderLauncherResolver.js +145 -0
  10. package/dist/acp/AcpNativeProviderLauncherResolver.js.map +1 -0
  11. package/dist/acp/AcpProcessSupervisor.d.ts.map +1 -1
  12. package/dist/acp/AcpProcessSupervisor.js +4 -1
  13. package/dist/acp/AcpProcessSupervisor.js.map +1 -1
  14. package/dist/acp/AcpProviderModelService.d.ts +1 -0
  15. package/dist/acp/AcpProviderModelService.d.ts.map +1 -1
  16. package/dist/acp/AcpProviderModelService.js +2 -2
  17. package/dist/acp/AcpProviderModelService.js.map +1 -1
  18. package/dist/acp/AcpSessionService.d.ts.map +1 -1
  19. package/dist/acp/AcpSessionService.js +40 -2
  20. package/dist/acp/AcpSessionService.js.map +1 -1
  21. package/dist/acp/acpTypes.d.ts +3 -0
  22. package/dist/acp/acpTypes.d.ts.map +1 -1
  23. package/dist/cloud/XenoCloudAgentTurnExecutionAdapter.d.ts.map +1 -1
  24. package/dist/cloud/XenoCloudAgentTurnExecutionAdapter.js +4 -0
  25. package/dist/cloud/XenoCloudAgentTurnExecutionAdapter.js.map +1 -1
  26. package/dist/index.d.ts +41 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +138 -10
  29. package/dist/index.js.map +1 -1
  30. package/dist/lease/ProcessLivenessProber.d.ts +30 -0
  31. package/dist/lease/ProcessLivenessProber.d.ts.map +1 -0
  32. package/dist/lease/ProcessLivenessProber.js +95 -0
  33. package/dist/lease/ProcessLivenessProber.js.map +1 -0
  34. package/dist/lease/ProviderWriterLeaseService.d.ts +43 -0
  35. package/dist/lease/ProviderWriterLeaseService.d.ts.map +1 -0
  36. package/dist/lease/ProviderWriterLeaseService.js +148 -0
  37. package/dist/lease/ProviderWriterLeaseService.js.map +1 -0
  38. package/dist/node.d.ts +8 -0
  39. package/dist/node.d.ts.map +1 -1
  40. package/dist/node.js +8 -0
  41. package/dist/node.js.map +1 -1
  42. package/dist/provider/ProviderLaneService.d.ts +71 -0
  43. package/dist/provider/ProviderLaneService.d.ts.map +1 -0
  44. package/dist/provider/ProviderLaneService.js +892 -0
  45. package/dist/provider/ProviderLaneService.js.map +1 -0
  46. package/dist/provider/ProviderTransitionOutboxDispatcher.d.ts +22 -0
  47. package/dist/provider/ProviderTransitionOutboxDispatcher.d.ts.map +1 -0
  48. package/dist/provider/ProviderTransitionOutboxDispatcher.js +60 -0
  49. package/dist/provider/ProviderTransitionOutboxDispatcher.js.map +1 -0
  50. package/dist/sdk/SdkNativeAgentTurnExecutionAdapter.d.ts +4 -1
  51. package/dist/sdk/SdkNativeAgentTurnExecutionAdapter.d.ts.map +1 -1
  52. package/dist/sdk/SdkNativeAgentTurnExecutionAdapter.js +37 -1
  53. package/dist/sdk/SdkNativeAgentTurnExecutionAdapter.js.map +1 -1
  54. package/dist/session/nativeTuiSessionPty.d.ts.map +1 -1
  55. package/dist/session/nativeTuiSessionPty.js +8 -0
  56. package/dist/session/nativeTuiSessionPty.js.map +1 -1
  57. package/dist/state/ProviderLaneSqliteStore.d.ts +64 -0
  58. package/dist/state/ProviderLaneSqliteStore.d.ts.map +1 -0
  59. package/dist/state/ProviderLaneSqliteStore.js +728 -0
  60. package/dist/state/ProviderLaneSqliteStore.js.map +1 -0
  61. package/dist/state/SqliteAgentStateRepository.d.ts +9 -0
  62. package/dist/state/SqliteAgentStateRepository.d.ts.map +1 -1
  63. package/dist/state/SqliteAgentStateRepository.js +171 -17
  64. package/dist/state/SqliteAgentStateRepository.js.map +1 -1
  65. package/dist/state/createAgentStateRepository.d.ts.map +1 -1
  66. package/dist/state/createAgentStateRepository.js +4 -2
  67. package/dist/state/createAgentStateRepository.js.map +1 -1
  68. package/dist/state/providerLaneSchema.d.ts +13 -0
  69. package/dist/state/providerLaneSchema.d.ts.map +1 -0
  70. package/dist/state/providerLaneSchema.js +129 -0
  71. package/dist/state/providerLaneSchema.js.map +1 -0
  72. package/dist/state/providerLaneTypes.d.ts +122 -0
  73. package/dist/state/providerLaneTypes.d.ts.map +1 -0
  74. package/dist/state/providerLaneTypes.js +28 -0
  75. package/dist/state/providerLaneTypes.js.map +1 -0
  76. package/dist/state/sdkProtectedStateCipher.d.ts +30 -0
  77. package/dist/state/sdkProtectedStateCipher.d.ts.map +1 -0
  78. package/dist/state/sdkProtectedStateCipher.js +50 -0
  79. package/dist/state/sdkProtectedStateCipher.js.map +1 -0
  80. package/dist/workspace/WorkspaceHostService.d.ts +33 -1
  81. package/dist/workspace/WorkspaceHostService.d.ts.map +1 -1
  82. package/dist/workspace/WorkspaceHostService.js +109 -14
  83. package/dist/workspace/WorkspaceHostService.js.map +1 -1
  84. package/package.json +10 -7
@@ -0,0 +1,728 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { toProviderLaneProjection, } from './providerLaneTypes.js';
3
+ const MAX_ID = 240;
4
+ const MAX_LABEL = 1024;
5
+ const MAX_REASON = 2048;
6
+ const MAX_CAPABILITIES_JSON = 256 * 1024;
7
+ const MAX_CONTINUATION_JSON = 4 * 1024 * 1024;
8
+ const MAX_CIPHERTEXT = 8 * 1024 * 1024;
9
+ const MAX_EVENT_JSON = 1024 * 1024;
10
+ const MAX_RECEIPT_JSON = 1024 * 1024;
11
+ const MAX_DIRECTORY_FINGERPRINTS = 1024;
12
+ export class ProviderLaneStoreError extends Error {
13
+ code;
14
+ constructor(code, message) {
15
+ super(message);
16
+ this.name = 'ProviderLaneStoreError';
17
+ this.code = code;
18
+ }
19
+ }
20
+ export class ProviderLaneSqliteStore {
21
+ database;
22
+ cipher;
23
+ constructor(database, cipher) {
24
+ this.database = database;
25
+ this.cipher = cipher;
26
+ }
27
+ listLaneProjections(conversationId, includeHistorical = true) {
28
+ const boundedConversationId = requiredString(conversationId, 'conversationId', MAX_ID);
29
+ const rows = this.database.prepare(`
30
+ SELECT * FROM provider_lanes
31
+ WHERE conversation_id = ? ${includeHistorical ? '' : 'AND is_active = 1'}
32
+ ORDER BY is_active DESC, updated_at DESC, lane_id ASC
33
+ `).all(boundedConversationId);
34
+ return rows.map((row) => toProviderLaneProjection(rowToLane(requireRecord(row), undefined, false)));
35
+ }
36
+ getLaneProjection(conversationId, laneId) {
37
+ const row = this.database.prepare('SELECT * FROM provider_lanes WHERE conversation_id = ? AND lane_id = ?').get(requiredString(conversationId, 'conversationId', MAX_ID), requiredString(laneId, 'laneId', MAX_ID));
38
+ return row ? toProviderLaneProjection(rowToLane(requireRecord(row), undefined, false)) : null;
39
+ }
40
+ getLane(laneId, includeContinuation = false) {
41
+ const row = this.database.prepare('SELECT * FROM provider_lanes WHERE lane_id = ?')
42
+ .get(requiredString(laneId, 'laneId', MAX_ID));
43
+ return row ? rowToLane(requireRecord(row), this.cipher, includeContinuation) : null;
44
+ }
45
+ getActiveLane(conversationId, includeContinuation = false) {
46
+ const row = this.database.prepare('SELECT * FROM provider_lanes WHERE conversation_id = ? AND is_active = 1').get(requiredString(conversationId, 'conversationId', MAX_ID));
47
+ return row ? rowToLane(requireRecord(row), this.cipher, includeContinuation) : null;
48
+ }
49
+ replayIdempotency(idempotencyKey, requestHash) {
50
+ const key = requiredString(idempotencyKey, 'idempotencyKey', MAX_ID);
51
+ requiredHash(requestHash, 'requestHash');
52
+ const receipt = this.readReceipt(key);
53
+ if (!receipt)
54
+ return { found: false };
55
+ if (receipt.requestHash !== requestHash)
56
+ return { found: true, conflict: true };
57
+ return { found: true, conflict: false, response: structuredClone(receipt.response) };
58
+ }
59
+ saveLane(lane, expectedRevision, makeActive = lane.isActive) {
60
+ try {
61
+ return inTransaction(this.database, () => this.saveLaneInTransaction(lane, expectedRevision, makeActive));
62
+ }
63
+ catch (error) {
64
+ return failure(error);
65
+ }
66
+ }
67
+ selectLane(conversationId, laneId, expectedRevision) {
68
+ try {
69
+ const boundedConversationId = requiredString(conversationId, 'conversationId', MAX_ID);
70
+ const boundedLaneId = requiredString(laneId, 'laneId', MAX_ID);
71
+ const revision = requiredRevision(expectedRevision, 'expectedRevision');
72
+ return inTransaction(this.database, () => {
73
+ const now = new Date().toISOString();
74
+ this.database.prepare(`
75
+ UPDATE provider_lanes
76
+ SET is_active = 0, revision = revision + 1, updated_at = ?
77
+ WHERE conversation_id = ? AND is_active = 1 AND lane_id <> ?
78
+ `).run(now, boundedConversationId, boundedLaneId);
79
+ const result = this.database.prepare(`
80
+ UPDATE provider_lanes
81
+ SET is_active = 1, revision = revision + 1, updated_at = ?
82
+ WHERE lane_id = ? AND conversation_id = ? AND revision = ?
83
+ `).run(now, boundedLaneId, boundedConversationId, revision);
84
+ if (changes(result) !== 1) {
85
+ throw new ProviderLaneStoreError('LANE_REVISION_CONFLICT', 'Provider lane revision changed before selection.');
86
+ }
87
+ const lane = this.requireLaneInTransaction(boundedLaneId, false);
88
+ return { success: true, lane, projection: toProviderLaneProjection(lane) };
89
+ });
90
+ }
91
+ catch (error) {
92
+ return failure(error);
93
+ }
94
+ }
95
+ commitLaneTransition(input) {
96
+ try {
97
+ validateTransitionInput(input);
98
+ return inTransaction(this.database, () => {
99
+ if (input.idempotency) {
100
+ const replay = this.readReceipt(input.idempotency.idempotencyKey);
101
+ if (replay) {
102
+ if (replay.requestHash !== input.idempotency.requestHash) {
103
+ throw new ProviderLaneStoreError('LANE_REVISION_CONFLICT', 'Idempotency key was already used for a different request.');
104
+ }
105
+ return {
106
+ success: true,
107
+ replayed: true,
108
+ response: replay.response,
109
+ outboxIds: [],
110
+ };
111
+ }
112
+ }
113
+ let laneResult;
114
+ if (input.lane) {
115
+ if (input.expectedRevision === undefined) {
116
+ throw new ProviderLaneStoreError('LANE_REVISION_CONFLICT', 'expectedRevision is required for lane mutation.');
117
+ }
118
+ laneResult = this.saveLaneInTransaction(input.lane, input.expectedRevision, input.lane.isActive);
119
+ }
120
+ const outboxIds = [];
121
+ const outbox = input.outbox ? (Array.isArray(input.outbox) ? input.outbox : [input.outbox]) : [];
122
+ for (const entry of outbox) {
123
+ validateOutboxDraft(entry);
124
+ const payloadJson = boundedJson(entry.payload, 'outbox payload', MAX_EVENT_JSON);
125
+ const payloadHash = sha256(payloadJson);
126
+ const now = new Date().toISOString();
127
+ this.database.prepare(`
128
+ INSERT INTO provider_transition_outbox(
129
+ outbox_id, schema_version, conversation_id, lane_id, lane_revision,
130
+ transition_type, event_id, idempotency_key, payload_json, payload_hash,
131
+ status, retry_count, created_at, updated_at
132
+ ) VALUES (?, 1, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?, ?)
133
+ `).run(entry.outboxId, entry.conversationId, entry.laneId, entry.laneRevision, entry.transitionType, entry.eventId, entry.idempotencyKey, payloadJson, payloadHash, now, now);
134
+ outboxIds.push(entry.outboxId);
135
+ }
136
+ if (input.lease)
137
+ this.insertLeaseInTransaction(input.lease);
138
+ let response;
139
+ if (input.idempotency) {
140
+ response = validateReceiptDraft(input.idempotency);
141
+ const responseJson = boundedJson(response, 'idempotency response', MAX_RECEIPT_JSON);
142
+ this.database.prepare(`
143
+ INSERT INTO idempotency_receipts(
144
+ idempotency_key, scope, request_id, request_hash, lane_id, conversation_id,
145
+ response_json, response_hash, created_at, expires_at
146
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
147
+ `).run(input.idempotency.idempotencyKey, input.idempotency.scope, input.idempotency.requestId, input.idempotency.requestHash, input.idempotency.laneId ?? input.lane?.laneId ?? null, input.idempotency.conversationId ?? input.lane?.conversationId ?? null, responseJson, sha256(responseJson), new Date().toISOString(), input.idempotency.expiresAt ?? null);
148
+ }
149
+ return {
150
+ success: true,
151
+ replayed: false,
152
+ ...(laneResult?.lane ? { lane: laneResult.lane, projection: laneResult.projection } : {}),
153
+ ...(response ? { response } : {}),
154
+ outboxIds,
155
+ };
156
+ });
157
+ }
158
+ catch (error) {
159
+ return { ...failure(error), outboxIds: [] };
160
+ }
161
+ }
162
+ listPendingOutbox(limit = 100) {
163
+ const boundedLimit = Number.isInteger(limit) && limit > 0 && limit <= 1000 ? limit : 100;
164
+ return this.database.prepare(`
165
+ SELECT * FROM provider_transition_outbox
166
+ WHERE status IN ('pending', 'failed')
167
+ ORDER BY created_at ASC, outbox_id ASC
168
+ LIMIT ?
169
+ `).all(boundedLimit).map((row) => rowToOutbox(requireRecord(row)));
170
+ }
171
+ markOutboxDelivered(outboxId, payloadHash) {
172
+ const id = requiredString(outboxId, 'outboxId', MAX_ID);
173
+ const hash = requiredHash(payloadHash, 'payloadHash');
174
+ return inTransaction(this.database, () => {
175
+ const now = new Date().toISOString();
176
+ const result = this.database.prepare(`
177
+ UPDATE provider_transition_outbox
178
+ SET status = 'delivered', delivered_at = ?, updated_at = ?, error = NULL
179
+ WHERE outbox_id = ? AND payload_hash = ? AND status IN ('pending', 'failed')
180
+ `).run(now, now, id, hash);
181
+ if (changes(result) === 1)
182
+ return true;
183
+ const existing = requireRecordOrNull(this.database.prepare('SELECT payload_hash, status FROM provider_transition_outbox WHERE outbox_id = ?').get(id));
184
+ if (!existing)
185
+ return false;
186
+ if (existing.payload_hash !== hash) {
187
+ throw new ProviderLaneStoreError('CONTINUATION_INVALID', 'Outbox payload hash conflict.');
188
+ }
189
+ return existing.status === 'delivered';
190
+ });
191
+ }
192
+ markOutboxFailed(outboxId, error) {
193
+ const result = this.database.prepare(`
194
+ UPDATE provider_transition_outbox
195
+ SET status = 'failed', retry_count = retry_count + 1, last_attempt_at = ?, error = ?, updated_at = ?
196
+ WHERE outbox_id = ? AND status <> 'delivered'
197
+ `).run(new Date().toISOString(), optionalString(error, 'outbox error', MAX_REASON) ?? 'Projection failed.', new Date().toISOString(), requiredString(outboxId, 'outboxId', MAX_ID));
198
+ return changes(result) === 1;
199
+ }
200
+ getActiveLease(laneId) {
201
+ const row = this.database.prepare(`
202
+ SELECT * FROM provider_writer_leases
203
+ WHERE lane_id = ? AND state IN ('active', 'releasing', 'unknown')
204
+ `).get(requiredString(laneId, 'laneId', MAX_ID));
205
+ return row ? rowToLease(requireRecord(row)) : null;
206
+ }
207
+ getLease(leaseId) {
208
+ const row = this.database.prepare('SELECT * FROM provider_writer_leases WHERE lease_id = ?')
209
+ .get(requiredString(leaseId, 'leaseId', MAX_ID));
210
+ return row ? rowToLease(requireRecord(row)) : null;
211
+ }
212
+ replaceExpiredLeaseAtomic(existingLeaseId, expectedLeaseRevision, replacement) {
213
+ try {
214
+ validateLease(replacement);
215
+ const existingId = requiredString(existingLeaseId, 'existingLeaseId', MAX_ID);
216
+ const expected = positiveInteger(expectedLeaseRevision, 'expectedLeaseRevision');
217
+ return inTransaction(this.database, () => {
218
+ const now = new Date().toISOString();
219
+ const released = this.database.prepare(`
220
+ UPDATE provider_writer_leases
221
+ SET state = 'stale', lease_revision = lease_revision + 1, updated_at = ?
222
+ WHERE lease_id = ? AND lease_revision = ? AND state IN ('active', 'releasing', 'unknown')
223
+ `).run(now, existingId, expected);
224
+ if (changes(released) !== 1) {
225
+ throw new ProviderLaneStoreError('LANE_WRITER_UNKNOWN', 'Writer lease changed during liveness recovery.');
226
+ }
227
+ this.insertLeaseInTransaction({ ...replacement, leaseRevision: 1 });
228
+ const stored = this.getLease(replacement.leaseId);
229
+ return { success: true, ...(stored ? { lease: stored } : {}) };
230
+ });
231
+ }
232
+ catch (error) {
233
+ if (isUniqueConstraint(error)) {
234
+ return { success: false, code: 'LANE_WRITER_ACTIVE', error: 'Provider lane already has a conflicting writer.' };
235
+ }
236
+ const failed = failure(error);
237
+ return { success: false, ...(failed.code ? { code: failed.code } : {}), ...(failed.error ? { error: failed.error } : {}) };
238
+ }
239
+ }
240
+ saveLeaseAtomic(lease, expectedLeaseRevision) {
241
+ try {
242
+ validateLease(lease);
243
+ const expected = requiredRevision(expectedLeaseRevision, 'expectedLeaseRevision');
244
+ return inTransaction(this.database, () => {
245
+ if (expected === 0) {
246
+ this.insertLeaseInTransaction({ ...lease, leaseRevision: 1 });
247
+ }
248
+ else {
249
+ const result = this.database.prepare(`
250
+ UPDATE provider_writer_leases SET
251
+ lane_revision = ?, native_provider_id = ?, native_session_hash = ?, owner_surface = ?,
252
+ owner_pid = ?, owner_process_start_time = ?, owner_process_identity = ?, state = ?,
253
+ expires_at = ?, renewed_at = ?, last_heartbeat_at = ?, workspace_epoch_id = ?,
254
+ authority_snapshot_hash = ?, lease_revision = lease_revision + 1, updated_at = ?
255
+ WHERE lease_id = ? AND lease_revision = ?
256
+ `).run(lease.laneRevision, lease.nativeProviderId ?? null, lease.nativeSessionHash ?? null, lease.ownerSurface, lease.ownerPid, lease.ownerProcessStartTime, lease.ownerProcessIdentity, lease.state, lease.expiresAt, lease.renewedAt ?? null, lease.lastHeartbeatAt, lease.workspaceEpochId, lease.authoritySnapshotHash, new Date().toISOString(), lease.leaseId, expected);
257
+ if (changes(result) !== 1) {
258
+ throw new ProviderLaneStoreError('LANE_REVISION_CONFLICT', 'Writer lease revision changed before mutation.');
259
+ }
260
+ }
261
+ const stored = this.database.prepare('SELECT * FROM provider_writer_leases WHERE lease_id = ?')
262
+ .get(lease.leaseId);
263
+ if (!stored)
264
+ throw new Error('Writer lease was not stored.');
265
+ return { success: true, lease: rowToLease(requireRecord(stored)) };
266
+ });
267
+ }
268
+ catch (error) {
269
+ if (isUniqueConstraint(error)) {
270
+ return { success: false, code: 'LANE_WRITER_ACTIVE', error: 'Provider lane already has a conflicting writer.' };
271
+ }
272
+ const failed = failure(error);
273
+ return { success: false, ...(failed.code ? { code: failed.code } : {}), ...(failed.error ? { error: failed.error } : {}) };
274
+ }
275
+ }
276
+ saveLaneInTransaction(lane, expectedRevision, makeActive) {
277
+ validateLane(lane);
278
+ const expected = requiredRevision(expectedRevision, 'expectedRevision');
279
+ const encoded = encodeLane(lane, this.cipher);
280
+ const now = new Date().toISOString();
281
+ if (makeActive) {
282
+ this.database.prepare(`
283
+ UPDATE provider_lanes
284
+ SET is_active = 0, revision = revision + 1, updated_at = ?
285
+ WHERE conversation_id = ? AND is_active = 1 AND lane_id <> ?
286
+ `).run(now, lane.conversationId, lane.laneId);
287
+ }
288
+ if (expected === 0) {
289
+ const existing = this.database.prepare('SELECT lane_id FROM provider_lanes WHERE lane_id = ?').get(lane.laneId);
290
+ if (existing)
291
+ throw new ProviderLaneStoreError('LANE_REVISION_CONFLICT', 'Provider lane already exists.');
292
+ this.database.prepare(`
293
+ INSERT INTO provider_lanes(
294
+ lane_id, conversation_id, runtime_provider_id, provider_kind, provider_display_name,
295
+ native_provider_id, model_id, model_display_name, resume_compatibility_key,
296
+ workspace_epoch_id, workspace_epoch_fingerprint, workspace_epoch_wire_mode, workspace_epoch_kind,
297
+ workspace_epoch_label, workspace_epoch_allowed_directory_fingerprints_json, status,
298
+ encrypted_continuation, continuation_hash, revision, is_active, capabilities_json,
299
+ last_request_id, last_used_at, last_structured_commit_id, last_handoff_capsule_id,
300
+ redacted_native_session_hash, invalidation_reason, created_at, updated_at
301
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
302
+ `).run(...encoded.insertValues, makeActive ? 1 : 0, encoded.capabilitiesJson, ...encoded.tailValues, now);
303
+ }
304
+ else {
305
+ const result = this.database.prepare(`
306
+ UPDATE provider_lanes SET
307
+ runtime_provider_id = ?, provider_kind = ?, provider_display_name = ?, native_provider_id = ?,
308
+ model_id = ?, model_display_name = ?, resume_compatibility_key = ?, workspace_epoch_id = ?,
309
+ workspace_epoch_fingerprint = ?, workspace_epoch_wire_mode = ?, workspace_epoch_kind = ?,
310
+ workspace_epoch_label = ?, workspace_epoch_allowed_directory_fingerprints_json = ?, status = ?,
311
+ encrypted_continuation = ?, continuation_hash = ?, is_active = ?, capabilities_json = ?,
312
+ last_request_id = ?, last_used_at = ?, last_structured_commit_id = ?, last_handoff_capsule_id = ?,
313
+ redacted_native_session_hash = ?, invalidation_reason = ?, revision = revision + 1, updated_at = ?
314
+ WHERE lane_id = ? AND conversation_id = ? AND revision = ?
315
+ `).run(...encoded.updateValues, makeActive ? 1 : 0, encoded.capabilitiesJson, ...encoded.updateTailValues, now, lane.laneId, lane.conversationId, expected);
316
+ if (changes(result) !== 1) {
317
+ throw new ProviderLaneStoreError('LANE_REVISION_CONFLICT', 'Provider lane revision changed before mutation.');
318
+ }
319
+ }
320
+ const stored = this.requireLaneInTransaction(lane.laneId, false);
321
+ return { success: true, lane: stored, projection: toProviderLaneProjection(stored) };
322
+ }
323
+ requireLaneInTransaction(laneId, includeContinuation) {
324
+ const row = this.database.prepare('SELECT * FROM provider_lanes WHERE lane_id = ?').get(laneId);
325
+ if (!row)
326
+ throw new ProviderLaneStoreError('CONVERSATION_NOT_FOUND', 'Provider lane was not found.');
327
+ return rowToLane(requireRecord(row), this.cipher, includeContinuation);
328
+ }
329
+ insertLeaseInTransaction(lease) {
330
+ validateLease(lease);
331
+ this.database.prepare(`
332
+ INSERT INTO provider_writer_leases(
333
+ lease_id, schema_version, lane_id, lane_revision, lease_revision, conversation_id,
334
+ host_instance_id, runtime_provider_id, native_provider_id, native_session_hash,
335
+ owner_surface, owner_pid, owner_process_start_time, owner_process_identity, state,
336
+ acquired_at, expires_at, renewed_at, last_heartbeat_at, workspace_epoch_id,
337
+ authority_snapshot_hash, created_at, updated_at
338
+ ) VALUES (?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
339
+ `).run(lease.leaseId, lease.laneId, lease.laneRevision, lease.leaseRevision, lease.conversationId, lease.hostInstanceId, lease.runtimeProviderId, lease.nativeProviderId ?? null, lease.nativeSessionHash ?? null, lease.ownerSurface, lease.ownerPid, lease.ownerProcessStartTime, lease.ownerProcessIdentity, lease.state, lease.acquiredAt, lease.expiresAt, lease.renewedAt ?? null, lease.lastHeartbeatAt, lease.workspaceEpochId, lease.authoritySnapshotHash, lease.createdAt, lease.updatedAt);
340
+ }
341
+ readReceipt(idempotencyKey) {
342
+ const row = this.database.prepare('SELECT request_hash, response_json, response_hash FROM idempotency_receipts WHERE idempotency_key = ?').get(requiredString(idempotencyKey, 'idempotencyKey', MAX_ID));
343
+ if (!row)
344
+ return null;
345
+ const record = requireRecord(row);
346
+ const responseJson = requiredString(record.response_json, 'stored response', MAX_RECEIPT_JSON);
347
+ if (sha256(responseJson) !== record.response_hash) {
348
+ throw new ProviderLaneStoreError('CONTINUATION_INVALID', 'Stored idempotency receipt is invalid.');
349
+ }
350
+ try {
351
+ const response = JSON.parse(responseJson);
352
+ if (!isRecord(response))
353
+ throw new Error('response is not an object');
354
+ return {
355
+ requestHash: requiredHash(record.request_hash, 'stored requestHash'),
356
+ response,
357
+ };
358
+ }
359
+ catch {
360
+ throw new ProviderLaneStoreError('CONTINUATION_INVALID', 'Stored idempotency receipt is invalid.');
361
+ }
362
+ }
363
+ }
364
+ function encodeLane(lane, cipher) {
365
+ const continuation = encryptContinuation(lane.continuation, cipher);
366
+ const capabilitiesJson = lane.capabilities
367
+ ? boundedJson(lane.capabilities, 'provider capabilities', MAX_CAPABILITIES_JSON)
368
+ : null;
369
+ const directoryFingerprints = boundedJson(lane.workspaceEpoch.allowedDirectoryFingerprints, 'allowed directory fingerprints', 64 * 1024);
370
+ const insertValues = [
371
+ lane.laneId,
372
+ lane.conversationId,
373
+ lane.runtimeProviderId,
374
+ lane.providerKind,
375
+ lane.providerDisplayName ?? null,
376
+ lane.nativeProviderId ?? null,
377
+ lane.modelId ?? null,
378
+ lane.modelDisplayName ?? null,
379
+ lane.resumeCompatibilityKey,
380
+ lane.workspaceEpoch.id,
381
+ lane.workspaceEpoch.fingerprint,
382
+ lane.workspaceEpoch.wireMode,
383
+ lane.workspaceEpoch.kind,
384
+ lane.workspaceEpoch.label ?? null,
385
+ directoryFingerprints,
386
+ lane.status,
387
+ continuation.ciphertext,
388
+ continuation.hash,
389
+ ];
390
+ const updateValues = insertValues.slice(2);
391
+ const tailValues = [
392
+ lane.lastRequestId ?? null,
393
+ lane.lastUsedAt ?? null,
394
+ lane.lastStructuredCommitId ?? null,
395
+ lane.lastHandoffCapsuleId ?? null,
396
+ lane.redactedNativeSessionHash ?? null,
397
+ lane.invalidationReason ?? null,
398
+ lane.createdAt,
399
+ ];
400
+ return {
401
+ insertValues,
402
+ updateValues,
403
+ capabilitiesJson,
404
+ tailValues,
405
+ updateTailValues: tailValues.slice(0, -1),
406
+ };
407
+ }
408
+ function encryptContinuation(value, cipher) {
409
+ if (value === undefined)
410
+ return { ciphertext: null, hash: null };
411
+ if (!cipher?.available) {
412
+ throw new ProviderLaneStoreError('SECURE_LANE_STORE_UNAVAILABLE', 'Secure provider lane storage is unavailable.');
413
+ }
414
+ const plaintext = boundedJson(value, 'provider continuation', MAX_CONTINUATION_JSON);
415
+ let ciphertext;
416
+ try {
417
+ ciphertext = cipher.encrypt(plaintext);
418
+ }
419
+ catch {
420
+ throw new ProviderLaneStoreError('SECURE_LANE_STORE_UNAVAILABLE', 'Secure provider lane storage is unavailable.');
421
+ }
422
+ requiredString(ciphertext, 'encrypted continuation', MAX_CIPHERTEXT);
423
+ return { ciphertext, hash: sha256(ciphertext) };
424
+ }
425
+ function decryptContinuation(ciphertext, cipher) {
426
+ if (ciphertext === null || ciphertext === undefined)
427
+ return undefined;
428
+ if (!cipher?.available) {
429
+ throw new ProviderLaneStoreError('SECURE_LANE_STORE_UNAVAILABLE', 'Secure provider lane storage is unavailable.');
430
+ }
431
+ try {
432
+ const encrypted = requiredString(ciphertext, 'encrypted continuation', MAX_CIPHERTEXT);
433
+ const plaintext = cipher.decrypt(encrypted);
434
+ requiredString(plaintext, 'provider continuation', MAX_CONTINUATION_JSON);
435
+ return JSON.parse(plaintext);
436
+ }
437
+ catch {
438
+ throw new ProviderLaneStoreError('CONTINUATION_INVALID', 'Protected provider continuation is invalid.');
439
+ }
440
+ }
441
+ function rowToLane(row, cipher, includeContinuation) {
442
+ const allowed = parseStringArray(row.workspace_epoch_allowed_directory_fingerprints_json);
443
+ const capabilities = parseOptionalRecord(row.capabilities_json);
444
+ const lane = {
445
+ schemaVersion: 1,
446
+ laneId: requiredString(row.lane_id, 'stored laneId', MAX_ID),
447
+ conversationId: requiredString(row.conversation_id, 'stored conversationId', MAX_ID),
448
+ runtimeProviderId: requiredString(row.runtime_provider_id, 'stored runtimeProviderId', MAX_ID),
449
+ providerKind: requiredString(row.provider_kind, 'stored providerKind', 120),
450
+ ...(optionalString(row.provider_display_name, 'stored providerDisplayName', 512) ? { providerDisplayName: String(row.provider_display_name) } : {}),
451
+ ...(optionalString(row.native_provider_id, 'stored nativeProviderId', MAX_ID) ? { nativeProviderId: String(row.native_provider_id) } : {}),
452
+ ...(optionalString(row.model_id, 'stored modelId', MAX_ID) ? { modelId: String(row.model_id) } : {}),
453
+ ...(optionalString(row.model_display_name, 'stored modelDisplayName', 512) ? { modelDisplayName: String(row.model_display_name) } : {}),
454
+ resumeCompatibilityKey: requiredString(row.resume_compatibility_key, 'stored resumeCompatibilityKey', 512),
455
+ workspaceEpoch: {
456
+ id: requiredString(row.workspace_epoch_id, 'stored workspaceEpochId', MAX_ID),
457
+ fingerprint: requiredString(row.workspace_epoch_fingerprint, 'stored workspaceEpochFingerprint', 512),
458
+ wireMode: requireOneOf(row.workspace_epoch_wire_mode, ['chat', 'development'], 'stored workspace wire mode'),
459
+ kind: requireOneOf(row.workspace_epoch_kind, ['private-chat', 'local-workspace'], 'stored workspace kind'),
460
+ ...(optionalString(row.workspace_epoch_label, 'stored workspaceEpochLabel', MAX_LABEL) ? { label: String(row.workspace_epoch_label) } : {}),
461
+ allowedDirectoryFingerprints: allowed,
462
+ },
463
+ status: requireOneOf(row.status, [
464
+ 'new', 'clean', 'active_structured', 'active_native', 'externally_advanced',
465
+ 'needs_reconciliation', 'portable_only', 'invalidated', 'error',
466
+ ], 'stored lane status'),
467
+ revision: requiredRevision(row.revision, 'stored revision'),
468
+ isActive: row.is_active === 1,
469
+ ...(capabilities ? { capabilities } : {}),
470
+ ...(optionalString(row.continuation_hash, 'stored continuationHash', 128) ? { continuationHash: String(row.continuation_hash) } : {}),
471
+ ...(optionalString(row.last_request_id, 'stored lastRequestId', MAX_ID) ? { lastRequestId: String(row.last_request_id) } : {}),
472
+ ...(optionalString(row.last_used_at, 'stored lastUsedAt', 64) ? { lastUsedAt: String(row.last_used_at) } : {}),
473
+ ...(optionalString(row.last_structured_commit_id, 'stored lastStructuredCommitId', MAX_ID) ? { lastStructuredCommitId: String(row.last_structured_commit_id) } : {}),
474
+ ...(optionalString(row.last_handoff_capsule_id, 'stored lastHandoffCapsuleId', MAX_ID) ? { lastHandoffCapsuleId: String(row.last_handoff_capsule_id) } : {}),
475
+ ...(optionalString(row.redacted_native_session_hash, 'stored redactedNativeSessionHash', 128) ? { redactedNativeSessionHash: String(row.redacted_native_session_hash) } : {}),
476
+ ...(optionalString(row.invalidation_reason, 'stored invalidationReason', MAX_REASON) ? { invalidationReason: String(row.invalidation_reason) } : {}),
477
+ createdAt: requiredString(row.created_at, 'stored createdAt', 64),
478
+ updatedAt: requiredString(row.updated_at, 'stored updatedAt', 64),
479
+ };
480
+ if (includeContinuation)
481
+ lane.continuation = decryptContinuation(row.encrypted_continuation, cipher);
482
+ return lane;
483
+ }
484
+ function rowToLease(row) {
485
+ return {
486
+ schemaVersion: 1,
487
+ leaseId: requiredString(row.lease_id, 'stored leaseId', MAX_ID),
488
+ laneId: requiredString(row.lane_id, 'stored laneId', MAX_ID),
489
+ laneRevision: requiredRevision(row.lane_revision, 'stored laneRevision'),
490
+ leaseRevision: positiveInteger(row.lease_revision, 'stored leaseRevision'),
491
+ conversationId: requiredString(row.conversation_id, 'stored conversationId', MAX_ID),
492
+ hostInstanceId: requiredString(row.host_instance_id, 'stored hostInstanceId', MAX_ID),
493
+ runtimeProviderId: requiredString(row.runtime_provider_id, 'stored runtimeProviderId', MAX_ID),
494
+ ...(optionalString(row.native_provider_id, 'stored nativeProviderId', MAX_ID) ? { nativeProviderId: String(row.native_provider_id) } : {}),
495
+ ...(optionalString(row.native_session_hash, 'stored nativeSessionHash', 128) ? { nativeSessionHash: String(row.native_session_hash) } : {}),
496
+ ownerSurface: requiredString(row.owner_surface, 'stored ownerSurface', 120),
497
+ ownerPid: positiveInteger(row.owner_pid, 'stored ownerPid'),
498
+ ownerProcessStartTime: requiredString(row.owner_process_start_time, 'stored process start time', 128),
499
+ ownerProcessIdentity: requiredString(row.owner_process_identity, 'stored process identity', 512),
500
+ state: requireOneOf(row.state, ['active', 'releasing', 'released', 'stale', 'unknown'], 'stored lease state'),
501
+ acquiredAt: requiredString(row.acquired_at, 'stored acquiredAt', 64),
502
+ expiresAt: requiredString(row.expires_at, 'stored expiresAt', 64),
503
+ ...(optionalString(row.renewed_at, 'stored renewedAt', 64) ? { renewedAt: String(row.renewed_at) } : {}),
504
+ lastHeartbeatAt: requiredString(row.last_heartbeat_at, 'stored lastHeartbeatAt', 64),
505
+ workspaceEpochId: requiredString(row.workspace_epoch_id, 'stored workspaceEpochId', MAX_ID),
506
+ authoritySnapshotHash: requiredString(row.authority_snapshot_hash, 'stored authoritySnapshotHash', 512),
507
+ createdAt: requiredString(row.created_at, 'stored createdAt', 64),
508
+ updatedAt: requiredString(row.updated_at, 'stored updatedAt', 64),
509
+ };
510
+ }
511
+ function rowToOutbox(row) {
512
+ let payload;
513
+ try {
514
+ const parsed = JSON.parse(requiredString(row.payload_json, 'stored outbox payload', MAX_EVENT_JSON));
515
+ if (!isRecord(parsed))
516
+ throw new Error('payload is not an object');
517
+ payload = parsed;
518
+ }
519
+ catch {
520
+ throw new ProviderLaneStoreError('CONTINUATION_INVALID', 'Stored outbox payload is invalid.');
521
+ }
522
+ return {
523
+ schemaVersion: 1,
524
+ outboxId: requiredString(row.outbox_id, 'stored outboxId', MAX_ID),
525
+ conversationId: requiredString(row.conversation_id, 'stored conversationId', MAX_ID),
526
+ laneId: requiredString(row.lane_id, 'stored laneId', MAX_ID),
527
+ laneRevision: requiredRevision(row.lane_revision, 'stored laneRevision'),
528
+ transitionType: requiredString(row.transition_type, 'stored transitionType', 120),
529
+ eventId: requiredString(row.event_id, 'stored eventId', MAX_ID),
530
+ idempotencyKey: requiredString(row.idempotency_key, 'stored idempotencyKey', MAX_ID),
531
+ payload,
532
+ payloadHash: requiredHash(row.payload_hash, 'stored payloadHash'),
533
+ status: requireOneOf(row.status, ['pending', 'delivered', 'failed', 'abandoned'], 'stored outbox status'),
534
+ retryCount: requiredRevision(row.retry_count, 'stored retryCount'),
535
+ ...(optionalString(row.last_attempt_at, 'stored lastAttemptAt', 64) ? { lastAttemptAt: String(row.last_attempt_at) } : {}),
536
+ ...(optionalString(row.error, 'stored outbox error', MAX_REASON) ? { error: String(row.error) } : {}),
537
+ createdAt: requiredString(row.created_at, 'stored createdAt', 64),
538
+ ...(optionalString(row.delivered_at, 'stored deliveredAt', 64) ? { deliveredAt: String(row.delivered_at) } : {}),
539
+ updatedAt: requiredString(row.updated_at, 'stored updatedAt', 64),
540
+ };
541
+ }
542
+ function validateLane(lane) {
543
+ if (!isRecord(lane) || lane.schemaVersion !== 1)
544
+ throw new Error('Invalid provider lane.');
545
+ requiredString(lane.laneId, 'laneId', MAX_ID);
546
+ requiredString(lane.conversationId, 'conversationId', MAX_ID);
547
+ requiredString(lane.runtimeProviderId, 'runtimeProviderId', MAX_ID);
548
+ requiredString(lane.providerKind, 'providerKind', 120);
549
+ requiredString(lane.resumeCompatibilityKey, 'resumeCompatibilityKey', 512);
550
+ requiredString(lane.workspaceEpoch.id, 'workspaceEpoch.id', MAX_ID);
551
+ requiredString(lane.workspaceEpoch.fingerprint, 'workspaceEpoch.fingerprint', 512);
552
+ if (lane.workspaceEpoch.allowedDirectoryFingerprints.length > MAX_DIRECTORY_FINGERPRINTS) {
553
+ throw new Error('Too many allowed directory fingerprints.');
554
+ }
555
+ for (const fingerprint of lane.workspaceEpoch.allowedDirectoryFingerprints) {
556
+ requiredString(fingerprint, 'allowed directory fingerprint', 512);
557
+ }
558
+ requiredRevision(lane.revision, 'lane revision');
559
+ requiredString(lane.createdAt, 'createdAt', 64);
560
+ requiredString(lane.updatedAt, 'updatedAt', 64);
561
+ optionalString(lane.invalidationReason, 'invalidationReason', MAX_REASON);
562
+ }
563
+ function validateLease(lease) {
564
+ if (!isRecord(lease) || lease.schemaVersion !== 1)
565
+ throw new Error('Invalid provider writer lease.');
566
+ for (const [field, value, max] of [
567
+ ['leaseId', lease.leaseId, MAX_ID],
568
+ ['laneId', lease.laneId, MAX_ID],
569
+ ['conversationId', lease.conversationId, MAX_ID],
570
+ ['hostInstanceId', lease.hostInstanceId, MAX_ID],
571
+ ['runtimeProviderId', lease.runtimeProviderId, MAX_ID],
572
+ ['ownerSurface', lease.ownerSurface, 120],
573
+ ['ownerProcessStartTime', lease.ownerProcessStartTime, 128],
574
+ ['ownerProcessIdentity', lease.ownerProcessIdentity, 512],
575
+ ['workspaceEpochId', lease.workspaceEpochId, MAX_ID],
576
+ ['authoritySnapshotHash', lease.authoritySnapshotHash, 512],
577
+ ])
578
+ requiredString(value, field, max);
579
+ requiredRevision(lease.laneRevision, 'laneRevision');
580
+ positiveInteger(lease.leaseRevision, 'leaseRevision');
581
+ positiveInteger(lease.ownerPid, 'ownerPid');
582
+ }
583
+ function validateTransitionInput(input) {
584
+ if (input.lane && input.expectedRevision === undefined) {
585
+ throw new ProviderLaneStoreError('LANE_REVISION_CONFLICT', 'expectedRevision is required for lane mutation.');
586
+ }
587
+ if (input.expectedRevision !== undefined)
588
+ requiredRevision(input.expectedRevision, 'expectedRevision');
589
+ if (input.idempotency)
590
+ validateReceiptDraft(input.idempotency);
591
+ }
592
+ function validateReceiptDraft(receipt) {
593
+ requiredString(receipt.idempotencyKey, 'idempotencyKey', MAX_ID);
594
+ requiredString(receipt.scope, 'idempotency scope', 120);
595
+ requiredString(receipt.requestId, 'requestId', MAX_ID);
596
+ requiredHash(receipt.requestHash, 'requestHash');
597
+ boundedJson(receipt.response, 'idempotency response', MAX_RECEIPT_JSON);
598
+ if (!isRecord(receipt.response))
599
+ throw new Error('Idempotency response must be an object.');
600
+ return receipt.response;
601
+ }
602
+ function validateOutboxDraft(entry) {
603
+ requiredString(entry.outboxId, 'outboxId', MAX_ID);
604
+ requiredString(entry.conversationId, 'conversationId', MAX_ID);
605
+ requiredString(entry.laneId, 'laneId', MAX_ID);
606
+ requiredRevision(entry.laneRevision, 'laneRevision');
607
+ requiredString(entry.transitionType, 'transitionType', 120);
608
+ requiredString(entry.eventId, 'eventId', MAX_ID);
609
+ requiredString(entry.idempotencyKey, 'idempotencyKey', MAX_ID);
610
+ boundedJson(entry.payload, 'outbox payload', MAX_EVENT_JSON);
611
+ }
612
+ function boundedJson(value, field, maxBytes) {
613
+ const json = JSON.stringify(value);
614
+ if (typeof json !== 'string')
615
+ throw new Error(`${field} is not serializable.`);
616
+ if (Buffer.byteLength(json, 'utf8') > maxBytes)
617
+ throw new Error(`${field} exceeds ${maxBytes} bytes.`);
618
+ return json;
619
+ }
620
+ function requiredString(value, field, max) {
621
+ if (typeof value !== 'string')
622
+ throw new Error(`${field} must be a string.`);
623
+ const normalized = value.trim();
624
+ if (!normalized || Buffer.byteLength(normalized, 'utf8') > max)
625
+ throw new Error(`${field} is invalid.`);
626
+ if (normalized.includes('\u0000'))
627
+ throw new Error(`${field} is invalid.`);
628
+ return normalized;
629
+ }
630
+ function optionalString(value, field, max) {
631
+ if (value === null || value === undefined || value === '')
632
+ return undefined;
633
+ return requiredString(value, field, max);
634
+ }
635
+ function requiredHash(value, field) {
636
+ const hash = requiredString(value, field, 128);
637
+ if (!/^[A-Za-z0-9:_-]+$/.test(hash))
638
+ throw new Error(`${field} is invalid.`);
639
+ return hash;
640
+ }
641
+ function requiredRevision(value, field) {
642
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0)
643
+ throw new Error(`${field} is invalid.`);
644
+ return value;
645
+ }
646
+ function positiveInteger(value, field) {
647
+ const integer = requiredRevision(value, field);
648
+ if (integer < 1)
649
+ throw new Error(`${field} is invalid.`);
650
+ return integer;
651
+ }
652
+ function parseStringArray(value) {
653
+ try {
654
+ const parsed = JSON.parse(requiredString(value, 'stored string array', 64 * 1024));
655
+ if (!Array.isArray(parsed) || parsed.length > MAX_DIRECTORY_FINGERPRINTS)
656
+ throw new Error('invalid array');
657
+ return parsed.map((entry) => requiredString(entry, 'stored string array entry', 512));
658
+ }
659
+ catch {
660
+ throw new ProviderLaneStoreError('CONTINUATION_INVALID', 'Stored provider lane metadata is invalid.');
661
+ }
662
+ }
663
+ function parseOptionalRecord(value) {
664
+ if (value === null || value === undefined)
665
+ return undefined;
666
+ try {
667
+ const parsed = JSON.parse(requiredString(value, 'stored JSON object', MAX_CAPABILITIES_JSON));
668
+ if (!isRecord(parsed))
669
+ throw new Error('not an object');
670
+ return parsed;
671
+ }
672
+ catch {
673
+ throw new ProviderLaneStoreError('CONTINUATION_INVALID', 'Stored provider lane metadata is invalid.');
674
+ }
675
+ }
676
+ function requireOneOf(value, choices, field) {
677
+ if (typeof value !== 'string' || !choices.includes(value))
678
+ throw new Error(`${field} is invalid.`);
679
+ return value;
680
+ }
681
+ function requireRecord(value) {
682
+ if (!isRecord(value))
683
+ throw new Error('SQLite returned an invalid row.');
684
+ return value;
685
+ }
686
+ function requireRecordOrNull(value) {
687
+ return value === undefined || value === null ? null : requireRecord(value);
688
+ }
689
+ function isRecord(value) {
690
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
691
+ }
692
+ function changes(result) {
693
+ return Number(result.changes ?? 0);
694
+ }
695
+ function sha256(value) {
696
+ return createHash('sha256').update(value).digest('hex');
697
+ }
698
+ function inTransaction(database, action) {
699
+ database.exec('BEGIN IMMEDIATE');
700
+ try {
701
+ const result = action();
702
+ database.exec('COMMIT');
703
+ return result;
704
+ }
705
+ catch (error) {
706
+ try {
707
+ database.exec('ROLLBACK');
708
+ }
709
+ catch {
710
+ // Preserve the original failure.
711
+ }
712
+ throw error;
713
+ }
714
+ }
715
+ function isUniqueConstraint(error) {
716
+ const message = error instanceof Error ? error.message : String(error);
717
+ return /UNIQUE constraint failed|SQLITE_CONSTRAINT_UNIQUE/i.test(message);
718
+ }
719
+ function failure(error) {
720
+ if (error instanceof ProviderLaneStoreError) {
721
+ return { success: false, code: error.code, error: error.message };
722
+ }
723
+ if (isUniqueConstraint(error)) {
724
+ return { success: false, code: 'LANE_REVISION_CONFLICT', error: 'Provider lane mutation conflicted with durable state.' };
725
+ }
726
+ return { success: false, error: 'Provider lane persistence failed.' };
727
+ }
728
+ //# sourceMappingURL=ProviderLaneSqliteStore.js.map