@effect-agent/platform-node 0.1.0-beta.9 → 0.1.0-beta.91

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 (37) hide show
  1. package/dist/NodeDurableAgentRuntime-Cs_E9W_9.d.mts +175 -0
  2. package/dist/NodeDurableAgentRuntime.d.mts +2 -0
  3. package/dist/NodeDurableAgentRuntime.mjs +221 -0
  4. package/dist/NodeDurableAgentRuntime.mjs.map +1 -0
  5. package/dist/NodeDurableHost-CG2xAhfb.mjs +194 -0
  6. package/dist/NodeDurableHost-CG2xAhfb.mjs.map +1 -0
  7. package/dist/NodeDurableHost.d.mts +160 -0
  8. package/dist/NodeDurableHost.mjs +2 -0
  9. package/dist/NodeScheduling.d.mts +22 -0
  10. package/dist/NodeScheduling.mjs +59 -0
  11. package/dist/NodeScheduling.mjs.map +1 -0
  12. package/dist/NodeSubscriptions.d.mts +27 -0
  13. package/dist/NodeSubscriptions.mjs +74 -0
  14. package/dist/NodeSubscriptions.mjs.map +1 -0
  15. package/dist/NodeWakeScheduler-CCgm1t-G.d.mts +27 -0
  16. package/dist/NodeWakeScheduler.d.mts +2 -0
  17. package/dist/NodeWakeScheduler.mjs +65 -0
  18. package/dist/NodeWakeScheduler.mjs.map +1 -0
  19. package/dist/NodeWorkflow.d.mts +27 -0
  20. package/dist/NodeWorkflow.mjs +159 -0
  21. package/dist/NodeWorkflow.mjs.map +1 -0
  22. package/dist/index.d.mts +6 -214
  23. package/dist/index.mjs +6 -286
  24. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  25. package/package.json +1 -45
  26. package/src/NodeDurableAgentRuntime.ts +590 -0
  27. package/src/NodeDurableHost.ts +358 -0
  28. package/src/NodeScheduling.ts +117 -0
  29. package/src/NodeSubscriptions.ts +158 -0
  30. package/src/{wake-scheduler.ts → NodeWakeScheduler.ts} +32 -17
  31. package/src/NodeWorkflow.ts +258 -0
  32. package/src/index.ts +5 -3
  33. package/src/internal/message-delivery.ts +53 -0
  34. package/src/internal/prepared-admission.ts +81 -0
  35. package/dist/index.mjs.map +0 -1
  36. package/src/host.ts +0 -215
  37. package/src/layers.ts +0 -381
