@effect-agent/platform-node 0.1.0-beta.44 → 0.1.0-beta.46

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/index.mjs CHANGED
@@ -1,436 +1,6 @@
1
- import { DEFAULT_OWNERSHIP_LEASE_DURATION, DeploymentId, DurableAgentRuntime, DurableRuntimeConfig, DurableRuntimeFailpoint, PersistedJson, PreparedInputAdmission, ProducerId, ReleaseOwnershipRequest, ScheduleDriver, ScheduleStorageError, ScheduleStore, ScheduleWake, ScheduledInputAdmission, ScheduledInputRetryable, Scheduling, SubmissionLedger, SubscriptionDriver, SubscriptionIntake, SubscriptionStore, Subscriptions, ToolReconciler, WakeScheduler, compileRegistrations, defaultSchedulingLimits, defaultSubscriptionLimits, makeWakeSubscriptionHub } from "@effect-agent/thread";
2
- import { NodeCrypto } from "@effect/platform-node";
3
- import { Cause, Context, Duration, Effect, Exit, Layer, Option, PubSub, Ref, Result, Schema, Stream } from "effect";
4
- import { CurrentToolFailureObserver, RunContextPreparationPassthrough, RunToolAuthorization, toolFailureObserverLayer } from "@effect-agent/engine";
5
- import { SqliteStorageConfig, SqliteStorageConfigValue, scheduleStoreLayer, storageFailpointLayer, submissionLedgerLayer, threadStoreLayer } from "@effect-agent/storage-sqlite";
6
- import { SqliteClient } from "@effect/sql-sqlite-node";
7
- //#region src/wake-scheduler.ts
8
- /**
9
- * Bounded in-process wake buffer. Wake hints are droppable by contract (the ledger-scan fallback
10
- * keeps liveness), so a full buffer slides out the oldest hint instead of growing without bound.
11
- */
12
- const WAKE_BUFFER_CAPACITY = 1024;
13
- /** Cadence authority for the Node wake scheduler's ledger-scan fallback loop. */
14
- var NodeWakeSchedulerConfig = class NodeWakeSchedulerConfig extends Context.Service()("@effect-agent/platform-node/NodeWakeSchedulerConfig") {
15
- static layer(options) {
16
- return Layer.succeed(NodeWakeSchedulerConfig)({ scanInterval: options.scanInterval });
17
- }
18
- };
19
- const makeWakeScheduler = Effect.gen(function* () {
20
- const ledger = yield* SubmissionLedger;
21
- const config = yield* NodeWakeSchedulerConfig;
22
- const hints = yield* PubSub.sliding(WAKE_BUFFER_CAPACITY);
23
- const progress = yield* makeWakeSubscriptionHub;
24
- yield* Effect.addFinalizer(() => PubSub.shutdown(hints));
25
- /**
26
- * One fallback scan: every Thread lane with nonterminal work, deduplicated. A scan
27
- * failure degrades to "no hints this round" — the wake channel has no error contract and the
28
- * next round retries — but is logged so a persistently failing ledger stays visible.
29
- */
30
- const scanOnce = Stream.runCollect(ledger.scanNonterminal).pipe(Effect.map((snapshots) => {
31
- const lanes = /* @__PURE__ */ new Set();
32
- for (const snapshot of snapshots) lanes.add(snapshot.threadId);
33
- return [...lanes];
34
- }), Effect.catch((error) => Effect.logWarning("NodeWakeScheduler fallback scan failed", error).pipe(Effect.as([]))));
35
- /**
36
- * Deployment §3: correctness must not depend on in-memory notifications, so every `wakes` run
37
- * merges its PubSub subscription with a Clock-driven ledger-scan loop. Both live entirely in
38
- * the consuming run's Scope; no fiber outlives its subscriber.
39
- */
40
- const fallbackScans = Stream.fromIterableEffectRepeat(Effect.sleep(config.scanInterval).pipe(Effect.andThen(scanOnce)));
41
- return WakeScheduler.of({
42
- notify: (threadId) => progress.notify(threadId).pipe(Effect.andThen(PubSub.publish(hints, threadId)), Effect.asVoid),
43
- subscribe: progress.subscribe,
44
- wakes: Stream.merge(Stream.fromPubSub(hints), fallbackScans)
45
- });
46
- });
47
- /**
48
- * In-process Node `WakeScheduler`: `notify` publishes to a bounded sliding PubSub for prompt
49
- * same-process wakeups, and every `wakes` subscription additionally runs a periodic
50
- * `SubmissionLedger.scanNonterminal` fallback so a dropped, coalesced, or never-sent notification
51
- * can never strand accepted work (persistence §14). Delivery may duplicate; consumers already
52
- * treat wakes as pure liveness hints.
53
- */
54
- const nodeWakeSchedulerLayer = Layer.effect(WakeScheduler)(makeWakeScheduler);
55
- //#endregion
56
- //#region src/layers.ts
57
- const PositiveMillis = Schema.Int.check(Schema.isGreaterThan(0));
58
- const NonNegativeMillis = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
59
- const WorkerConcurrency = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(64));
60
- /** The supplied Node durable runtime configuration failed schema validation (DEPLOY-003). */
61
- var NodePlatformConfigError = class extends Schema.TaggedError()("NodePlatformConfigError", {
62
- message: Schema.String,
63
- cause: Schema.optionalKey(Schema.Defect())
64
- }) {};
65
- /**
66
- * Validated Node durable runtime configuration (deployment §4: decoded once during Layer
67
- * construction, exposed as a typed service). Every cadence is in milliseconds and every bound is
68
- * finite; `workerConcurrency` caps how many worker loops `NodeDurableHost.runWorkers` drives.
69
- */
70
- var NodeDurableRuntimeConfigValue = class extends Schema.Class("@effect-agent/platform-node/NodeDurableRuntimeConfigValue")({
71
- /** SQLite database file backing BOTH the Thread Log and the Submission Ledger. */
72
- filename: Schema.NonEmptyString,
73
- deploymentId: DeploymentId,
74
- producerId: ProducerId,
75
- /** Submission ownership lease duration (D5); liveness hint only, epochs stay authoritative. */
76
- ownershipLeaseDuration: PositiveMillis,
77
- /** Finite bound on concurrent worker loops per host (rule 10). */
78
- workerConcurrency: WorkerConcurrency,
79
- /** Ledger-scan fallback cadence of the Node wake scheduler (deployment §3). */
80
- wakeScanInterval: PositiveMillis,
81
- /** `awaitSettlement` ledger re-check cadence when no wake arrives. */
82
- settlementPollInterval: PositiveMillis,
83
- /** Worker ownership-lease renewal cadence. */
84
- leaseRenewalInterval: PositiveMillis,
85
- /** Active-Run abort-intent poll cadence. */
86
- abortPollInterval: PositiveMillis,
87
- /** Bounded SQLITE_BUSY retry window for write-lock acquisition. */
88
- busyTimeout: NonNegativeMillis,
89
- /** Canonical observation poll cadence of the SQLite store. */
90
- observationPollInterval: NonNegativeMillis,
91
- /** Opt-in full payload/digest-chain audit while opening the store. */
92
- verifyOnOpen: Schema.Boolean
93
- }) {};
94
- /** Explicit configuration authority for the assembled Node durable runtime. */
95
- var NodeDurableRuntimeConfig = class extends Context.Service()("@effect-agent/platform-node/NodeDurableRuntimeConfig") {};
96
- const decodeConfigValue = Schema.decodeUnknownEffect(NodeDurableRuntimeConfigValue);
97
- const configFromOptions = (options) => decodeConfigValue({
98
- filename: options.filename,
99
- deploymentId: options.deploymentId,
100
- producerId: options.producerId,
101
- ownershipLeaseDuration: options.ownershipLeaseDuration ?? Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION),
102
- workerConcurrency: options.workerConcurrency ?? 1,
103
- wakeScanInterval: options.wakeScanInterval ?? 1e3,
104
- settlementPollInterval: options.settlementPollInterval ?? 500,
105
- leaseRenewalInterval: options.leaseRenewalInterval ?? 1e4,
106
- abortPollInterval: options.abortPollInterval ?? 500,
107
- busyTimeout: options.busyTimeout ?? 5e3,
108
- observationPollInterval: options.observationPollInterval ?? 25,
109
- verifyOnOpen: options.verifyOnOpen ?? false
110
- }).pipe(Effect.mapError((error) => NodePlatformConfigError.make({
111
- message: `Invalid Node durable runtime configuration: ${error.message}`,
112
- cause: error
113
- })));
114
- /** SQLite storage configuration derived from the single validated Node configuration. */
115
- const sqliteStorageConfigLayer = Layer.effect(SqliteStorageConfig)(Effect.gen(function* () {
116
- const config = yield* NodeDurableRuntimeConfig;
117
- return SqliteStorageConfigValue.make({
118
- observationPollInterval: config.observationPollInterval,
119
- busyTimeout: config.busyTimeout,
120
- ownershipLeaseDuration: config.ownershipLeaseDuration,
121
- verifyOnOpen: config.verifyOnOpen
122
- });
123
- }));
124
- /** Thread coordinator configuration derived from the single validated Node configuration. */
125
- const durableRuntimeConfigLayer = (estimateCostMicrousd) => Layer.effect(DurableRuntimeConfig)(Effect.gen(function* () {
126
- const config = yield* NodeDurableRuntimeConfig;
127
- return DurableRuntimeConfig.make({
128
- deploymentId: config.deploymentId,
129
- producerId: config.producerId,
130
- settlementPollInterval: Duration.millis(config.settlementPollInterval),
131
- leaseRenewalInterval: Duration.millis(config.leaseRenewalInterval),
132
- abortPollInterval: Duration.millis(config.abortPollInterval),
133
- ...estimateCostMicrousd === void 0 ? {} : { estimateCostMicrousd }
134
- });
135
- }));
136
- /** Wake fallback-scan cadence derived from the single validated Node configuration. */
137
- const wakeSchedulerConfigLayer = Layer.effect(NodeWakeSchedulerConfig)(Effect.gen(function* () {
138
- const config = yield* NodeDurableRuntimeConfig;
139
- return { scanInterval: Duration.millis(config.wakeScanInterval) };
140
- }));
141
- const releaseTrackedOwnership = (ledger, registry) => Effect.gen(function* () {
142
- const tracked = yield* Ref.getAndSet(registry, /* @__PURE__ */ new Map());
143
- for (const [submissionId, ownershipToken] of tracked) yield* ledger.releaseOwnership(ReleaseOwnershipRequest.make({
144
- submissionId,
145
- ownershipToken
146
- })).pipe(Effect.catchTags({
147
- OwnershipLost: () => Effect.void,
148
- LedgerError: (error) => Effect.logWarning("Ownership drain failed; the lease will expire instead", error)
149
- }));
150
- });
151
- /**
152
- * Shutdown-drain decorator for a `SubmissionLedger` (deployment §6 step 6): every ownership
153
- * period granted through this Layer is tracked — claims start tracking, renewals follow token
154
- * rotation, releases and settlement finalizations stop it — and every ownership still held when
155
- * the Layer's Scope closes is released so another host can claim the lane immediately instead of
156
- * waiting for lease expiry. The drain is a liveness courtesy only; producer-epoch fencing remains
157
- * the correctness authority (DUR-006), and a forced kill simply falls back to lease expiry.
158
- */
159
- const ownershipDrainLayer = Layer.effect(SubmissionLedger)(Effect.gen(function* () {
160
- const ledger = yield* SubmissionLedger;
161
- const registry = yield* Ref.make(/* @__PURE__ */ new Map());
162
- const track = (submissionId, ownershipToken) => Ref.update(registry, (tracked) => new Map(tracked).set(submissionId, ownershipToken));
163
- const untrack = (submissionId) => Ref.update(registry, (tracked) => {
164
- const next = new Map(tracked);
165
- next.delete(submissionId);
166
- return next;
167
- });
168
- yield* Effect.addFinalizer(() => releaseTrackedOwnership(ledger, registry));
169
- return SubmissionLedger.of({
170
- capabilities: ledger.capabilities,
171
- admit: ledger.admit,
172
- markReady: ledger.markReady,
173
- lookup: ledger.lookup,
174
- resolveAdmission: ledger.resolveAdmission,
175
- recordChildSettled: ledger.recordChildSettled,
176
- reserveChildBudget: ledger.reserveChildBudget,
177
- attachChildToReservation: ledger.attachChildToReservation,
178
- beginChildBudgetRelease: ledger.beginChildBudgetRelease,
179
- releaseChildBudget: ledger.releaseChildBudget,
180
- claim: (request) => ledger.claim(request).pipe(Effect.tap((claimed) => claimed._tag === "Some" ? track(claimed.value.submissionId, claimed.value.ownershipToken) : Effect.void)),
181
- renewOwnership: (request) => ledger.renewOwnership(request).pipe(Effect.tap((renewal) => track(request.submissionId, renewal.ownershipToken)), Effect.tapError((error) => error._tag === "OwnershipLost" ? untrack(request.submissionId) : Effect.void)),
182
- releaseOwnership: (request) => ledger.releaseOwnership(request).pipe(Effect.tap(() => untrack(request.submissionId)), Effect.tapError((error) => error._tag === "OwnershipLost" ? untrack(request.submissionId) : Effect.void)),
183
- markInputApplied: ledger.markInputApplied,
184
- reserveSettlement: ledger.reserveSettlement,
185
- finalizeSettlement: (request) => ledger.finalizeSettlement(request).pipe(Effect.tap(() => untrack(request.submissionId))),
186
- requestAbort: ledger.requestAbort,
187
- claimJoining: ledger.claimJoining,
188
- markJoined: ledger.markJoined,
189
- revertJoining: ledger.revertJoining,
190
- suspend: (request) => ledger.suspend(request).pipe(Effect.tap(() => untrack(request.submissionId))),
191
- recordApprovalDecision: ledger.recordApprovalDecision,
192
- markUnknown: ledger.markUnknown,
193
- recordUnknownResolution: ledger.recordUnknownResolution,
194
- scanNonterminal: ledger.scanNonterminal,
195
- loadRecoverySnapshot: ledger.loadRecoverySnapshot
196
- });
197
- }));
198
- /**
199
- * The DN Layer assembly (deployment §12: a Layer-assembly library, not an app entrypoint).
200
- * `layer(options)` decodes the configuration, opens ONE SQLite database serving both the
201
- * Thread Log and the Submission Ledger (so claims fence the same producer epochs), wires
202
- * the Node wake scheduler with its ledger-scan fallback, wraps the ledger with the shutdown
203
- * ownership drain, defaults the Tool reconciliation policy to the fail-closed
204
- * `ToolReconciler.uncertain` (override via `options.toolReconciler`), and provides a ready
205
- * `DurableAgentRuntime` on top. Storage compatibility is
206
- * verified during construction: an incompatible database file fails the Layer with
207
- * `SqliteStorageCompatibilityError` before anything is mutated (DEPLOY-008).
208
- */
209
- var NodeDurableRuntime = class {
210
- /** Validated configuration Layer; fails typed when the supplied options are out of bounds. */
211
- static configLayer(options) {
212
- return Layer.effect(NodeDurableRuntimeConfig)(configFromOptions(options));
213
- }
214
- /** The full DN runtime stack over one SQLite file. */
215
- static layer(options) {
216
- return Layer.unwrap(Effect.map(configFromOptions(options), (config) => {
217
- const nodeConfigLayer = Layer.succeed(NodeDurableRuntimeConfig)(config);
218
- const infrastructure = Layer.mergeAll(sqliteStorageConfigLayer, storageFailpointLayer({
219
- filename: config.filename,
220
- failpoint: options.storageFailpoint
221
- }), SqliteClient.layer({ filename: config.filename }), NodeCrypto.layer);
222
- const runtimeFailpointLayer = options.runtimeFailpoint === void 0 ? DurableRuntimeFailpoint.layer : Layer.succeed(DurableRuntimeFailpoint)({ hit: options.runtimeFailpoint });
223
- const reconcilerLayer = options.toolReconciler ?? ToolReconciler.uncertain;
224
- const observerLayer = options.toolFailureObserver === void 0 ? Layer.succeed(CurrentToolFailureObserver)(void 0) : toolFailureObserverLayer(options.toolFailureObserver);
225
- const ports = Layer.mergeAll(threadStoreLayer, scheduleStoreLayer, nodeWakeSchedulerLayer.pipe(Layer.provideMerge(ownershipDrainLayer.pipe(Layer.provide(submissionLedgerLayer)))));
226
- return DurableAgentRuntime.layerWithServices.pipe(Layer.provideMerge(Layer.mergeAll(ports, durableRuntimeConfigLayer(options.estimateCostMicrousd))), Layer.provide(Layer.mergeAll(wakeSchedulerConfigLayer, runtimeFailpointLayer, reconcilerLayer, observerLayer)), Layer.provideMerge(infrastructure), Layer.provideMerge(nodeConfigLayer), Layer.provide(Layer.mergeAll(options.runContext ?? RunContextPreparationPassthrough, options.toolAuthorization ?? RunToolAuthorization.allowAll).pipe(Layer.provide(NodeCrypto.layer))));
227
- }));
228
- }
229
- };
230
- //#endregion
231
- //#region src/host.ts
232
- /**
233
- * Admission is not open on this host: it is shutting down (deployment §6 step 1, DEPLOY-005).
234
- * Accepted work is unaffected — only NEW admissions are refused.
235
- */
236
- var AdmissionClosed = class extends Schema.TaggedError()("AdmissionClosed", { message: Schema.String }) {};
237
- const makeHost = (bindings) => Effect.gen(function* () {
238
- const runtime = yield* DurableAgentRuntime;
239
- const config = yield* NodeDurableRuntimeConfig;
240
- const startupRecovery = yield* runtime.runRecovery;
241
- const admission = yield* Ref.make(true);
242
- yield* Effect.addFinalizer(() => Ref.set(admission, false));
243
- const requireAdmission = Ref.get(admission).pipe(Effect.flatMap((open) => open ? Effect.void : Effect.fail(AdmissionClosed.make({ message: "The host is shutting down; admission is closed." }))));
244
- const submit = (agent, input, options) => requireAdmission.pipe(Effect.andThen(runtime.submit(agent, input, options)));
245
- const runWorkers = (worker) => Effect.forEach(Array.from({ length: config.workerConcurrency }, (_, index) => index), () => worker, {
246
- concurrency: "unbounded",
247
- discard: true
248
- });
249
- const runResolvedWorkers = runWorkers(runtime.runResolvedWorker(bindings));
250
- return NodeDurableHost.of({
251
- startupRecovery,
252
- admissionOpen: Ref.get(admission),
253
- submit,
254
- awaitSettlement: runtime.awaitSettlement,
255
- observe: runtime.observe,
256
- abort: runtime.abort,
257
- explain: runtime.explain,
258
- explainThread: runtime.explainThread,
259
- verify: runtime.verify,
260
- retry: runtime.retry,
261
- wake: runtime.wake,
262
- scanObligations: runtime.scanObligations,
263
- runWorkers,
264
- runResolvedWorkers
265
- });
266
- });
267
- /**
268
- * Operational host lifecycle for the DN runtime (deployment §2/§5/§6).
269
- *
270
- * Startup gates run during Layer construction, so the service existing implies readiness:
271
- * configuration was schema-decoded, the SQLite file passed the exact-version compatibility check,
272
- * and every nonterminal Submission went through one full recovery pass BEFORE admission opened.
273
- * `startupRecovery` is the auditable evidence of that reconciliation pass.
274
- *
275
- * Shutdown runs in reverse Layer order when the owning Scope closes: `submit` starts refusing
276
- * with `AdmissionClosed` first, then the runtime Layer's ownership drain releases every claim
277
- * still held so another host can take over the lanes immediately, then the SQLite resources
278
- * close. Forced termination at any point stays safe — the durability protocol, not graceful
279
- * shutdown, provides correctness (DEPLOY-006).
280
- */
281
- var NodeDurableHost = class NodeDurableHost extends Context.Service()("@effect-agent/platform-node/NodeDurableHost") {
282
- /**
283
- * Compile typed registrations and acquire the complete host in one Layer Scope.
284
- * Node supplies Crypto; model, tool, instruction, and schema services remain required.
285
- * Startup recovery and shutdown gates are unchanged. Workers start only when the caller
286
- * runs runResolvedWorkers; this constructor never starts a background worker.
287
- */
288
- static layerRegistered(registrations, options) {
289
- return Layer.unwrap(Effect.map(compileRegistrations(registrations), (bindings) => NodeDurableHost.layerStack({
290
- ...options,
291
- bindings
292
- }))).pipe(Layer.provide(NodeCrypto.layer));
293
- }
294
- /**
295
- * Host gates over an assembled `NodeDurableRuntime` stack. Bindings must carry the exact
296
- * digests stored by submitters. Omission registers no Agents, so resolved work fails closed.
297
- */
298
- static layer = (bindings = []) => Layer.effect(NodeDurableHost)(makeHost(bindings));
299
- /** The complete DN host: `NodeDurableRuntime.layer(options)` plus the host lifecycle gates. */
300
- static layerStack(options) {
301
- const { bindings = [], ...runtimeOptions } = options;
302
- return NodeDurableHost.layer(bindings).pipe(Layer.provideMerge(NodeDurableRuntime.layer(runtimeOptions)));
303
- }
304
- };
305
- //#endregion
306
- //#region src/subscriptions.ts
307
- const passthroughSubmitAgent = (agentId) => ({ definition: {
308
- id: agentId,
309
- input: PersistedJson
310
- } });
311
- const ambiguous = () => ScheduledInputRetryable.make({ reason: "ambiguous" });
312
- const corrupt = (operation) => ScheduleStorageError.make({
313
- operation,
314
- reason: "corrupt"
315
- });
316
- /** Ordinary prepared admission through the Scope-owned Node host gate. */
317
- const nodePreparedInputAdmissionLayer = Layer.effect(PreparedInputAdmission, Effect.gen(function* () {
318
- const host = yield* NodeDurableHost;
319
- return PreparedInputAdmission.of({ submit: (envelope) => host.submit(passthroughSubmitAgent(envelope.agentId), envelope.input, {
320
- threadId: envelope.threadId,
321
- principal: envelope.deliveryPrincipal,
322
- idempotencyKey: envelope.admissionKey,
323
- definitions: envelope.definitions
324
- }).pipe(Effect.catchTags({
325
- AdmissionClosed: () => Effect.fail(ScheduledInputRetryable.make({ reason: "host-closed" })),
326
- AgentInputError: () => Effect.fail(corrupt("prepared admission input")),
327
- AdmissionConflict: () => Effect.fail(corrupt("prepared admission conflict")),
328
- DigestError: () => Effect.fail(ambiguous()),
329
- LedgerError: () => Effect.fail(ambiguous()),
330
- ThreadStoreError: () => Effect.fail(ambiguous()),
331
- ThreadNotMaterialized: () => Effect.fail(ambiguous()),
332
- AppendConflict: () => Effect.fail(ambiguous()),
333
- FenceRejected: () => Effect.fail(ambiguous()),
334
- DurableRuntimeFailpointError: () => Effect.fail(ambiguous())
335
- })) });
336
- }));
337
- const preparedFromSchedule = (envelope) => ({
338
- schemaVersion: 1,
339
- threadId: envelope.threadId,
340
- deliveryPrincipal: envelope.deliveryPrincipal,
341
- agentId: envelope.agentId,
342
- definitions: envelope.definitions,
343
- input: envelope.input,
344
- inputDigest: envelope.inputDigest,
345
- admissionKey: envelope.admissionKey,
346
- authorization: envelope.authorization
347
- });
348
- const nodeScheduledInputAdmissionLayer = Layer.effect(ScheduledInputAdmission, Effect.map(PreparedInputAdmission, (admission) => ScheduledInputAdmission.of({ submit: (envelope) => admission.submit(preparedFromSchedule(envelope)) }))).pipe(Layer.provide(nodePreparedInputAdmissionLayer));
349
- const reportPassFailure$1 = (cause) => Cause.hasInterruptsOnly(cause) ? Effect.interrupt : Effect.logWarning("Node subscription pass failed").pipe(Effect.annotateLogs({ failureTag: Option.match(Cause.findErrorOption(cause), {
350
- onNone: () => "Defect",
351
- onSome: (error) => error._tag
352
- }) }), Effect.as(false));
353
- const nodeSubscriptionDriverLayer = (limits) => Layer.effectDiscard(Effect.gen(function* () {
354
- const driver = yield* SubscriptionDriver;
355
- const store = yield* SubscriptionStore;
356
- const run = Effect.gen(function* () {
357
- while (true) {
358
- if (!(yield* driver.runDue.pipe(Effect.map((pass) => pass.failed === 0), Effect.catchCause(reportPassFailure$1)))) {
359
- yield* Effect.sleep(Duration.millis(limits.retryMillis));
360
- continue;
361
- }
362
- const deadline = yield* store.nextDeadline.pipe(Effect.exit);
363
- if (Exit.isFailure(deadline)) {
364
- yield* reportPassFailure$1(deadline.cause);
365
- yield* Effect.sleep(Duration.millis(limits.retryMillis));
366
- continue;
367
- }
368
- const nowMillis = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
369
- const delay = deadline.value === null ? limits.retryMillis : Math.max(1, Math.min(deadline.value - nowMillis, limits.retryMillis));
370
- yield* Effect.sleep(Duration.millis(delay));
371
- }
372
- });
373
- yield* Effect.forkScoped(run);
374
- }));
375
- /**
376
- * One Scope-owned subscription partition in the sole process owning its SQLite database.
377
- * Indexed polling repairs restart and lost wake state; closing the Scope interrupts the driver.
378
- */
379
- var NodeSubscriptions = class {
380
- static layer(options = {}) {
381
- const limits = options.limits ?? defaultSubscriptionLimits;
382
- const publicServices = Layer.merge(Subscriptions.layer(limits), SubscriptionIntake.layer(limits));
383
- const driver = nodeSubscriptionDriverLayer(limits).pipe(Layer.provide(SubscriptionDriver.layer(limits)));
384
- return Layer.merge(publicServices, driver).pipe(Layer.provide(nodePreparedInputAdmissionLayer), Layer.provide(NodeCrypto.layer));
385
- }
386
- };
387
- //#endregion
388
- //#region src/scheduling.ts
389
- /** One bounded hint slot for the single supported Node scheduler. Indexed polling repairs loss. */
390
- const nodeScheduleWakeLayer = Layer.effect(ScheduleWake, Effect.gen(function* () {
391
- const hints = yield* PubSub.sliding(1);
392
- const subscription = yield* PubSub.subscribe(hints);
393
- yield* Effect.addFinalizer(() => PubSub.shutdown(hints));
394
- return ScheduleWake.of({
395
- notify: PubSub.publish(hints, void 0).pipe(Effect.asVoid),
396
- await: PubSub.take(subscription)
397
- });
398
- }));
399
- const reportPassFailure = (cause) => Cause.hasInterruptsOnly(cause) ? Effect.interrupt : Effect.logWarning("Node scheduling pass failed").pipe(Effect.annotateLogs({ failureTag: Option.match(Cause.findErrorOption(cause), {
400
- onNone: () => "Defect",
401
- onSome: (error) => error._tag
402
- }) }), Effect.as(false));
403
- const nodeSchedulingDriverLayer = (limits) => Layer.effectDiscard(Effect.gen(function* () {
404
- const scheduling = yield* ScheduleDriver;
405
- const store = yield* ScheduleStore;
406
- const wake = yield* ScheduleWake;
407
- const run = Effect.gen(function* () {
408
- while (true) {
409
- const passSucceeded = yield* scheduling.runDue().pipe(Effect.map((pass) => pass.failed === 0), Effect.catchCause(reportPassFailure));
410
- const deadlineResult = passSucceeded ? yield* store.nextDeadline().pipe(Effect.result) : Result.fail(ScheduleStorageError.make({
411
- operation: "driver pass",
412
- reason: "unavailable"
413
- }));
414
- if (Result.isFailure(deadlineResult) && passSucceeded) yield* Effect.logWarning("Node scheduling deadline query failed");
415
- const nowMillis = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
416
- const deadlineDelay = Result.isSuccess(deadlineResult) && deadlineResult.success !== null ? Math.max(0, deadlineResult.success - nowMillis) : limits.recoveryPollMillis;
417
- const delay = Math.min(deadlineDelay, limits.recoveryPollMillis);
418
- yield* Effect.raceFirst(wake.await, Effect.sleep(Duration.millis(delay)));
419
- }
420
- });
421
- yield* Effect.forkScoped(run);
422
- }));
423
- /**
424
- * Optional Scope-owned Node scheduling service and driver. The caller must provide the existing
425
- * host, its SQLite ScheduleStore, and an explicit ScheduleAuthorizer.
426
- */
427
- var NodeScheduling = class {
428
- static layer(options = {}) {
429
- const limits = options.limits ?? defaultSchedulingLimits;
430
- return nodeSchedulingDriverLayer(limits).pipe(Layer.provide(ScheduleDriver.layer(limits)), Layer.merge(Scheduling.layer(limits))).pipe(Layer.provide(Layer.mergeAll(nodeScheduledInputAdmissionLayer, nodeScheduleWakeLayer, NodeCrypto.layer)));
431
- }
432
- };
433
- //#endregion
434
- export { AdmissionClosed, NodeDurableHost, NodeDurableRuntime, NodeDurableRuntimeConfig, NodeDurableRuntimeConfigValue, NodePlatformConfigError, NodeScheduling, NodeSubscriptions, NodeWakeSchedulerConfig, nodePreparedInputAdmissionLayer, nodeScheduleWakeLayer, nodeScheduledInputAdmissionLayer, nodeWakeSchedulerLayer, ownershipDrainLayer };
435
-
436
- //# sourceMappingURL=index.mjs.map
1
+ import { t as NodeWakeScheduler_exports } from "./NodeWakeScheduler.mjs";
2
+ import { t as NodeDurableAgentRuntime_exports } from "./NodeDurableAgentRuntime.mjs";
3
+ import { t as NodeDurableHost_exports } from "./NodeDurableHost.mjs";
4
+ import { t as NodeSubscriptions_exports } from "./NodeSubscriptions.mjs";
5
+ import { t as NodeScheduling_exports } from "./NodeScheduling.mjs";
6
+ export { NodeDurableAgentRuntime_exports as NodeDurableAgentRuntime, NodeDurableHost_exports as NodeDurableHost, NodeScheduling_exports as NodeScheduling, NodeSubscriptions_exports as NodeSubscriptions, NodeWakeScheduler_exports as NodeWakeScheduler };
@@ -0,0 +1,13 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ //#endregion
13
+ export { __exportAll as t };
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@effect-agent/platform-node","version":"0.1.0-beta.44","dependencies":{"@effect-agent/core":"0.1.0-beta.44","@effect-agent/engine":"0.1.0-beta.44","@effect-agent/storage-sqlite":"0.1.0-beta.44","@effect-agent/thread":"0.1.0-beta.44","@effect/platform-node":"4.0.0-rc.112","@effect/sql-sqlite-node":"4.0.0-rc.112"},"devDependencies":{"@effect-agent/capabilities":"0.1.0-beta.44","@effect/vitest":"4.0.0-rc.112","effect":"4.0.0-rc.112","typescript":"7.0.2","vite-plus":"0.3.0"},"peerDependencies":{"effect":"^4.0.0-rc.112"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"}},"description":"Class DN layer assembly for Effect Agent: the durable Node/SQLite runtime host, wake scheduler, and ownership drain.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/danieljvdm/effect-agent.git","directory":"packages/platform-node"},"files":["dist","src"],"type":"module","publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json","test":"vp test --passWithNoTests"}}
1
+ {"name":"@effect-agent/platform-node","version":"0.1.0-beta.46","dependencies":{"@effect-agent/core":"0.1.0-beta.46","@effect-agent/engine":"0.1.0-beta.46","@effect-agent/storage-sqlite":"0.1.0-beta.46","@effect-agent/thread":"0.1.0-beta.46","@effect-agent/workflow":"0.1.0-beta.46","@effect/platform-node":"4.0.0-rc.112","@effect/sql-sqlite-node":"4.0.0-rc.112"},"devDependencies":{"@effect-agent/capabilities":"0.1.0-beta.46","@effect/vitest":"4.0.0-rc.112","effect":"4.0.0-rc.112","typescript":"7.0.2","vite-plus":"0.3.0"},"peerDependencies":{"effect":"^4.0.0-rc.112"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./NodeDurableAgentRuntime":{"types":"./dist/NodeDurableAgentRuntime.d.mts","default":"./dist/NodeDurableAgentRuntime.mjs"},"./NodeDurableHost":{"types":"./dist/NodeDurableHost.d.mts","default":"./dist/NodeDurableHost.mjs"},"./NodeScheduling":{"types":"./dist/NodeScheduling.d.mts","default":"./dist/NodeScheduling.mjs"},"./NodeSubscriptions":{"types":"./dist/NodeSubscriptions.d.mts","default":"./dist/NodeSubscriptions.mjs"},"./NodeWakeScheduler":{"types":"./dist/NodeWakeScheduler.d.mts","default":"./dist/NodeWakeScheduler.mjs"},"./NodeWorkflow":{"types":"./dist/NodeWorkflow.d.mts","default":"./dist/NodeWorkflow.mjs"}},"description":"Class DN layer assembly for Effect Agent: the durable Node/SQLite runtime host, wake scheduler, and ownership drain.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/danieljvdm/effect-agent.git","directory":"packages/platform-node"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json","test":"vp test --passWithNoTests"}}