lalph 0.1.113 → 0.2.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.
@@ -0,0 +1,62 @@
1
+ import { Array, Effect, Option, pipe, String } from "effect"
2
+ import { Command, Prompt } from "effect/unstable/cli"
3
+ import { allProjects, getAllProjects, selectProject } from "../../Projects.ts"
4
+ import { CurrentProjectId, Settings } from "../../Settings.ts"
5
+ import { Project } from "../../domain/Project.ts"
6
+ import { IssueSource } from "../../IssueSource.ts"
7
+ import { CurrentIssueSource } from "../../IssueSources.ts"
8
+
9
+ export const commandProjectsEdit = Command.make("edit").pipe(
10
+ Command.withDescription("Modify a project"),
11
+ Command.withHandler(
12
+ Effect.fnUntraced(function* () {
13
+ const projects = yield* getAllProjects
14
+ const project = yield* selectProject
15
+ const concurrency = yield* Prompt.integer({
16
+ message: "Concurrency",
17
+ min: 1,
18
+ })
19
+ const targetBranch = pipe(
20
+ yield* Prompt.text({
21
+ message: "Target branch (leave empty to use HEAD)",
22
+ }),
23
+ String.trim,
24
+ Option.liftPredicate(String.isNonEmpty),
25
+ )
26
+ const gitFlow = yield* Prompt.select({
27
+ message: "Git flow",
28
+ choices: [
29
+ { title: "Pull Request", value: "pr" },
30
+ { title: "Commit", value: "commit" },
31
+ ] as const,
32
+ })
33
+ const reviewAgent = yield* Prompt.toggle({
34
+ message: "Enable review agent?",
35
+ })
36
+
37
+ const nextProject = new Project({
38
+ ...project,
39
+ concurrency,
40
+ targetBranch,
41
+ gitFlow,
42
+ reviewAgent,
43
+ })
44
+ yield* Settings.set(
45
+ allProjects,
46
+ Option.some(
47
+ Array.map(projects, (p) =>
48
+ p.id === nextProject.id ? nextProject : p,
49
+ ),
50
+ ),
51
+ )
52
+
53
+ const source = yield* IssueSource
54
+ yield* source.reset.pipe(
55
+ Effect.provideService(CurrentProjectId, nextProject.id),
56
+ )
57
+ yield* source.settings(project.id)
58
+ }),
59
+ ),
60
+ Command.provide(Settings.layer),
61
+ Command.provide(CurrentIssueSource.layer),
62
+ )
@@ -0,0 +1,38 @@
1
+ import { Effect, Option } from "effect"
2
+ import { Command } from "effect/unstable/cli"
3
+ import { IssueSource } from "../../IssueSource.ts"
4
+ import { CurrentIssueSource } from "../../IssueSources.ts"
5
+ import { getAllProjects } from "../../Projects.ts"
6
+ import { Settings } from "../../Settings.ts"
7
+
8
+ export const commandProjectsLs = Command.make("ls").pipe(
9
+ Command.withDescription("List all configured projects and their settings"),
10
+ Command.withHandler(
11
+ Effect.fnUntraced(function* () {
12
+ const meta = yield* CurrentIssueSource
13
+ const source = yield* IssueSource
14
+ console.log("Issue source:", meta.name)
15
+ console.log("")
16
+
17
+ const projects = yield* getAllProjects
18
+
19
+ for (const project of projects) {
20
+ console.log(`Project: ${project.id}`)
21
+ yield* source.info(project.id)
22
+ console.log(` Concurrency: ${project.concurrency}`)
23
+ if (Option.isSome(project.targetBranch)) {
24
+ console.log(` Target Branch: ${project.targetBranch.value}`)
25
+ }
26
+ console.log(
27
+ ` Git flow: ${project.gitFlow === "pr" ? "Pull Request" : "Commit"}`,
28
+ )
29
+ console.log(
30
+ ` Review agent: ${project.reviewAgent ? "Enabled" : "Disabled"}`,
31
+ )
32
+ console.log("")
33
+ }
34
+ }),
35
+ ),
36
+ Command.provide(Settings.layer),
37
+ Command.provide(CurrentIssueSource.layer),
38
+ )
@@ -0,0 +1,22 @@
1
+ import { Array, Effect, Option } from "effect"
2
+ import { Command } from "effect/unstable/cli"
3
+ import { allProjects, getAllProjects, selectProject } from "../../Projects.ts"
4
+ import { Settings } from "../../Settings.ts"
5
+
6
+ export const commandProjectsRm = Command.make("rm").pipe(
7
+ Command.withDescription("Remove a project"),
8
+ Command.withHandler(
9
+ Effect.fnUntraced(function* () {
10
+ const projects = yield* getAllProjects
11
+ const project = yield* selectProject
12
+ const newProjects = projects.filter((p) => p.id !== project.id)
13
+ if (!Array.isArrayNonEmpty(newProjects)) {
14
+ return yield* Effect.log(
15
+ "You cannot remove the last remaining project.",
16
+ )
17
+ }
18
+ yield* Settings.set(allProjects, Option.some(newProjects))
19
+ }),
20
+ ),
21
+ Command.provide(Settings.layer),
22
+ )
@@ -0,0 +1,37 @@
1
+ import { Array, Effect, Option } from "effect"
2
+ import { Command, Prompt } from "effect/unstable/cli"
3
+ import { allProjects, getAllProjects } from "../../Projects.ts"
4
+ import { Settings } from "../../Settings.ts"
5
+ import { Project } from "../../domain/Project.ts"
6
+
7
+ export const commandProjectsToggle = Command.make("toggle").pipe(
8
+ Command.withDescription("Enable or disable projects"),
9
+ Command.withHandler(
10
+ Effect.fnUntraced(function* () {
11
+ const projects = yield* getAllProjects
12
+ const enabled = yield* Prompt.multiSelect({
13
+ message: "Select projects to enable",
14
+ choices: projects.map((project) => ({
15
+ title: project.id,
16
+ value: project.id,
17
+ selected: project.enabled,
18
+ })),
19
+ })
20
+
21
+ yield* Settings.set(
22
+ allProjects,
23
+ Option.some(
24
+ Array.map(
25
+ projects,
26
+ (p) =>
27
+ new Project({
28
+ ...p,
29
+ enabled: enabled.includes(p.id),
30
+ }),
31
+ ),
32
+ ),
33
+ )
34
+ }),
35
+ ),
36
+ Command.provide(Settings.layer),
37
+ )
@@ -0,0 +1,24 @@
1
+ import { Command } from "effect/unstable/cli"
2
+ import { commandProjectsLs } from "./projects/ls.ts"
3
+ import { commandProjectsAdd } from "./projects/add.ts"
4
+ import { commandProjectsRm } from "./projects/rm.ts"
5
+ import { commandProjectsEdit } from "./projects/edit.ts"
6
+ import { commandProjectsToggle } from "./projects/toggle.ts"
7
+
8
+ const subcommands = Command.withSubcommands([
9
+ commandProjectsLs,
10
+ commandProjectsAdd,
11
+ commandProjectsEdit,
12
+ commandProjectsToggle,
13
+ commandProjectsRm,
14
+ ])
15
+
16
+ export const commandProjects = Command.make("projects").pipe(
17
+ Command.withDescription("Manage projects"),
18
+ subcommands,
19
+ )
20
+
21
+ export const commandProjectsAlias = Command.make("p").pipe(
22
+ Command.withDescription("Alias for 'projects' command"),
23
+ subcommands,
24
+ )
@@ -16,7 +16,7 @@ import { Prd } from "../Prd.ts"
16
16
  import { ChildProcess } from "effect/unstable/process"
