@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.
@@ -0,0 +1,90 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { NodeDurableAgentRuntime, NodeDurableAgentRuntimeConfig } from "./NodeDurableAgentRuntime.mjs";
3
+ import "@effect-agent/core/Identifiers";
4
+ import "@effect-agent/thread/AgentRegistration";
5
+ import { DurableAgentRuntime } from "@effect-agent/thread/DurableAgentRuntime";
6
+ import "@effect-agent/thread/Records";
7
+ import "@effect-agent/thread/SubmissionLedger";
8
+ import "@effect-agent/thread/ThreadStore";
9
+ import { Context, Effect, Layer, Ref, Schema } from "effect";
10
+ import "@effect-agent/thread/Admin";
11
+ import "@effect-agent/thread/OperationAuthorizer";
12
+ //#region src/NodeDurableHost.ts
13
+ var NodeDurableHost_exports = /* @__PURE__ */ __exportAll({
14
+ AdmissionClosed: () => AdmissionClosed,
15
+ NodeDurableHost: () => NodeDurableHost
16
+ });
17
+ /**
18
+ * Admission is not open on this host: it is shutting down (deployment §6 step 1, DEPLOY-005).
19
+ * Accepted work is unaffected — only NEW admissions are refused.
20
+ */
21
+ var AdmissionClosed = class extends Schema.TaggedError()("AdmissionClosed", { message: Schema.String }) {};
22
+ const makeHost = Effect.gen(function* () {
23
+ const runtime = yield* DurableAgentRuntime;
24
+ const config = yield* NodeDurableAgentRuntimeConfig;
25
+ const startupRecovery = yield* runtime.runRecovery;
26
+ const admission = yield* Ref.make(true);
27
+ yield* Effect.addFinalizer(() => Ref.set(admission, false));
28
+ 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." }))));
29
+ const submit = (agent, input, options) => requireAdmission.pipe(Effect.andThen(runtime.submit(agent, input, options)));
30
+ const runWorkers = (worker) => Effect.forEach(Array.from({ length: config.workerConcurrency }, (_, index) => index), () => worker, {
31
+ concurrency: "unbounded",
32
+ discard: true
33
+ });
34
+ const runResolvedWorkers = runWorkers(runtime.runResolvedWorker);
35
+ return NodeDurableHost.of({
36
+ startupRecovery,
37
+ admissionOpen: Ref.get(admission),
38
+ submit,
39
+ awaitSettlement: runtime.awaitSettlement,
40
+ observe: runtime.observe,
41
+ abort: runtime.abort,
42
+ explain: runtime.explain,
43
+ explainThread: runtime.explainThread,
44
+ verify: runtime.verify,
45
+ retry: runtime.retry,
46
+ wake: runtime.wake,
47
+ scanObligations: runtime.scanObligations,
48
+ runWorkers,
49
+ runResolvedWorkers
50
+ });
51
+ });
52
+ /**
53
+ * Operational host lifecycle for the DN runtime (deployment §2/§5/§6).
54
+ *
55
+ * Startup gates run during Layer construction, so the service existing implies readiness:
56
+ * configuration was schema-decoded, the SQLite file passed the exact-version compatibility check,
57
+ * and every nonterminal Submission went through one full recovery pass BEFORE admission opened.
58
+ * `startupRecovery` is the auditable evidence of that reconciliation pass.
59
+ *
60
+ * Shutdown runs in reverse Layer order when the owning Scope closes: `submit` starts refusing
61
+ * with `AdmissionClosed` first, then the runtime Layer's ownership drain releases every claim
62
+ * still held so another host can take over the lanes immediately, then the SQLite resources
63
+ * close. Forced termination at any point stays safe — the durability protocol, not graceful
64
+ * shutdown, provides correctness (DEPLOY-006).
65
+ */
66
+ var NodeDurableHost = class NodeDurableHost extends Context.Service()("@effect-agent/platform-node/NodeDurableHost") {
67
+ /**
68
+ * Compile typed registrations and acquire the complete host in one Layer Scope.
69
+ * Node supplies Crypto; model, tool, instruction, and schema services remain required.
70
+ * Startup recovery and shutdown gates are unchanged. Workers start only when the caller
71
+ * runs runResolvedWorkers; this constructor never starts a background worker.
72
+ */
73
+ static layerRegistered(registrations, options) {
74
+ return NodeDurableHost.layer.pipe(Layer.provideMerge(NodeDurableAgentRuntime.layerRegistered(registrations, options)));
75
+ }
76
+ /**
77
+ * Host gates over an assembled `NodeDurableAgentRuntime` stack. The runtime Layer owns
78
+ * executable registrations; omission registers no Agents, so resolved work fails closed.
79
+ */
80
+ static layer = Layer.effect(NodeDurableHost)(makeHost);
81
+ /** The complete DN host: `NodeDurableAgentRuntime.layer(options)` plus the host lifecycle gates. */
82
+ static layerStack(options) {
83
+ const { bindings = [], ...runtimeOptions } = options;
84
+ return NodeDurableHost.layer.pipe(Layer.provideMerge(NodeDurableAgentRuntime.layerWithBindings(bindings, runtimeOptions)));
85
+ }
86
+ };
87
+ //#endregion
88
+ export { AdmissionClosed, NodeDurableHost, NodeDurableHost_exports as t };
89
+
90
+ //# sourceMappingURL=NodeDurableHost.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NodeDurableHost.mjs","names":[],"sources":["../src/NodeDurableHost.ts"],"sourcesContent":["import { type ThreadId, type SubmissionId } from \"@effect-agent/core/Identifiers\";\nimport {\n type IntegrityReport,\n type ObligationReport,\n type ObligationThresholds,\n type RecoveryExplanation,\n type RetryCommand,\n} from \"@effect-agent/thread/Admin\";\nimport {\n type AgentRegistration,\n type ResolvedBinding,\n type DurableBindingFailure,\n} from \"@effect-agent/thread/AgentRegistration\";\nimport {\n DurableAgentRuntime,\n type DurableAbortFailure,\n type DurableAwaitFailure,\n type DurableExplainFailure,\n type DurableObserveOptions,\n type DurableObligationFailure,\n type DurableRetryFailure,\n type DurableSubmitAgent,\n type DurableSubmitFailure,\n type DurableSubmitOptions,\n type DurableVerifyFailure,\n type DurableWorkerFailure,\n type Receipt,\n type RecoveryReport,\n} from \"@effect-agent/thread/DurableAgentRuntime\";\nimport { type OperationDenied } from \"@effect-agent/thread/OperationAuthorizer\";\nimport { type CanonicalRecordEnvelope } from \"@effect-agent/thread/Records\";\nimport {\n type AbortCommand,\n type AbortIntent,\n type Settlement,\n} from \"@effect-agent/thread/SubmissionLedger\";\nimport {\n type ThreadNotMaterialized,\n type ThreadStoreError,\n} from \"@effect-agent/thread/ThreadStore\";\nimport { type Stream, Context, type Crypto, Effect, Layer, Ref, Schema } from \"effect\";\n\nimport {\n NodeDurableAgentRuntime,\n NodeDurableAgentRuntimeConfig,\n type NodeDurableAgentRuntimeInitializationError,\n type NodeDurableAgentRuntimeOptions,\n type NodeDurableAgentRuntimeServices,\n} from \"./NodeDurableAgentRuntime.ts\";\n\n/**\n * Admission is not open on this host: it is shutting down (deployment §6 step 1, DEPLOY-005).\n * Accepted work is unaffected — only NEW admissions are refused.\n */\nexport class AdmissionClosed extends Schema.TaggedError<AdmissionClosed>()(\"AdmissionClosed\", {\n message: Schema.String,\n}) {}\n\nconst makeHost = Effect.gen(function* () {\n const runtime = yield* DurableAgentRuntime;\n const config = yield* NodeDurableAgentRuntimeConfig;\n\n // Startup gate (deployment §5, plan §host): configuration decoding and storage compatibility\n // already gated this Layer's dependencies; the last gate before admission opens is recovering\n // EVERY nonterminal Submission. Work needing a live Agent Binding is reported `deferred` and\n // stays a visible obligation for `runWorkers`; lanes durably blocked on an Unknown Outcome are\n // reported `unknown` and wait for the authorized `resolveUnknown` path (DUR-017) — they consume\n // no worker permit while the settlement obligation stays owed.\n const startupRecovery = yield* runtime.runRecovery;\n\n const admission = yield* Ref.make(true);\n\n // Shutdown step 1 (DEPLOY-005): admission closes before the ownership drain in the runtime\n // Layer below releases claims and before the stores close in reverse acquisition order.\n yield* Effect.addFinalizer(() => Ref.set(admission, false));\n\n const requireAdmission: Effect.Effect<void, AdmissionClosed> = Ref.get(admission).pipe(\n Effect.flatMap((open) =>\n open\n ? Effect.void\n : Effect.fail(\n AdmissionClosed.make({ message: \"The host is shutting down; admission is closed.\" }),\n ),\n ),\n );\n\n const submit = <InputSchema extends Schema.Top>(\n agent: DurableSubmitAgent<InputSchema>,\n input: InputSchema[\"Type\"],\n options: DurableSubmitOptions,\n ): Effect.Effect<\n Receipt,\n AdmissionClosed | DurableSubmitFailure,\n InputSchema[\"EncodingServices\"]\n > => requireAdmission.pipe(Effect.andThen(runtime.submit(agent, input, options)));\n\n const runWorkers = <A, E, R>(worker: Effect.Effect<A, E, R>): Effect.Effect<void, E, R> =>\n Effect.forEach(\n Array.from({ length: config.workerConcurrency }, (_, index) => index),\n () => worker,\n { concurrency: \"unbounded\", discard: true },\n );\n\n // S2 multi-binding pool: every claimed head resolves its exact registered Binding through\n // the host's exact registrations, so one bounded\n // pool serves parent and child lanes — the spec §12 smallest-pool suspension/wakeup proof\n // runs `workerConcurrency: 1` over exactly this loop.\n const runResolvedWorkers = runWorkers(runtime.runResolvedWorker);\n\n return NodeDurableHost.of({\n startupRecovery,\n admissionOpen: Ref.get(admission),\n submit,\n awaitSettlement: runtime.awaitSettlement,\n observe: runtime.observe,\n abort: runtime.abort,\n explain: runtime.explain,\n explainThread: runtime.explainThread,\n verify: runtime.verify,\n retry: runtime.retry,\n wake: runtime.wake,\n scanObligations: runtime.scanObligations,\n runWorkers,\n runResolvedWorkers,\n });\n});\n\n/**\n * Operational host lifecycle for the DN runtime (deployment §2/§5/§6).\n *\n * Startup gates run during Layer construction, so the service existing implies readiness:\n * configuration was schema-decoded, the SQLite file passed the exact-version compatibility check,\n * and every nonterminal Submission went through one full recovery pass BEFORE admission opened.\n * `startupRecovery` is the auditable evidence of that reconciliation pass.\n *\n * Shutdown runs in reverse Layer order when the owning Scope closes: `submit` starts refusing\n * with `AdmissionClosed` first, then the runtime Layer's ownership drain releases every claim\n * still held so another host can take over the lanes immediately, then the SQLite resources\n * close. Forced termination at any point stays safe — the durability protocol, not graceful\n * shutdown, provides correctness (DEPLOY-006).\n */\nexport class NodeDurableHost extends Context.Service<\n NodeDurableHost,\n {\n /**\n * The recovery decisions executed (or deferred) by this host's startup reconciliation.\n * Reports with the `unknown` disposition identify lanes blocked on Unknown Outcomes that\n * only the authorized DUR-017 resolution path can release.\n */\n readonly startupRecovery: ReadonlyArray<RecoveryReport>;\n /** Admission-role readiness (deployment §7): true until shutdown begins. */\n readonly admissionOpen: Effect.Effect<boolean>;\n /** `DurableAgentRuntime.submit` behind the host admission gate. */\n readonly submit: <InputSchema extends Schema.Top>(\n agent: DurableSubmitAgent<InputSchema>,\n input: InputSchema[\"Type\"],\n options: DurableSubmitOptions,\n ) => Effect.Effect<\n Receipt,\n AdmissionClosed | DurableSubmitFailure,\n InputSchema[\"EncodingServices\"]\n >;\n readonly awaitSettlement: (receipt: Receipt) => Effect.Effect<Settlement, DurableAwaitFailure>;\n readonly observe: (\n receipt: Receipt,\n options?: DurableObserveOptions,\n ) => Stream.Stream<\n CanonicalRecordEnvelope,\n ThreadStoreError | ThreadNotMaterialized | OperationDenied\n >;\n readonly abort: (command: AbortCommand) => Effect.Effect<AbortIntent, DurableAbortFailure>;\n /** `DurableAgentRuntime.explain` — read-only recovery explanation of one Submission (P7). */\n readonly explain: (\n submissionId: SubmissionId,\n ) => Effect.Effect<RecoveryExplanation, DurableExplainFailure>;\n /** `DurableAgentRuntime.explainThread` — explain every nonterminal lane member. */\n readonly explainThread: (\n threadId: ThreadId,\n ) => Effect.Effect<ReadonlyArray<RecoveryExplanation>, DurableExplainFailure>;\n /** `DurableAgentRuntime.verify` — read-only integrity checks, never a repair (P7). */\n readonly verify: (threadId: ThreadId) => Effect.Effect<IntegrityReport, DurableVerifyFailure>;\n /** `DurableAgentRuntime.retry` — audited single-Submission re-drive with typed refusals. */\n readonly retry: (command: RetryCommand) => Effect.Effect<RecoveryReport, DurableRetryFailure>;\n /** `DurableAgentRuntime.wake` — the documented operator liveness nudge for one lane. */\n readonly wake: (threadId: ThreadId) => Effect.Effect<void, OperationDenied>;\n /** `DurableAgentRuntime.scanObligations` — the scan-based DUR-017/OPS-001 report. */\n readonly scanObligations: (\n thresholds: ObligationThresholds,\n ) => Effect.Effect<ObligationReport, DurableObligationFailure>;\n /**\n * Run `workerConcurrency` copies of the given worker effect (typically\n * `DurableAgentRuntime.runWorker(agent)`) until the caller's Scope interrupts them. The\n * bound is the validated finite configuration value; the host never forks daemon fibers.\n */\n readonly runWorkers: <A, E, R>(worker: Effect.Effect<A, E, R>) => Effect.Effect<void, E, R>;\n /**\n * Run `workerConcurrency` copies of `DurableAgentRuntime.runResolvedWorker` over the host's\n * registered Bindings (S2): every claimed head resolves its exact stored Binding before any\n * code runs (SUB-023), so one bounded pool serves parent and attached-child lanes.\n */\n readonly runResolvedWorkers: Effect.Effect<void, DurableWorkerFailure | DurableBindingFailure>;\n }\n>()(\"@effect-agent/platform-node/NodeDurableHost\") {\n /**\n * Compile typed registrations and acquire the complete host in one Layer Scope.\n * Node supplies Crypto; model, tool, instruction, and schema services remain required.\n * Startup recovery and shutdown gates are unchanged. Workers start only when the caller\n * runs runResolvedWorkers; this constructor never starts a background worker.\n */\n static layerRegistered<\n const Entries extends ReadonlyArray<AgentRegistration>,\n ContextError = never,\n ContextRequirements = never,\n AuthorizationError = never,\n AuthorizationRequirements = never,\n >(\n registrations: Entries,\n options: NodeDurableAgentRuntimeOptions<\n ContextError,\n ContextRequirements,\n AuthorizationError,\n AuthorizationRequirements\n >,\n ) {\n return NodeDurableHost.layer.pipe(\n Layer.provideMerge(NodeDurableAgentRuntime.layerRegistered(registrations, options)),\n );\n }\n\n /**\n * Host gates over an assembled `NodeDurableAgentRuntime` stack. The runtime Layer owns\n * executable registrations; omission registers no Agents, so resolved work fails closed.\n */\n static readonly layer: Layer.Layer<\n NodeDurableHost,\n DurableWorkerFailure,\n DurableAgentRuntime | NodeDurableAgentRuntimeConfig\n > = Layer.effect(NodeDurableHost)(makeHost);\n\n /** The complete DN host: `NodeDurableAgentRuntime.layer(options)` plus the host lifecycle gates. */\n static layerStack<\n ContextError = never,\n ContextRequirements = never,\n AuthorizationError = never,\n AuthorizationRequirements = never,\n >(\n options: NodeDurableAgentRuntimeOptions<\n ContextError,\n ContextRequirements,\n AuthorizationError,\n AuthorizationRequirements\n > & { readonly bindings?: ReadonlyArray<ResolvedBinding> },\n ): Layer.Layer<\n NodeDurableHost | NodeDurableAgentRuntimeServices,\n | DurableWorkerFailure\n | NodeDurableAgentRuntimeInitializationError\n | ContextError\n | AuthorizationError,\n Exclude<ContextRequirements | AuthorizationRequirements, Crypto.Crypto>\n > {\n const { bindings = [], ...runtimeOptions } = options;\n\n return NodeDurableHost.layer.pipe(\n Layer.provideMerge(NodeDurableAgentRuntime.layerWithBindings(bindings, runtimeOptions)),\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAsDA,IAAa,kBAAb,cAAqC,OAAO,YAA6B,CAAC,CAAC,mBAAmB,EAC5F,SAAS,OAAO,OAClB,CAAC,CAAC,CAAC,CAAC;AAEJ,MAAM,WAAW,OAAO,IAAI,aAAa;CACvC,MAAM,UAAU,OAAO;CACvB,MAAM,SAAS,OAAO;CAQtB,MAAM,kBAAkB,OAAO,QAAQ;CAEvC,MAAM,YAAY,OAAO,IAAI,KAAK,IAAI;CAItC,OAAO,OAAO,mBAAmB,IAAI,IAAI,WAAW,KAAK,CAAC;CAE1D,MAAM,mBAAyD,IAAI,IAAI,SAAS,CAAC,CAAC,KAChF,OAAO,SAAS,SACd,OACI,OAAO,OACP,OAAO,KACL,gBAAgB,KAAK,EAAE,SAAS,kDAAkD,CAAC,CACrF,CACN,CACF;CAEA,MAAM,UACJ,OACA,OACA,YAKG,iBAAiB,KAAK,OAAO,QAAQ,QAAQ,OAAO,OAAO,OAAO,OAAO,CAAC,CAAC;CAEhF,MAAM,cAAuB,WAC3B,OAAO,QACL,MAAM,KAAK,EAAE,QAAQ,OAAO,kBAAkB,IAAI,GAAG,UAAU,KAAK,SAC9D,QACN;EAAE,aAAa;EAAa,SAAS;CAAK,CAC5C;CAMF,MAAM,qBAAqB,WAAW,QAAQ,iBAAiB;CAE/D,OAAO,gBAAgB,GAAG;EACxB;EACA,eAAe,IAAI,IAAI,SAAS;EAChC;EACA,iBAAiB,QAAQ;EACzB,SAAS,QAAQ;EACjB,OAAO,QAAQ;EACf,SAAS,QAAQ;EACjB,eAAe,QAAQ;EACvB,QAAQ,QAAQ;EAChB,OAAO,QAAQ;EACf,MAAM,QAAQ;EACd,iBAAiB,QAAQ;EACzB;EACA;CACF,CAAC;AACH,CAAC;;;;;;;;;;;;;;;AAgBD,IAAa,kBAAb,MAAa,wBAAwB,QAAQ,QA6D3C,CAAC,CAAC,6CAA6C,CAAC,CAAC;;;;;;;CAOjD,OAAO,gBAOL,eACA,SAMA;EACA,OAAO,gBAAgB,MAAM,KAC3B,MAAM,aAAa,wBAAwB,gBAAgB,eAAe,OAAO,CAAC,CACpF;CACF;;;;;CAMA,OAAgB,QAIZ,MAAM,OAAO,eAAe,CAAC,CAAC,QAAQ;;CAG1C,OAAO,WAML,SAaA;EACA,MAAM,EAAE,WAAW,CAAC,GAAG,GAAG,mBAAmB;EAE7C,OAAO,gBAAgB,MAAM,KAC3B,MAAM,aAAa,wBAAwB,kBAAkB,UAAU,cAAc,CAAC,CACxF;CACF;AACF"}
@@ -0,0 +1,22 @@
1
+ import { NodeDurableHost } from "./NodeDurableHost.mjs";
2
+ import { ScheduleAuthorizer, ScheduleStore, ScheduleValidationError, ScheduleWake, SchedulingLimits } from "@effect-agent/thread/Schedule";
3
+ import { Layer } from "effect";
4
+ import { Scheduling } from "@effect-agent/thread/Scheduling";
5
+ declare namespace NodeScheduling_d_exports {
6
+ export { NodeScheduling, NodeSchedulingOptions, nodeScheduleWakeLayer };
7
+ }
8
+ /** One bounded hint slot for the single supported Node scheduler. Indexed polling repairs loss. */
9
+ declare const nodeScheduleWakeLayer: Layer.Layer<ScheduleWake>;
10
+ interface NodeSchedulingOptions {
11
+ readonly limits?: SchedulingLimits | undefined;
12
+ }
13
+ /**
14
+ * Optional Scope-owned Node scheduling service and driver. The caller must provide the existing
15
+ * host, its SQLite ScheduleStore, and an explicit ScheduleAuthorizer.
16
+ */
17
+ declare class NodeScheduling {
18
+ static layer(options?: NodeSchedulingOptions): Layer.Layer<Scheduling, ScheduleValidationError, NodeDurableHost | ScheduleStore | ScheduleAuthorizer>;
19
+ }
20
+ //#endregion
21
+ export { NodeScheduling, NodeSchedulingOptions, nodeScheduleWakeLayer, NodeScheduling_d_exports as t };
22
+ //# sourceMappingURL=NodeScheduling.d.mts.map
@@ -0,0 +1,59 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { nodeScheduledInputAdmissionLayer } from "./NodeSubscriptions.mjs";
3
+ import { ScheduleStorageError, ScheduleStore, ScheduleWake, defaultSchedulingLimits } from "@effect-agent/thread/Schedule";
4
+ import { NodeCrypto } from "@effect/platform-node";
5
+ import { Cause, Duration, Effect, Layer, Option, PubSub, Result } from "effect";
6
+ import { ScheduleDriver, Scheduling } from "@effect-agent/thread/Scheduling";
7
+ //#region src/NodeScheduling.ts
8
+ var NodeScheduling_exports = /* @__PURE__ */ __exportAll({
9
+ NodeScheduling: () => NodeScheduling,
10
+ nodeScheduleWakeLayer: () => nodeScheduleWakeLayer
11
+ });
12
+ /** One bounded hint slot for the single supported Node scheduler. Indexed polling repairs loss. */
13
+ const nodeScheduleWakeLayer = Layer.effect(ScheduleWake, Effect.gen(function* () {
14
+ const hints = yield* PubSub.sliding(1);
15
+ const subscription = yield* PubSub.subscribe(hints);
16
+ yield* Effect.addFinalizer(() => PubSub.shutdown(hints));
17
+ return ScheduleWake.of({
18
+ notify: PubSub.publish(hints, void 0).pipe(Effect.asVoid),
19
+ await: PubSub.take(subscription)
20
+ });
21
+ }));
22
+ const reportPassFailure = (cause) => Cause.hasInterruptsOnly(cause) ? Effect.interrupt : Effect.logWarning("Node scheduling pass failed").pipe(Effect.annotateLogs({ failureTag: Option.match(Cause.findErrorOption(cause), {
23
+ onNone: () => "Defect",
24
+ onSome: (error) => error._tag
25
+ }) }), Effect.as(false));
26
+ const nodeSchedulingDriverLayer = (limits) => Layer.effectDiscard(Effect.gen(function* () {
27
+ const scheduling = yield* ScheduleDriver;
28
+ const store = yield* ScheduleStore;
29
+ const wake = yield* ScheduleWake;
30
+ const run = Effect.gen(function* () {
31
+ while (true) {
32
+ const passSucceeded = yield* scheduling.runDue().pipe(Effect.map((pass) => pass.failed === 0), Effect.catchCause(reportPassFailure));
33
+ const deadlineResult = passSucceeded ? yield* store.nextDeadline().pipe(Effect.result) : Result.fail(ScheduleStorageError.make({
34
+ operation: "driver pass",
35
+ reason: "unavailable"
36
+ }));
37
+ if (Result.isFailure(deadlineResult) && passSucceeded) yield* Effect.logWarning("Node scheduling deadline query failed");
38
+ const nowMillis = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
39
+ const deadlineDelay = Result.isSuccess(deadlineResult) && deadlineResult.success !== null ? Math.max(0, deadlineResult.success - nowMillis) : limits.recoveryPollMillis;
40
+ const delay = Math.min(deadlineDelay, limits.recoveryPollMillis);
41
+ yield* Effect.raceFirst(wake.await, Effect.sleep(Duration.millis(delay)));
42
+ }
43
+ });
44
+ yield* Effect.forkScoped(run);
45
+ }));
46
+ /**
47
+ * Optional Scope-owned Node scheduling service and driver. The caller must provide the existing
48
+ * host, its SQLite ScheduleStore, and an explicit ScheduleAuthorizer.
49
+ */
50
+ var NodeScheduling = class {
51
+ static layer(options = {}) {
52
+ const limits = options.limits ?? defaultSchedulingLimits;
53
+ return nodeSchedulingDriverLayer(limits).pipe(Layer.provide(ScheduleDriver.layer(limits)), Layer.merge(Scheduling.layer(limits))).pipe(Layer.provide(Layer.mergeAll(nodeScheduledInputAdmissionLayer, nodeScheduleWakeLayer, NodeCrypto.layer)));
54
+ }
55
+ };
56
+ //#endregion
57
+ export { NodeScheduling, nodeScheduleWakeLayer, NodeScheduling_exports as t };
58
+
59
+ //# sourceMappingURL=NodeScheduling.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NodeScheduling.mjs","names":[],"sources":["../src/NodeScheduling.ts"],"sourcesContent":["import {\n type ScheduleAuthorizer,\n type SchedulingLimits,\n ScheduleStorageError,\n ScheduleStore,\n type ScheduleValidationError,\n ScheduleWake,\n defaultSchedulingLimits,\n} from \"@effect-agent/thread/Schedule\";\nimport {\n type ScheduleProcessFailure,\n Scheduling,\n ScheduleDriver,\n} from \"@effect-agent/thread/Scheduling\";\nimport { NodeCrypto } from \"@effect/platform-node\";\nimport { Cause, Duration, Effect, Layer, Option, PubSub, Result } from \"effect\";\n\nimport type { NodeDurableHost } from \"./NodeDurableHost.ts\";\nimport { nodeScheduledInputAdmissionLayer } from \"./NodeSubscriptions.ts\";\n\n/** One bounded hint slot for the single supported Node scheduler. Indexed polling repairs loss. */\nexport const nodeScheduleWakeLayer: Layer.Layer<ScheduleWake> = Layer.effect(\n ScheduleWake,\n Effect.gen(function* () {\n const hints = yield* PubSub.sliding<void>(1);\n const subscription = yield* PubSub.subscribe(hints);\n\n yield* Effect.addFinalizer(() => PubSub.shutdown(hints));\n\n return ScheduleWake.of({\n notify: PubSub.publish(hints, undefined).pipe(Effect.asVoid),\n await: PubSub.take(subscription),\n });\n }),\n);\n\nconst reportPassFailure = (cause: Cause.Cause<ScheduleProcessFailure>): Effect.Effect<boolean> =>\n Cause.hasInterruptsOnly(cause)\n ? Effect.interrupt\n : Effect.logWarning(\"Node scheduling pass failed\").pipe(\n Effect.annotateLogs({\n failureTag: Option.match(Cause.findErrorOption(cause), {\n onNone: () => \"Defect\",\n onSome: (error) => error._tag,\n }),\n }),\n Effect.as(false),\n );\n\nconst nodeSchedulingDriverLayer = (\n limits: SchedulingLimits,\n): Layer.Layer<never, never, ScheduleDriver | ScheduleStore | ScheduleWake> =>\n Layer.effectDiscard(\n Effect.gen(function* () {\n const scheduling = yield* ScheduleDriver;\n const store = yield* ScheduleStore;\n const wake = yield* ScheduleWake;\n\n const run = Effect.gen(function* () {\n while (true) {\n const passSucceeded = yield* scheduling.runDue().pipe(\n Effect.map((pass) => pass.failed === 0),\n Effect.catchCause(reportPassFailure),\n );\n\n const deadlineResult = passSucceeded\n ? yield* store.nextDeadline().pipe(Effect.result)\n : Result.fail(\n ScheduleStorageError.make({ operation: \"driver pass\", reason: \"unavailable\" }),\n );\n\n if (Result.isFailure(deadlineResult) && passSucceeded) {\n yield* Effect.logWarning(\"Node scheduling deadline query failed\");\n }\n const nowMillis = yield* Effect.clockWith((clock) => clock.currentTimeMillis);\n\n const deadlineDelay =\n Result.isSuccess(deadlineResult) && deadlineResult.success !== null\n ? Math.max(0, deadlineResult.success - nowMillis)\n : limits.recoveryPollMillis;\n\n const delay = Math.min(deadlineDelay, limits.recoveryPollMillis);\n\n yield* Effect.raceFirst(wake.await, Effect.sleep(Duration.millis(delay)));\n }\n });\n\n yield* Effect.forkScoped(run);\n }),\n );\n\nexport interface NodeSchedulingOptions {\n readonly limits?: SchedulingLimits | undefined;\n}\n\n/**\n * Optional Scope-owned Node scheduling service and driver. The caller must provide the existing\n * host, its SQLite ScheduleStore, and an explicit ScheduleAuthorizer.\n */\nexport class NodeScheduling {\n static layer(\n options: NodeSchedulingOptions = {},\n ): Layer.Layer<\n Scheduling,\n ScheduleValidationError,\n NodeDurableHost | ScheduleStore | ScheduleAuthorizer\n > {\n const limits = options.limits ?? defaultSchedulingLimits;\n\n const schedulingWithDriver = nodeSchedulingDriverLayer(limits).pipe(\n Layer.provide(ScheduleDriver.layer(limits)),\n Layer.merge(Scheduling.layer(limits)),\n );\n\n return schedulingWithDriver.pipe(\n Layer.provide(\n Layer.mergeAll(nodeScheduledInputAdmissionLayer, nodeScheduleWakeLayer, NodeCrypto.layer),\n ),\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;AAqBA,MAAa,wBAAmD,MAAM,OACpE,cACA,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,OAAO,OAAO,QAAc,CAAC;CAC3C,MAAM,eAAe,OAAO,OAAO,UAAU,KAAK;CAElD,OAAO,OAAO,mBAAmB,OAAO,SAAS,KAAK,CAAC;CAEvD,OAAO,aAAa,GAAG;EACrB,QAAQ,OAAO,QAAQ,OAAO,KAAA,CAAS,CAAC,CAAC,KAAK,OAAO,MAAM;EAC3D,OAAO,OAAO,KAAK,YAAY;CACjC,CAAC;AACH,CAAC,CACH;AAEA,MAAM,qBAAqB,UACzB,MAAM,kBAAkB,KAAK,IACzB,OAAO,YACP,OAAO,WAAW,6BAA6B,CAAC,CAAC,KAC/C,OAAO,aAAa,EAClB,YAAY,OAAO,MAAM,MAAM,gBAAgB,KAAK,GAAG;CACrD,cAAc;CACd,SAAS,UAAU,MAAM;AAC3B,CAAC,EACH,CAAC,GACD,OAAO,GAAG,KAAK,CACjB;AAEN,MAAM,6BACJ,WAEA,MAAM,cACJ,OAAO,IAAI,aAAa;CACtB,MAAM,aAAa,OAAO;CAC1B,MAAM,QAAQ,OAAO;CACrB,MAAM,OAAO,OAAO;CAEpB,MAAM,MAAM,OAAO,IAAI,aAAa;EAClC,OAAO,MAAM;GACX,MAAM,gBAAgB,OAAO,WAAW,OAAO,CAAC,CAAC,KAC/C,OAAO,KAAK,SAAS,KAAK,WAAW,CAAC,GACtC,OAAO,WAAW,iBAAiB,CACrC;GAEA,MAAM,iBAAiB,gBACnB,OAAO,MAAM,aAAa,CAAC,CAAC,KAAK,OAAO,MAAM,IAC9C,OAAO,KACL,qBAAqB,KAAK;IAAE,WAAW;IAAe,QAAQ;GAAc,CAAC,CAC/E;GAEJ,IAAI,OAAO,UAAU,cAAc,KAAK,eACtC,OAAO,OAAO,WAAW,uCAAuC;GAElE,MAAM,YAAY,OAAO,OAAO,WAAW,UAAU,MAAM,iBAAiB;GAE5E,MAAM,gBACJ,OAAO,UAAU,cAAc,KAAK,eAAe,YAAY,OAC3D,KAAK,IAAI,GAAG,eAAe,UAAU,SAAS,IAC9C,OAAO;GAEb,MAAM,QAAQ,KAAK,IAAI,eAAe,OAAO,kBAAkB;GAE/D,OAAO,OAAO,UAAU,KAAK,OAAO,OAAO,MAAM,SAAS,OAAO,KAAK,CAAC,CAAC;EAC1E;CACF,CAAC;CAED,OAAO,OAAO,WAAW,GAAG;AAC9B,CAAC,CACH;;;;;AAUF,IAAa,iBAAb,MAA4B;CAC1B,OAAO,MACL,UAAiC,CAAC,GAKlC;EACA,MAAM,SAAS,QAAQ,UAAU;EAOjC,OAL6B,0BAA0B,MAAM,CAAC,CAAC,KAC7D,MAAM,QAAQ,eAAe,MAAM,MAAM,CAAC,GAC1C,MAAM,MAAM,WAAW,MAAM,MAAM,CAAC,CAGZ,CAAC,CAAC,KAC1B,MAAM,QACJ,MAAM,SAAS,kCAAkC,uBAAuB,WAAW,KAAK,CAC1F,CACF;CACF;AACF"}
@@ -0,0 +1,27 @@
1
+ import { NodeDurableHost } from "./NodeDurableHost.mjs";
2
+ import { ScheduledInputAdmission } from "@effect-agent/thread/Schedule";
3
+ import { Layer } from "effect";
4
+ import { EventSources } from "@effect-agent/thread/EventSource";
5
+ import { PreparedInputAdmission } from "@effect-agent/thread/PreparedInputAdmission";
6
+ import { SubscriptionAuthorizer, SubscriptionError, SubscriptionLimits, SubscriptionStore } from "@effect-agent/thread/Subscription";
7
+ import { SubscriptionInputBindings } from "@effect-agent/thread/SubscriptionInput";
8
+ import { SubscriptionIntake, Subscriptions } from "@effect-agent/thread/Subscriptions";
9
+ declare namespace NodeSubscriptions_d_exports {
10
+ export { NodeSubscriptions, NodeSubscriptionsOptions, nodePreparedInputAdmissionLayer, nodeScheduledInputAdmissionLayer };
11
+ }
12
+ /** Ordinary prepared admission through the Scope-owned Node host gate. */
13
+ declare const nodePreparedInputAdmissionLayer: Layer.Layer<PreparedInputAdmission, never, NodeDurableHost>;
14
+ declare const nodeScheduledInputAdmissionLayer: Layer.Layer<ScheduledInputAdmission, never, NodeDurableHost>;
15
+ interface NodeSubscriptionsOptions {
16
+ readonly limits?: SubscriptionLimits | undefined;
17
+ }
18
+ /**
19
+ * One Scope-owned subscription partition in the sole process owning its SQLite database.
20
+ * Indexed polling repairs restart and lost wake state; closing the Scope interrupts the driver.
21
+ */
22
+ declare class NodeSubscriptions {
23
+ static layer(options?: NodeSubscriptionsOptions): Layer.Layer<Subscriptions | SubscriptionIntake, SubscriptionError, NodeDurableHost | SubscriptionStore | SubscriptionAuthorizer | EventSources | SubscriptionInputBindings>;
24
+ }
25
+ //#endregion
26
+ export { NodeSubscriptions, NodeSubscriptionsOptions, nodePreparedInputAdmissionLayer, nodeScheduledInputAdmissionLayer, NodeSubscriptions_d_exports as t };
27
+ //# sourceMappingURL=NodeSubscriptions.d.mts.map
@@ -0,0 +1,103 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { NodeDurableHost } from "./NodeDurableHost.mjs";
3
+ import "@effect-agent/core/Identifiers";
4
+ import "@effect-agent/thread/DurableAgentRuntime";
5
+ import { PersistedJson } from "@effect-agent/thread/Records";
6
+ import { ScheduleStorageError, ScheduledInputAdmission, ScheduledInputRetryable } from "@effect-agent/thread/Schedule";
7
+ import { NodeCrypto } from "@effect/platform-node";
8
+ import { Cause, Duration, Effect, Exit, Layer, Option } from "effect";
9
+ import "@effect-agent/thread/EventSource";
10
+ import { PreparedInputAdmission } from "@effect-agent/thread/PreparedInputAdmission";
11
+ import { SubscriptionStore, defaultSubscriptionLimits } from "@effect-agent/thread/Subscription";
12
+ import "@effect-agent/thread/SubscriptionInput";
13
+ import { SubscriptionDriver, SubscriptionIntake, Subscriptions } from "@effect-agent/thread/Subscriptions";
14
+ //#region src/NodeSubscriptions.ts
15
+ var NodeSubscriptions_exports = /* @__PURE__ */ __exportAll({
16
+ NodeSubscriptions: () => NodeSubscriptions,
17
+ nodePreparedInputAdmissionLayer: () => nodePreparedInputAdmissionLayer,
18
+ nodeScheduledInputAdmissionLayer: () => nodeScheduledInputAdmissionLayer
19
+ });
20
+ const passthroughSubmitAgent = (agentId) => ({ definition: {
21
+ id: agentId,
22
+ input: PersistedJson
23
+ } });
24
+ const ambiguous = () => ScheduledInputRetryable.make({ reason: "ambiguous" });
25
+ const corrupt = (operation) => ScheduleStorageError.make({
26
+ operation,
27
+ reason: "corrupt"
28
+ });
29
+ /** Ordinary prepared admission through the Scope-owned Node host gate. */
30
+ const nodePreparedInputAdmissionLayer = Layer.effect(PreparedInputAdmission, Effect.gen(function* () {
31
+ const host = yield* NodeDurableHost;
32
+ return PreparedInputAdmission.of({ submit: (envelope) => host.submit(passthroughSubmitAgent(envelope.agentId), envelope.input, {
33
+ threadId: envelope.threadId,
34
+ principal: envelope.deliveryPrincipal,
35
+ idempotencyKey: envelope.admissionKey,
36
+ definitions: envelope.definitions
37
+ }).pipe(Effect.catchTags({
38
+ AdmissionClosed: () => Effect.fail(ScheduledInputRetryable.make({ reason: "host-closed" })),
39
+ AgentInputError: () => Effect.fail(corrupt("prepared admission input")),
40
+ AdmissionConflict: () => Effect.fail(corrupt("prepared admission conflict")),
41
+ DigestError: () => Effect.fail(ambiguous()),
42
+ LedgerError: () => Effect.fail(ambiguous()),
43
+ ThreadStoreError: () => Effect.fail(ambiguous()),
44
+ ThreadNotMaterialized: () => Effect.fail(ambiguous()),
45
+ AppendConflict: () => Effect.fail(ambiguous()),
46
+ FenceRejected: () => Effect.fail(ambiguous()),
47
+ DurableRuntimeFailpointError: () => Effect.fail(ambiguous())
48
+ })) });
49
+ }));
50
+ const preparedFromSchedule = (envelope) => ({
51
+ schemaVersion: 1,
52
+ threadId: envelope.threadId,
53
+ deliveryPrincipal: envelope.deliveryPrincipal,
54
+ agentId: envelope.agentId,
55
+ definitions: envelope.definitions,
56
+ input: envelope.input,
57
+ inputDigest: envelope.inputDigest,
58
+ admissionKey: envelope.admissionKey,
59
+ authorization: envelope.authorization
60
+ });
61
+ const nodeScheduledInputAdmissionLayer = Layer.effect(ScheduledInputAdmission, Effect.map(PreparedInputAdmission, (admission) => ScheduledInputAdmission.of({ submit: (envelope) => admission.submit(preparedFromSchedule(envelope)) }))).pipe(Layer.provide(nodePreparedInputAdmissionLayer));
62
+ const reportPassFailure = (cause) => Cause.hasInterruptsOnly(cause) ? Effect.interrupt : Effect.logWarning("Node subscription pass failed").pipe(Effect.annotateLogs({ failureTag: Option.match(Cause.findErrorOption(cause), {
63
+ onNone: () => "Defect",
64
+ onSome: (error) => error._tag
65
+ }) }), Effect.as(false));
66
+ const nodeSubscriptionDriverLayer = (limits) => Layer.effectDiscard(Effect.gen(function* () {
67
+ const driver = yield* SubscriptionDriver;
68
+ const store = yield* SubscriptionStore;
69
+ const run = Effect.gen(function* () {
70
+ while (true) {
71
+ if (!(yield* driver.runDue.pipe(Effect.map((pass) => pass.failed === 0), Effect.catchCause(reportPassFailure)))) {
72
+ yield* Effect.sleep(Duration.millis(limits.retryMillis));
73
+ continue;
74
+ }
75
+ const deadline = yield* store.nextDeadline.pipe(Effect.exit);
76
+ if (Exit.isFailure(deadline)) {
77
+ yield* reportPassFailure(deadline.cause);
78
+ yield* Effect.sleep(Duration.millis(limits.retryMillis));
79
+ continue;
80
+ }
81
+ const nowMillis = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
82
+ const delay = deadline.value === null ? limits.retryMillis : Math.max(1, Math.min(deadline.value - nowMillis, limits.retryMillis));
83
+ yield* Effect.sleep(Duration.millis(delay));
84
+ }
85
+ });
86
+ yield* Effect.forkScoped(run);
87
+ }));
88
+ /**
89
+ * One Scope-owned subscription partition in the sole process owning its SQLite database.
90
+ * Indexed polling repairs restart and lost wake state; closing the Scope interrupts the driver.
91
+ */
92
+ var NodeSubscriptions = class {
93
+ static layer(options = {}) {
94
+ const limits = options.limits ?? defaultSubscriptionLimits;
95
+ const publicServices = Layer.merge(Subscriptions.layer(limits), SubscriptionIntake.layer(limits));
96
+ const driver = nodeSubscriptionDriverLayer(limits).pipe(Layer.provide(SubscriptionDriver.layer(limits)));
97
+ return Layer.merge(publicServices, driver).pipe(Layer.provide(nodePreparedInputAdmissionLayer), Layer.provide(NodeCrypto.layer));
98
+ }
99
+ };
100
+ //#endregion
101
+ export { NodeSubscriptions, nodePreparedInputAdmissionLayer, nodeScheduledInputAdmissionLayer, NodeSubscriptions_exports as t };
102
+
103
+ //# sourceMappingURL=NodeSubscriptions.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NodeSubscriptions.mjs","names":[],"sources":["../src/NodeSubscriptions.ts"],"sourcesContent":["import { type AgentId } from \"@effect-agent/core/Identifiers\";\nimport { type DurableSubmitAgent } from \"@effect-agent/thread/DurableAgentRuntime\";\nimport { type EventSources } from \"@effect-agent/thread/EventSource\";\nimport { PreparedInputAdmission } from \"@effect-agent/thread/PreparedInputAdmission\";\nimport { PersistedJson } from \"@effect-agent/thread/Records\";\nimport {\n type ScheduledEnvelope,\n ScheduledInputAdmission,\n ScheduledInputRetryable,\n ScheduleStorageError,\n} from \"@effect-agent/thread/Schedule\";\nimport {\n type PreparedInput,\n type SubscriptionAuthorizer,\n type SubscriptionError,\n type SubscriptionLimits,\n type SubscriptionStoreFailure,\n SubscriptionStore,\n defaultSubscriptionLimits,\n} from \"@effect-agent/thread/Subscription\";\nimport { type SubscriptionInputBindings } from \"@effect-agent/thread/SubscriptionInput\";\nimport {\n SubscriptionDriver,\n SubscriptionIntake,\n Subscriptions,\n} from \"@effect-agent/thread/Subscriptions\";\nimport { NodeCrypto } from \"@effect/platform-node\";\nimport { Cause, Duration, Effect, Exit, Layer, Option } from \"effect\";\n\nimport { NodeDurableHost } from \"./NodeDurableHost.ts\";\n\nconst passthroughSubmitAgent = (agentId: AgentId): DurableSubmitAgent<typeof PersistedJson> => ({\n definition: { id: agentId, input: PersistedJson },\n});\n\nconst ambiguous = (): ScheduledInputRetryable =>\n ScheduledInputRetryable.make({ reason: \"ambiguous\" });\n\nconst corrupt = (operation: string): ScheduleStorageError =>\n ScheduleStorageError.make({ operation, reason: \"corrupt\" });\n\n/** Ordinary prepared admission through the Scope-owned Node host gate. */\nexport const nodePreparedInputAdmissionLayer: Layer.Layer<\n PreparedInputAdmission,\n never,\n NodeDurableHost\n> = Layer.effect(\n PreparedInputAdmission,\n Effect.gen(function* () {\n const host = yield* NodeDurableHost;\n\n return PreparedInputAdmission.of({\n submit: (envelope) =>\n host\n .submit(passthroughSubmitAgent(envelope.agentId), envelope.input, {\n threadId: envelope.threadId,\n principal: envelope.deliveryPrincipal,\n idempotencyKey: envelope.admissionKey,\n definitions: envelope.definitions,\n })\n .pipe(\n Effect.catchTags({\n AdmissionClosed: () =>\n Effect.fail(ScheduledInputRetryable.make({ reason: \"host-closed\" })),\n AgentInputError: () => Effect.fail(corrupt(\"prepared admission input\")),\n AdmissionConflict: () => Effect.fail(corrupt(\"prepared admission conflict\")),\n DigestError: () => Effect.fail(ambiguous()),\n LedgerError: () => Effect.fail(ambiguous()),\n ThreadStoreError: () => Effect.fail(ambiguous()),\n ThreadNotMaterialized: () => Effect.fail(ambiguous()),\n AppendConflict: () => Effect.fail(ambiguous()),\n FenceRejected: () => Effect.fail(ambiguous()),\n DurableRuntimeFailpointError: () => Effect.fail(ambiguous()),\n }),\n ),\n });\n }),\n);\n\nconst preparedFromSchedule = (envelope: ScheduledEnvelope): PreparedInput => ({\n schemaVersion: 1,\n threadId: envelope.threadId,\n deliveryPrincipal: envelope.deliveryPrincipal,\n agentId: envelope.agentId,\n definitions: envelope.definitions,\n input: envelope.input,\n inputDigest: envelope.inputDigest,\n admissionKey: envelope.admissionKey,\n authorization: envelope.authorization,\n});\n\n/** Compatibility adapter retaining the public scheduling admission port. */\nconst nodeScheduledInputAdmissionFromPreparedLayer: Layer.Layer<\n ScheduledInputAdmission,\n never,\n PreparedInputAdmission\n> = Layer.effect(\n ScheduledInputAdmission,\n Effect.map(PreparedInputAdmission, (admission) =>\n ScheduledInputAdmission.of({\n submit: (envelope) => admission.submit(preparedFromSchedule(envelope)),\n }),\n ),\n);\n\nexport const nodeScheduledInputAdmissionLayer: Layer.Layer<\n ScheduledInputAdmission,\n never,\n NodeDurableHost\n> = nodeScheduledInputAdmissionFromPreparedLayer.pipe(\n Layer.provide(nodePreparedInputAdmissionLayer),\n);\n\nconst reportPassFailure = (cause: Cause.Cause<SubscriptionStoreFailure>): Effect.Effect<boolean> =>\n Cause.hasInterruptsOnly(cause)\n ? Effect.interrupt\n : Effect.logWarning(\"Node subscription pass failed\").pipe(\n Effect.annotateLogs({\n failureTag: Option.match(Cause.findErrorOption(cause), {\n onNone: () => \"Defect\",\n onSome: (error) => error._tag,\n }),\n }),\n Effect.as(false),\n );\n\nconst nodeSubscriptionDriverLayer = (\n limits: SubscriptionLimits,\n): Layer.Layer<never, never, SubscriptionDriver | SubscriptionStore> =>\n Layer.effectDiscard(\n Effect.gen(function* () {\n const driver = yield* SubscriptionDriver;\n const store = yield* SubscriptionStore;\n\n const run = Effect.gen(function* () {\n while (true) {\n const passSucceeded = yield* driver.runDue.pipe(\n Effect.map((pass) => pass.failed === 0),\n Effect.catchCause(reportPassFailure),\n );\n\n if (!passSucceeded) {\n yield* Effect.sleep(Duration.millis(limits.retryMillis));\n continue;\n }\n\n const deadline = yield* store.nextDeadline.pipe(Effect.exit);\n\n if (Exit.isFailure(deadline)) {\n yield* reportPassFailure(deadline.cause);\n yield* Effect.sleep(Duration.millis(limits.retryMillis));\n continue;\n }\n\n const nowMillis = yield* Effect.clockWith((clock) => clock.currentTimeMillis);\n\n const delay =\n deadline.value === null\n ? limits.retryMillis\n : Math.max(1, Math.min(deadline.value - nowMillis, limits.retryMillis));\n\n yield* Effect.sleep(Duration.millis(delay));\n }\n });\n\n yield* Effect.forkScoped(run);\n }),\n );\n\nexport interface NodeSubscriptionsOptions {\n readonly limits?: SubscriptionLimits | undefined;\n}\n\n/**\n * One Scope-owned subscription partition in the sole process owning its SQLite database.\n * Indexed polling repairs restart and lost wake state; closing the Scope interrupts the driver.\n */\nexport class NodeSubscriptions {\n static layer(\n options: NodeSubscriptionsOptions = {},\n ): Layer.Layer<\n Subscriptions | SubscriptionIntake,\n SubscriptionError,\n | NodeDurableHost\n | SubscriptionStore\n | SubscriptionAuthorizer\n | EventSources\n | SubscriptionInputBindings\n > {\n const limits = options.limits ?? defaultSubscriptionLimits;\n\n const publicServices = Layer.merge(\n Subscriptions.layer(limits),\n SubscriptionIntake.layer(limits),\n );\n\n const driver = nodeSubscriptionDriverLayer(limits).pipe(\n Layer.provide(SubscriptionDriver.layer(limits)),\n );\n\n return Layer.merge(publicServices, driver).pipe(\n Layer.provide(nodePreparedInputAdmissionLayer),\n Layer.provide(NodeCrypto.layer),\n );\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { SubmissionLedger } from "@effect-agent/thread/SubmissionLedger";
2
+ import { WakeScheduler } from "@effect-agent/thread/WakeScheduler";
3
+ import { Context, Duration, Layer } from "effect";
4
+ declare namespace NodeWakeScheduler_d_exports {
5
+ export { NodeWakeSchedulerConfig, nodeWakeSchedulerLayer };
6
+ }
7
+ declare const NodeWakeSchedulerConfig_base: Context.ServiceClass<NodeWakeSchedulerConfig, "@effect-agent/platform-node/NodeWakeSchedulerConfig", {
8
+ /** Interval between ledger scans that re-emit every nonterminal Thread lane. */
9
+ readonly scanInterval: Duration.Duration;
10
+ }>;
11
+ /** Cadence authority for the Node wake scheduler's ledger-scan fallback loop. */
12
+ declare class NodeWakeSchedulerConfig extends NodeWakeSchedulerConfig_base {
13
+ static layer(options: {
14
+ readonly scanInterval: Duration.Duration;
15
+ }): Layer.Layer<NodeWakeSchedulerConfig>;
16
+ }
17
+ /**
18
+ * In-process Node `WakeScheduler`: `notify` publishes to a bounded sliding PubSub for prompt
19
+ * same-process wakeups, and every `wakes` subscription additionally runs a periodic
20
+ * `SubmissionLedger.scanNonterminal` fallback so a dropped, coalesced, or never-sent notification
21
+ * can never strand accepted work (persistence §14). Delivery may duplicate; consumers already
22
+ * treat wakes as pure liveness hints.
23
+ */
24
+ declare const nodeWakeSchedulerLayer: Layer.Layer<WakeScheduler, never, SubmissionLedger | NodeWakeSchedulerConfig>;
25
+ //#endregion
26
+ export { NodeWakeScheduler_d_exports as n, nodeWakeSchedulerLayer as r, NodeWakeSchedulerConfig as t };
27
+ //# sourceMappingURL=NodeWakeScheduler-DWdHIhWA.d.mts.map
@@ -0,0 +1,2 @@
1
+ import { r as nodeWakeSchedulerLayer, t as NodeWakeSchedulerConfig } from "./NodeWakeScheduler-DWdHIhWA.mjs";
2
+ export { NodeWakeSchedulerConfig, nodeWakeSchedulerLayer };
@@ -0,0 +1,61 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import "@effect-agent/core/Identifiers";
3
+ import { SubmissionLedger } from "@effect-agent/thread/SubmissionLedger";
4
+ import { WakeScheduler, makeWakeSubscriptionHub } from "@effect-agent/thread/WakeScheduler";
5
+ import { Context, Effect, Layer, PubSub, Stream } from "effect";
6
+ //#region src/NodeWakeScheduler.ts
7
+ var NodeWakeScheduler_exports = /* @__PURE__ */ __exportAll({
8
+ NodeWakeSchedulerConfig: () => NodeWakeSchedulerConfig,
9
+ nodeWakeSchedulerLayer: () => nodeWakeSchedulerLayer
10
+ });
11
+ /**
12
+ * Bounded in-process wake buffer. Wake hints are droppable by contract (the ledger-scan fallback
13
+ * keeps liveness), so a full buffer slides out the oldest hint instead of growing without bound.
14
+ */
15
+ const WAKE_BUFFER_CAPACITY = 1024;
16
+ /** Cadence authority for the Node wake scheduler's ledger-scan fallback loop. */
17
+ var NodeWakeSchedulerConfig = class NodeWakeSchedulerConfig extends Context.Service()("@effect-agent/platform-node/NodeWakeSchedulerConfig") {
18
+ static layer(options) {
19
+ return Layer.succeed(NodeWakeSchedulerConfig)({ scanInterval: options.scanInterval });
20
+ }
21
+ };
22
+ const makeWakeScheduler = Effect.gen(function* () {
23
+ const ledger = yield* SubmissionLedger;
24
+ const config = yield* NodeWakeSchedulerConfig;
25
+ const hints = yield* PubSub.sliding(WAKE_BUFFER_CAPACITY);
26
+ const progress = yield* makeWakeSubscriptionHub;
27
+ yield* Effect.addFinalizer(() => PubSub.shutdown(hints));
28
+ /**
29
+ * One fallback scan: every Thread lane with nonterminal work, deduplicated. A scan
30
+ * failure degrades to "no hints this round" — the wake channel has no error contract and the
31
+ * next round retries — but is logged so a persistently failing ledger stays visible.
32
+ */
33
+ const scanOnce = Stream.runCollect(ledger.scanNonterminal).pipe(Effect.map((snapshots) => {
34
+ const lanes = /* @__PURE__ */ new Set();
35
+ for (const snapshot of snapshots) lanes.add(snapshot.threadId);
36
+ return [...lanes];
37
+ }), Effect.catch((error) => Effect.logWarning("NodeWakeScheduler fallback scan failed", error).pipe(Effect.as([]))));
38
+ /**
39
+ * Deployment §3: correctness must not depend on in-memory notifications, so every `wakes` run
40
+ * merges its PubSub subscription with a Clock-driven ledger-scan loop. Both live entirely in
41
+ * the consuming run's Scope; no fiber outlives its subscriber.
42
+ */
43
+ const fallbackScans = Stream.fromIterableEffectRepeat(Effect.sleep(config.scanInterval).pipe(Effect.andThen(scanOnce)));
44
+ return WakeScheduler.of({
45
+ notify: (threadId) => progress.notify(threadId).pipe(Effect.andThen(PubSub.publish(hints, threadId)), Effect.asVoid),
46
+ subscribe: progress.subscribe,
47
+ wakes: Stream.merge(Stream.fromPubSub(hints), fallbackScans)
48
+ });
49
+ });
50
+ /**
51
+ * In-process Node `WakeScheduler`: `notify` publishes to a bounded sliding PubSub for prompt
52
+ * same-process wakeups, and every `wakes` subscription additionally runs a periodic
53
+ * `SubmissionLedger.scanNonterminal` fallback so a dropped, coalesced, or never-sent notification
54
+ * can never strand accepted work (persistence §14). Delivery may duplicate; consumers already
55
+ * treat wakes as pure liveness hints.
56
+ */
57
+ const nodeWakeSchedulerLayer = Layer.effect(WakeScheduler)(makeWakeScheduler);
58
+ //#endregion
59
+ export { NodeWakeSchedulerConfig, nodeWakeSchedulerLayer, NodeWakeScheduler_exports as t };
60
+
61
+ //# sourceMappingURL=NodeWakeScheduler.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NodeWakeScheduler.mjs","names":[],"sources":["../src/NodeWakeScheduler.ts"],"sourcesContent":["import { type ThreadId } from \"@effect-agent/core/Identifiers\";\nimport { SubmissionLedger } from \"@effect-agent/thread/SubmissionLedger\";\nimport { makeWakeSubscriptionHub, WakeScheduler } from \"@effect-agent/thread/WakeScheduler\";\nimport type { Duration } from \"effect\";\nimport { Context, Effect, Layer, PubSub, Stream } from \"effect\";\n\n/**\n * Bounded in-process wake buffer. Wake hints are droppable by contract (the ledger-scan fallback\n * keeps liveness), so a full buffer slides out the oldest hint instead of growing without bound.\n */\nconst WAKE_BUFFER_CAPACITY = 1_024;\n\n/** Cadence authority for the Node wake scheduler's ledger-scan fallback loop. */\nexport class NodeWakeSchedulerConfig extends Context.Service<\n NodeWakeSchedulerConfig,\n {\n /** Interval between ledger scans that re-emit every nonterminal Thread lane. */\n readonly scanInterval: Duration.Duration;\n }\n>()(\"@effect-agent/platform-node/NodeWakeSchedulerConfig\") {\n static layer(options: {\n readonly scanInterval: Duration.Duration;\n }): Layer.Layer<NodeWakeSchedulerConfig> {\n return Layer.succeed(NodeWakeSchedulerConfig)({ scanInterval: options.scanInterval });\n }\n}\n\nconst makeWakeScheduler = Effect.gen(function* () {\n const ledger = yield* SubmissionLedger;\n const config = yield* NodeWakeSchedulerConfig;\n const hints = yield* PubSub.sliding<ThreadId>(WAKE_BUFFER_CAPACITY);\n const progress = yield* makeWakeSubscriptionHub;\n\n yield* Effect.addFinalizer(() => PubSub.shutdown(hints));\n\n /**\n * One fallback scan: every Thread lane with nonterminal work, deduplicated. A scan\n * failure degrades to \"no hints this round\" — the wake channel has no error contract and the\n * next round retries — but is logged so a persistently failing ledger stays visible.\n */\n const scanOnce: Effect.Effect<ReadonlyArray<ThreadId>> = Stream.runCollect(\n ledger.scanNonterminal,\n ).pipe(\n Effect.map((snapshots) => {\n const lanes = new Set<ThreadId>();\n\n for (const snapshot of snapshots) {\n lanes.add(snapshot.threadId);\n }\n\n return [...lanes];\n }),\n Effect.catch((error) =>\n Effect.logWarning(\"NodeWakeScheduler fallback scan failed\", error).pipe(\n Effect.as([] as ReadonlyArray<ThreadId>),\n ),\n ),\n );\n\n /**\n * Deployment §3: correctness must not depend on in-memory notifications, so every `wakes` run\n * merges its PubSub subscription with a Clock-driven ledger-scan loop. Both live entirely in\n * the consuming run's Scope; no fiber outlives its subscriber.\n */\n const fallbackScans: Stream.Stream<ThreadId> = Stream.fromIterableEffectRepeat(\n Effect.sleep(config.scanInterval).pipe(Effect.andThen(scanOnce)),\n );\n\n return WakeScheduler.of({\n notify: (threadId) =>\n progress\n .notify(threadId)\n .pipe(Effect.andThen(PubSub.publish(hints, threadId)), Effect.asVoid),\n subscribe: progress.subscribe,\n wakes: Stream.merge(Stream.fromPubSub(hints), fallbackScans),\n });\n});\n\n/**\n * In-process Node `WakeScheduler`: `notify` publishes to a bounded sliding PubSub for prompt\n * same-process wakeups, and every `wakes` subscription additionally runs a periodic\n * `SubmissionLedger.scanNonterminal` fallback so a dropped, coalesced, or never-sent notification\n * can never strand accepted work (persistence §14). Delivery may duplicate; consumers already\n * treat wakes as pure liveness hints.\n */\nexport const nodeWakeSchedulerLayer: Layer.Layer<\n WakeScheduler,\n never,\n SubmissionLedger | NodeWakeSchedulerConfig\n> = Layer.effect(WakeScheduler)(makeWakeScheduler);\n"],"mappings":";;;;;;;;;;;;;;AAUA,MAAM,uBAAuB;;AAG7B,IAAa,0BAAb,MAAa,gCAAgC,QAAQ,QAMnD,CAAC,CAAC,qDAAqD,CAAC,CAAC;CACzD,OAAO,MAAM,SAE4B;EACvC,OAAO,MAAM,QAAQ,uBAAuB,CAAC,CAAC,EAAE,cAAc,QAAQ,aAAa,CAAC;CACtF;AACF;AAEA,MAAM,oBAAoB,OAAO,IAAI,aAAa;CAChD,MAAM,SAAS,OAAO;CACtB,MAAM,SAAS,OAAO;CACtB,MAAM,QAAQ,OAAO,OAAO,QAAkB,oBAAoB;CAClE,MAAM,WAAW,OAAO;CAExB,OAAO,OAAO,mBAAmB,OAAO,SAAS,KAAK,CAAC;;;;;;CAOvD,MAAM,WAAmD,OAAO,WAC9D,OAAO,eACT,CAAC,CAAC,KACA,OAAO,KAAK,cAAc;EACxB,MAAM,wBAAQ,IAAI,IAAc;EAEhC,KAAK,MAAM,YAAY,WACrB,MAAM,IAAI,SAAS,QAAQ;EAG7B,OAAO,CAAC,GAAG,KAAK;CAClB,CAAC,GACD,OAAO,OAAO,UACZ,OAAO,WAAW,0CAA0C,KAAK,CAAC,CAAC,KACjE,OAAO,GAAG,CAAC,CAA4B,CACzC,CACF,CACF;;;;;;CAOA,MAAM,gBAAyC,OAAO,yBACpD,OAAO,MAAM,OAAO,YAAY,CAAC,CAAC,KAAK,OAAO,QAAQ,QAAQ,CAAC,CACjE;CAEA,OAAO,cAAc,GAAG;EACtB,SAAS,aACP,SACG,OAAO,QAAQ,CAAC,CAChB,KAAK,OAAO,QAAQ,OAAO,QAAQ,OAAO,QAAQ,CAAC,GAAG,OAAO,MAAM;EACxE,WAAW,SAAS;EACpB,OAAO,OAAO,MAAM,OAAO,WAAW,KAAK,GAAG,aAAa;CAC7D,CAAC;AACH,CAAC;;;;;;;;AASD,MAAa,yBAIT,MAAM,OAAO,aAAa,CAAC,CAAC,iBAAiB"}
@@ -0,0 +1,27 @@
1
+ import { Cause, Duration, Layer, Schema } from "effect";
2
+ import { WorkflowDispatchError, WorkflowDispatchStore, WorkflowRepairTrigger } from "@effect-agent/workflow/WorkflowDispatch";
3
+ import { SqlClient } from "effect/unstable/sql";
4
+ //#region src/NodeWorkflow.d.ts
5
+ /**
6
+ * Durable dispatch outbox over an application-supplied SqlClient. This adapter uses
7
+ * SQLite/PostgreSQL SQL syntax and is certified with SQLite. It does not own an engine
8
+ * or a database connection. Agent admission, dispatch persistence, and native Workflow
9
+ * storage are separate commits; the registered repair trigger closes those gaps.
10
+ * Stored version or shape mismatches fail typed and require an explicit data reset.
11
+ */
12
+ declare class SqlWorkflowDispatchStore {
13
+ static readonly layer: Layer.Layer<WorkflowDispatchStore, WorkflowDispatchError, SqlClient.SqlClient>;
14
+ }
15
+ declare const NodeWorkflowRepairConfigError_base: Schema.Class<NodeWorkflowRepairConfigError, Schema.TaggedStruct<"NodeWorkflowRepairConfigError", {
16
+ readonly message: Schema.String;
17
+ }>, Cause.YieldableError>;
18
+ declare class NodeWorkflowRepairConfigError extends NodeWorkflowRepairConfigError_base {}
19
+ /** A host-scoped startup and polling trigger. No ordinary Node agent worker is started. */
20
+ declare class NodeWorkflowRepairTrigger {
21
+ static layer(options?: {
22
+ readonly interval?: Duration.Input;
23
+ }): Layer.Layer<WorkflowRepairTrigger, NodeWorkflowRepairConfigError>;
24
+ }
25
+ //#endregion
26
+ export { NodeWorkflowRepairConfigError, NodeWorkflowRepairTrigger, SqlWorkflowDispatchStore };
27
+ //# sourceMappingURL=NodeWorkflow.d.mts.map