@yolk-sdk/harness 0.1.0-canary.100

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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +206 -0
  3. package/dist/coordinator.d.mts +72 -0
  4. package/dist/coordinator.d.mts.map +1 -0
  5. package/dist/coordinator.mjs +154 -0
  6. package/dist/coordinator.mjs.map +1 -0
  7. package/dist/driver/durable-object.d.mts +20 -0
  8. package/dist/driver/durable-object.d.mts.map +1 -0
  9. package/dist/driver/durable-object.mjs +12 -0
  10. package/dist/driver/durable-object.mjs.map +1 -0
  11. package/dist/driver/memory.d.mts +21 -0
  12. package/dist/driver/memory.d.mts.map +1 -0
  13. package/dist/driver/memory.mjs +15 -0
  14. package/dist/driver/memory.mjs.map +1 -0
  15. package/dist/driver.d.mts +126 -0
  16. package/dist/driver.d.mts.map +1 -0
  17. package/dist/driver.mjs +96 -0
  18. package/dist/driver.mjs.map +1 -0
  19. package/dist/inbox.d.mts +368 -0
  20. package/dist/inbox.d.mts.map +1 -0
  21. package/dist/inbox.mjs +235 -0
  22. package/dist/inbox.mjs.map +1 -0
  23. package/dist/index.d.mts +1 -0
  24. package/dist/index.mjs +1 -0
  25. package/dist/outcome-constructors-internal.d.mts +403 -0
  26. package/dist/outcome-constructors-internal.d.mts.map +1 -0
  27. package/dist/outcome-constructors-internal.mjs +11 -0
  28. package/dist/outcome-constructors-internal.mjs.map +1 -0
  29. package/dist/outcome.d.mts +361 -0
  30. package/dist/outcome.d.mts.map +1 -0
  31. package/dist/outcome.mjs +136 -0
  32. package/dist/outcome.mjs.map +1 -0
  33. package/dist/store.d.mts +39 -0
  34. package/dist/store.d.mts.map +1 -0
  35. package/dist/store.mjs +98 -0
  36. package/dist/store.mjs.map +1 -0
  37. package/package.json +90 -0
  38. package/src/coordinator.ts +355 -0
  39. package/src/driver/durable-object.ts +28 -0
  40. package/src/driver/memory.ts +39 -0
  41. package/src/driver.ts +270 -0
  42. package/src/inbox.ts +528 -0
  43. package/src/index.ts +2 -0
  44. package/src/outcome-constructors-internal.ts +18 -0
  45. package/src/outcome.ts +377 -0
  46. package/src/store.ts +161 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yolk SDK contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # @yolk-sdk/harness
