@effect-agent/platform-node 0.1.0-beta.45 → 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.
Files changed (33) hide show
  1. package/dist/NodeDurableAgentRuntime-Ceye0Exk.d.mts +170 -0
  2. package/dist/NodeDurableAgentRuntime.d.mts +2 -0
  3. package/dist/NodeDurableAgentRuntime.mjs +217 -0
  4. package/dist/NodeDurableAgentRuntime.mjs.map +1 -0
  5. package/dist/NodeDurableHost.d.mts +120 -0
  6. package/dist/NodeDurableHost.mjs +90 -0
  7. package/dist/NodeDurableHost.mjs.map +1 -0
  8. package/dist/NodeScheduling.d.mts +22 -0
  9. package/dist/NodeScheduling.mjs +59 -0
  10. package/dist/NodeScheduling.mjs.map +1 -0
  11. package/dist/NodeSubscriptions.d.mts +27 -0
  12. package/dist/NodeSubscriptions.mjs +103 -0
  13. package/dist/NodeSubscriptions.mjs.map +1 -0
  14. package/dist/NodeWakeScheduler-DWdHIhWA.d.mts +27 -0
  15. package/dist/NodeWakeScheduler.d.mts +2 -0
  16. package/dist/NodeWakeScheduler.mjs +61 -0
  17. package/dist/NodeWakeScheduler.mjs.map +1 -0
  18. package/dist/{workflow.d.mts → NodeWorkflow.d.mts} +3 -3
  19. package/dist/{workflow.mjs → NodeWorkflow.mjs} +32 -6
  20. package/dist/NodeWorkflow.mjs.map +1 -0
  21. package/dist/index.d.mts +6 -313
  22. package/dist/index.mjs +6 -444
  23. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  24. package/package.json +1 -1
  25. package/src/{layers.ts → NodeDurableAgentRuntime.ts} +24 -17
  26. package/src/{host.ts → NodeDurableHost.ts} +24 -16
  27. package/src/{scheduling.ts → NodeScheduling.ts} +7 -5
  28. package/src/{subscriptions.ts → NodeSubscriptions.ts} +17 -13
  29. package/src/{wake-scheduler.ts → NodeWakeScheduler.ts} +3 -2
  30. package/src/{workflow.ts → NodeWorkflow.ts} +52 -3
  31. package/src/index.ts +5 -5
  32. package/dist/index.mjs.map +0 -1
  33. package/dist/workflow.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,444 +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, defaultSchedulingLimits, defaultSubscriptionLimits, makeWakeSubscriptionHub } from "@effect-agent/thread";
