@llm4ts/flow 0.1.0

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 (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +41 -0
  3. package/dist/AzureDevOpsTool.d.ts +60 -0
  4. package/dist/AzureDevOpsTool.d.ts.map +1 -0
  5. package/dist/AzureDevOpsTool.js +163 -0
  6. package/dist/AzureDevOpsTool.js.map +1 -0
  7. package/dist/Bench.d.ts +133 -0
  8. package/dist/Bench.d.ts.map +1 -0
  9. package/dist/Bench.js +272 -0
  10. package/dist/Bench.js.map +1 -0
  11. package/dist/BenchReport.d.ts +9 -0
  12. package/dist/BenchReport.d.ts.map +1 -0
  13. package/dist/BenchReport.js +360 -0
  14. package/dist/BenchReport.js.map +1 -0
  15. package/dist/CapabilityGuard.d.ts +6 -0
  16. package/dist/CapabilityGuard.d.ts.map +1 -0
  17. package/dist/CapabilityGuard.js +25 -0
  18. package/dist/CapabilityGuard.js.map +1 -0
  19. package/dist/Chat.d.ts +15 -0
  20. package/dist/Chat.d.ts.map +1 -0
  21. package/dist/Chat.js +36 -0
  22. package/dist/Chat.js.map +1 -0
  23. package/dist/Classified.d.ts +14 -0
  24. package/dist/Classified.d.ts.map +1 -0
  25. package/dist/Classified.js +48 -0
  26. package/dist/Classified.js.map +1 -0
  27. package/dist/CostLedger.d.ts +55 -0
  28. package/dist/CostLedger.d.ts.map +1 -0
  29. package/dist/CostLedger.js +126 -0
  30. package/dist/CostLedger.js.map +1 -0
  31. package/dist/CostTracker.d.ts +13 -0
  32. package/dist/CostTracker.d.ts.map +1 -0
  33. package/dist/CostTracker.js +116 -0
  34. package/dist/CostTracker.js.map +1 -0
  35. package/dist/Equiv.d.ts +163 -0
  36. package/dist/Equiv.d.ts.map +1 -0
  37. package/dist/Equiv.js +256 -0
  38. package/dist/Equiv.js.map +1 -0
  39. package/dist/EquivReport.d.ts +16 -0
  40. package/dist/EquivReport.d.ts.map +1 -0
  41. package/dist/EquivReport.js +77 -0
  42. package/dist/EquivReport.js.map +1 -0
  43. package/dist/Flow.d.ts +25 -0
  44. package/dist/Flow.d.ts.map +1 -0
  45. package/dist/Flow.js +39 -0
  46. package/dist/Flow.js.map +1 -0
  47. package/dist/FlowContext.d.ts +23 -0
  48. package/dist/FlowContext.d.ts.map +1 -0
  49. package/dist/FlowContext.js +4 -0
  50. package/dist/FlowContext.js.map +1 -0
  51. package/dist/FlowError.d.ts +80 -0
  52. package/dist/FlowError.d.ts.map +1 -0
  53. package/dist/FlowError.js +92 -0
  54. package/dist/FlowError.js.map +1 -0
  55. package/dist/FlowEvents.d.ts +101 -0
  56. package/dist/FlowEvents.d.ts.map +1 -0
  57. package/dist/FlowEvents.js +103 -0
  58. package/dist/FlowEvents.js.map +1 -0
  59. package/dist/FlowRecorder.d.ts +26 -0
  60. package/dist/FlowRecorder.d.ts.map +1 -0
  61. package/dist/FlowRecorder.js +74 -0
  62. package/dist/FlowRecorder.js.map +1 -0
  63. package/dist/GitHubTool.d.ts +53 -0
  64. package/dist/GitHubTool.d.ts.map +1 -0
  65. package/dist/GitHubTool.js +191 -0
  66. package/dist/GitHubTool.js.map +1 -0
  67. package/dist/GitTool.d.ts +46 -0
  68. package/dist/GitTool.d.ts.map +1 -0
  69. package/dist/GitTool.js +97 -0
  70. package/dist/GitTool.js.map +1 -0
  71. package/dist/Interaction.d.ts +21 -0
  72. package/dist/Interaction.d.ts.map +1 -0
  73. package/dist/Interaction.js +9 -0
  74. package/dist/Interaction.js.map +1 -0
  75. package/dist/McpServer.d.ts +18 -0
  76. package/dist/McpServer.d.ts.map +1 -0
  77. package/dist/McpServer.js +122 -0
  78. package/dist/McpServer.js.map +1 -0
  79. package/dist/Mermaid.d.ts +7 -0
  80. package/dist/Mermaid.d.ts.map +1 -0
  81. package/dist/Mermaid.js +98 -0
  82. package/dist/Mermaid.js.map +1 -0
  83. package/dist/Pack.d.ts +40 -0
  84. package/dist/Pack.d.ts.map +1 -0
  85. package/dist/Pack.js +141 -0
  86. package/dist/Pack.js.map +1 -0
  87. package/dist/Package.d.ts +2 -0
  88. package/dist/Package.d.ts.map +1 -0
  89. package/dist/Package.js +2 -0
  90. package/dist/Package.js.map +1 -0
  91. package/dist/Persistence.d.ts +32 -0
  92. package/dist/Persistence.d.ts.map +1 -0
  93. package/dist/Persistence.js +85 -0
  94. package/dist/Persistence.js.map +1 -0
  95. package/dist/Plan.d.ts +26 -0
  96. package/dist/Plan.d.ts.map +1 -0
  97. package/dist/Plan.js +101 -0
  98. package/dist/Plan.js.map +1 -0
  99. package/dist/PlanExecution.d.ts +7 -0
  100. package/dist/PlanExecution.d.ts.map +1 -0
  101. package/dist/PlanExecution.js +19 -0
  102. package/dist/PlanExecution.js.map +1 -0
  103. package/dist/Planner.d.ts +32 -0
  104. package/dist/Planner.d.ts.map +1 -0
  105. package/dist/Planner.js +97 -0
  106. package/dist/Planner.js.map +1 -0
  107. package/dist/PrSummary.d.ts +15 -0
  108. package/dist/PrSummary.d.ts.map +1 -0
  109. package/dist/PrSummary.js +29 -0
  110. package/dist/PrSummary.js.map +1 -0
  111. package/dist/PriceList.d.ts +11 -0
  112. package/dist/PriceList.d.ts.map +1 -0
  113. package/dist/PriceList.js +79 -0
  114. package/dist/PriceList.js.map +1 -0
  115. package/dist/Provenance.d.ts +27 -0
  116. package/dist/Provenance.d.ts.map +1 -0
  117. package/dist/Provenance.js +38 -0
  118. package/dist/Provenance.js.map +1 -0
  119. package/dist/Replay.d.ts +29 -0
  120. package/dist/Replay.d.ts.map +1 -0
  121. package/dist/Replay.js +181 -0
  122. package/dist/Replay.js.map +1 -0
  123. package/dist/Review.d.ts +71 -0
  124. package/dist/Review.d.ts.map +1 -0
  125. package/dist/Review.js +204 -0
  126. package/dist/Review.js.map +1 -0
  127. package/dist/ReviewCache.d.ts +6 -0
  128. package/dist/ReviewCache.d.ts.map +1 -0
  129. package/dist/ReviewCache.js +28 -0
  130. package/dist/ReviewCache.js.map +1 -0
  131. package/dist/Reviewer.d.ts +12 -0
  132. package/dist/Reviewer.d.ts.map +1 -0
  133. package/dist/Reviewer.js +30 -0
  134. package/dist/Reviewer.js.map +1 -0
  135. package/dist/SpecChecks.d.ts +17 -0
  136. package/dist/SpecChecks.d.ts.map +1 -0
  137. package/dist/SpecChecks.js +107 -0
  138. package/dist/SpecChecks.js.map +1 -0
  139. package/dist/Survey.d.ts +32 -0
  140. package/dist/Survey.d.ts.map +1 -0
  141. package/dist/Survey.js +119 -0
  142. package/dist/Survey.js.map +1 -0
  143. package/dist/TransientRetry.d.ts +16 -0
  144. package/dist/TransientRetry.d.ts.map +1 -0
  145. package/dist/TransientRetry.js +102 -0
  146. package/dist/TransientRetry.js.map +1 -0
  147. package/dist/Workspace.d.ts +39 -0
  148. package/dist/Workspace.d.ts.map +1 -0
  149. package/dist/Workspace.js +192 -0
  150. package/dist/Workspace.js.map +1 -0
  151. package/dist/WorkspaceLayout.d.ts +3 -0
  152. package/dist/WorkspaceLayout.d.ts.map +1 -0
  153. package/dist/WorkspaceLayout.js +19 -0
  154. package/dist/WorkspaceLayout.js.map +1 -0
  155. package/dist/WorkspaceTools.d.ts +4 -0
  156. package/dist/WorkspaceTools.d.ts.map +1 -0
  157. package/dist/WorkspaceTools.js +130 -0
  158. package/dist/WorkspaceTools.js.map +1 -0
  159. package/package.json +91 -0
  160. package/src/AzureDevOpsTool.ts +291 -0
  161. package/src/Bench.ts +340 -0
  162. package/src/BenchReport.ts +447 -0
  163. package/src/CapabilityGuard.ts +43 -0
  164. package/src/Chat.ts +60 -0
  165. package/src/Classified.ts +67 -0
  166. package/src/CostLedger.ts +171 -0
  167. package/src/CostTracker.ts +170 -0
  168. package/src/Equiv.ts +378 -0
  169. package/src/EquivReport.ts +92 -0
  170. package/src/Flow.ts +72 -0
  171. package/src/FlowContext.ts +23 -0
  172. package/src/FlowError.ts +112 -0
  173. package/src/FlowEvents.ts +147 -0
  174. package/src/FlowRecorder.ts +116 -0
  175. package/src/GitHubTool.ts +315 -0
  176. package/src/GitTool.ts +214 -0
  177. package/src/Interaction.ts +23 -0
  178. package/src/McpServer.ts +168 -0
  179. package/src/Mermaid.ts +112 -0
  180. package/src/Pack.ts +213 -0
  181. package/src/Package.ts +1 -0
  182. package/src/Persistence.ts +198 -0
  183. package/src/Plan.ts +119 -0
  184. package/src/PlanExecution.ts +46 -0
  185. package/src/Planner.ts +134 -0
  186. package/src/PrSummary.ts +37 -0
  187. package/src/PriceList.ts +91 -0
  188. package/src/Provenance.ts +85 -0
  189. package/src/Replay.ts +265 -0
  190. package/src/Review.ts +337 -0
  191. package/src/ReviewCache.ts +45 -0
  192. package/src/Reviewer.ts +31 -0
  193. package/src/SpecChecks.ts +143 -0
  194. package/src/Survey.ts +144 -0
  195. package/src/TransientRetry.ts +238 -0
  196. package/src/Workspace.ts +255 -0
  197. package/src/WorkspaceLayout.ts +22 -0
  198. package/src/WorkspaceTools.ts +188 -0
package/src/Bench.ts ADDED
@@ -0,0 +1,340 @@
1
+ import * as Effect from "effect/Effect"
2
+ import * as Ref from "effect/Ref"
3
+ import * as Schema from "effect/Schema"
4
+ import type * as Scope from "effect/Scope"
5
+ import * as Stream from "effect/Stream"
6
+ import { TokenUsage } from "@llm4ts/core/Models"
7
+ import type { FlowEvent, FlowEventHub, FlowEventsShape } from "./FlowEvents.ts"
8
+ import { estimateCostUsd } from "./PriceList.ts"
9
+
10
+ export class BenchTokens extends Schema.Class<BenchTokens>("BenchTokens")({
11
+ prompt: Schema.Int,
12
+ completion: Schema.Int,
13
+ cached: Schema.Int
14
+ }) {
15
+ get total(): number {
16
+ return this.prompt + this.completion
17
+ }
18
+
19
+ add(that: BenchTokens): BenchTokens {
20
+ return new BenchTokens({
21
+ prompt: this.prompt + that.prompt,
22
+ completion: this.completion + that.completion,
23
+ cached: this.cached + that.cached
24
+ })
25
+ }
26
+ }
27
+
28
+ export const emptyBenchTokens = (): BenchTokens =>
29
+ BenchTokens.make({ prompt: 0, completion: 0, cached: 0 })
30
+
31
+ export const benchTokensFromUsage = (usage: TokenUsage): BenchTokens =>
32
+ BenchTokens.make({
33
+ prompt: usage.prompt,
34
+ completion: usage.completion,
35
+ cached: usage.cached ?? 0
36
+ })
37
+
38
+ export class BenchCounters extends Schema.Class<BenchCounters>("BenchCounters")({
39
+ flakyRetries: Schema.Int,
40
+ transientRetries: Schema.Int,
41
+ autoResumes: Schema.Int,
42
+ turnLimitTrips: Schema.Int,
43
+ shrinks: Schema.Int
44
+ }) {
45
+ get selfHealing(): number {
46
+ return this.flakyRetries + this.transientRetries + this.autoResumes + this.shrinks
47
+ }
48
+ }
49
+
50
+ export const emptyBenchCounters = (): BenchCounters =>
51
+ BenchCounters.make({
52
+ flakyRetries: 0,
53
+ transientRetries: 0,
54
+ autoResumes: 0,
55
+ turnLimitTrips: 0,
56
+ shrinks: 0
57
+ })
58
+
59
+ export class BenchPhase extends Schema.Class<BenchPhase>("BenchPhase")({
60
+ name: Schema.String,
61
+ ms: Schema.Int,
62
+ tokens: BenchTokens,
63
+ costUsd: Schema.optionalKey(Schema.Number),
64
+ counters: BenchCounters,
65
+ detail: Schema.Record(Schema.String, Schema.String)
66
+ }) {}
67
+
68
+ export class BenchScore extends Schema.Class<BenchScore>("BenchScore")({
69
+ name: Schema.String,
70
+ score: Schema.Int,
71
+ max: Schema.Int,
72
+ reasoning: Schema.String
73
+ }) {}
74
+
75
+ export class BenchJudge extends Schema.Class<BenchJudge>("BenchJudge")({
76
+ provider: Schema.String,
77
+ model: Schema.String,
78
+ fixed: Schema.Boolean
79
+ }) {}
80
+
81
+ export class BenchMachine extends Schema.Class<BenchMachine>("BenchMachine")({
82
+ hostname: Schema.String,
83
+ os: Schema.String,
84
+ arch: Schema.String,
85
+ cores: Schema.Int,
86
+ memoryGb: Schema.Number,
87
+ runtime: Schema.String
88
+ }) {}
89
+
90
+ export class BenchQuality extends Schema.Class<BenchQuality>("BenchQuality")({
91
+ buildPassed: Schema.optionalKey(Schema.Boolean),
92
+ testsRun: Schema.optionalKey(Schema.Int),
93
+ testsFailed: Schema.optionalKey(Schema.Int),
94
+ coveragePct: Schema.optionalKey(Schema.Number),
95
+ featureFiles: Schema.Int,
96
+ scenarios: Schema.Int,
97
+ malformedFeatures: Schema.Int,
98
+ specFiles: Schema.Int,
99
+ programs: Schema.Int,
100
+ sourceFiles: Schema.Int,
101
+ sourceLines: Schema.Int,
102
+ testFiles: Schema.Int,
103
+ testLines: Schema.Int,
104
+ deterministicFindings: Schema.Int,
105
+ judgeFindings: Schema.Int,
106
+ gateCleared: Schema.optionalKey(Schema.Boolean),
107
+ gateRounds: Schema.optionalKey(Schema.Int),
108
+ gateOpenIssues: Schema.optionalKey(Schema.Int),
109
+ judgeScores: Schema.Array(BenchScore)
110
+ }) {}
111
+
112
+ export class BenchRecord extends Schema.Class<BenchRecord>("BenchRecord")({
113
+ schemaVersion: Schema.Int,
114
+ runId: Schema.String,
115
+ label: Schema.optionalKey(Schema.String),
116
+ startedAt: Schema.String,
117
+ finishedAt: Schema.String,
118
+ provider: Schema.String,
119
+ modelRequested: Schema.String,
120
+ modelsServed: Schema.Array(Schema.String),
121
+ judge: BenchJudge,
122
+ toolVersion: Schema.optionalKey(Schema.String),
123
+ llm4tsVersion: Schema.String,
124
+ pack: Schema.String,
125
+ fixtureFingerprint: Schema.String,
126
+ machine: BenchMachine,
127
+ outcome: Schema.String,
128
+ resumed: Schema.optionalKey(Schema.Boolean),
129
+ totalMs: Schema.Int,
130
+ phases: Schema.Array(BenchPhase),
131
+ quality: Schema.optionalKey(BenchQuality)
132
+ }) {
133
+ get completed(): boolean {
134
+ return this.outcome === "completed"
135
+ }
136
+
137
+ get totalTokens(): number {
138
+ return this.phases.reduce((sum, phase) => sum + phase.tokens.total + phase.tokens.cached, 0)
139
+ }
140
+
141
+ get totalCostUsd(): number | undefined {
142
+ const values = this.phases.flatMap((phase) =>
143
+ phase.costUsd === undefined ? [] : [phase.costUsd]
144
+ )
145
+ return values.length === 0 ? undefined : values.reduce((sum, value) => sum + value, 0)
146
+ }
147
+
148
+ get selfHealing(): number {
149
+ return this.phases.reduce((sum, phase) => sum + phase.counters.selfHealing, 0)
150
+ }
151
+ }
152
+
153
+ export const CurrentBenchSchema = 1
154
+ export const BenchUnstaged = "(none)"
155
+
156
+ export interface BenchObservation {
157
+ readonly openStages: ReadonlyArray<string>
158
+ readonly cells: ReadonlyMap<string, BenchTokens>
159
+ readonly cellKeys: ReadonlyMap<string, readonly [stage: string, model: string]>
160
+ readonly counters: ReadonlyMap<string, BenchCounters>
161
+ readonly modelsServed: ReadonlySet<string>
162
+ }
163
+
164
+ export const emptyBenchObservation = (): BenchObservation => ({
165
+ openStages: [],
166
+ cells: new Map(),
167
+ cellKeys: new Map(),
168
+ counters: new Map(),
169
+ modelsServed: new Set()
170
+ })
171
+
172
+ const currentStage = (observation: BenchObservation): string =>
173
+ observation.openStages[observation.openStages.length - 1] ?? BenchUnstaged
174
+
175
+ const dropFirst = (values: ReadonlyArray<string>, value: string): ReadonlyArray<string> => {
176
+ const index = values.indexOf(value)
177
+ return index < 0 ? values : [...values.slice(0, index), ...values.slice(index + 1)]
178
+ }
179
+
180
+ const classifyNotice = (message: string, counters: BenchCounters): BenchCounters | undefined => {
181
+ if (message.startsWith("⟳ flaky")) {
182
+ return new BenchCounters({
183
+ ...counters,
184
+ flakyRetries: counters.flakyRetries + 1
185
+ })
186
+ }
187
+ if (message.startsWith("⟳ transient")) {
188
+ return new BenchCounters({
189
+ ...counters,
190
+ transientRetries: counters.transientRetries + 1
191
+ })
192
+ }
193
+ if (message.startsWith("↻ auto-resume")) {
194
+ return new BenchCounters({
195
+ ...counters,
196
+ autoResumes: counters.autoResumes + 1
197
+ })
198
+ }
199
+ if (message.includes("turn limit hit")) {
200
+ return new BenchCounters({
201
+ ...counters,
202
+ turnLimitTrips: counters.turnLimitTrips + 1
203
+ })
204
+ }
205
+ if (message.includes("shrinking to")) {
206
+ return new BenchCounters({
207
+ ...counters,
208
+ shrinks: counters.shrinks + 1
209
+ })
210
+ }
211
+ return undefined
212
+ }
213
+
214
+ export const observeBench = (observation: BenchObservation, event: FlowEvent): BenchObservation => {
215
+ switch (event._tag) {
216
+ case "StageStarted":
217
+ return {
218
+ ...observation,
219
+ openStages: [event.stage, ...observation.openStages]
220
+ }
221
+ case "StageCompleted":
222
+ case "StageFailed":
223
+ return {
224
+ ...observation,
225
+ openStages: dropFirst(observation.openStages, event.stage)
226
+ }
227
+ case "TokensUsed": {
228
+ const stage = currentStage(observation)
229
+ const model = event.model ?? ""
230
+ const key = JSON.stringify([stage, model])
231
+ const cells = new Map(observation.cells)
232
+ cells.set(key, (cells.get(key) ?? emptyBenchTokens()).add(benchTokensFromUsage(event.usage)))
233
+ return {
234
+ ...observation,
235
+ cells,
236
+ cellKeys: new Map(observation.cellKeys).set(key, [stage, model]),
237
+ modelsServed:
238
+ event.model === undefined
239
+ ? observation.modelsServed
240
+ : new Set([...observation.modelsServed, event.model])
241
+ }
242
+ }
243
+ case "Info": {
244
+ const stage = currentStage(observation)
245
+ const current = observation.counters.get(stage) ?? emptyBenchCounters()
246
+ const next = classifyNotice(event.message, current)
247
+ return next === undefined
248
+ ? observation
249
+ : {
250
+ ...observation,
251
+ counters: new Map(observation.counters).set(stage, next)
252
+ }
253
+ }
254
+ default:
255
+ return observation
256
+ }
257
+ }
258
+
259
+ export const stageBenchTokens = (observation: BenchObservation, stage: string): BenchTokens =>
260
+ [...observation.cells.entries()].reduce((total, [key, tokens]) => {
261
+ const parts = observation.cellKeys.get(key)
262
+ return parts?.[0] === stage ? total.add(tokens) : total
263
+ }, emptyBenchTokens())
264
+
265
+ export const stageBenchCounters = (observation: BenchObservation, stage: string): BenchCounters =>
266
+ observation.counters.get(stage) ?? emptyBenchCounters()
267
+
268
+ export const makeBenchPhase = (
269
+ observation: BenchObservation,
270
+ stage: string,
271
+ ms: number,
272
+ detail: Readonly<Record<string, string>> = {}
273
+ ): BenchPhase => {
274
+ const costs = [...observation.cells.entries()].flatMap(([key, tokens]) => {
275
+ const parts = observation.cellKeys.get(key)
276
+ if (parts?.[0] !== stage) {
277
+ return []
278
+ }
279
+ const cost = estimateCostUsd(
280
+ parts[1],
281
+ TokenUsage.make({
282
+ prompt: tokens.prompt,
283
+ completion: tokens.completion,
284
+ total: tokens.total,
285
+ ...(tokens.cached <= 0 ? {} : { cached: tokens.cached })
286
+ })
287
+ )
288
+ return cost === undefined ? [] : [cost]
289
+ })
290
+ return BenchPhase.make({
291
+ name: stage,
292
+ ms,
293
+ tokens: stageBenchTokens(observation, stage),
294
+ counters: stageBenchCounters(observation, stage),
295
+ detail,
296
+ ...(costs.length === 0 ? {} : { costUsd: costs.reduce((sum, cost) => sum + cost, 0) })
297
+ })
298
+ }
299
+
300
+ const isHub = (events: FlowEventsShape): events is FlowEventHub =>
301
+ "subscribe" in events && "publishedCount" in events && "stream" in events
302
+
303
+ export interface BenchTap {
304
+ readonly get: Effect.Effect<BenchObservation>
305
+ readonly reset: Effect.Effect<void>
306
+ }
307
+
308
+ export const makeBenchTap = Effect.fn("@llm4ts/flow/Bench.makeTap")(function* (
309
+ events: FlowEventsShape
310
+ ): Effect.fn.Return<BenchTap, never, Scope.Scope> {
311
+ const observation = yield* Ref.make(emptyBenchObservation())
312
+ const consumed = yield* Ref.make(0)
313
+ if (!isHub(events)) {
314
+ return {
315
+ get: Ref.get(observation),
316
+ reset: Ref.set(observation, emptyBenchObservation())
317
+ }
318
+ }
319
+ const subscription = yield* events.subscribe
320
+ const baseline = yield* events.publishedCount
321
+ yield* Stream.fromSubscription(subscription).pipe(
322
+ Stream.runForEach((event) =>
323
+ Ref.update(observation, (current) => observeBench(current, event)).pipe(
324
+ Effect.andThen(Ref.update(consumed, (count) => count + 1))
325
+ )
326
+ ),
327
+ Effect.forkScoped
328
+ )
329
+ const drain: Effect.Effect<void> = Effect.suspend(() =>
330
+ Effect.all([events.publishedCount, Ref.get(consumed)]).pipe(
331
+ Effect.flatMap(([published, seen]) =>
332
+ seen >= published - baseline ? Effect.void : Effect.yieldNow.pipe(Effect.andThen(drain))
333
+ )
334
+ )
335
+ )
336
+ return {
337
+ get: drain.pipe(Effect.andThen(Ref.get(observation))),
338
+ reset: drain.pipe(Effect.andThen(Ref.set(observation, emptyBenchObservation())))
339
+ }
340
+ })