17
17
  import { Worktree } from "../Worktree.ts"
18
18
  import { getCommandPrefix, getOrSelectCliAgent } from "./agent.ts"
19
- import { Flag, CliError, Command } from "effect/unstable/cli"
19
+ import { Flag, Command } from "effect/unstable/cli"
20
20
  import { IssueSource } from "../IssueSource.ts"
21
21
  import {
22
22
  checkForWork,
@@ -29,7 +29,7 @@ import { agentChooser } from "../Agents/chooser.ts"
29
29
  import { RunnerStalled } from "../domain/Errors.ts"
30
30
  import { agentReviewer } from "../Agents/reviewer.ts"
31
31
  import { agentTimeout } from "../Agents/timeout.ts"
32
- import { Settings } from "../Settings.ts"
32
+ import { CurrentProjectId, Settings } from "../Settings.ts"
33
33
  import { Atom, AtomRegistry, Reactivity } from "effect/unstable/reactivity"
34
34
  import {
35
35
  activeWorkerLoggingAtom,
@@ -39,6 +39,8 @@ import {
39
39
  import { WorkerStatus } from "../domain/WorkerState.ts"
40
40
  import { GitFlow, GitFlowCommit, GitFlowPR } from "../GitFlow.ts"
41
41
  import { parseBranch } from "../shared/git.ts"
42
+ import { getAllProjects } from "../Projects.ts"
43
+ import type { Project } from "../domain/Project.ts"
42
44
 
43
45
  // Main iteration run logic
44
46
 
@@ -54,6 +56,7 @@ const run = Effect.fnUntraced(
54
56
  ) => ChildProcess.Command
55
57
  readonly review: boolean
56
58
  }) {
59
+ const projectId = yield* CurrentProjectId
57
60
  const fs = yield* FileSystem.FileSystem
58
61
  const pathService = yield* Path.Path
59
62
  const worktree = yield* Worktree
@@ -127,7 +130,7 @@ const run = Effect.fnUntraced(
127
130
  yield* prd.setChosenIssueId(taskId)
128
131
  yield* prd.setAutoMerge(chosenTask.prd.autoMerge)
129
132
 
130
- yield* source.ensureInProgress(taskId).pipe(
133
+ yield* source.ensureInProgress(projectId, taskId).pipe(
131
134
  Effect.timeoutOrElse({
132
135
  duration: "1 minute",
133
136
  onTimeout: () => Effect.fail(new RunnerStalled()),
@@ -221,6 +224,111 @@ const run = Effect.fnUntraced(
221
224
  Effect.provide(Prd.layer, { local: true }),
222
225
  )
223
226
 
227
+ const runProject = Effect.fnUntraced(
228
+ function* (options: {
229
+ readonly iterations: number
230
+ readonly project: Project
231
+ readonly specsDirectory: string
232
+ readonly stallTimeout: Duration.Duration
233
+ readonly runTimeout: Duration.Duration
234
+ readonly commandPrefix: (
235
+ command: ChildProcess.Command,
236
+ ) => ChildProcess.Command
237
+ }) {
238
+ const isFinite = Number.isFinite(options.iterations)
239
+ const iterationsDisplay = isFinite ? options.iterations : "unlimited"
240
+ const semaphore = Effect.makeSemaphoreUnsafe(options.project.concurrency)
241
+ const fibers = yield* FiberSet.make()
242
+
243
+ yield* resetInProgress.pipe(Effect.withSpan("Main.resetInProgress"))
244
+
245
+ yield* Effect.log(
246
+ `Executing ${iterationsDisplay} iteration(s) with concurrency ${options.project.concurrency}`,
247
+ )
248
+
249
+ let iterations = options.iterations
250
+ let iteration = 0
251
+ let quit = false
252
+
253
+ yield* Atom.mount(activeWorkerLoggingAtom)
254
+
255
+ while (true) {
256
+ yield* semaphore.take(1)
257
+ if (quit || (isFinite && iteration >= iterations)) {
258
+ break
259
+ }
260
+
261
+ const currentIteration = iteration
262
+
263
+ const startedDeferred = yield* Deferred.make<void>()
264
+
265
+ yield* checkForWork.pipe(
266
+ Effect.andThen(
267
+ run({
268
+ startedDeferred,
269
+ targetBranch: options.project.targetBranch,
270
+ specsDirectory: options.specsDirectory,
271
+ stallTimeout: options.stallTimeout,
272
+ runTimeout: options.runTimeout,
273
+ commandPrefix: options.commandPrefix,
274
+ review: options.project.reviewAgent,
275
+ }).pipe(
276
+ Effect.provide(
277
+ options.project.gitFlow === "commit" ? GitFlowCommit : GitFlowPR,
278
+ { local: true },
279
+ ),
280
+ withWorkerState(options.project.id),
281
+ ),
282
+ ),
283
+ Effect.catchFilter(
284
+ (e) =>
285
+ e._tag === "NoMoreWork" || e._tag === "QuitError"
286
+ ? Filter.fail(e)
287
+ : e,
288
+ (e) =>
289
+ Effect.logWarning(Cause.fail(e)).pipe(
290
+ Effect.andThen(Effect.sleep(Duration.seconds(10))),
291
+ ),
292
+ ),
293
+ Effect.catchTags({
294
+ NoMoreWork(_) {
295
+ if (isFinite) {
296
+ // If we have a finite number of iterations, we exit when no more
297
+ // work is found
298
+ iterations = currentIteration
299
+ return Effect.log(
300
+ `No more work to process, ending after ${currentIteration} iteration(s).`,
301
+ )
302
+ }
303
+ const log =
304
+ Iterable.size(fibers) <= 1
305
+ ? Effect.log("No more work to process, waiting 30 seconds...")
306
+ : Effect.void
307
+ return Effect.andThen(log, Effect.sleep(Duration.seconds(30)))
308
+ },
309
+ QuitError(_) {
310
+ quit = true
311
+ return Effect.void
312
+ },
313
+ }),
314
+ Effect.ensuring(semaphore.release(1)),
315
+ Effect.ensuring(Deferred.completeWith(startedDeferred, Effect.void)),
316
+ FiberSet.run(fibers),
317
+ )
318
+
319
+ yield* Deferred.await(startedDeferred)
320
+
321
+ iteration++
322
+ }
323
+
324
+ yield* FiberSet.awaitEmpty(fibers)
325
+ },
326
+ (effect, options) =>
327
+ Effect.annotateLogs(effect, {
328
+ project: options.project.id,
329
+ }),
330
+ )
331
+
224
332
  // Command
225
333
 
226
334
  const iterations = Flag.integer("iterations").pipe(
@@ -229,37 +337,6 @@ const iterations = Flag.integer("iterations").pipe(
229
337
  Flag.withDefault(Number.POSITIVE_INFINITY),
230
338
  )
231
339
 
232
- const concurrency = Flag.integer("concurrency").pipe(
233
- Flag.withDescription("Number of concurrent agents, defaults to 1"),
234
- Flag.withAlias("c"),
235
- Flag.withDefault(1),
236
- )
237
-
238
- const targetBranch = Flag.string("target-branch").pipe(
239
- Flag.withDescription(
240
- "Target branch for PRs. Defaults to current branch. Env variable: LALPH_TARGET_BRANCH",
241
- ),
242
- Flag.withAlias("b"),
243
- Flag.withFallbackConfig(Config.string("LALPH_TARGET_BRANCH")),
244
- Flag.withDefault(
245
- ChildProcess.make`git branch --show-current`.pipe(
246
- ChildProcess.string,
247
- Effect.orDie,
248
- Effect.flatMap((output) => {
249
- const branch = output.trim()
250
- return branch === ""
251
- ? Effect.fail(
252
- new CliError.MissingOption({
253
- option: "--target-branch",
254
- }),
255
- )
256
- : Effect.succeed(branch)
257
- }),
258
- ),
259
- ),
260
- Flag.optional,
261
- )
262
-
263
340
  const maxIterationMinutes = Flag.integer("max-minutes").pipe(
264
341
  Flag.withDescription(
265
342
  "Maximum number of minutes to allow an iteration to run. Defaults to 90 minutes. Env variable: LALPH_MAX_MINUTES",
@@ -285,36 +362,15 @@ const specsDirectory = Flag.directory("specs").pipe(
285
362
  Flag.withDefault(".specs"),
286
363
  )
287
364
 
288
- const commitMode = Flag.boolean("commit").pipe(
289
- Flag.withDescription("Commit to the target branch instead of creating PRs"),
290
- )
291
-
292
365
  const verbose = Flag.boolean("verbose").pipe(
293
366
  Flag.withDescription("Enable verbose logging"),
294
367
  Flag.withAlias("v"),
295
368
  )
296
369
 
297
- const review = Flag.boolean("review").pipe(
298
- Flag.withDescription(
299
- "Enable the AI peer-review step. Will use LALPH_REVIEW.md if present.",
300
- ),
301
- )
302
-
303
- // handled in cli.ts
304
- const reset = Flag.boolean("reset").pipe(
305
- Flag.withDescription("Reset the current issue source before running"),
306
- Flag.withAlias("r"),
307
- )
308
-
309
370
  export const commandRoot = Command.make("lalph", {
310
371
  iterations,
311
- concurrency,
312
- targetBranch,
313
372
  maxIterationMinutes,
314
373
  stallMinutes,
315
- commitMode,
316
- reset,
317
- review,
318
374
  specsDirectory,
319
375
  verbose,
320
376
  }).pipe(
@@ -322,110 +378,26 @@ export const commandRoot = Command.make("lalph", {
322
378
  Effect.fnUntraced(
323
379
  function* ({
324
380
  iterations,
325
- concurrency,
326
- targetBranch,
327
381
  maxIterationMinutes,
328
382
  stallMinutes,
329
383
  specsDirectory,
330
- review,
331
- commitMode,
332
384
  }) {
333
385
  const commandPrefix = yield* getCommandPrefix
334
386
  yield* getOrSelectCliAgent
335
-
336
- const isFinite = Number.isFinite(iterations)
337
- const iterationsDisplay = isFinite ? iterations : "unlimited"
338
- const runConcurrency = Math.max(1, concurrency)
339
- const semaphore = Effect.makeSemaphoreUnsafe(runConcurrency)
340
- const fibers = yield* FiberSet.make()
341
-
342
- yield* resetInProgress.pipe(Effect.withSpan("Main.resetInProgress"))
343
-
344
- yield* Effect.log(
345
- `Executing ${iterationsDisplay} iteration(s) with concurrency ${runConcurrency}`,
387
+ const projects = (yield* getAllProjects).filter((p) => p.enabled)
388
+ yield* Effect.forEach(
389
+ projects,
390
+ (project) =>
391
+ runProject({
392
+ iterations,
393
+ project,
394
+ specsDirectory,
395
+ stallTimeout: Duration.minutes(stallMinutes),
396
+ runTimeout: Duration.minutes(maxIterationMinutes),
397
+ commandPrefix,
398
+ }).pipe(Effect.provideService(CurrentProjectId, project.id)),
399
+ { concurrency: "unbounded", discard: true },
346
400
  )
347
- if (Option.isSome(targetBranch)) {
348
- yield* Effect.log(`Using target branch: ${targetBranch.value}`)
349
- }
350
-
351
- let iteration = 0
352
- let quit = false
353
-
354
- yield* Atom.mount(activeWorkerLoggingAtom)
355
-
356
- while (true) {
357
- yield* semaphore.take(1)
358
- if (quit || (isFinite && iteration >= iterations)) {
359
- break
360
- }
361
-
362
- const currentIteration = iteration
363
-
364
- const startedDeferred = yield* Deferred.make<void>()
365
-
366
- yield* checkForWork.pipe(
367
- Effect.andThen(
368
- run({
369
- startedDeferred,
370
- targetBranch,
371
- specsDirectory,
372
- stallTimeout: Duration.minutes(stallMinutes),
373
- runTimeout: Duration.minutes(maxIterationMinutes),
374
- commandPrefix,
375
- review,
376
- }).pipe(
377
- Effect.provide(commitMode ? GitFlowCommit : GitFlowPR, {
378
- local: true,
379
- }),
380
- withWorkerState(currentIteration),
381
- ),
382
- ),
383
- Effect.catchFilter(
384
- (e) =>
385
- e._tag === "NoMoreWork" || e._tag === "QuitError"
386
- ? Filter.fail(e)
387
- : e,
388
- (e) => Effect.logWarning(Cause.fail(e)),
389
- ),
390
- Effect.catchTags({
391
- NoMoreWork(_) {
392
- if (isFinite) {
393
- // If we have a finite number of iterations, we exit when no more
394
- // work is found
395
- iterations = currentIteration
396
- return Effect.log(
397
- `No more work to process, ending after ${currentIteration} iteration(s).`,
398
- )
399
- }
400
- const log =
401
- Iterable.size(fibers) <= 1
402
- ? Effect.log(
403
- "No more work to process, waiting 30 seconds...",
404
- )
405
- : Effect.void
406
- return Effect.andThen(log, Effect.sleep(Duration.seconds(30)))
407
- },
408
- QuitError(_) {
409
- quit = true
410
- return Effect.void
411
- },
412
- }),
413
- Effect.annotateLogs({
414
- iteration: currentIteration,
415
- }),
416
- Effect.ensuring(semaphore.release(1)),
417
- Effect.ensuring(
418
- Deferred.completeWith(startedDeferred, Effect.void),
419
- ),
420
- FiberSet.run(fibers),
421
- )
422
-
423
- yield* Deferred.await(startedDeferred)
424
-
425
- iteration++
426
- }
427
-
428
- yield* FiberSet.awaitEmpty(fibers)
429
401
  },
430
402
  Effect.scoped,
431
403
  Effect.provide([
@@ -1,10 +1,11 @@
1
1
  import { Command } from "effect/unstable/cli"
2
- import { Effect, FileSystem, Path } from "effect"
2
+ import { Effect, FileSystem, Layer, Path } from "effect"
3
3
  import { ChildProcess } from "effect/unstable/process"
4
4
  import { Prd } from "../Prd.ts"
5
5
  import { Worktree } from "../Worktree.ts"
6
+ import { layerProjectIdPrompt } from "../Projects.ts"
6
7
 
7
- export const commandShell = Command.make("shell").pipe(
8
+ export const commandSh = Command.make("sh").pipe(
8
9
  Command.withDescription("Enter an interactive shell in the worktree"),
9
10
  Command.withHandler(
10
11
  Effect.fnUntraced(
@@ -18,6 +19,10 @@ export const commandShell = Command.make("shell").pipe(
18
19
  pathService.resolve(pathService.join(".lalph", "config")),
19
20
  pathService.join(worktree.directory, ".lalph", "config"),
20
21
  )
22
+ yield* fs.symlink(
23
+ pathService.resolve(pathService.join(".lalph", "projects")),
24
+ pathService.join(worktree.directory, ".lalph", "projects"),
25
+ )
21
26
 
22
27
  yield* ChildProcess.make(process.env.SHELL || "/bin/bash", [], {
23
28
  cwd: worktree.directory,
@@ -27,7 +32,9 @@ export const commandShell = Command.make("shell").pipe(
27
32
  }).pipe(ChildProcess.exitCode)
28
33
  },
29
34
  Effect.scoped,
30
- Effect.provide(Prd.layerProvided),
35
+ Effect.provide(
36
+ Prd.layerProvided.pipe(Layer.provideMerge(layerProjectIdPrompt)),
37
+ ),
31
38
  ),
32
39
  ),
33
40
  )
@@ -0,0 +1,22 @@
1
+ import { Option, Schema } from "effect"
2
+
3
+ export const ProjectId = Schema.String.pipe(Schema.brand("lalph/ProjectId"))
4
+ export type ProjectId = typeof ProjectId.Type
5
+
6
+ export class Project extends Schema.Class<Project>("lalph/Project")({
7
+ id: ProjectId,
8
+ enabled: Schema.Boolean,
9
+ targetBranch: Schema.Option(Schema.String),
10
+ concurrency: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
11
+ gitFlow: Schema.Literals(["pr", "commit"]),
12
+ reviewAgent: Schema.Boolean,
13
+ }) {
14
+ static defaultProject = new Project({
15
+ id: ProjectId.makeUnsafe("default"),
16
+ enabled: true,
17
+ targetBranch: Option.none(),
18
+ concurrency: 1,
19
+ gitFlow: "pr",
20
+ reviewAgent: true,
21
+ })
22
+ }
@@ -1,13 +1,18 @@
1
1
  import { Data, DateTime, Exit } from "effect"
2
+ import type { ProjectId } from "./Project.ts"
2
3
 
3
4
  export class WorkerState extends Data.Class<{
4
- iteration: number
5
+ id: number
6
+ projectId: ProjectId
5
7
  status: WorkerStatus
6
8
  lastTransitionAt: DateTime.Utc
7
9
  }> {
8
- static initial(iteration: number) {
10
+ static initial(options: {
11
+ readonly projectId: ProjectId
12
+ readonly id: number
13
+ }) {
9
14
  return new WorkerState({
10
- iteration,
15
+ ...options,
11
16
  status: WorkerStatus.Booting(),
12
17
  lastTransitionAt: DateTime.nowUnsafe(),
13
18
  })
@@ -15,7 +20,7 @@ export class WorkerState extends Data.Class<{
15
20
 
16
21
  transitionTo(status: WorkerStatus): WorkerState {
17
22
  return new WorkerState({
18
- iteration: this.iteration,
23
+ ...this,
19
24
  status,
20
25
  lastTransitionAt: DateTime.nowUnsafe(),
21
26
  })