2
- import { Cause, Context, Duration, Effect, Exit, Layer, Option, PubSub, Ref, Result, Schema, Stream } from "effect";
3
- import { CurrentToolFailureObserver, RunContextPreparationPassthrough, RunToolAuthorization, toolFailureObserverLayer } from "@effect-agent/engine";
4
- import { SqliteStorageConfig, SqliteStorageConfigValue, scheduleStoreLayer, storageFailpointLayer, submissionLedgerLayer, threadStoreLayer } from "@effect-agent/storage-sqlite";
5
- import { NodeCrypto } from "@effect/platform-node";
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 NodeDurableAgentRuntimeConfigValue = class extends Schema.Class("@effect-agent/platform-node/NodeDurableAgentRuntimeConfigValue")({
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 NodeDurableAgentRuntimeConfig = class extends Context.Service()("@effect-agent/platform-node/NodeDurableAgentRuntimeConfig") {};
96
- const decodeConfigValue = Schema.decodeUnknownEffect(NodeDurableAgentRuntimeConfigValue);
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* NodeDurableAgentRuntimeConfig;
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* NodeDurableAgentRuntimeConfig;
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* NodeDurableAgentRuntimeConfig;
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 NodeDurableAgentRuntime = class NodeDurableAgentRuntime {
210
- /** Validated configuration Layer; fails typed when the supplied options are out of bounds. */
211
- static configLayer(options) {
212
- return Layer.effect(NodeDurableAgentRuntimeConfig)(configFromOptions(options));
213
- }
214
- /** The full DN runtime stack over one SQLite file. */
215
- static layer(options) {
216
- return NodeDurableAgentRuntime.assemble(DurableAgentRuntime.layerWithServices, options);
217
- }
218
- /** Own typed executable registrations for every worker using this Node runtime. */
219
- static layerRegistered(registrations, options) {
220
- return NodeDurableAgentRuntime.assemble(DurableAgentRuntime.layerRegistered(registrations), options);
221
- }
222
- /** Construct from registrations already resolved within the enclosing application Scope. */
223
- static layerWithBindings(bindings, options) {
224
- return NodeDurableAgentRuntime.assemble(DurableAgentRuntime.layerWithBindings(bindings), options);
225
- }
226
- static assemble(runtimeLayer, options) {
227
- return Layer.unwrap(Effect.map(configFromOptions(options), (config) => {
228
- const nodeConfigLayer = Layer.succeed(NodeDurableAgentRuntimeConfig)(config);
229
- const infrastructure = Layer.mergeAll(sqliteStorageConfigLayer, storageFailpointLayer({
230
- filename: config.filename,
231
- failpoint: options.storageFailpoint
232
- }), SqliteClient.layer({ filename: config.filename }), NodeCrypto.layer);
233
- const runtimeFailpointLayer = options.runtimeFailpoint === void 0 ? DurableRuntimeFailpoint.layer : Layer.succeed(DurableRuntimeFailpoint)({ hit: options.runtimeFailpoint });
234
- const reconcilerLayer = options.toolReconciler ?? ToolReconciler.uncertain;
235
- const observerLayer = options.toolFailureObserver === void 0 ? Layer.succeed(CurrentToolFailureObserver)(void 0) : toolFailureObserverLayer(options.toolFailureObserver);
236
- const ports = Layer.mergeAll(threadStoreLayer, scheduleStoreLayer, nodeWakeSchedulerLayer.pipe(Layer.provideMerge(ownershipDrainLayer.pipe(Layer.provide(submissionLedgerLayer)))));
237
- return runtimeLayer.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))));
238
- }));
239
- }
240
- };
241
- //#endregion
242
- //#region src/host.ts
243
- /**
244
- * Admission is not open on this host: it is shutting down (deployment §6 step 1, DEPLOY-005).
245
- * Accepted work is unaffected — only NEW admissions are refused.
246
- */
247
- var AdmissionClosed = class extends Schema.TaggedError()("AdmissionClosed", { message: Schema.String }) {};
248
- const makeHost = Effect.gen(function* () {
249
- const runtime = yield* DurableAgentRuntime;
250
- const config = yield* NodeDurableAgentRuntimeConfig;
251
- const startupRecovery = yield* runtime.runRecovery;
252
- const admission = yield* Ref.make(true);
253
- yield* Effect.addFinalizer(() => Ref.set(admission, false));
254
- 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." }))));
255
- const submit = (agent, input, options) => requireAdmission.pipe(Effect.andThen(runtime.submit(agent, input, options)));
256
- const runWorkers = (worker) => Effect.forEach(Array.from({ length: config.workerConcurrency }, (_, index) => index), () => worker, {
257
- concurrency: "unbounded",
258
- discard: true
259
- });
260
- const runResolvedWorkers = runWorkers(runtime.runResolvedWorker);
261
- return NodeDurableHost.of({
262
- startupRecovery,
263
- admissionOpen: Ref.get(admission),
264
- submit,
265
- awaitSettlement: runtime.awaitSettlement,
266
- observe: runtime.observe,
267
- abort: runtime.abort,
268
- explain: runtime.explain,
269
- explainThread: runtime.explainThread,
270
- verify: runtime.verify,
271
- retry: runtime.retry,
272
- wake: runtime.wake,
273
- scanObligations: runtime.scanObligations,
274
- runWorkers,
275
- runResolvedWorkers
276
- });
277
- });
278
- /**
279
- * Operational host lifecycle for the DN runtime (deployment §2/§5/§6).
280
- *
281
- * Startup gates run during Layer construction, so the service existing implies readiness:
282
- * configuration was schema-decoded, the SQLite file passed the exact-version compatibility check,
283
- * and every nonterminal Submission went through one full recovery pass BEFORE admission opened.
284
- * `startupRecovery` is the auditable evidence of that reconciliation pass.
285
- *
286
- * Shutdown runs in reverse Layer order when the owning Scope closes: `submit` starts refusing
287
- * with `AdmissionClosed` first, then the runtime Layer's ownership drain releases every claim
288
- * still held so another host can take over the lanes immediately, then the SQLite resources
289
- * close. Forced termination at any point stays safe — the durability protocol, not graceful
290
- * shutdown, provides correctness (DEPLOY-006).
291
- */
292
- var NodeDurableHost = class NodeDurableHost extends Context.Service()("@effect-agent/platform-node/NodeDurableHost") {
293
- /**
294
- * Compile typed registrations and acquire the complete host in one Layer Scope.
295
- * Node supplies Crypto; model, tool, instruction, and schema services remain required.
296
- * Startup recovery and shutdown gates are unchanged. Workers start only when the caller
297
- * runs runResolvedWorkers; this constructor never starts a background worker.
298
- */
299
- static layerRegistered(registrations, options) {
300
- return NodeDurableHost.layer.pipe(Layer.provideMerge(NodeDurableAgentRuntime.layerRegistered(registrations, options)));
301
- }
302
- /**
303
- * Host gates over an assembled `NodeDurableAgentRuntime` stack. The runtime Layer owns
304
- * executable registrations; omission registers no Agents, so resolved work fails closed.
305
- */
306
- static layer = Layer.effect(NodeDurableHost)(makeHost);
307
- /** The complete DN host: `NodeDurableAgentRuntime.layer(options)` plus the host lifecycle gates. */
308
- static layerStack(options) {
309
- const { bindings = [], ...runtimeOptions } = options;
310
- return NodeDurableHost.layer.pipe(Layer.provideMerge(NodeDurableAgentRuntime.layerWithBindings(bindings, runtimeOptions)));
311
- }
312
- };
313
- //#endregion
314
- //#region src/subscriptions.ts
315
- const passthroughSubmitAgent = (agentId) => ({ definition: {
316
- id: agentId,
317
- input: PersistedJson
318
- } });
319
- const ambiguous = () => ScheduledInputRetryable.make({ reason: "ambiguous" });
320
- const corrupt = (operation) => ScheduleStorageError.make({
321
- operation,
322
- reason: "corrupt"
323
- });
324
- /** Ordinary prepared admission through the Scope-owned Node host gate. */
325
- const nodePreparedInputAdmissionLayer = Layer.effect(PreparedInputAdmission, Effect.gen(function* () {
326
- const host = yield* NodeDurableHost;
327
- return PreparedInputAdmission.of({ submit: (envelope) => host.submit(passthroughSubmitAgent(envelope.agentId), envelope.input, {
328
- threadId: envelope.threadId,
329
- principal: envelope.deliveryPrincipal,
330
- idempotencyKey: envelope.admissionKey,
331
- definitions: envelope.definitions
332
- }).pipe(Effect.catchTags({
333
- AdmissionClosed: () => Effect.fail(ScheduledInputRetryable.make({ reason: "host-closed" })),
334
- AgentInputError: () => Effect.fail(corrupt("prepared admission input")),
335
- AdmissionConflict: () => Effect.fail(corrupt("prepared admission conflict")),
336
- DigestError: () => Effect.fail(ambiguous()),
337
- LedgerError: () => Effect.fail(ambiguous()),
338
- ThreadStoreError: () => Effect.fail(ambiguous()),
339
- ThreadNotMaterialized: () => Effect.fail(ambiguous()),
340
- AppendConflict: () => Effect.fail(ambiguous()),
341
- FenceRejected: () => Effect.fail(ambiguous()),
342
- DurableRuntimeFailpointError: () => Effect.fail(ambiguous())
343
- })) });
344
- }));
345
- const preparedFromSchedule = (envelope) => ({
346
- schemaVersion: 1,
347
- threadId: envelope.threadId,
348
- deliveryPrincipal: envelope.deliveryPrincipal,
349
- agentId: envelope.agentId,
350
- definitions: envelope.definitions,
351
- input: envelope.input,
352
- inputDigest: envelope.inputDigest,
353
- admissionKey: envelope.admissionKey,
354
- authorization: envelope.authorization
355
- });
356
- const nodeScheduledInputAdmissionLayer = Layer.effect(ScheduledInputAdmission, Effect.map(PreparedInputAdmission, (admission) => ScheduledInputAdmission.of({ submit: (envelope) => admission.submit(preparedFromSchedule(envelope)) }))).pipe(Layer.provide(nodePreparedInputAdmissionLayer));
357
- const reportPassFailure$1 = (cause) => Cause.hasInterruptsOnly(cause) ? Effect.interrupt : Effect.logWarning("Node subscription pass failed").pipe(Effect.annotateLogs({ failureTag: Option.match(Cause.findErrorOption(cause), {
358
- onNone: () => "Defect",
359
- onSome: (error) => error._tag
360
- }) }), Effect.as(false));
361
- const nodeSubscriptionDriverLayer = (limits) => Layer.effectDiscard(Effect.gen(function* () {
362
- const driver = yield* SubscriptionDriver;
363
- const store = yield* SubscriptionStore;
364
- const run = Effect.gen(function* () {
365
- while (true) {
366
- if (!(yield* driver.runDue.pipe(Effect.map((pass) => pass.failed === 0), Effect.catchCause(reportPassFailure$1)))) {
367
- yield* Effect.sleep(Duration.millis(limits.retryMillis));
368
- continue;
369
- }
370
- const deadline = yield* store.nextDeadline.pipe(Effect.exit);
371
- if (Exit.isFailure(deadline)) {
372
- yield* reportPassFailure$1(deadline.cause);
373
- yield* Effect.sleep(Duration.millis(limits.retryMillis));
374
- continue;
375
- }
376
- const nowMillis = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
377
- const delay = deadline.value === null ? limits.retryMillis : Math.max(1, Math.min(deadline.value - nowMillis, limits.retryMillis));
378
- yield* Effect.sleep(Duration.millis(delay));
379
- }
380
- });
381
- yield* Effect.forkScoped(run);
382
- }));
383
- /**
384
- * One Scope-owned subscription partition in the sole process owning its SQLite database.
385
- * Indexed polling repairs restart and lost wake state; closing the Scope interrupts the driver.
386
- */
387
- var NodeSubscriptions = class {
388
- static layer(options = {}) {
389
- const limits = options.limits ?? defaultSubscriptionLimits;
390
- const publicServices = Layer.merge(Subscriptions.layer(limits), SubscriptionIntake.layer(limits));
391
- const driver = nodeSubscriptionDriverLayer(limits).pipe(Layer.provide(SubscriptionDriver.layer(limits)));
392
- return Layer.merge(publicServices, driver).pipe(Layer.provide(nodePreparedInputAdmissionLayer), Layer.provide(NodeCrypto.layer));
393
- }
394
- };
395
- //#endregion
396
- //#region src/scheduling.ts
397
- /** One bounded hint slot for the single supported Node scheduler. Indexed polling repairs loss. */
398
- const nodeScheduleWakeLayer = Layer.effect(ScheduleWake, Effect.gen(function* () {
399
- const hints = yield* PubSub.sliding(1);
400
- const subscription = yield* PubSub.subscribe(hints);
401
- yield* Effect.addFinalizer(() => PubSub.shutdown(hints));
402
- return ScheduleWake.of({
403
- notify: PubSub.publish(hints, void 0).pipe(Effect.asVoid),
404
- await: PubSub.take(subscription)
405
- });
406
- }));
407
- const reportPassFailure = (cause) => Cause.hasInterruptsOnly(cause) ? Effect.interrupt : Effect.logWarning("Node scheduling pass failed").pipe(Effect.annotateLogs({ failureTag: Option.match(Cause.findErrorOption(cause), {
408
- onNone: () => "Defect",
409
- onSome: (error) => error._tag
410
- }) }), Effect.as(false));
411
- const nodeSchedulingDriverLayer = (limits) => Layer.effectDiscard(Effect.gen(function* () {
412
- const scheduling = yield* ScheduleDriver;
413
- const store = yield* ScheduleStore;
414
- const wake = yield* ScheduleWake;
415
- const run = Effect.gen(function* () {
416
- while (true) {
417
- const passSucceeded = yield* scheduling.runDue().pipe(Effect.map((pass) => pass.failed === 0), Effect.catchCause(reportPassFailure));
418
- const deadlineResult = passSucceeded ? yield* store.nextDeadline().pipe(Effect.result) : Result.fail(ScheduleStorageError.make({
419
- operation: "driver pass",
420
- reason: "unavailable"
421
- }));
422
- if (Result.isFailure(deadlineResult) && passSucceeded) yield* Effect.logWarning("Node scheduling deadline query failed");
423
- const nowMillis = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
424
- const deadlineDelay = Result.isSuccess(deadlineResult) && deadlineResult.success !== null ? Math.max(0, deadlineResult.success - nowMillis) : limits.recoveryPollMillis;
425
- const delay = Math.min(deadlineDelay, limits.recoveryPollMillis);
426
- yield* Effect.raceFirst(wake.await, Effect.sleep(Duration.millis(delay)));
427
- }
428
- });
429
- yield* Effect.forkScoped(run);
430
- }));
431
- /**
432
- * Optional Scope-owned Node scheduling service and driver. The caller must provide the existing
433
- * host, its SQLite ScheduleStore, and an explicit ScheduleAuthorizer.
434
- */
435
- var NodeScheduling = class {
436
- static layer(options = {}) {
437
- const limits = options.limits ?? defaultSchedulingLimits;
438
- return nodeSchedulingDriverLayer(limits).pipe(Layer.provide(ScheduleDriver.layer(limits)), Layer.merge(Scheduling.layer(limits))).pipe(Layer.provide(Layer.mergeAll(nodeScheduledInputAdmissionLayer, nodeScheduleWakeLayer, NodeCrypto.layer)));
439
- }
440
- };
441
- //#endregion
442
- export { AdmissionClosed, NodeDurableAgentRuntime, NodeDurableAgentRuntimeConfig, NodeDurableAgentRuntimeConfigValue, NodeDurableHost, NodePlatformConfigError, NodeScheduling, NodeSubscriptions, NodeWakeSchedulerConfig, nodePreparedInputAdmissionLayer, nodeScheduleWakeLayer, nodeScheduledInputAdmissionLayer, nodeWakeSchedulerLayer, ownershipDrainLayer };
443
-
444
- //# 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.45","dependencies":{"@effect-agent/core":"0.1.0-beta.45","@effect-agent/engine":"0.1.0-beta.45","@effect-agent/storage-sqlite":"0.1.0-beta.45","@effect-agent/thread":"0.1.0-beta.45","@effect-agent/workflow":"0.1.0-beta.45","@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.45","@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"},"./workflow":{"types":"./dist/workflow.d.mts","default":"./dist/workflow.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"}}
@@ -1,4 +1,4 @@
1
- import type { SubmissionId } from "@effect-agent/core";
1
+ import { type SubmissionId } from "@effect-agent/core/Identifiers";
2
2
  import {
3
3
  CurrentToolFailureObserver,
4
4
  RunContextPreparationPassthrough,
@@ -7,40 +7,47 @@ import {
7
7
  type RunContextPreparation,
8
8
  type RunCostEstimator,
9
9
  type RunToolFailureObserver,
10
- } from "@effect-agent/engine";
10
+ } from "@effect-agent/engine/RunOptions";
11
+ import { scheduleStoreLayer } from "@effect-agent/storage-sqlite/SqliteScheduleStore";
11
12
  import {
12
- threadStoreLayer,
13
- scheduleStoreLayer,
14
13
  SqliteStorageConfig,
15
14
  SqliteStorageConfigValue,
15
+ } from "@effect-agent/storage-sqlite/SqliteStorageConfig";
16
+ import { type SqliteStorageFailpointHandler } from "@effect-agent/storage-sqlite/SqliteStorageFailpoint";
17
+ import { submissionLedgerLayer } from "@effect-agent/storage-sqlite/SqliteSubmissionLedger";
18
+ import {
19
+ threadStoreLayer,
16
20
  storageFailpointLayer,
17
- submissionLedgerLayer,
18
- type SqliteStorageFailpointHandler,
19
21
  type SqliteStorageInitializationError,
20
- } from "@effect-agent/storage-sqlite";
22
+ } from "@effect-agent/storage-sqlite/SqliteThreadStore";
21
23
  import {
22
24
  type AgentRegistration,
23
25
  type ResolvedBinding,
24
- type ScheduleStore,
25
- type ThreadStore,
26
- type WakeScheduler,
27
- DEFAULT_OWNERSHIP_LEASE_DURATION,
28
- DeploymentId,
26
+ } from "@effect-agent/thread/AgentRegistration";
27
+ import {
29
28
  DurableAgentRuntime,
30
29
  DurableRuntimeConfig,
30
+ } from "@effect-agent/thread/DurableAgentRuntime";
31
+ import {
31
32
  DurableRuntimeFailpoint,
32
- ProducerId,
33
+ type DurableRuntimeFailpointHandler,
34
+ } from "@effect-agent/thread/DurableFailpoint";
35
+ import { DeploymentId, ProducerId } from "@effect-agent/thread/Records";
36
+ import { type ScheduleStore } from "@effect-agent/thread/Schedule";
37
+ import {
38
+ DEFAULT_OWNERSHIP_LEASE_DURATION,
33
39
  ReleaseOwnershipRequest,
34
40
  SubmissionLedger,
35
- ToolReconciler,
36
- type DurableRuntimeFailpointHandler,
37
41
  type OwnershipToken,
38
- } from "@effect-agent/thread";
42
+ } from "@effect-agent/thread/SubmissionLedger";
43
+ import { type ThreadStore } from "@effect-agent/thread/ThreadStore";
44
+ import { ToolReconciler } from "@effect-agent/thread/ToolReconciler";
45
+ import { type WakeScheduler } from "@effect-agent/thread/WakeScheduler";
39
46
  import { NodeCrypto } from "@effect/platform-node";