@@ -0,0 +1,590 @@
1
+ import { messageDeliveryStoreLayer } from "@effect-agent/storage-sqlite/sqlite-message-delivery-store";
2
+ import { scheduleStoreLayer } from "@effect-agent/storage-sqlite/sqlite-schedule-store";
3
+ import {
4
+ SqliteStorageConfig,
5
+ SqliteStorageConfigValue,
6
+ } from "@effect-agent/storage-sqlite/sqlite-storage-config";
7
+ import {
8
+ type SqliteStorageFailpoint,
9
+ type SqliteStorageFailpointHandler,
10
+ } from "@effect-agent/storage-sqlite/sqlite-storage-failpoint";
11
+ import { submissionLedgerLayer } from "@effect-agent/storage-sqlite/sqlite-submission-ledger";
12
+ import {
13
+ threadStoreLayer,
14
+ storageFailpointLayer,
15
+ type SqliteStorageInitializationError,
16
+ } from "@effect-agent/storage-sqlite/sqlite-thread-store";
17
+ import { NodeCrypto } from "@effect/platform-node";
18
+ import { SqliteClient } from "@effect/sql-sqlite-node";
19
+ import { Context, type Crypto, Duration, Effect, Layer, Ref, Schema } from "effect";
20
+ import { type AgentRegistration, type ResolvedBinding } from "effect-agent/agent-registration";
21
+ import { DurableAgentRuntime, DurableRuntimeConfig } from "effect-agent/durable-agent-runtime";
22
+ import {
23
+ DurableRuntimeFailpoint,
24
+ type DurableRuntimeFailpointHandler,
25
+ } from "effect-agent/durable-failpoint";
26
+ import { type SubmissionId } from "effect-agent/identifiers";
27
+ import {
28
+ type MessageDeliveryError,
29
+ type MessageDeliveryStore,
30
+ } from "effect-agent/message-delivery";
31
+ import { DeploymentId, ProducerId } from "effect-agent/records";
32
+ import {
33
+ CurrentToolFailureObserver,
34
+ RunContextPreparationPassthrough,
35
+ RunToolAuthorization,
36
+ toolFailureObserverLayer,
37
+ type RunContextPreparation,
38
+ type RunCostEstimator,
39
+ type RunToolFailureObserver,
40
+ } from "effect-agent/run-options";
41
+ import { type ScheduleStore } from "effect-agent/schedule";
42
+ import {
43
+ DEFAULT_OWNERSHIP_LEASE_DURATION,
44
+ ReleaseOwnershipRequest,
45
+ SubmissionLedger,
46
+ type OwnershipToken,
47
+ } from "effect-agent/submission-ledger";
48
+ import { type ThreadStore } from "effect-agent/thread-store";
49
+ import { ToolReconciler } from "effect-agent/tool-reconciler";
50
+ import { type WakeScheduler } from "effect-agent/wake-scheduler";
51
+ import type * as SqlClientService from "effect/unstable/sql/SqlClient";
52
+
53
+ import { NodeWakeSchedulerConfig, nodeWakeSchedulerLayer } from "./NodeWakeScheduler.ts";
54
+
55
+ const PositiveMillis = Schema.Int.check(Schema.isGreaterThan(0));
56
+ const NonNegativeMillis = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
57
+
58
+ const WorkerConcurrency = Schema.Int.check(
59
+ Schema.isGreaterThanOrEqualTo(1),
60
+ Schema.isLessThanOrEqualTo(64),
61
+ );
62
+
63
+ /** The supplied Node durable runtime configuration failed schema validation (DEPLOY-003). */
64
+ export class NodePlatformConfigError extends Schema.TaggedError<NodePlatformConfigError>()(
65
+ "NodePlatformConfigError",
66
+ {
67
+ message: Schema.String,
68
+ cause: Schema.optionalKey(Schema.Defect()),
69
+ },
70
+ ) {}
71
+
72
+ /**
73
+ * Validated Node durable runtime configuration (deployment §4: decoded once during Layer
74
+ * construction, exposed as a typed service). Every cadence is in milliseconds and every bound is
75
+ * finite; `workerConcurrency` caps how many worker loops `NodeDurableHost.runWorkers` drives.
76
+ */
77
+ export class NodeDurableAgentRuntimeConfigValue extends Schema.Class<NodeDurableAgentRuntimeConfigValue>(
78
+ "@effect-agent/platform-node/NodeDurableAgentRuntimeConfigValue",
79
+ )({
80
+ /** SQLite database file backing the Thread Log, Submission Ledger, and delivery obligations. */
81
+ filename: Schema.NonEmptyString,
82
+ deploymentId: DeploymentId,
83
+ producerId: ProducerId,
84
+ /** Submission ownership lease duration (D5); liveness hint only, epochs stay authoritative. */
85
+ ownershipLeaseDuration: PositiveMillis,
86
+ /** Finite bound on concurrent worker loops per host (rule 10). */
87
+ workerConcurrency: WorkerConcurrency,
88
+ /** Fallback scan cadence for the Node wake scheduler and independent message delivery recovery. */
89
+ wakeScanInterval: PositiveMillis,
90
+ /** `awaitSettlement` ledger re-check cadence when no wake arrives. */
91
+ settlementPollInterval: PositiveMillis,
92
+ /** Worker ownership-lease renewal cadence. */
93
+ leaseRenewalInterval: PositiveMillis,
94
+ /** Active-Run abort-intent poll cadence. */
95
+ abortPollInterval: PositiveMillis,
96
+ /** Bounded SQLITE_BUSY retry window for write-lock acquisition. */
97
+ busyTimeout: NonNegativeMillis,
98
+ /** Canonical observation poll cadence of the SQLite store. */
99
+ observationPollInterval: NonNegativeMillis,
100
+ /** Opt-in full payload/digest-chain audit while opening the store. */
101
+ verifyOnOpen: Schema.Boolean,
102
+ }) {}
103
+
104
+ /** Explicit configuration authority for the assembled Node durable runtime. */
105
+ export class NodeDurableAgentRuntimeConfig extends Context.Service<
106
+ NodeDurableAgentRuntimeConfig,
107
+ NodeDurableAgentRuntimeConfigValue
108
+ >()("@effect-agent/platform-node/NodeDurableAgentRuntimeConfig") {}
109
+
110
+ /**
111
+ * Raw (unvalidated) construction options for `NodeDurableAgentRuntime.layer`. Optional fields default
112
+ * to the documented production values; everything is schema-decoded into
113
+ * `NodeDurableAgentRuntimeConfigValue` before any resource opens (deployment §5 gate 1).
114
+ */
115
+ export interface NodeDurableAgentRuntimeOptions<
116
+ ContextError = never,
117
+ ContextRequirements = never,
118
+ AuthorizationError = never,
119
+ AuthorizationRequirements = never,
120
+ ReconcilerError = never,
121
+ ReconcilerRequirements = never,
122
+ > {
123
+ readonly filename: string;
124
+ readonly deploymentId: string;
125
+ readonly producerId: string;
126
+ /** Milliseconds; default `DEFAULT_OWNERSHIP_LEASE_DURATION` (30s, D5). */
127
+ readonly ownershipLeaseDuration?: number | undefined;
128
+ /** Default 1; bounded to 1..64. */
129
+ readonly workerConcurrency?: number | undefined;
130
+ /** Milliseconds; default 1000. */
131
+ readonly wakeScanInterval?: number | undefined;
132
+ /** Milliseconds; default 500. */
133
+ readonly settlementPollInterval?: number | undefined;
134
+ /** Milliseconds; default 10000. */
135
+ readonly leaseRenewalInterval?: number | undefined;
136
+ /** Milliseconds; default 500. */
137
+ readonly abortPollInterval?: number | undefined;
138
+ /** Deployment-owned pricing authority used by durable cost budgets and settlements. */
139
+ readonly estimateCostMicrousd?: RunCostEstimator | undefined;
140
+ /** Closed trusted Tool failure reporting. Omission masks ambient observers at construction. */
141
+ readonly toolFailureObserver?: RunToolFailureObserver | undefined;
142
+ /** Milliseconds; default 5000. */
143
+ readonly busyTimeout?: number | undefined;
144
+ /** Milliseconds; default 25. */
145
+ readonly observationPollInterval?: number | undefined;
146
+ /** Default false. */
147
+ readonly verifyOnOpen?: boolean | undefined;
148
+ /** SQLite adapter fault injection (`ledger:*` / `append:*` locations); default none. */
149
+ readonly storageFailpoint?: SqliteStorageFailpointHandler | undefined;
150
+ /** Coordinator fault injection (`submit:*` / `terminalize:*` locations); default none. */
151
+ readonly runtimeFailpoint?: DurableRuntimeFailpointHandler | undefined;
152
+ /**
153
+ * Reconciliation policy consulted for open ordinary Tool Calls before an Unknown Outcome is
154
+ * recorded (durability §10, DUR-009). Defaults to the fail-closed `ToolReconciler.uncertain`:
155
+ * with no registered policy, every open call stays Unknown and routes to the authorized
156
+ * DUR-017 resolution path. Construction errors and application dependencies remain in the
157
+ * assembled Layer's E and R; the platform supplies Crypto.
158
+ */
159
+ readonly toolReconciler?:
160
+ | Layer.Layer<ToolReconciler, ReconcilerError, ReconcilerRequirements | Crypto.Crypto>
161
+ | undefined;
162
+ /** Host prompt preparation, acquired once with the runtime; default pass-through. */
163
+ readonly runContext?:
164
+ | Layer.Layer<RunContextPreparation, ContextError, ContextRequirements | Crypto.Crypto>
165
+ | undefined;
166
+ /**
167
+ * Independent action-time Tool authority, acquired once with the runtime; default allow-all.
168
+ * Construction errors and application dependencies remain in the assembled Layer's E and R.
169
+ * The platform supplies Crypto to these extension Layers.
170
+ */
171
+ readonly toolAuthorization?:
172
+ | Layer.Layer<
173
+ RunToolAuthorization,
174
+ AuthorizationError,
175
+ AuthorizationRequirements | Crypto.Crypto
176
+ >
177
+ | undefined;
178
+ }
179
+
180
+ /** Built-in construction failures. `layer` also preserves supplied service Layers' errors. */
181
+ export type NodeDurableAgentRuntimeInitializationError =
182
+ | NodePlatformConfigError
183
+ | MessageDeliveryError
184
+ | SqliteStorageInitializationError;
185
+
186
+ /**
187
+ * The services `NodeDurableAgentRuntime.layer` provides. Additional SQLite adapters can use
188
+ * the same client and storage configuration, sharing its serialized connection and Scope.
189
+ */
190
+ export type NodeDurableAgentRuntimeServices =
191
+ | DurableAgentRuntime
192
+ | SubmissionLedger
193
+ | ThreadStore
194
+ | ScheduleStore
195
+ | MessageDeliveryStore
196
+ | WakeScheduler
197
+ | DurableRuntimeConfig
198
+ | NodeDurableAgentRuntimeConfig
199
+ | SqlClientService.SqlClient
200
+ | SqliteStorageConfig
201
+ | SqliteStorageFailpoint;
202
+
203
+ const decodeConfigValue = Schema.decodeUnknownEffect(NodeDurableAgentRuntimeConfigValue);
204
+
205
+ const configFromOptions = (
206
+ options: Omit<
207
+ NodeDurableAgentRuntimeOptions,
208
+ "runContext" | "toolAuthorization" | "toolReconciler"
209
+ >,
210
+ ): Effect.Effect<NodeDurableAgentRuntimeConfigValue, NodePlatformConfigError> =>
211
+ decodeConfigValue({
212
+ filename: options.filename,
213
+ deploymentId: options.deploymentId,
214
+ producerId: options.producerId,
215
+ ownershipLeaseDuration:
216
+ options.ownershipLeaseDuration ?? Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION),
217
+ workerConcurrency: options.workerConcurrency ?? 1,
218
+ wakeScanInterval: options.wakeScanInterval ?? 1_000,
219
+ settlementPollInterval: options.settlementPollInterval ?? 500,
220
+ leaseRenewalInterval: options.leaseRenewalInterval ?? 10_000,
221
+ abortPollInterval: options.abortPollInterval ?? 500,
222
+ busyTimeout: options.busyTimeout ?? 5_000,
223
+ observationPollInterval: options.observationPollInterval ?? 25,
224
+ verifyOnOpen: options.verifyOnOpen ?? false,
225
+ }).pipe(
226
+ Effect.mapError((error) =>
227
+ NodePlatformConfigError.make({
228
+ message: `Invalid Node durable runtime configuration: ${error.message}`,
229
+ cause: error,
230
+ }),
231
+ ),
232
+ );
233
+
234
+ /** SQLite storage configuration derived from the single validated Node configuration. */
235
+ const sqliteStorageConfigLayer: Layer.Layer<
236
+ SqliteStorageConfig,
237
+ never,
238
+ NodeDurableAgentRuntimeConfig
239
+ > = Layer.effect(SqliteStorageConfig)(
240
+ Effect.gen(function* () {
241
+ const config = yield* NodeDurableAgentRuntimeConfig;
242
+
243
+ return SqliteStorageConfigValue.make({
244
+ observationPollInterval: config.observationPollInterval,
245
+ busyTimeout: config.busyTimeout,
246
+ ownershipLeaseDuration: config.ownershipLeaseDuration,
247
+ verifyOnOpen: config.verifyOnOpen,
248
+ });
249
+ }),
250
+ );
251
+
252
+ /** Thread coordinator configuration derived from the single validated Node configuration. */
253
+ const durableRuntimeConfigLayer = (
254
+ estimateCostMicrousd: RunCostEstimator | undefined,
255
+ ): Layer.Layer<DurableRuntimeConfig, never, NodeDurableAgentRuntimeConfig> =>
256
+ Layer.effect(DurableRuntimeConfig)(
257
+ Effect.gen(function* () {
258
+ const config = yield* NodeDurableAgentRuntimeConfig;
259
+
260
+ return DurableRuntimeConfig.make({
261
+ deploymentId: config.deploymentId,
262
+ producerId: config.producerId,
263
+ settlementPollInterval: Duration.millis(config.settlementPollInterval),
264
+ leaseRenewalInterval: Duration.millis(config.leaseRenewalInterval),
265
+ abortPollInterval: Duration.millis(config.abortPollInterval),
266
+ ...(estimateCostMicrousd === undefined ? {} : { estimateCostMicrousd }),
267
+ });
268
+ }),
269
+ );
270
+
271
+ /** Wake fallback-scan cadence derived from the single validated Node configuration. */
272
+ const wakeSchedulerConfigLayer: Layer.Layer<
273
+ NodeWakeSchedulerConfig,
274
+ never,
275
+ NodeDurableAgentRuntimeConfig
276
+ > = Layer.effect(NodeWakeSchedulerConfig)(
277
+ Effect.gen(function* () {
278
+ const config = yield* NodeDurableAgentRuntimeConfig;
279
+
280
+ return { scanInterval: Duration.millis(config.wakeScanInterval) };
281
+ }),
282
+ );
283
+
284
+ const releaseTrackedOwnership = (
285
+ ledger: SubmissionLedger["Service"],
286
+ registry: Ref.Ref<ReadonlyMap<SubmissionId, OwnershipToken>>,
287
+ ): Effect.Effect<void> =>
288
+ Effect.gen(function* () {
289
+ const tracked = yield* Ref.getAndSet(registry, new Map<SubmissionId, OwnershipToken>());
290
+
291
+ for (const [submissionId, ownershipToken] of tracked) {
292
+ yield* ledger
293
+ .releaseOwnership(ReleaseOwnershipRequest.make({ submissionId, ownershipToken }))
294
+ .pipe(
295
+ Effect.catchTags({
296
+ // A newer epoch already owns (or settled) the lane: nothing left to drain.
297
+ OwnershipLost: () => Effect.void,
298
+ // Drain is best-effort by design: the lease still expires and the durability protocol,
299
+ // not graceful shutdown, provides correctness (DEPLOY-006).
300
+ LedgerError: (error) =>
301
+ Effect.logWarning("Ownership drain failed; the lease will expire instead", error),
302
+ }),
303
+ );
304
+ }
305
+ });
306
+
307
+ /**
308
+ * Shutdown-drain decorator for a `SubmissionLedger` (deployment §6 step 6): every ownership
309
+ * period granted through this Layer is tracked — claims start tracking, renewals follow token
310
+ * rotation, releases and settlement finalizations stop it — and every ownership still held when
311
+ * the Layer's Scope closes is released so another host can claim the lane immediately instead of
312
+ * waiting for lease expiry. The drain is a liveness courtesy only; producer-epoch fencing remains
313
+ * the correctness authority (DUR-006), and a forced kill simply falls back to lease expiry.
314
+ */
315
+ export const ownershipDrainLayer: Layer.Layer<SubmissionLedger, never, SubmissionLedger> =
316
+ Layer.effect(SubmissionLedger)(
317
+ Effect.gen(function* () {
318
+ const ledger = yield* SubmissionLedger;
319
+
320
+ const registry = yield* Ref.make<ReadonlyMap<SubmissionId, OwnershipToken>>(
321
+ new Map<SubmissionId, OwnershipToken>(),
322
+ );
323
+
324
+ const track = (submissionId: SubmissionId, ownershipToken: OwnershipToken) =>
325
+ Ref.update(registry, (tracked) => new Map(tracked).set(submissionId, ownershipToken));
326
+
327
+ const untrack = (submissionId: SubmissionId) =>
328
+ Ref.update(registry, (tracked) => {
329
+ const next = new Map(tracked);
330
+
331
+ next.delete(submissionId);
332
+
333
+ return next;
334
+ });
335
+
336
+ yield* Effect.addFinalizer(() => releaseTrackedOwnership(ledger, registry));
337
+
338
+ return SubmissionLedger.of({
339
+ capabilities: ledger.capabilities,
340
+ admit: ledger.admit,
341
+ markReady: ledger.markReady,
342
+ lookup: ledger.lookup,
343
+ // The S2 subagent ops forward untouched: none of them grants an ownership period, so
344
+ // the drain has nothing to track for them (`suspend` below already stops tracking the
345
+ // waitingForChild ownership period the moment it ends).
346
+ resolveAdmission: ledger.resolveAdmission,
347
+ recordChildSettled: ledger.recordChildSettled,
348
+ reserveChildBudget: ledger.reserveChildBudget,
349
+ attachChildToReservation: ledger.attachChildToReservation,
350
+ beginChildBudgetRelease: ledger.beginChildBudgetRelease,
351
+ releaseChildBudget: ledger.releaseChildBudget,
352
+ claim: (request) =>
353
+ ledger
354
+ .claim(request)
355
+ .pipe(
356
+ Effect.tap((claimed) =>
357
+ claimed._tag === "Some"
358
+ ? track(claimed.value.submissionId, claimed.value.ownershipToken)
359
+ : Effect.void,
360
+ ),
361
+ ),
362
+ renewOwnership: (request) =>
363
+ ledger.renewOwnership(request).pipe(
364
+ Effect.tap((renewal) => track(request.submissionId, renewal.ownershipToken)),
365
+ Effect.tapError((error) =>
366
+ error._tag === "OwnershipLost" ? untrack(request.submissionId) : Effect.void,
367
+ ),
368
+ ),
369
+ releaseOwnership: (request) =>
370
+ ledger.releaseOwnership(request).pipe(
371
+ Effect.tap(() => untrack(request.submissionId)),
372
+ Effect.tapError((error) =>
373
+ error._tag === "OwnershipLost" ? untrack(request.submissionId) : Effect.void,
374
+ ),
375
+ ),
376
+ markInputApplied: ledger.markInputApplied,
377
+ reserveSettlement: ledger.reserveSettlement,
378
+ finalizeSettlement: (request) =>
379
+ ledger.finalizeSettlement(request).pipe(Effect.tap(() => untrack(request.submissionId))),
380
+ requestAbort: ledger.requestAbort,
381
+ readAbortIntent: ledger.readAbortIntent,
382
+ claimJoining: ledger.claimJoining,
383
+ markJoined: ledger.markJoined,
384
+ revertJoining: ledger.revertJoining,
385
+ // Suspension ends the ownership period by contract, so the drain stops tracking it.
386
+ suspend: (request) =>
387
+ ledger.suspend(request).pipe(Effect.tap(() => untrack(request.submissionId))),
388
+ recordApprovalDecision: ledger.recordApprovalDecision,
389
+ markUnknown: ledger.markUnknown,
390
+ recordUnknownResolution: ledger.recordUnknownResolution,
391
+ scanNonterminal: ledger.scanNonterminal,
392
+ loadRecoverySnapshot: ledger.loadRecoverySnapshot,
393
+ });
394
+ }),
395
+ );
396
+
397
+ /**
398
+ * The DN Layer assembly (deployment §12: a Layer-assembly library, not an app entrypoint).
399
+ * `layer(options)` decodes the configuration, opens ONE SQLite database serving both the
400
+ * Thread Log and the Submission Ledger (so claims fence the same producer epochs), wires
401
+ * the Node wake scheduler with its ledger-scan fallback, exposes independent message delivery
402
+ * storage, wraps the ledger with the shutdown
403
+ * ownership drain, defaults the Tool reconciliation policy to the fail-closed
404
+ * `ToolReconciler.uncertain` (override via `options.toolReconciler`), and provides a ready
405
+ * `DurableAgentRuntime` on top. Storage compatibility is
406
+ * verified during construction: an incompatible database file fails the Layer with
407
+ * `SqliteStorageCompatibilityError` before anything is mutated (DEPLOY-008).
408
+ */
409
+ export class NodeDurableAgentRuntime {
410
+ /** Validated configuration Layer; fails typed when the supplied options are out of bounds. */
411
+ static configLayer<
412
+ ContextError = never,
413
+ ContextRequirements = never,
414
+ AuthorizationError = never,
415
+ AuthorizationRequirements = never,
416
+ ReconcilerError = never,
417
+ ReconcilerRequirements = never,
418
+ >(
419
+ options: NodeDurableAgentRuntimeOptions<
420
+ ContextError,
421
+ ContextRequirements,
422
+ AuthorizationError,
423
+ AuthorizationRequirements,
424
+ ReconcilerError,
425
+ ReconcilerRequirements
426
+ >,
427
+ ): Layer.Layer<NodeDurableAgentRuntimeConfig, NodePlatformConfigError> {
428
+ return Layer.effect(NodeDurableAgentRuntimeConfig)(configFromOptions(options));
429
+ }
430
+
431
+ /** The full DN runtime stack over one SQLite file. */
432
+ static layer<
433
+ ContextError = never,
434
+ ContextRequirements = never,
435
+ AuthorizationError = never,
436
+ AuthorizationRequirements = never,
437
+ ReconcilerError = never,
438
+ ReconcilerRequirements = never,
439
+ >(
440
+ options: NodeDurableAgentRuntimeOptions<
441
+ ContextError,
442
+ ContextRequirements,
443
+ AuthorizationError,
444
+ AuthorizationRequirements,
445
+ ReconcilerError,
446
+ ReconcilerRequirements
447
+ >,
448
+ ) {
449
+ return NodeDurableAgentRuntime.assemble(DurableAgentRuntime.layerWithServices, options);
450
+ }
451
+
452
+ /** Own typed executable registrations for every worker using this Node runtime. */
453
+ static layerRegistered<
454
+ const Entries extends ReadonlyArray<AgentRegistration>,
455
+ ContextError = never,
456
+ ContextRequirements = never,
457
+ AuthorizationError = never,
458
+ AuthorizationRequirements = never,
459
+ ReconcilerError = never,
460
+ ReconcilerRequirements = never,
461
+ >(
462
+ registrations: Entries,
463
+ options: NodeDurableAgentRuntimeOptions<
464
+ ContextError,
465
+ ContextRequirements,
466
+ AuthorizationError,
467
+ AuthorizationRequirements,
468
+ ReconcilerError,
469
+ ReconcilerRequirements
470
+ >,
471
+ ) {
472
+ return NodeDurableAgentRuntime.assemble(
473
+ DurableAgentRuntime.layerRegistered(registrations),
474
+ options,
475
+ );
476
+ }
477
+
478
+ /** Construct from registrations already resolved within the enclosing application Scope. */
479
+ static layerWithBindings<
480
+ ContextError = never,
481
+ ContextRequirements = never,
482
+ AuthorizationError = never,
483
+ AuthorizationRequirements = never,
484
+ ReconcilerError = never,
485
+ ReconcilerRequirements = never,
486
+ >(
487
+ bindings: ReadonlyArray<ResolvedBinding>,
488
+ options: NodeDurableAgentRuntimeOptions<
489
+ ContextError,
490
+ ContextRequirements,
491
+ AuthorizationError,
492
+ AuthorizationRequirements,
493
+ ReconcilerError,
494
+ ReconcilerRequirements
495
+ >,
496
+ ) {
497
+ return NodeDurableAgentRuntime.assemble(
498
+ DurableAgentRuntime.layerWithBindings(bindings),
499
+ options,
500
+ );
501
+ }
502
+
503
+ private static assemble<
504
+ RuntimeError,
505
+ RuntimeRequirements,
506
+ ContextError,
507
+ ContextRequirements,
508
+ AuthorizationError,
509
+ AuthorizationRequirements,
510
+ ReconcilerError,
511
+ ReconcilerRequirements,
512
+ >(
513
+ runtimeLayer: Layer.Layer<DurableAgentRuntime, RuntimeError, RuntimeRequirements>,
514
+ options: NodeDurableAgentRuntimeOptions<
515
+ ContextError,
516
+ ContextRequirements,
517
+ AuthorizationError,
518
+ AuthorizationRequirements,
519
+ ReconcilerError,
520
+ ReconcilerRequirements
521
+ >,
522
+ ) {
523
+ const assembled = Layer.unwrap(
524
+ Effect.map(configFromOptions(options), (config) => {
525
+ const nodeConfigLayer = Layer.succeed(NodeDurableAgentRuntimeConfig)(config);
526
+
527
+ const infrastructure = Layer.mergeAll(
528
+ sqliteStorageConfigLayer,
529
+ storageFailpointLayer({
530
+ filename: config.filename,
531
+ failpoint: options.storageFailpoint,
532
+ }),
533
+ SqliteClient.layer({ filename: config.filename }),
534
+ NodeCrypto.layer,
535
+ );
536
+
537
+ const runtimeFailpointLayer =
538
+ options.runtimeFailpoint === undefined
539
+ ? DurableRuntimeFailpoint.layer
540
+ : Layer.succeed(DurableRuntimeFailpoint)({ hit: options.runtimeFailpoint });
541
+
542
+ const reconcilerLayer = options.toolReconciler ?? ToolReconciler.uncertain;
543
+
544
+ const observerLayer =
545
+ options.toolFailureObserver === undefined
546
+ ? Layer.succeed(CurrentToolFailureObserver)(undefined)
547
+ : toolFailureObserverLayer(options.toolFailureObserver);
548
+
549
+ const ports = Layer.mergeAll(
550
+ threadStoreLayer,
551
+ scheduleStoreLayer,
552
+ messageDeliveryStoreLayer(),
553
+ nodeWakeSchedulerLayer.pipe(
554
+ Layer.provideMerge(ownershipDrainLayer.pipe(Layer.provide(submissionLedgerLayer))),
555
+ ),
556
+ );
557
+
558
+ return runtimeLayer.pipe(
559
+ Layer.provideMerge(
560
+ Layer.mergeAll(ports, durableRuntimeConfigLayer(options.estimateCostMicrousd)),
561
+ ),
562
+ Layer.provide(
563
+ Layer.mergeAll(
564
+ wakeSchedulerConfigLayer,
565
+ runtimeFailpointLayer,
566
+ reconcilerLayer,
567
+ observerLayer,
568
+ ),
569
+ ),
570
+ Layer.provideMerge(infrastructure),
571
+ Layer.provideMerge(nodeConfigLayer),
572
+ Layer.provide(
573
+ Layer.mergeAll(
574
+ options.runContext ?? RunContextPreparationPassthrough,
575
+ options.toolAuthorization ?? RunToolAuthorization.allowAll,
576
+ ).pipe(Layer.provide(NodeCrypto.layer)),
577
+ ),
578
+ );
579
+ }),
580
+ );
581
+
582
+ const runtime: Layer.Layer<
583
+ NodeDurableAgentRuntimeServices,
584
+ Layer.Error<typeof assembled>,
585
+ Layer.Services<typeof assembled>
586
+ > = assembled;
587
+
588
+ return runtime;
589
+ }
590
+ }