@yolk-sdk/harness 0.1.0-canary.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,134 @@
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-beta.80
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
+ ## Example
28
+
29
+ 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`.
30
+
31
+ `@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.
32
+
33
+ Setup:
34
+
35
+ ```bash
36
+ pnpm add @yolk-sdk/harness@canary effect@4.0.0-beta.80
37
+ pnpm add -D tsx
38
+ ```
39
+
40
+ Filename: `drain-example.mts`
41
+
42
+ ```ts
43
+ import { Effect } from 'effect'
44
+ import { admit, Driver, type Drain } from '@yolk-sdk/harness/driver'
45
+ import { makeInMemoryHarnessLayer } from '@yolk-sdk/harness/driver/memory'
46
+ import { Inbox } from '@yolk-sdk/harness/inbox'
47
+
48
+ const drain: Drain = (runId, _force, scope, context) =>
49
+ Effect.gen(function* () {
50
+ const inbox = yield* Inbox
51
+ const item = yield* inbox.takePromotable(runId, scope, context.drainToken)
52
+ if (item === undefined) {
53
+ yield* Effect.sync(() => {
54
+ console.log(`no work for ${runId}`)
55
+ })
56
+ return
57
+ }
58
+ yield* Effect.sync(() => {
59
+ console.log(`drained ${item.id} for ${runId}`)
60
+ })
61
+ })
62
+
63
+ const program = Effect.gen(function* () {
64
+ yield* admit({
65
+ id: 'item_1',
66
+ runId: 'run_1',
67
+ delivery: 'input',
68
+ kind: 'input'
69
+ })
70
+ const driver = yield* Driver
71
+ yield* driver.awaitIdle('run_1')
72
+ }).pipe(Effect.provide(makeInMemoryHarnessLayer({ drain })))
73
+
74
+ await Effect.runPromise(program)
75
+ ```
76
+
77
+ Run:
78
+
79
+ ```bash
80
+ pnpm exec tsx drain-example.mts
81
+ ```
82
+
83
+ Expected output:
84
+
85
+ ```txt
86
+ drained item_1 for run_1
87
+ ```
88
+
89
+ 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.
90
+
91
+ 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`.
92
+
93
+ `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.
94
+
95
+ ## Restart contract
96
+
97
+ 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.
98
+
99
+ Startup is explicit: build the shared Driver/Inbox/Store, restore host-owned waiting checkpoints through `run` / `pause`, then call `Driver.resumeSuspended` before exposing ingress.
100
+
101
+ `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.
102
+
103
+ `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.
104
+
105
+ `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.
106
+
107
+ ### HITL contract
108
+
109
+ - Hosts persist typed HITL payloads by `itemId`. Inbox stores only opaque generation, request ids, and response item ids.
110
+ - Resume requires the current park generation plus `outcome.matchHitlResponse` / `resumeHitlIfMatched` kind/request/tool-call identity. Mismatch never wakes.
111
+ - All sibling request ids must be answered before continuation. Partial accepts do not start a tool drain.
112
+ - 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.
113
+ - `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.
114
+ - 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.
115
+ - 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.
116
+ - 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.
117
+
118
+ ## Step outcomes
119
+
120
+ `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.
121
+
122
+ `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.
123
+
124
+ | Owner | What it retries |
125
+ | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
126
+ | `@yolk-sdk/agent/loop` `run` / `runModelTurn` | Generic retryable LLM errors (`LoopConfig.maxRetries`) inside the invocation |
127
+ | `makeContextOverflowRetryProvider` | In-process overflow, silent compact-and-retry **once per provider stream**, and only before published output |
128
+ | Harness `Compacted` | Durable persist-then-retry. Host stores compacted messages plus `overflowCompactionAttempt` and re-enters the step |
129
+
130
+ 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.
131
+
132
+ Do not persist `ContextTransformer` checkpoints. Do not wrap the provider in `makeContextOverflowRetryProvider` and also treat `Compacted` as durable compaction on the same path.
133
+
134
+ 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,62 @@
1
+ import { Effect, Exit, Scope } from "effect";
2
+
3
+ //#region src/coordinator.d.ts
4
+ /** `"input"` subsumes `"steer"` when coalescing wakes. */
5
+ type Promotable = 'input' | 'steer';
6
+ type CapturedRun<E> = {
7
+ readonly _tag: 'Started';
8
+ readonly join: Effect.Effect<void, E>;
9
+ } | {
10
+ readonly _tag: 'Joined';
11
+ readonly join: Effect.Effect<void, E>;
12
+ } | {
13
+ readonly _tag: 'Stopping';
14
+ readonly awaitSettlement: Effect.Effect<void>;
15
+ };
16
+ type StopReceipt = {
17
+ readonly _tag: 'Idle';
18
+ } | {
19
+ readonly _tag: 'Interrupted';
20
+ } | {
21
+ readonly _tag: 'LiveStopping';
22
+ } | {
23
+ readonly _tag: 'Settling';
24
+ };
25
+ type Coordinator<Key, E, Reason = never> = {
26
+ readonly active: Effect.Effect<ReadonlySet<Key>>;
27
+ readonly isActive: (key: Key) => Effect.Effect<boolean>; /** Starts an execution while idle, or joins the active execution. */
28
+ readonly run: (key: Key) => Effect.Effect<void, E>;
29
+ /**
30
+ * Captures a run waiter without awaiting settlement.
31
+ * Idle starts force=true (`Started`); an active owner is joined (`Joined`),
32
+ * including during natural settlement. A stopping owner yields `Stopping` with a
33
+ * settlement waiter and no start.
34
+ */
35
+ readonly captureRun: (key: Key) => Effect.Effect<CapturedRun<E>>; /** Rings the doorbell: idle starts; active drains again before settling. */
36
+ readonly wake: (key: Key, scope?: Promotable) => Effect.Effect<void>;
37
+ /**
38
+ * Stops the active execution and clears its doorbell. No-op when idle.
39
+ * Resolves once the interruption request is delivered, not when cleanup settles.
40
+ * First accepted `reason` is kept; use `terminalStop` to escalate.
41
+ */
42
+ readonly interrupt: (key: Key, reason?: Reason, options?: {
43
+ readonly awaitSettlement?: boolean;
44
+ }) => Effect.Effect<boolean>;
45
+ /**
46
+ * Terminal stop receipt. Always clears pendingWake. Settling means the owner
47
+ * is already gone and the settled callback is chosen; Driver may release the
48
+ * claim under the Inbox gate. LiveStopping/Interrupted leave release to that
49
+ * owner's future settled callback. LiveStopping joins the same outstanding
50
+ * interruption request rather than forking a second one.
51
+ */
52
+ readonly terminalStop: (key: Key, reason: Reason) => Effect.Effect<StopReceipt>; /** Resolves once no execution is active. Never starts work. */
53
+ readonly awaitIdle: (key: Key) => Effect.Effect<void>;
54
+ };
55
+ declare const makeCoordinator: <Key, E, Reason = never>(options: {
56
+ readonly drain: (key: Key, force: boolean, scope: Promotable) => Effect.Effect<void, E>;
57
+ readonly started?: (key: Key) => Effect.Effect<void>;
58
+ readonly settled?: (key: Key, exit: Exit.Exit<void, E>, reason?: Reason) => Effect.Effect<void>;
59
+ }) => Effect.Effect<Coordinator<Key, E, Reason>, never, Scope.Scope>;
60
+ //#endregion
61
+ export { CapturedRun, Coordinator, Promotable, StopReceipt, makeCoordinator };
62
+ //# sourceMappingURL=coordinator.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordinator.d.mts","names":[],"sources":["../src/coordinator.ts"],"mappings":";;;;KAmBY,UAAA;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,WAR9B;EAAA,SAUD,GAAA,GAAM,GAAA,EAAK,GAAA,KAAQ,MAAA,CAAO,MAAA,OAAa,CAAA;;;;;;;WAOvC,UAAA,GAAa,GAAA,EAAK,GAAA,KAAQ,MAAA,CAAO,MAAA,CAAO,WAAA,CAAY,CAAA,IAb5C;EAAA,SAeR,IAAA,GAAO,GAAA,EAAK,GAAA,EAAK,KAAA,GAAQ,UAAA,KAAe,MAAA,CAAO,MAAA;EAbnC;;;;;EAAA,SAmBZ,SAAA,GACP,GAAA,EAAK,GAAA,EACL,MAAA,GAAS,MAAA,EACT,OAAA;IAAA,SAAqB,eAAA;EAAA,MAClB,MAAA,CAAO,MAAA;EAnBoC;;;;;;;EAAA,SA2BvC,YAAA,GAAe,GAAA,EAAK,GAAA,EAAK,MAAA,EAAQ,MAAA,KAAW,MAAA,CAAO,MAAA,CAAO,WAAA,GAlBlB;EAAA,SAoBxC,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"}
@@ -0,0 +1,129 @@
1
+ import { Deferred, Effect, Fiber, FiberSet } from "effect";
2
+ //#region src/coordinator.ts
3
+ /**
4
+ * Process-local run coordinator.
5
+ *
6
+ * Port of OpenCode v2 `SessionRunCoordinator` (MIT): one busy period per key,
7
+ * a doorbell that coalesces wakes, and interrupt that claims pending wakes so a
8
+ * dead intent cannot restart. Steering lands at the next drain boundary.
9
+ *
10
+ * @see https://github.com/sst/opencode (packages/core/src/session/run-coordinator.ts)
11
+ */
12
+ const awaitDone = (done) => Deferred.await(done).pipe(Effect.flatMap((exit) => exit));
13
+ const widerScope = (current, next) => current === "input" || next === "input" ? "input" : "steer";
14
+ const makeCoordinator = (options) => Effect.gen(function* () {
15
+ const executions = /* @__PURE__ */ new Map();
16
+ const fork = yield* FiberSet.makeRuntime();
17
+ const loop = (key, execution, force) => Effect.suspend(() => {
18
+ if (execution.stopping) return Effect.void;
19
+ return options.drain(key, force, execution.scope);
20
+ }).pipe(Effect.andThen(Effect.suspend(() => {
21
+ if (execution.stopping || execution.pendingWake === void 0) return Effect.void;
22
+ execution.scope = execution.pendingWake;
23
+ execution.pendingWake = void 0;
24
+ return Effect.yieldNow.pipe(Effect.andThen(loop(key, execution, false)));
25
+ })));
26
+ const settle = (key, execution, exit) => {
27
+ if (execution.pendingWake !== void 0) start(key, false, execution.pendingWake);
28
+ else executions.delete(key);
29
+ return Deferred.succeed(execution.done, exit).pipe(Effect.asVoid);
30
+ };
31
+ const start = (key, force, scope) => {
32
+ const execution = {
33
+ done: Deferred.makeUnsafe(),
34
+ scope,
35
+ stopping: false
36
+ };
37
+ executions.set(key, execution);
38
+ 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(() => {
39
+ execution.owner = void 0;
40
+ return options.settled?.(key, exit, execution.interruptionReason) ?? Effect.void;
41
+ })), Effect.onExit((exit) => settle(key, execution, exit)), Effect.exit, Effect.asVoid));
42
+ return execution;
43
+ };
44
+ const forkInterruptionRequest = (owner) => fork(Effect.yieldNow.pipe(Effect.andThen(Effect.sync(() => owner.interruptUnsafe()))));
45
+ const interruptNow = (key, reason) => Effect.suspend(() => {
46
+ const execution = executions.get(key);
47
+ if (execution === void 0 || execution.stopping) return Effect.succeed(false);
48
+ if (execution.owner === void 0) {
49
+ execution.pendingWake = void 0;
50
+ return Effect.succeed(false);
51
+ }
52
+ const owner = execution.owner;
53
+ execution.stopping = true;
54
+ execution.pendingWake = void 0;
55
+ execution.interruptionReason = reason;
56
+ const request = forkInterruptionRequest(owner);
57
+ execution.request = request;
58
+ return Fiber.join(request).pipe(Effect.as(true));
59
+ });
60
+ const awaitIdle = (key) => Effect.suspend(() => {
61
+ const execution = executions.get(key);
62
+ if (execution === void 0) return Effect.void;
63
+ return awaitDone(execution.done).pipe(Effect.ignoreCause, Effect.andThen(awaitIdle(key)));
64
+ });
65
+ return {
66
+ active: Effect.sync(() => new Set(executions.keys())),
67
+ isActive: (key) => Effect.sync(() => executions.has(key)),
68
+ run: (key) => Effect.suspend(() => {
69
+ const execution = executions.get(key);
70
+ if (execution === void 0) return awaitDone(start(key, true, "input").done);
71
+ if (!execution.stopping) return awaitDone(execution.done);
72
+ return awaitDone(execution.done).pipe(Effect.ignoreCause, Effect.andThen(Effect.suspend(() => {
73
+ const next = executions.get(key);
74
+ return next === void 0 ? awaitDone(start(key, true, "input").done) : awaitDone(next.done);
75
+ })));
76
+ }),
77
+ captureRun: (key) => Effect.sync(() => {
78
+ const execution = executions.get(key);
79
+ if (execution === void 0) return {
80
+ _tag: "Started",
81
+ join: awaitDone(start(key, true, "input").done)
82
+ };
83
+ if (!execution.stopping) return {
84
+ _tag: "Joined",
85
+ join: awaitDone(execution.done)
86
+ };
87
+ return {
88
+ _tag: "Stopping",
89
+ awaitSettlement: awaitDone(execution.done).pipe(Effect.ignoreCause)
90
+ };
91
+ }),
92
+ wake: (key, scope = "input") => Effect.sync(() => {
93
+ const execution = executions.get(key);
94
+ if (execution !== void 0) {
95
+ execution.pendingWake = widerScope(execution.pendingWake, scope);
96
+ return;
97
+ }
98
+ start(key, false, scope);
99
+ }),
100
+ interrupt: (key, reason, options) => Effect.suspend(() => {
101
+ const execution = executions.get(key);
102
+ return interruptNow(key, reason).pipe(Effect.tap(() => options?.awaitSettlement === true && execution !== void 0 ? awaitDone(execution.done).pipe(Effect.ignoreCause) : Effect.void));
103
+ }),
104
+ terminalStop: (key, reason) => Effect.suspend(() => {
105
+ const execution = executions.get(key);
106
+ if (execution === void 0) return Effect.succeed({ _tag: "Idle" });
107
+ execution.pendingWake = void 0;
108
+ if (execution.owner === void 0) {
109
+ execution.stopping = true;
110
+ return Effect.succeed({ _tag: "Settling" });
111
+ }
112
+ execution.interruptionReason = reason;
113
+ if (execution.stopping) {
114
+ const request = execution.request;
115
+ return request === void 0 ? Effect.succeed({ _tag: "LiveStopping" }) : Fiber.join(request).pipe(Effect.as({ _tag: "LiveStopping" }));
116
+ }
117
+ const owner = execution.owner;
118
+ execution.stopping = true;
119
+ const request = forkInterruptionRequest(owner);
120
+ execution.request = request;
121
+ return Fiber.join(request).pipe(Effect.as({ _tag: "Interrupted" }));
122
+ }),
123
+ awaitIdle
124
+ };
125
+ });
126
+ //#endregion
127
+ export { makeCoordinator };
128
+
129
+ //# sourceMappingURL=coordinator.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordinator.mjs","names":[],"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 { Deferred, Effect, Fiber, FiberSet } from 'effect'\nimport type { Exit, Scope } from 'effect'\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\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 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 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 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 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 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 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 if (execution === undefined || execution.stopping) return Effect.succeed(false)\n if (execution.owner === undefined) {\n execution.pendingWake = undefined\n return Effect.succeed(false)\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 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 if (execution === undefined) return Effect.void\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 if (execution === undefined) return awaitDone(start(key, true, 'input').done)\n if (!execution.stopping) return awaitDone(execution.done)\n return awaitDone(execution.done).pipe(\n Effect.ignoreCause,\n Effect.andThen(\n Effect.suspend(() => {\n const next = executions.get(key)\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 if (execution === undefined) {\n const started = start(key, true, 'input')\n return { _tag: 'Started' as const, join: awaitDone(started.done) }\n }\n if (!execution.stopping) {\n return { _tag: 'Joined' as const, join: awaitDone(execution.done) }\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 if (execution !== undefined) {\n execution.pendingWake = widerScope(execution.pendingWake, scope)\n return\n }\n start(key, false, scope)\n }),\n interrupt: (key, reason, options) =>\n Effect.suspend(() => {\n const execution = executions.get(key)\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 if (execution === undefined) return Effect.succeed({ _tag: 'Idle' } as const)\n execution.pendingWake = undefined\n if (execution.owner === undefined) {\n execution.stopping = true\n return Effect.succeed({ _tag: 'Settling' } as const)\n }\n execution.interruptionReason = reason\n if (execution.stopping) {\n const request = execution.request\n return request === undefined\n ? Effect.succeed({ _tag: 'LiveStopping' } as const)\n : Fiber.join(request).pipe(Effect.as({ _tag: 'LiveStopping' } as const))\n }\n const owner = execution.owner\n execution.stopping = true\n const request = forkInterruptionRequest(owner)\n execution.request = request\n return Fiber.join(request).pipe(Effect.as({ _tag: 'Interrupted' } as const))\n }),\n awaitIdle\n }\n })\n"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { Inbox } from "../inbox.mjs";
2
+ import { DurableRunStoreSnapshot, RunStore } from "../store.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":";;;;;;KAcK,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 { makeInMemoryInboxLayer } from "../inbox.mjs";
2
+ import { makeSnapshotRunStoreLayer } from "../store.mjs";
3
+ import { makeDriverLayer } from "../driver.mjs";
4
+ import { Layer } from "effect";
5
+ //#region src/driver/durable-object.ts
6
+ function makeDurableObjectDriverLayer(options) {
7
+ return makeDriverLayer(options).pipe(Layer.provideMerge(makeSnapshotRunStoreLayer(options)), Layer.provideMerge(makeInMemoryInboxLayer()));
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 {\n makeDriverLayer,\n type Drain,\n type Driver,\n type InvalidMaxResumeAttempts\n} from '../driver.ts'\nimport { makeInMemoryInboxLayer } from '../inbox.ts'\nimport type { Inbox } from '../inbox.ts'\nimport { makeSnapshotRunStoreLayer, type DurableRunStoreSnapshot, type RunStore } 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 makeDriverLayer(options).pipe(\n Layer.provideMerge(makeSnapshotRunStoreLayer(options)),\n Layer.provideMerge(makeInMemoryInboxLayer())\n )\n}\n"],"mappings":";;;;;AA0BA,SAAgB,6BACd,SACA;CACA,OAAO,gBAAgB,OAAO,EAAE,KAC9B,MAAM,aAAa,0BAA0B,OAAO,CAAC,GACrD,MAAM,aAAa,uBAAuB,CAAC,CAC7C;AACF"}
@@ -0,0 +1,21 @@
1
+ import { Inbox } from "../inbox.mjs";
2
+ import { RunStore } from "../store.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":";;;;;;iBAWgB,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 { makeInMemoryInboxLayer } from "../inbox.mjs";
2
+ import { makeInMemoryRunStoreLayer } from "../store.mjs";
3
+ import { makeDriverLayer } from "../driver.mjs";
4
+ import { Layer } from "effect";
5
+ //#region src/driver/memory.ts
6
+ function makeInMemoryDriverLayer(options) {
7
+ return options === void 0 ? makeDriverLayer() : makeDriverLayer(options);
8
+ }
9
+ function makeInMemoryHarnessLayer(options) {
10
+ return (options === void 0 ? makeDriverLayer() : makeDriverLayer(options)).pipe(Layer.provideMerge(makeInMemoryRunStoreLayer()), Layer.provideMerge(makeInMemoryInboxLayer()));
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 makeDriverLayer,\n type Drain,\n type Driver,\n type DriverLayerOptions,\n type InvalidMaxResumeAttempts\n} from '../driver.ts'\nimport { makeInMemoryInboxLayer, type Inbox } from '../inbox.ts'\nimport { makeInMemoryRunStoreLayer, type 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 ? makeDriverLayer() : makeDriverLayer(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 = options === undefined ? makeDriverLayer() : makeDriverLayer(options)\n return driver.pipe(\n Layer.provideMerge(makeInMemoryRunStoreLayer()),\n Layer.provideMerge(makeInMemoryInboxLayer())\n )\n}\n"],"mappings":";;;;;AAmBA,SAAgB,wBAAwB,SAA8B;CACpE,OAAO,YAAY,KAAA,IAAY,gBAAgB,IAAI,gBAAgB,OAAO;AAC5E;AAUA,SAAgB,yBAAyB,SAA8B;CAErE,QADe,YAAY,KAAA,IAAY,gBAAgB,IAAI,gBAAgB,OAAO,GACpE,KACZ,MAAM,aAAa,0BAA0B,CAAC,GAC9C,MAAM,aAAa,uBAAuB,CAAC,CAC7C;AACF"}
@@ -0,0 +1,67 @@
1
+ import { Promotable } from "./coordinator.mjs";
2
+ import { HitlAdmission, HitlDecision, Inbox, InboxItem, ParkedResponse, PauseDecision } from "./inbox.mjs";
3
+ import { RunStore } from "./store.mjs";
4
+ import { Cause, Context, Effect, Layer } from "effect";
5
+
6
+ //#region src/driver.d.ts
7
+ type InterruptReason = 'user' | 'shutdown';
8
+ declare const defaultMaxResumeAttempts = 10;
9
+ declare const InvalidMaxResumeAttempts_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => Cause.YieldableError & {
10
+ readonly _tag: "InvalidMaxResumeAttempts";
11
+ } & Readonly<A>;
12
+ declare class InvalidMaxResumeAttempts extends InvalidMaxResumeAttempts_base<{
13
+ readonly maxResumeAttempts: number;
14
+ }> {}
15
+ type ResumeSuspendedResult = {
16
+ readonly resumed: ReadonlyArray<string>;
17
+ readonly exhausted: ReadonlyArray<string>;
18
+ };
19
+ type DrainContext = {
20
+ readonly drainToken: string;
21
+ readonly readyResponses: ReadonlyArray<ParkedResponse>;
22
+ };
23
+ type Drain = (runId: string, force: boolean, scope: Promotable, context: DrainContext) => Effect.Effect<void, never, Inbox | RunStore>;
24
+ type DriverLayerOptions = {
25
+ readonly drain?: Drain;
26
+ readonly maxResumeAttempts?: number;
27
+ };
28
+ type StopDecision = {
29
+ readonly _tag: 'Interrupted';
30
+ } | {
31
+ readonly _tag: 'ParkCleared';
32
+ } | {
33
+ readonly _tag: 'Idle';
34
+ };
35
+ type DriverShape = {
36
+ readonly active: Effect.Effect<ReadonlySet<string>>;
37
+ readonly isActive: (runId: string) => Effect.Effect<boolean>;
38
+ readonly run: (runId: string) => Effect.Effect<void>;
39
+ readonly wake: (runId: string, scope?: Promotable) => Effect.Effect<void>;
40
+ readonly interrupt: (runId: string, options?: {
41
+ readonly reason?: InterruptReason;
42
+ readonly awaitSettlement?: boolean;
43
+ }) => Effect.Effect<boolean>;
44
+ readonly awaitIdle: (runId: string) => Effect.Effect<void>;
45
+ readonly resumeSuspended: Effect.Effect<ResumeSuspendedResult>;
46
+ readonly admit: (item: InboxItem) => Effect.Effect<void>;
47
+ readonly pause: (runId: string, requestIds: ReadonlyArray<string>, drainToken: string) => Effect.Effect<PauseDecision>;
48
+ readonly resumeHitl: (runId: string, admission: HitlAdmission) => Effect.Effect<HitlDecision>;
49
+ readonly stop: (runId: string) => Effect.Effect<StopDecision>;
50
+ };
51
+ declare const Driver_base: Context.ServiceClass<Driver, "@yolk-sdk/harness/Driver", DriverShape>;
52
+ declare class Driver extends Driver_base {}
53
+ declare const makeHarness: <DE, DR, SE, SR, IE, IR>(input: {
54
+ readonly driver: Layer.Layer<Driver, DE, DR>;
55
+ readonly store: Layer.Layer<RunStore, SE, SR>;
56
+ readonly inbox: Layer.Layer<Inbox, IE, IR>;
57
+ }) => Layer.Layer<Driver | RunStore | Inbox, DE | SE | IE, DR | SR | IR>;
58
+ declare const admit: (item: InboxItem) => Effect.Effect<void, never, Driver>;
59
+ declare function makeDriverLayer(): Layer.Layer<Driver, never, RunStore | Inbox>;
60
+ declare function makeDriverLayer(options?: {
61
+ readonly drain?: Drain;
62
+ readonly maxResumeAttempts?: undefined;
63
+ }): Layer.Layer<Driver, never, RunStore | Inbox>;
64
+ declare function makeDriverLayer(options?: DriverLayerOptions): Layer.Layer<Driver, InvalidMaxResumeAttempts, RunStore | Inbox>;
65
+ //#endregion
66
+ export { Drain, DrainContext, Driver, DriverLayerOptions, DriverShape, InterruptReason, InvalidMaxResumeAttempts, ResumeSuspendedResult, StopDecision, admit, defaultMaxResumeAttempts, makeDriverLayer, makeHarness };
67
+ //# sourceMappingURL=driver.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driver.d.mts","names":[],"sources":["../src/driver.ts"],"mappings":";;;;;;KAYY,eAAA;AAAA,cAEC,wBAAA;AAAA,cAA6B,6BAAA;;;cAE7B,wBAAA,SAAiC,6BAAA;EAAA,SACnC,iBAAiB;AAAA;AAAA,KAKhB,qBAAA;EAAA,SACD,OAAA,EAAS,aAAA;EAAA,SACT,SAAA,EAAW,aAAa;AAAA;AAAA,KAGvB,YAAA;EAAA,SACD,UAAA;EAAA,SACA,cAAA,EAAgB,aAAa,CAAC,cAAA;AAAA;AAAA,KAG7B,KAAA,IACV,KAAA,UACA,KAAA,WACA,KAAA,EAAO,UAAA,EACP,OAAA,EAAS,YAAA,KACN,MAAA,CAAO,MAAA,cAAoB,KAAA,GAAQ,QAAA;AAAA,KAE5B,kBAAA;EAAA,SACD,KAAA,GAAQ,KAAK;EAAA,SACb,iBAAA;AAAA;AAAA,KAGC,YAAA;EAAA,SACG,IAAA;AAAA;EAAA,SACA,IAAA;AAAA;EAAA,SACA,IAAA;AAAA;AAAA,KAEH,WAAA;EAAA,SACD,MAAA,EAAQ,MAAA,CAAO,MAAA,CAAO,WAAA;EAAA,SACtB,QAAA,GAAW,KAAA,aAAkB,MAAA,CAAO,MAAA;EAAA,SACpC,GAAA,GAAM,KAAA,aAAkB,MAAA,CAAO,MAAA;EAAA,SAC/B,IAAA,GAAO,KAAA,UAAe,KAAA,GAAQ,UAAA,KAAe,MAAA,CAAO,MAAA;EAAA,SACpD,SAAA,GACP,KAAA,UACA,OAAA;IAAA,SAAqB,MAAA,GAAS,eAAA;IAAA,SAA0B,eAAA;EAAA,MACrD,MAAA,CAAO,MAAA;EAAA,SACH,SAAA,GAAY,KAAA,aAAkB,MAAA,CAAO,MAAA;EAAA,SACrC,eAAA,EAAiB,MAAA,CAAO,MAAA,CAAO,qBAAA;EAAA,SAC/B,KAAA,GAAQ,IAAA,EAAM,SAAA,KAAc,MAAA,CAAO,MAAA;EAAA,SACnC,KAAA,GACP,KAAA,UACA,UAAA,EAAY,aAAA,UACZ,UAAA,aACG,MAAA,CAAO,MAAA,CAAO,aAAA;EAAA,SACV,UAAA,GAAa,KAAA,UAAe,SAAA,EAAW,aAAA,KAAkB,MAAA,CAAO,MAAA,CAAO,YAAA;EAAA,SACvE,IAAA,GAAO,KAAA,aAAkB,MAAA,CAAO,MAAA,CAAO,YAAA;AAAA;AAAA,cACjD,WAAA;cAEY,MAAA,SAAe,WAAkE;AAAA,cAEjF,WAAA,2BAAuC,KAAA;EAAA,SACzC,MAAA,EAAQ,KAAA,CAAM,KAAA,CAAM,MAAA,EAAQ,EAAA,EAAI,EAAA;EAAA,SAChC,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,QAAA,EAAU,EAAA,EAAI,EAAA;EAAA,SACjC,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,KAAA,EAAO,EAAA,EAAI,EAAA;AAAA,MACrC,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,QAAA,GAAW,KAAA,EAAO,EAAA,GAAK,EAAA,GAAK,EAAA,EAAI,EAAA,GAAK,EAAA,GAAK,EAAA;AAAA,cAGtD,KAAA,GAAS,IAAA,EAAM,SAAA,KAAS,MAAA,CAAA,MAAA,cAAA,MAAA;AAAA,iBA2JrB,eAAA,CAAA,GAAmB,KAAA,CAAM,KAAA,CAAM,MAAA,SAAe,QAAA,GAAW,KAAA;AAAA,iBACzD,eAAA,CAAgB,OAAA;EAAA,SACrB,KAAA,GAAQ,KAAA;EAAA,SACR,iBAAA;AAAA,IACP,KAAA,CAAM,KAAA,CAAM,MAAA,SAAe,QAAA,GAAW,KAAA;AAAA,iBAC1B,eAAA,CACd,OAAA,GAAU,kBAAA,GACT,KAAA,CAAM,KAAA,CAAM,MAAA,EAAQ,wBAAA,EAA0B,QAAA,GAAW,KAAA"}