40
47
  import { SqliteClient } from "@effect/sql-sqlite-node";
41
48
  import { Context, type Crypto, Duration, Effect, Layer, Ref, Schema } from "effect";
42
49
 
43
- import { NodeWakeSchedulerConfig, nodeWakeSchedulerLayer } from "./wake-scheduler.ts";
50
+ import { NodeWakeSchedulerConfig, nodeWakeSchedulerLayer } from "./NodeWakeScheduler.ts";
44
51
 
45
52
  const PositiveMillis = Schema.Int.check(Schema.isGreaterThan(0));
46
53
  const NonNegativeMillis = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
@@ -1,16 +1,20 @@
1
- import type { ThreadId, SubmissionId } from "@effect-agent/core";
1
+ import { type ThreadId, type SubmissionId } from "@effect-agent/core/Identifiers";
2
+ import {
3
+ type IntegrityReport,
4
+ type ObligationReport,
5
+ type ObligationThresholds,
6
+ type RecoveryExplanation,
7
+ type RetryCommand,
8
+ } from "@effect-agent/thread/Admin";
2
9
  import {
3
10
  type AgentRegistration,
11
+ type ResolvedBinding,
12
+ type DurableBindingFailure,
13
+ } from "@effect-agent/thread/AgentRegistration";
14
+ import {
4
15
  DurableAgentRuntime,
5
- type AbortCommand,
6
- type AbortIntent,
7
- type CanonicalRecordEnvelope,
8
- type ThreadNotMaterialized,
9
- type ThreadStoreError,
10
16
  type DurableAbortFailure,
11
17
  type DurableAwaitFailure,
12
- type ResolvedBinding,
13
- type DurableBindingFailure,
14
18
  type DurableExplainFailure,
15
19
  type DurableObserveOptions,
16
20
  type DurableObligationFailure,
@@ -20,16 +24,20 @@ import {
20
24
  type DurableSubmitOptions,
21
25
  type DurableVerifyFailure,
22
26
  type DurableWorkerFailure,
23
- type IntegrityReport,
24
- type ObligationReport,
25
- type ObligationThresholds,
26
- type OperationDenied,
27
27
  type Receipt,
28
- type RecoveryExplanation,
29
28
  type RecoveryReport,
30
- type RetryCommand,
29
+ } from "@effect-agent/thread/DurableAgentRuntime";
30
+ import { type OperationDenied } from "@effect-agent/thread/OperationAuthorizer";
31
+ import { type CanonicalRecordEnvelope } from "@effect-agent/thread/Records";
32
+ import {
33
+ type AbortCommand,
34
+ type AbortIntent,
31
35
  type Settlement,
32
- } from "@effect-agent/thread";
36
+ } from "@effect-agent/thread/SubmissionLedger";
37
+ import {
38
+ type ThreadNotMaterialized,
39
+ type ThreadStoreError,
40
+ } from "@effect-agent/thread/ThreadStore";
33
41
  import { type Stream, Context, type Crypto, Effect, Layer, Ref, Schema } from "effect";