2
+
3
+ Domain-free **run lifecycle** for Yolk agents: who owns a live run, how wakes coalesce, and how
4
+ claims survive a crash. The model/tool loop stays in `@yolk-sdk/agent/loop`.
5
+
6
+ Canary APIs are unstable. Keep all `@yolk-sdk/*` packages on the same version.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pnpm add @yolk-sdk/harness@canary effect@4.0.0
12
+ ```
13
+
14
+ ## Subpaths
15
+
16
+ | Subpath | Purpose |
17
+ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------- |
18
+ | `@yolk-sdk/harness` | Tiny root |
19
+ | `@yolk-sdk/harness/coordinator` | Process-local doorbell coordinator |
20
+ | `@yolk-sdk/harness/store` | `RunStore` claim/release |
21
+ | `@yolk-sdk/harness/inbox` | Admission / steer / queue items; in-memory HITL park tokens (not durable) |
22
+ | `@yolk-sdk/harness/driver` | `Driver` + `makeHarness` |
23
+ | `@yolk-sdk/harness/driver/memory` | In-memory driver for tests |
24
+ | `@yolk-sdk/harness/driver/durable-object` | Durable Object storage-backed claims + driver |
25
+ | `@yolk-sdk/harness/outcome` | Classify one model/tool attempt: Completed / Retry / Continue / RecoverFull / Compacted / AwaitingInput |
26
+
27
+ ## Tagged constructors
28
+
29
+ Inbox, driver, and outcome ADTs are `Data.taggedEnum` **values** on the existing subpaths. They are
30
+ plain objects with `_tag` last, not `Equal`/`Hash` Data classes.
31
+
32
+ ```ts
33
+ import {
34
+ DrainBegin,
35
+ HitlDecision,
36
+ PauseDecision,
37
+ RecoveryAdmission,
38
+ RecoveryAttempt
39
+ } from '@yolk-sdk/harness/inbox'
40
+ import { StopDecision } from '@yolk-sdk/harness/driver'
41
+ import { HitlMatch, OverflowCompactionResult, StepOutcome } from '@yolk-sdk/harness/outcome'
42
+ ```
43
+
44
+ | Subpath | Value constructors |
45
+ | --------- | ------------------------------------------------------------------------------------- |
46
+ | `inbox` | `HitlDecision`, `PauseDecision`, `RecoveryAttempt`, `RecoveryAdmission`, `DrainBegin` |
47
+ | `driver` | `StopDecision` (`Idle` / `Interrupted` / `ParkCleared`) |
48
+ | `outcome` | `OverflowCompactionResult`, `StepOutcome`, `HitlMatch` |
49
+
50
+ `StopDecision` is the same internal tagged enum, re-exported as `export type` + `export const` from
51
+ `./driver`. Driver recovery uses Inbox `RecoveryAttempt` (`Skip` / `Exhausted` / `Resume`). Prefer
52
+ `Match.tag` / `Predicate.isTagged` over `_tag ===`. `HitlMatch.Match({ requestId })` /
53
+ `HitlMatch.Mismatch()` replace handwritten `{ _tag: 'Match' | 'Mismatch' }` objects.
54
+
55
+ ## Example
56
+
57
+ Omitting `drain` uses a no-op (`Effect.void`): `driver.run` only claims and releases ownership. Attach host work with a custom drain. `Inbox.takePromotable` requires that drain's live `drainToken`.
58
+
59
+ `@yolk-sdk/harness` is introduced after `0.1.0-canary.76`. It is unavailable in `0.1.0-canary.76` and earlier. Choose a newer matching canary once available.
60
+
61
+ Setup:
62
+
63
+ ```bash
64
+ pnpm add @yolk-sdk/harness@canary effect@4.0.0
65
+ pnpm add -D tsx
66
+ ```
67
+
68
+ Filename: `drain-example.mts`
69
+
70
+ ```ts
71
+ import { Effect } from 'effect'
72
+ import { admit, Driver, type Drain } from '@yolk-sdk/harness/driver'
73
+ import { makeInMemoryHarnessLayer } from '@yolk-sdk/harness/driver/memory'
74
+ import { Inbox } from '@yolk-sdk/harness/inbox'
75
+
76
+ const drain: Drain = (runId, _force, scope, context) =>
77
+ Effect.gen(function* () {
78
+ const inbox = yield* Inbox
79
+ const item = yield* inbox.takePromotable(runId, scope, context.drainToken)
80
+ if (item === undefined) {
81
+ yield* Effect.sync(() => {
82
+ console.log(`no work for ${runId}`)
83
+ })
84
+ return
85
+ }
86
+ yield* Effect.sync(() => {
87
+ console.log(`drained ${item.id} for ${runId}`)
88
+ })
89
+ })
90
+
91
+ const program = Effect.gen(function* () {
92
+ yield* admit({
93
+ id: 'item_1',
94
+ runId: 'run_1',
95
+ delivery: 'input',
96
+ kind: 'input'
97
+ })
98
+ const driver = yield* Driver
99
+ yield* driver.awaitIdle('run_1')
100
+ }).pipe(Effect.provide(makeInMemoryHarnessLayer({ drain })))
101
+
102
+ await Effect.runPromise(program)
103
+ ```
104
+
105
+ Run:
106
+
107
+ ```bash
108
+ pnpm exec tsx drain-example.mts
109
+ ```
110
+
111
+ Expected output:
112
+
113
+ ```txt
114
+ drained item_1 for run_1
115
+ ```
116
+
117
+ Durable Object claim persistence requires host `load` / `save` callbacks on `makeDurableObjectDriverLayer`. That snapshot stores claimed ids and resume counts. Its Inbox is still in-memory and does not persist queued input, parked HITL, host closures, or transcript storage.
118
+
119
+ Hosts still own tools, prompts, auth, HITL payload persistence, and `'use workflow'` / `'use step'` files. Durable Object claims ship as `@yolk-sdk/harness/driver/durable-object`. There is no Vercel Workflow driver in this package and no hook registry or `World`.
120
+
121
+ `Driver.pause` / `resumeHitl` / `stop` compose HITL onto the existing coordinator. Inbox items have no payload. Protocol match helpers live in `@yolk-sdk/harness/outcome`. Parked waits are not durable and are not shutdown claims.
122
+
123
+ ## Token types
124
+
125
+ `DrainToken` and `ParkGeneration` are distinct branded string schemas/types exported from
126
+ `@yolk-sdk/harness/inbox`. Forward `context.drainToken` into dequeue/pause and the returned park
127
+ generation into HITL admission. Custom adapters and typed fixtures must now construct or decode
128
+ these types rather than return raw strings. JSON stays unchanged.
129
+
130
+ The brands are nominal only: even an empty or forged string can decode. Inbox still checks live
131
+ ownership and freshness; decoding does not authorize a token or make an old park restorable.
132
+
133
+ ## Restart contract
134
+
135
+ Ownership is per shared Driver instance. The host or platform supplies cross-process exclusivity and quiesces producers before closing that instance. Raw administrative `RunStore` mutations must be quiesced with Driver; runtime claim ownership belongs to Driver.
136
+
137
+ Startup is explicit: build the shared Driver/Inbox/Store, restore host-owned waiting checkpoints through `run` / `pause`, then call `Driver.resumeSuspended` before exposing ingress.
138
+
139
+ `resumeSuspended` is a finite current-snapshot sweep. Candidate IDs are hints, not durable incarnation IDs. Under the Inbox admission gate it rechecks live coordinator activity then the current claim, skips blocked parks, and charges a validated `maxResumeAttempts` budget before granting pending input. Stop with no newer intent skips; a later idle claimed run with the same id may recover the current host checkpoint only. The sweep does not wait for drain settlement while holding that gate.
140
+
141
+ `maxResumeAttempts` is a finite nonnegative safe integer. The default is 10. Exhausted eligible claims are released and those IDs are returned in `exhausted`. Active candidates and candidates with blocked parks are skipped and are not released. `0` schedules, drains, and increments nothing: the sweep still releases eligible candidates as exhausted. Zero is not a nonmutating pause that preserves those claims. Omitted args, whole options `undefined`, or explicit `undefined` `maxResumeAttempts` keeps Layer `E = never`. A numeric or `number | undefined` config types `InvalidMaxResumeAttempts` and fails Layer init for invalid values.
142
+
143
+ `wake` / `resumeSuspended` schedule work; `awaitIdle` is quiescence, not a success receipt. `run` observes start and settlement failures. A failed `started` claim must not automatically release a prior or never-acquired claim. Explicit user-terminal authority (`Driver.stop` or generic `interrupt` defaulting to `user`) still releases a leftover claim after that owner has actually settled. Shutdown interrupt keeps the claim.
144
+
145
+ ### HITL contract
146
+
147
+ - Hosts persist typed HITL payloads by `itemId`. Inbox stores only opaque generation, request ids, and response item ids.
148
+ - Resume requires the current park generation plus `outcome.matchHitlResponse` / `resumeHitlIfMatched` kind/request/tool-call identity. Mismatch never wakes.
149
+ - All sibling request ids must be answered before continuation. Partial accepts do not start a tool drain.
150
+ - Host drain receives `DrainContext.drainToken` and `readyResponses`. Pause with that token; do not park from a stale token. `Inbox.takePromotable(runId, scope, drainToken)` also requires that live token and dequeues only while it still owns the run. Missing, empty, inactive, stale, or wrong-run tokens return `undefined` and leave the queue unchanged. This canary signature is intentionally required; there is no optional/unchecked take.
151
+ - `Inbox.beginDrain(runId, scope)` and `wakeIfUnblocked(runId, scope, wake)` are scope-aware. Pending input subsumes steer. A steer drain does not consume a later input intent, so an input admitted after Coordinator captures steer still drains.
152
+ - A successful drain that leased a Ready generation acknowledges and clears those refs. Failure or interruption keeps them (at-least-once). Host side effects must be idempotent.
153
+ - Human pause releases the busy claim. User `stop` is terminal at the captured owner's settlement (including shutdown then user-stop). Shutdown interrupt keeps the claim. The Durable Object snapshot claim store does not make Inbox durable.
154
+ - After process restart, hosts enumerate their own persisted waiting checkpoints and rebuild a fresh park with the current drain token. Do not import old Inbox parks, drain tokens, or generation strings. Correlate later responses to the new park and host identity policy. Persisted partial responses may be re-admitted only after protocol/host validation against that fresh park.
155
+
156
+ ## Type imports (breaking)
157
+
158
+ Runtime/wire is unchanged. These public type exports were renamed; there are no `Shape` aliases.
159
+
160
+ ```ts
161
+ import type { RunStoreApi } from '@yolk-sdk/harness/store'
162
+ import type { InboxApi } from '@yolk-sdk/harness/inbox'
163
+ import type { DriverApi } from '@yolk-sdk/harness/driver'
164
+ ```
165
+
166
+ | Old type import | New type import |
167
+ | ---------------------------------------------- | --------------- |
168
+ | `RunStoreShape` from `@yolk-sdk/harness/store` | `RunStoreApi` |
169
+ | `InboxShape` from `@yolk-sdk/harness/inbox` | `InboxApi` |
170
+ | `DriverShape` from `@yolk-sdk/harness/driver` | `DriverApi` |
171
+
172
+ ## Owner layers
173
+
174
+ Each service is acquired through its owning static layer factory, which holds the real
175
+ construction logic. The historical `make*` factories keep their signatures and behavior
176
+ and delegate to these canonical owners:
177
+
178
+ | Canonical owner layer | Backward-compatible factory |
179
+ | ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
180
+ | `RunStore.inMemoryLayer()` / `snapshotLayer()` | `makeInMemoryRunStoreLayer` / `makeSnapshotRunStoreLayer` |
181
+ | `Inbox.layer()` | `makeInMemoryInboxLayer` |
182
+ | `RunCoordinator.layer()` (claims via `RunStore`) | `makeCoordinator` stays as the scoped doorbell factory |
183
+ | `Driver.layer()` (requires `RunStore` + `Inbox` + `RunCoordinator`) + `Driver.coordinatedLayer()` (default coordinator, lazy options) | `makeDriverLayer` delegates to `coordinatedLayer`, keeping the `RunStore` + `Inbox` requirement |
184
+
185
+ Every factory call builds fresh layers, so composed harnesses never share `Ref` state.
186
+ Within one composed harness, the driver and the merged output share a single store
187
+ instance. `InterruptReason` is owned by `@yolk-sdk/harness/coordinator` and re-exported
188
+ from `@yolk-sdk/harness/driver`.
189
+
190
+ ## Step outcomes
191
+
192
+ `attemptModelTurn` classifies **one `runModelTurn` invocation** after that stream's configured loop/provider retries. It does not add another retry loop or persist. Hosts that schedule physical attempts themselves should pass `LoopConfig.maxRetries: 0` and skip retry decorators.
193
+
194
+ `attemptToolBatch` sets `needsContinuation` when the batch executed calls (the next step is a model turn). `runToolBatch` fences all execution when any HITL request is pending, so `AwaitingInput` does not carry executed sibling calls.
195
+
196
+ | Owner | What it retries |
197
+ | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
198
+ | `@yolk-sdk/agent/loop` `run` / `runModelTurn` | Generic retryable LLM errors (`LoopConfig.maxRetries`) inside the invocation |
199
+ | `makeContextOverflowRetryProvider` | In-process overflow, silent compact-and-retry **once per provider stream**, and only before published output |
200
+ | Harness `Compacted` | Durable persist-then-retry. Host stores compacted messages plus `overflowCompactionAttempt` and re-enters the step |
201
+
202
+ Pass `overflowCompactionAttempt` (default `0`). A successful compact returns `attempt + 1`. Compact is allowed only while `attempt < 1`. Invalid/negative/nonfinite counts fail as `validation_error`. If the host omits or resets the count, there is no cross-invocation budget.
203
+
204
+ Do not persist `ContextTransformer` checkpoints. Do not wrap the provider in `makeContextOverflowRetryProvider` and also treat `Compacted` as durable compaction on the same path.
205
+
206
+ Incomplete streams (`LLMError.responseIssue === 'missing_done'`, still `invalid_response` / `retryable: false`) become `RecoverFull` before output or `Continue` after partial text/reasoning/completed calls. Content-filter and other nonretryable provider terminals stay failed. `onEvent` sink errors and compact/Abort errors are not classified as provider failures. `Continue` does not invent message IDs or execute tools.
@@ -0,0 +1,72 @@
1
+ import { RunStore } from "./store.mjs";
2
+ import { Context, Effect, Exit, Layer, Scope } from "effect";
3
+
4
+ //#region src/coordinator.d.ts
5
+ /** `"input"` subsumes `"steer"` when coalescing wakes. */
6
+ type Promotable = 'input' | 'steer';
7
+ /** Shutdown retains the claim; explicit user stop releases it after settlement. */
8
+ type InterruptReason = 'user' | 'shutdown';
9
+ type CapturedRun<E> = {
10
+ readonly _tag: 'Started';
11
+ readonly join: Effect.Effect<void, E>;
12
+ } | {
13
+ readonly _tag: 'Joined';
14
+ readonly join: Effect.Effect<void, E>;
15
+ } | {
16
+ readonly _tag: 'Stopping';
17
+ readonly awaitSettlement: Effect.Effect<void>;
18
+ };
19
+ type StopReceipt = {
20
+ readonly _tag: 'Idle';
21
+ } | {
22
+ readonly _tag: 'Interrupted';
23
+ } | {
24
+ readonly _tag: 'LiveStopping';
25
+ } | {
26
+ readonly _tag: 'Settling';
27
+ };
28
+ type Coordinator<Key, E, Reason = never> = {
29
+ readonly active: Effect.Effect<ReadonlySet<Key>>;
30
+ readonly isActive: (key: Key) => Effect.Effect<boolean>; /** Starts an execution while idle, or joins the active execution. */
31
+ readonly run: (key: Key) => Effect.Effect<void, E>;
32
+ /**
33
+ * Captures a run waiter without awaiting settlement.
34
+ * Idle starts force=true (`Started`); an active owner is joined (`Joined`),
35
+ * including during natural settlement. A stopping owner yields `Stopping` with a
36
+ * settlement waiter and no start.
37
+ */
38
+ readonly captureRun: (key: Key) => Effect.Effect<CapturedRun<E>>; /** Rings the doorbell: idle starts; active drains again before settling. */
39
+ readonly wake: (key: Key, scope?: Promotable) => Effect.Effect<void>;
40
+ /**
41
+ * Stops the active execution and clears its doorbell. No-op when idle.
42
+ * Resolves once the interruption request is delivered, not when cleanup settles.
43
+ * First accepted `reason` is kept; use `terminalStop` to escalate.
44
+ */
45
+ readonly interrupt: (key: Key, reason?: Reason, options?: {
46
+ readonly awaitSettlement?: boolean;
47
+ }) => Effect.Effect<boolean>;
48
+ /**
49
+ * Terminal stop receipt. Always clears pendingWake. Settling means the owner
50
+ * is already gone and the settled callback is chosen; Driver may release the
51
+ * claim under the Inbox gate. LiveStopping/Interrupted leave release to that
52
+ * owner's future settled callback. LiveStopping joins the same outstanding
53
+ * interruption request rather than forking a second one.
54
+ */
55
+ readonly terminalStop: (key: Key, reason: Reason) => Effect.Effect<StopReceipt>; /** Resolves once no execution is active. Never starts work. */
56
+ readonly awaitIdle: (key: Key) => Effect.Effect<void>;
57
+ };
58
+ declare const makeCoordinator: <Key, E, Reason = never>(options: {
59
+ readonly drain: (key: Key, force: boolean, scope: Promotable) => Effect.Effect<void, E>;
60
+ readonly started?: (key: Key) => Effect.Effect<void>;
61
+ readonly settled?: (key: Key, exit: Exit.Exit<void, E>, reason?: Reason) => Effect.Effect<void>;
62
+ }) => Effect.Effect<Coordinator<Key, E, Reason>, never, Scope.Scope>;
63
+ declare const RunCoordinator_base: Context.ServiceClass<RunCoordinator, "@yolk-sdk/harness/RunCoordinator", Coordinator<string, never, InterruptReason>>;
64
+ /** Owning coordinator layer, with per-acquisition claim receipts and settlement policy. */
65
+ declare class RunCoordinator extends RunCoordinator_base {
66
+ static layer: (options?: {
67
+ readonly drain?: (runId: string, force: boolean, scope: Promotable) => Effect.Effect<void>;
68
+ }) => Layer.Layer<RunCoordinator, never, RunStore>;
69
+ }
70
+ //#endregion
71
+ export { CapturedRun, Coordinator, InterruptReason, Promotable, RunCoordinator, StopReceipt, makeCoordinator };
72
+ //# sourceMappingURL=coordinator.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordinator.d.mts","names":[],"sources":["../src/coordinator.ts"],"mappings":";;;;;KAqBY,UAAA;AAKZ;AAAA,KAFY,eAAA;AAAA,KAEA,WAAA;EAAA,SACG,IAAA;EAAA,SAA0B,IAAA,EAAM,MAAA,CAAO,MAAA,OAAa,CAAA;AAAA;EAAA,SACpD,IAAA;EAAA,SAAyB,IAAA,EAAM,MAAA,CAAO,MAAA,OAAa,CAAA;AAAA;EAAA,SACnD,IAAA;EAAA,SAA2B,eAAA,EAAiB,MAAA,CAAO,MAAA;AAAA;AAAA,KAEtD,WAAA;EAAA,SACG,IAAA;AAAA;EAAA,SACA,IAAA;AAAA;EAAA,SACA,IAAA;AAAA;EAAA,SACA,IAAA;AAAA;AAAA,KAEH,WAAA;EAAA,SACD,MAAA,EAAQ,MAAA,CAAO,MAAA,CAAO,WAAA,CAAY,GAAA;EAAA,SAClC,QAAA,GAAW,GAAA,EAAK,GAAA,KAAQ,MAAA,CAAO,MAAA,WAVwB;EAAA,SAYvD,GAAA,GAAM,GAAA,EAAK,GAAA,KAAQ,MAAA,CAAO,MAAA,OAAa,CAAA;EAZsB;AAExE;;;;;EAFwE,SAmB7D,UAAA,GAAa,GAAA,EAAK,GAAA,KAAQ,MAAA,CAAO,MAAA,CAAO,WAAA,CAAY,CAAA,IAdhD;EAAA,SAgBJ,IAAA,GAAO,GAAA,EAAK,GAAA,EAAK,KAAA,GAAQ,UAAA,KAAe,MAAA,CAAO,MAAA;EAfvC;AAAA;AAEnB;;;EAFmB,SAqBR,SAAA,GACP,GAAA,EAAK,GAAA,EACL,MAAA,GAAS,MAAA,EACT,OAAA;IAAA,SAAqB,eAAA;EAAA,MAClB,MAAA,CAAO,MAAA;EArBa;;;;;;;EAAA,SA6BhB,YAAA,GAAe,GAAA,EAAK,GAAA,EAAK,MAAA,EAAQ,MAAA,KAAW,MAAA,CAAO,MAAA,CAAO,WAAA,GApBhC;EAAA,SAsB1B,SAAA,GAAY,GAAA,EAAK,GAAA,KAAQ,MAAA,CAAO,MAAA;AAAA;AAAA,cAgB9B,eAAA,2BAA2C,OAAA;EAAA,SAC7C,KAAA,GAAQ,GAAA,EAAK,GAAA,EAAK,KAAA,WAAgB,KAAA,EAAO,UAAA,KAAe,MAAA,CAAO,MAAA,OAAa,CAAA;EAAA,SAC5E,OAAA,IAAW,GAAA,EAAK,GAAA,KAAQ,MAAA,CAAO,MAAA;EAAA,SAC/B,OAAA,IAAW,GAAA,EAAK,GAAA,EAAK,IAAA,EAAM,IAAA,CAAK,IAAA,OAAW,CAAA,GAAI,MAAA,GAAS,MAAA,KAAW,MAAA,CAAO,MAAA;AAAA,MACjF,MAAA,CAAO,MAAA,CAAO,WAAA,CAAY,GAAA,EAAK,CAAA,EAAG,MAAA,UAAgB,KAAA,CAAM,KAAA;AAAA,cAyMxD,mBAAA;;cAGS,cAAA,SAAuB,mBAAA;EAAA,OAI3B,KAAA,GAAS,OAAA;IAAA,SACL,KAAA,IAAS,KAAA,UAAe,KAAA,WAAgB,KAAA,EAAO,UAAA,KAAe,MAAA,CAAO,MAAA;EAAA,MAC5E,KAAA,CAAM,KAAA,CAAM,cAAA,SAAuB,QAAA;AAAA"}
@@ -0,0 +1,154 @@
1
+ import { StopReceipt } from "./outcome-constructors-internal.mjs";
2
+ import { RunStore } from "./store.mjs";
3
+ import { Cause, Context, Deferred, Effect, Exit, Fiber, FiberSet, Layer } from "effect";
4
+ //#region src/coordinator.ts
5
+ /**
6
+ * Process-local run coordinator.
7
+ *
8
+ * Port of OpenCode v2 `SessionRunCoordinator` (MIT): one busy period per key,
9
+ * a doorbell that coalesces wakes, and interrupt that claims pending wakes so a
10
+ * dead intent cannot restart. Steering lands at the next drain boundary.
11
+ *
12
+ * @see https://github.com/sst/opencode (packages/core/src/session/run-coordinator.ts)
13
+ */
14
+ const awaitDone = (done) => Deferred.await(done).pipe(Effect.flatMap((exit) => exit));
15
+ const widerScope = (current, next) => current === "input" || next === "input" ? "input" : "steer";
16
+ const makeCoordinator = (options) => Effect.gen(function* () {
17
+ const executions = /* @__PURE__ */ new Map();
18
+ const fork = yield* FiberSet.makeRuntime();
19
+ const loop = (key, execution, force) => Effect.suspend(() => {
20
+ if (execution.stopping) return Effect.void;
21
+ return options.drain(key, force, execution.scope);
22
+ }).pipe(Effect.andThen(Effect.suspend(() => {
23
+ if (execution.stopping || execution.pendingWake === void 0) return Effect.void;
24
+ execution.scope = execution.pendingWake;
25
+ execution.pendingWake = void 0;
26
+ return Effect.yieldNow.pipe(Effect.andThen(loop(key, execution, false)));
27
+ })));
28
+ const settle = (key, execution, exit) => {
29
+ if (execution.pendingWake !== void 0) start(key, false, execution.pendingWake);
30
+ else executions.delete(key);
31
+ return Deferred.succeed(execution.done, exit).pipe(Effect.asVoid);
32
+ };
33
+ const start = (key, force, scope) => {
34
+ const execution = {
35
+ done: Deferred.makeUnsafe(),
36
+ scope,
37
+ stopping: false
38
+ };
39
+ executions.set(key, execution);
40
+ execution.owner = fork(Effect.yieldNow.pipe(Effect.andThen(Effect.uninterruptible(Effect.suspend(() => options.started?.(key) ?? Effect.void))), Effect.andThen(loop(key, execution, force)), Effect.onExit((exit) => Effect.suspend(() => {
41
+ execution.owner = void 0;
42
+ return options.settled?.(key, exit, execution.interruptionReason) ?? Effect.void;
43
+ })), Effect.onExit((exit) => settle(key, execution, exit)), Effect.exit, Effect.asVoid));
44
+ return execution;
45
+ };
46
+ const forkInterruptionRequest = (owner) => fork(Effect.yieldNow.pipe(Effect.andThen(Effect.sync(() => owner.interruptUnsafe()))));
47
+ const interruptNow = (key, reason) => Effect.suspend(() => {
48
+ const execution = executions.get(key);
49
+ if (execution === void 0 || execution.stopping) return Effect.succeed(false);
50
+ if (execution.owner === void 0) {
51
+ execution.pendingWake = void 0;
52
+ return Effect.succeed(false);
53
+ }
54
+ const owner = execution.owner;
55
+ execution.stopping = true;
56
+ execution.pendingWake = void 0;
57
+ execution.interruptionReason = reason;
58
+ const request = forkInterruptionRequest(owner);
59
+ execution.request = request;
60
+ return Fiber.join(request).pipe(Effect.as(true));
61
+ });
62
+ const awaitIdle = (key) => Effect.suspend(() => {
63
+ const execution = executions.get(key);
64
+ if (execution === void 0) return Effect.void;
65
+ return awaitDone(execution.done).pipe(Effect.ignoreCause, Effect.andThen(awaitIdle(key)));
66
+ });
67
+ return {
68
+ active: Effect.sync(() => new Set(executions.keys())),
69
+ isActive: (key) => Effect.sync(() => executions.has(key)),
70
+ run: (key) => Effect.suspend(() => {
71
+ const execution = executions.get(key);
72
+ if (execution === void 0) return awaitDone(start(key, true, "input").done);
73
+ if (!execution.stopping) return awaitDone(execution.done);
74
+ return awaitDone(execution.done).pipe(Effect.ignoreCause, Effect.andThen(Effect.suspend(() => {
75
+ const next = executions.get(key);
76
+ return next === void 0 ? awaitDone(start(key, true, "input").done) : awaitDone(next.done);
77
+ })));
78
+ }),
79
+ captureRun: (key) => Effect.sync(() => {
80
+ const execution = executions.get(key);
81
+ if (execution === void 0) return {
82
+ _tag: "Started",
83
+ join: awaitDone(start(key, true, "input").done)
84
+ };
85
+ if (!execution.stopping) return {
86
+ _tag: "Joined",
87
+ join: awaitDone(execution.done)
88
+ };
89
+ return {
90
+ _tag: "Stopping",
91
+ awaitSettlement: awaitDone(execution.done).pipe(Effect.ignoreCause)
92
+ };
93
+ }),
94
+ wake: (key, scope = "input") => Effect.sync(() => {
95
+ const execution = executions.get(key);
96
+ if (execution !== void 0) {
97
+ execution.pendingWake = widerScope(execution.pendingWake, scope);
98
+ return;
99
+ }
100
+ start(key, false, scope);
101
+ }),
102
+ interrupt: (key, reason, options) => Effect.suspend(() => {
103
+ const execution = executions.get(key);
104
+ return interruptNow(key, reason).pipe(Effect.tap(() => options?.awaitSettlement === true && execution !== void 0 ? awaitDone(execution.done).pipe(Effect.ignoreCause) : Effect.void));
105
+ }),
106
+ terminalStop: (key, reason) => Effect.suspend(() => {
107
+ const execution = executions.get(key);
108
+ if (execution === void 0) return Effect.succeed(StopReceipt.Idle());
109
+ execution.pendingWake = void 0;
110
+ if (execution.owner === void 0) {
111
+ execution.stopping = true;
112
+ return Effect.succeed(StopReceipt.Settling());
113
+ }
114
+ execution.interruptionReason = reason;
115
+ if (execution.stopping) {
116
+ const request = execution.request;
117
+ return request === void 0 ? Effect.succeed(StopReceipt.LiveStopping()) : Fiber.join(request).pipe(Effect.as(StopReceipt.LiveStopping()));
118
+ }
119
+ const owner = execution.owner;
120
+ execution.stopping = true;
121
+ const request = forkInterruptionRequest(owner);
122
+ execution.request = request;
123
+ return Fiber.join(request).pipe(Effect.as(StopReceipt.Interrupted()));
124
+ }),
125
+ awaitIdle
126
+ };
127
+ });
128
+ /** Owning coordinator layer, with per-acquisition claim receipts and settlement policy. */
129
+ var RunCoordinator = class extends Context.Service()("@yolk-sdk/harness/RunCoordinator") {
130
+ static layer = (options) => Layer.effect(this, Effect.gen(function* () {
131
+ const store = yield* RunStore;
132
+ const drain = options?.drain ?? ((_runId, _force, _scope) => Effect.void);
133
+ const acquired = /* @__PURE__ */ new Set();
134
+ return yield* makeCoordinator({
135
+ drain,
136
+ started: (runId) => store.claim(runId).pipe(Effect.tap(() => Effect.sync(() => {
137
+ acquired.add(runId);
138
+ }))),
139
+ settled: (runId, exit, reason) => Effect.suspend(() => {
140
+ const owned = acquired.delete(runId);
141
+ if (owned && (reason === "user" || reason === void 0 && !Exit.hasInterrupts(exit))) return store.release(runId);
142
+ if (reason === "user" && !owned) return store.isClaimed(runId).pipe(Effect.flatMap((claimed) => claimed ? store.release(runId) : Effect.void));
143
+ return Effect.void;
144
+ }).pipe(Effect.exit, Effect.flatMap((settledExit) => {
145
+ if (Exit.isSuccess(settledExit)) return Effect.void;
146
+ return Effect.failCause(Exit.isFailure(exit) ? Cause.combine(exit.cause, settledExit.cause) : settledExit.cause);
147
+ }))
148
+ });
149
+ }));
150
+ };
151
+ //#endregion
152
+ export { RunCoordinator, makeCoordinator };
153
+
154
+ //# sourceMappingURL=coordinator.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordinator.mjs","names":["stopReceipt"],"sources":["../src/coordinator.ts"],"sourcesContent":["/**\n * Process-local run coordinator.\n *\n * Port of OpenCode v2 `SessionRunCoordinator` (MIT): one busy period per key,\n * a doorbell that coalesces wakes, and interrupt that claims pending wakes so a\n * dead intent cannot restart. Steering lands at the next drain boundary.\n *\n * @see https://github.com/sst/opencode (packages/core/src/session/run-coordinator.ts)\n */\nimport { Cause, Context, Deferred, Effect, Exit, Fiber, FiberSet, Layer } from 'effect'\nimport type { Scope } from 'effect'\nimport { StopReceipt as stopReceipt } from './outcome-constructors-internal.ts'\nimport { RunStore } from './store.ts'\n\n// Private settlement receipt: succeed the Deferred with Exit as a value so\n// interrupt fan-out does not skip Effect 4.0.0-beta.80 Deferred listeners.\n// Public waiters flatten that Exit; this is not a global Deferred fix.\nconst awaitDone = <E>(done: Deferred.Deferred<Exit.Exit<void, E>>): Effect.Effect<void, E> =>\n Deferred.await(done).pipe(Effect.flatMap(exit => exit))\n\n/** `\"input\"` subsumes `\"steer\"` when coalescing wakes. */\nexport type Promotable = 'input' | 'steer'\n\n/** Shutdown retains the claim; explicit user stop releases it after settlement. */\nexport type InterruptReason = 'user' | 'shutdown'\n\nexport type CapturedRun<E> =\n | { readonly _tag: 'Started'; readonly join: Effect.Effect<void, E> }\n | { readonly _tag: 'Joined'; readonly join: Effect.Effect<void, E> }\n | { readonly _tag: 'Stopping'; readonly awaitSettlement: Effect.Effect<void> }\n\nexport type StopReceipt =\n | { readonly _tag: 'Idle' }\n | { readonly _tag: 'Interrupted' }\n | { readonly _tag: 'LiveStopping' }\n | { readonly _tag: 'Settling' }\n\nexport type Coordinator<Key, E, Reason = never> = {\n readonly active: Effect.Effect<ReadonlySet<Key>>\n readonly isActive: (key: Key) => Effect.Effect<boolean>\n /** Starts an execution while idle, or joins the active execution. */\n readonly run: (key: Key) => Effect.Effect<void, E>\n /**\n * Captures a run waiter without awaiting settlement.\n * Idle starts force=true (`Started`); an active owner is joined (`Joined`),\n * including during natural settlement. A stopping owner yields `Stopping` with a\n * settlement waiter and no start.\n */\n readonly captureRun: (key: Key) => Effect.Effect<CapturedRun<E>>\n /** Rings the doorbell: idle starts; active drains again before settling. */\n readonly wake: (key: Key, scope?: Promotable) => Effect.Effect<void>\n /**\n * Stops the active execution and clears its doorbell. No-op when idle.\n * Resolves once the interruption request is delivered, not when cleanup settles.\n * First accepted `reason` is kept; use `terminalStop` to escalate.\n */\n readonly interrupt: (\n key: Key,\n reason?: Reason,\n options?: { readonly awaitSettlement?: boolean }\n ) => Effect.Effect<boolean>\n /**\n * Terminal stop receipt. Always clears pendingWake. Settling means the owner\n * is already gone and the settled callback is chosen; Driver may release the\n * claim under the Inbox gate. LiveStopping/Interrupted leave release to that\n * owner's future settled callback. LiveStopping joins the same outstanding\n * interruption request rather than forking a second one.\n */\n readonly terminalStop: (key: Key, reason: Reason) => Effect.Effect<StopReceipt>\n /** Resolves once no execution is active. Never starts work. */\n readonly awaitIdle: (key: Key) => Effect.Effect<void>\n}\n\ntype Execution<E, Reason> = {\n readonly done: Deferred.Deferred<Exit.Exit<void, E>>\n owner?: Fiber.Fiber<void>\n request?: Fiber.Fiber<void>\n scope: Promotable\n pendingWake?: Promotable\n stopping: boolean\n interruptionReason?: Reason\n}\n\nconst widerScope = (current: Promotable | undefined, next: Promotable): Promotable =>\n current === 'input' || next === 'input' ? 'input' : 'steer'\n\nexport const makeCoordinator = <Key, E, Reason = never>(options: {\n readonly drain: (key: Key, force: boolean, scope: Promotable) => Effect.Effect<void, E>\n readonly started?: (key: Key) => Effect.Effect<void>\n readonly settled?: (key: Key, exit: Exit.Exit<void, E>, reason?: Reason) => Effect.Effect<void>\n}): Effect.Effect<Coordinator<Key, E, Reason>, never, Scope.Scope> =>\n Effect.gen(function* () {\n const executions = new Map<Key, Execution<E, Reason>>()\n const fork = yield* FiberSet.makeRuntime<never, void, never>()\n\n const loop = (\n key: Key,\n execution: Execution<E, Reason>,\n force: boolean\n ): Effect.Effect<void, E> =>\n Effect.suspend(() => {\n if (execution.stopping) return Effect.void\n\n return options.drain(key, force, execution.scope)\n }).pipe(\n Effect.andThen(\n Effect.suspend(() => {\n if (execution.stopping || execution.pendingWake === undefined) return Effect.void\n execution.scope = execution.pendingWake\n execution.pendingWake = undefined\n\n return Effect.yieldNow.pipe(Effect.andThen(loop(key, execution, false)))\n })\n )\n )\n\n const settle = (key: Key, execution: Execution<E, Reason>, exit: Exit.Exit<void, E>) => {\n if (execution.pendingWake !== undefined) start(key, false, execution.pendingWake)\n else executions.delete(key)\n\n return Deferred.succeed(execution.done, exit).pipe(Effect.asVoid)\n }\n\n const start = (key: Key, force: boolean, scope: Promotable) => {\n const execution: Execution<E, Reason> = {\n done: Deferred.makeUnsafe<Exit.Exit<void, E>>(),\n scope,\n stopping: false\n }\n\n executions.set(key, execution)\n execution.owner = fork(\n Effect.yieldNow.pipe(\n Effect.andThen(\n Effect.uninterruptible(Effect.suspend(() => options.started?.(key) ?? Effect.void))\n ),\n Effect.andThen(loop(key, execution, force)),\n Effect.onExit(exit =>\n Effect.suspend(() => {\n execution.owner = undefined\n\n return options.settled?.(key, exit, execution.interruptionReason) ?? Effect.void\n })\n ),\n Effect.onExit(exit => settle(key, execution, exit)),\n Effect.exit,\n Effect.asVoid\n )\n )\n\n return execution\n }\n\n const forkInterruptionRequest = (owner: Fiber.Fiber<void>): Fiber.Fiber<void> =>\n fork(Effect.yieldNow.pipe(Effect.andThen(Effect.sync(() => owner.interruptUnsafe()))))\n\n const interruptNow = (key: Key, reason?: Reason): Effect.Effect<boolean> =>\n Effect.suspend(() => {\n const execution = executions.get(key)\n\n if (execution === undefined || execution.stopping) return Effect.succeed(false)\n\n if (execution.owner === undefined) {\n execution.pendingWake = undefined\n\n return Effect.succeed(false)\n }\n\n const owner = execution.owner\n execution.stopping = true\n execution.pendingWake = undefined\n execution.interruptionReason = reason\n // Capture the exact old owner and fork the request in this same lazy\n // transition so cancellation cannot leave stopping=true with no request.\n // Initial yield breaks owner -> resumed caller -> owner reentrancy.\n // Join the request fiber only; interruptUnsafe may run interruptible\n // finalizers inline until they suspend. Do not await owner settlement.\n const request = forkInterruptionRequest(owner)\n execution.request = request\n\n return Fiber.join(request).pipe(Effect.as(true))\n })\n\n const awaitIdle = (key: Key): Effect.Effect<void> =>\n Effect.suspend(() => {\n const execution = executions.get(key)\n\n if (execution === undefined) return Effect.void\n\n return awaitDone(execution.done).pipe(Effect.ignoreCause, Effect.andThen(awaitIdle(key)))\n })\n\n return {\n active: Effect.sync(() => new Set(executions.keys())),\n isActive: key => Effect.sync(() => executions.has(key)),\n run: key =>\n Effect.suspend(() => {\n const execution = executions.get(key)\n\n if (execution === undefined) return awaitDone(start(key, true, 'input').done)\n\n if (!execution.stopping) return awaitDone(execution.done)\n\n return awaitDone(execution.done).pipe(\n Effect.ignoreCause,\n Effect.andThen(\n Effect.suspend(() => {\n const next = executions.get(key)\n\n return next === undefined\n ? awaitDone(start(key, true, 'input').done)\n : awaitDone(next.done)\n })\n )\n )\n }),\n captureRun: key =>\n Effect.sync(() => {\n const execution = executions.get(key)\n\n if (execution === undefined) {\n const started = start(key, true, 'input')\n\n return { _tag: 'Started' as const, join: awaitDone(started.done) }\n }\n\n if (!execution.stopping) {\n return { _tag: 'Joined' as const, join: awaitDone(execution.done) }\n }\n\n return {\n _tag: 'Stopping' as const,\n awaitSettlement: awaitDone(execution.done).pipe(Effect.ignoreCause)\n }\n }),\n wake: (key, scope = 'input') =>\n Effect.sync(() => {\n const execution = executions.get(key)\n\n if (execution !== undefined) {\n execution.pendingWake = widerScope(execution.pendingWake, scope)\n\n return\n }\n\n start(key, false, scope)\n }),\n interrupt: (key, reason, options) =>\n Effect.suspend(() => {\n const execution = executions.get(key)\n\n return interruptNow(key, reason).pipe(\n Effect.tap(() =>\n options?.awaitSettlement === true && execution !== undefined\n ? awaitDone(execution.done).pipe(Effect.ignoreCause)\n : Effect.void\n )\n )\n }),\n terminalStop: (key, reason) =>\n Effect.suspend((): Effect.Effect<StopReceipt> => {\n const execution = executions.get(key)\n\n if (execution === undefined) return Effect.succeed(stopReceipt.Idle())\n execution.pendingWake = undefined\n\n if (execution.owner === undefined) {\n execution.stopping = true\n\n return Effect.succeed(stopReceipt.Settling())\n }\n\n execution.interruptionReason = reason\n\n if (execution.stopping) {\n const request = execution.request\n\n return request === undefined\n ? Effect.succeed(stopReceipt.LiveStopping())\n : Fiber.join(request).pipe(Effect.as(stopReceipt.LiveStopping()))\n }\n\n const owner = execution.owner\n execution.stopping = true\n const request = forkInterruptionRequest(owner)\n execution.request = request\n\n return Fiber.join(request).pipe(Effect.as(stopReceipt.Interrupted()))\n }),\n awaitIdle\n }\n })\n\n/** Owning coordinator layer, with per-acquisition claim receipts and settlement policy. */\nexport class RunCoordinator extends Context.Service<\n RunCoordinator,\n Coordinator<string, never, InterruptReason>\n>()('@yolk-sdk/harness/RunCoordinator') {\n static layer = (options?: {\n readonly drain?: (runId: string, force: boolean, scope: Promotable) => Effect.Effect<void>\n }): Layer.Layer<RunCoordinator, never, RunStore> =>\n Layer.effect(\n this,\n Effect.gen(function* () {\n const store = yield* RunStore\n const drain = options?.drain ?? ((_runId, _force, _scope) => Effect.void)\n const acquired = new Set<string>()\n\n return yield* makeCoordinator<string, never, InterruptReason>({\n drain,\n started: runId =>\n store.claim(runId).pipe(\n Effect.tap(() =>\n Effect.sync(() => {\n acquired.add(runId)\n })\n )\n ),\n settled: (runId, exit, reason) =>\n Effect.suspend(() => {\n const owned = acquired.delete(runId)\n\n if (\n owned &&\n (reason === 'user' || (reason === undefined && !Exit.hasInterrupts(exit)))\n ) {\n return store.release(runId)\n }\n\n // Acquisition receipt guards automatic failed-start settlement only.\n // Explicit user-terminal authority still releases a leftover claim\n // after this owner has actually settled, matching Idle/Settling stop.\n if (reason === 'user' && !owned) {\n return store\n .isClaimed(runId)\n .pipe(Effect.flatMap(claimed => (claimed ? store.release(runId) : Effect.void)))\n }\n\n return Effect.void\n }).pipe(\n Effect.exit,\n Effect.flatMap(settledExit => {\n if (Exit.isSuccess(settledExit)) return Effect.void\n\n return Effect.failCause(\n Exit.isFailure(exit)\n ? Cause.combine(exit.cause, settledExit.cause)\n : settledExit.cause\n )\n })\n )\n })\n })\n )\n}\n"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { DurableRunStoreSnapshot, RunStore } from "../store.mjs";
2
+ import { Inbox } from "../inbox.mjs";
3
+ import { Drain, Driver, InvalidMaxResumeAttempts } from "../driver.mjs";
4
+ import { Effect, Layer } from "effect";
5
+
6
+ //#region src/driver/durable-object.d.ts
7
+ type DurableObjectDriverBase = {
8
+ readonly load: Effect.Effect<DurableRunStoreSnapshot | undefined>;
9
+ readonly save: (snapshot: DurableRunStoreSnapshot) => Effect.Effect<void>;
10
+ readonly drain?: Drain;
11
+ };
12
+ declare function makeDurableObjectDriverLayer(options: DurableObjectDriverBase & {
13
+ readonly maxResumeAttempts?: undefined;
14
+ }): Layer.Layer<Driver | RunStore | Inbox>;
15
+ declare function makeDurableObjectDriverLayer(options: DurableObjectDriverBase & {
16
+ readonly maxResumeAttempts?: number;
17
+ }): Layer.Layer<Driver | RunStore | Inbox, InvalidMaxResumeAttempts>;
18
+ //#endregion
19
+ export { type DurableRunStoreSnapshot, makeDurableObjectDriverLayer };
20
+ //# sourceMappingURL=durable-object.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-object.d.mts","names":[],"sources":["../../src/driver/durable-object.ts"],"mappings":";;;;;;KAQK,uBAAA;EAAA,SACM,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,uBAAA;EAAA,SACpB,IAAA,GAAO,QAAA,EAAU,uBAAA,KAA4B,MAAA,CAAO,MAAA;EAAA,SACpD,KAAA,GAAQ,KAAA;AAAA;AAAA,iBAGH,4BAAA,CACd,OAAA,EAAS,uBAAA;EAAA,SAAqC,iBAAA;AAAA,IAC7C,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,QAAA,GAAW,KAAA;AAAA,iBACnB,4BAAA,CACd,OAAA,EAAS,uBAAA;EAAA,SAAqC,iBAAA;AAAA,IAC7C,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,QAAA,GAAW,KAAA,EAAO,wBAAA"}
@@ -0,0 +1,12 @@
1
+ import { RunStore } from "../store.mjs";
2
+ import { Inbox } from "../inbox.mjs";
3
+ import { Driver } from "../driver.mjs";
4
+ import { Layer } from "effect";
5
+ //#region src/driver/durable-object.ts
6
+ function makeDurableObjectDriverLayer(options) {
7
+ return Driver.coordinatedLayer(options).pipe(Layer.provideMerge(RunStore.snapshotLayer(options)), Layer.provideMerge(Inbox.layer()));
8
+ }
9
+ //#endregion
10
+ export { makeDurableObjectDriverLayer };
11
+
12
+ //# sourceMappingURL=durable-object.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-object.mjs","names":[],"sources":["../../src/driver/durable-object.ts"],"sourcesContent":["import { Layer } from 'effect'\nimport type { Effect } from 'effect'\nimport { Driver, type Drain, type InvalidMaxResumeAttempts } from '../driver.ts'\nimport { Inbox } from '../inbox.ts'\nimport { RunStore, type DurableRunStoreSnapshot } from '../store.ts'\n\nexport type { DurableRunStoreSnapshot }\n\ntype DurableObjectDriverBase = {\n readonly load: Effect.Effect<DurableRunStoreSnapshot | undefined>\n readonly save: (snapshot: DurableRunStoreSnapshot) => Effect.Effect<void>\n readonly drain?: Drain\n}\n\nexport function makeDurableObjectDriverLayer(\n options: DurableObjectDriverBase & { readonly maxResumeAttempts?: undefined }\n): Layer.Layer<Driver | RunStore | Inbox>\nexport function makeDurableObjectDriverLayer(\n options: DurableObjectDriverBase & { readonly maxResumeAttempts?: number }\n): Layer.Layer<Driver | RunStore | Inbox, InvalidMaxResumeAttempts>\nexport function makeDurableObjectDriverLayer(\n options: DurableObjectDriverBase & { readonly maxResumeAttempts?: number }\n) {\n return Driver.coordinatedLayer(options).pipe(\n Layer.provideMerge(RunStore.snapshotLayer(options)),\n Layer.provideMerge(Inbox.layer())\n )\n}\n"],"mappings":";;;;;AAoBA,SAAgB,6BACd,SACA;CACA,OAAO,OAAO,iBAAiB,OAAO,EAAE,KACtC,MAAM,aAAa,SAAS,cAAc,OAAO,CAAC,GAClD,MAAM,aAAa,MAAM,MAAM,CAAC,CAClC;AACF"}
@@ -0,0 +1,21 @@
1
+ import { RunStore } from "../store.mjs";
2
+ import { Inbox } from "../inbox.mjs";
3
+ import { Drain, Driver, DriverLayerOptions, InvalidMaxResumeAttempts } from "../driver.mjs";
4
+ import { Layer } from "effect";
5
+
6
+ //#region src/driver/memory.d.ts
7
+ declare function makeInMemoryDriverLayer(): Layer.Layer<Driver, never, RunStore | Inbox>;
8
+ declare function makeInMemoryDriverLayer(options?: {
9
+ readonly drain?: Drain;
10
+ readonly maxResumeAttempts?: undefined;
11
+ }): Layer.Layer<Driver, never, RunStore | Inbox>;
12
+ declare function makeInMemoryDriverLayer(options?: DriverLayerOptions): Layer.Layer<Driver, InvalidMaxResumeAttempts, RunStore | Inbox>;
13
+ declare function makeInMemoryHarnessLayer(): Layer.Layer<Driver | RunStore | Inbox>;
14
+ declare function makeInMemoryHarnessLayer(options?: {
15
+ readonly drain?: Drain;
16
+ readonly maxResumeAttempts?: undefined;
17
+ }): Layer.Layer<Driver | RunStore | Inbox>;
18
+ declare function makeInMemoryHarnessLayer(options?: DriverLayerOptions): Layer.Layer<Driver | RunStore | Inbox, InvalidMaxResumeAttempts>;
19
+ //#endregion
20
+ export { makeInMemoryDriverLayer, makeInMemoryHarnessLayer };
21
+ //# sourceMappingURL=memory.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.mts","names":[],"sources":["../../src/driver/memory.ts"],"mappings":";;;;;;iBAUgB,uBAAA,CAAA,GAA2B,KAAA,CAAM,KAAA,CAAM,MAAA,SAAe,QAAA,GAAW,KAAA;AAAA,iBACjE,uBAAA,CAAwB,OAAA;EAAA,SAC7B,KAAA,GAAQ,KAAA;EAAA,SACR,iBAAA;AAAA,IACP,KAAA,CAAM,KAAA,CAAM,MAAA,SAAe,QAAA,GAAW,KAAA;AAAA,iBAC1B,uBAAA,CACd,OAAA,GAAU,kBAAA,GACT,KAAA,CAAM,KAAA,CAAM,MAAA,EAAQ,wBAAA,EAA0B,QAAA,GAAW,KAAA;AAAA,iBAK5C,wBAAA,CAAA,GAA4B,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,QAAA,GAAW,KAAA;AAAA,iBAC5D,wBAAA,CAAyB,OAAA;EAAA,SAC9B,KAAA,GAAQ,KAAA;EAAA,SACR,iBAAA;AAAA,IACP,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,QAAA,GAAW,KAAA;AAAA,iBACpB,wBAAA,CACd,OAAA,GAAU,kBAAA,GACT,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,QAAA,GAAW,KAAA,EAAO,wBAAA"}
@@ -0,0 +1,15 @@
1
+ import { RunStore } from "../store.mjs";
2
+ import { Inbox } from "../inbox.mjs";
3
+ import { Driver } from "../driver.mjs";
4
+ import { Layer } from "effect";
5
+ //#region src/driver/memory.ts
6
+ function makeInMemoryDriverLayer(options) {
7
+ return options === void 0 ? Driver.coordinatedLayer() : Driver.coordinatedLayer(options);
8
+ }
9
+ function makeInMemoryHarnessLayer(options) {
10
+ return (options === void 0 ? Driver.coordinatedLayer() : Driver.coordinatedLayer(options)).pipe(Layer.provideMerge(RunStore.inMemoryLayer()), Layer.provideMerge(Inbox.layer()));
11
+ }
12
+ //#endregion
13
+ export { makeInMemoryDriverLayer, makeInMemoryHarnessLayer };
14
+
15
+ //# sourceMappingURL=memory.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.mjs","names":[],"sources":["../../src/driver/memory.ts"],"sourcesContent":["import { Layer } from 'effect'\nimport {\n Driver,\n type Drain,\n type DriverLayerOptions,\n type InvalidMaxResumeAttempts\n} from '../driver.ts'\nimport { Inbox } from '../inbox.ts'\nimport { RunStore } from '../store.ts'\n\nexport function makeInMemoryDriverLayer(): Layer.Layer<Driver, never, RunStore | Inbox>\nexport function makeInMemoryDriverLayer(options?: {\n readonly drain?: Drain\n readonly maxResumeAttempts?: undefined\n}): Layer.Layer<Driver, never, RunStore | Inbox>\nexport function makeInMemoryDriverLayer(\n options?: DriverLayerOptions\n): Layer.Layer<Driver, InvalidMaxResumeAttempts, RunStore | Inbox>\nexport function makeInMemoryDriverLayer(options?: DriverLayerOptions) {\n return options === undefined ? Driver.coordinatedLayer() : Driver.coordinatedLayer(options)\n}\n\nexport function makeInMemoryHarnessLayer(): Layer.Layer<Driver | RunStore | Inbox>\nexport function makeInMemoryHarnessLayer(options?: {\n readonly drain?: Drain\n readonly maxResumeAttempts?: undefined\n}): Layer.Layer<Driver | RunStore | Inbox>\nexport function makeInMemoryHarnessLayer(\n options?: DriverLayerOptions\n): Layer.Layer<Driver | RunStore | Inbox, InvalidMaxResumeAttempts>\nexport function makeInMemoryHarnessLayer(options?: DriverLayerOptions) {\n const driver =\n options === undefined ? Driver.coordinatedLayer() : Driver.coordinatedLayer(options)\n\n return driver.pipe(\n Layer.provideMerge(RunStore.inMemoryLayer()),\n Layer.provideMerge(Inbox.layer())\n )\n}\n"],"mappings":";;;;;AAkBA,SAAgB,wBAAwB,SAA8B;CACpE,OAAO,YAAY,KAAA,IAAY,OAAO,iBAAiB,IAAI,OAAO,iBAAiB,OAAO;AAC5F;AAUA,SAAgB,yBAAyB,SAA8B;CAIrE,QAFE,YAAY,KAAA,IAAY,OAAO,iBAAiB,IAAI,OAAO,iBAAiB,OAAO,GAEvE,KACZ,MAAM,aAAa,SAAS,cAAc,CAAC,GAC3C,MAAM,aAAa,MAAM,MAAM,CAAC,CAClC;AACF"}