34
42
 
35
43
  import {
@@ -38,7 +46,7 @@ import {
38
46
  type NodeDurableAgentRuntimeInitializationError,
39
47
  type NodeDurableAgentRuntimeOptions,
40
48
  type NodeDurableAgentRuntimeServices,
41
- } from "./layers.ts";
49
+ } from "./NodeDurableAgentRuntime.ts";
42
50
 
43
51
  /**
44
52
  * Admission is not open on this host: it is shutting down (deployment §6 step 1, DEPLOY-005).
@@ -1,20 +1,22 @@
1
1
  import {
2
- type ScheduleProcessFailure,
3
2
  type ScheduleAuthorizer,
4
3
  type SchedulingLimits,
5
4
  ScheduleStorageError,
6
5
  ScheduleStore,
7
6
  type ScheduleValidationError,
8
7
  ScheduleWake,
8
+ defaultSchedulingLimits,
9
+ } from "@effect-agent/thread/Schedule";
10
+ import {
11
+ type ScheduleProcessFailure,
9
12
  Scheduling,
10
13
  ScheduleDriver,
11
- defaultSchedulingLimits,
12
- } from "@effect-agent/thread";
14
+ } from "@effect-agent/thread/Scheduling";
13
15
  import { NodeCrypto } from "@effect/platform-node";
14
16
  import { Cause, Duration, Effect, Layer, Option, PubSub, Result } from "effect";
15
17
 
16
- import type { NodeDurableHost } from "./host.ts";
17
- import { nodeScheduledInputAdmissionLayer } from "./subscriptions.ts";
18
+ import type { NodeDurableHost } from "./NodeDurableHost.ts";
19
+ import { nodeScheduledInputAdmissionLayer } from "./NodeSubscriptions.ts";
18
20
 
19
21
  /** One bounded hint slot for the single supported Node scheduler. Indexed polling repairs loss. */
20
22
  export const nodeScheduleWakeLayer: Layer.Layer<ScheduleWake> = Layer.effect(