task-while 0.0.2 → 0.0.3

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 (41) hide show
  1. package/README.md +32 -34
  2. package/package.json +2 -2
  3. package/src/adapters/fs/harness-store.ts +84 -0
  4. package/src/agents/claude.ts +159 -9
  5. package/src/agents/codex.ts +68 -4
  6. package/src/agents/event-log.ts +160 -15
  7. package/src/batch/discovery.ts +1 -1
  8. package/src/commands/batch.ts +63 -164
  9. package/src/commands/run-branch-helpers.ts +81 -0
  10. package/src/commands/run-providers.ts +77 -0
  11. package/src/commands/run.ts +121 -177
  12. package/src/core/create-runtime-ports.ts +118 -0
  13. package/src/core/runtime.ts +15 -36
  14. package/src/harness/in-memory-store.ts +45 -0
  15. package/src/harness/kernel.ts +226 -0
  16. package/src/harness/state.ts +47 -0
  17. package/src/harness/store.ts +26 -0
  18. package/src/harness/workflow-builders.ts +87 -0
  19. package/src/harness/workflow-program.ts +86 -0
  20. package/src/ports/agent.ts +17 -0
  21. package/src/ports/code-host.ts +23 -0
  22. package/src/programs/batch.ts +139 -0
  23. package/src/programs/run-direct.ts +209 -0
  24. package/src/programs/run-pr-transitions.ts +81 -0
  25. package/src/programs/run-pr.ts +290 -0
  26. package/src/programs/shared-steps.ts +252 -0
  27. package/src/schedulers/scheduler.ts +208 -0
  28. package/src/session/session.ts +127 -0
  29. package/src/workflow/config.ts +15 -0
  30. package/src/core/engine-helpers.ts +0 -114
  31. package/src/core/engine-outcomes.ts +0 -166
  32. package/src/core/engine.ts +0 -223
  33. package/src/core/orchestrator-helpers.ts +0 -52
  34. package/src/core/orchestrator-integrate-resume.ts +0 -149
  35. package/src/core/orchestrator-review-resume.ts +0 -228
  36. package/src/core/orchestrator-task-attempt.ts +0 -257
  37. package/src/core/orchestrator.ts +0 -99
  38. package/src/runtime/fs-runtime.ts +0 -209
  39. package/src/workflow/direct-preset.ts +0 -44
  40. package/src/workflow/preset.ts +0 -86
  41. package/src/workflow/pull-request-preset.ts +0 -312
@@ -0,0 +1,226 @@
1
+ import {
2
+ createInitialState,
3
+ TaskStatus,
4
+ type Artifact,
5
+ type TaskState,
6
+ } from './state'
7
+ import {
8
+ WorkflowNodeType,
9
+ type DomainResult,
10
+ type Transition,
11
+ type TransitionRule,
12
+ type TypedArtifactMap,
13
+ type WorkflowContext,
14
+ type WorkflowNode,
15
+ type WorkflowProgram,
16
+ } from './workflow-program'
17
+
18
+ import type { HarnessStore } from './store'
19
+
20
+ export enum KernelResultKind {
21
+ Error = 'error',
22
+ GateFail = 'gate.fail',
23
+ GatePass = 'gate.pass',
24
+ }
25
+
26
+ export function retryBudgetReached(state: TaskState, maxIterations: number) {
27
+ return Math.max(0, ...Object.values(state.phaseIterations)) >= maxIterations
28
+ }
29
+
30
+ export function errorRetry(maxIterations: number) {
31
+ return (retryPhase: string): TransitionRule =>
32
+ (input) =>
33
+ retryBudgetReached(input.state, maxIterations)
34
+ ? { nextPhase: null, status: TaskStatus.Blocked }
35
+ : { nextPhase: retryPhase, status: TaskStatus.Running }
36
+ }
37
+
38
+ export interface KernelResult {
39
+ status: TaskStatus
40
+ }
41
+
42
+ export async function runKernel(input: {
43
+ config: Record<string, unknown>
44
+ program: WorkflowProgram
45
+ protocol: string
46
+ store: HarnessStore
47
+ subjectId: string
48
+ }): Promise<KernelResult> {
49
+ const { config, program, protocol, store, subjectId } = input
50
+
51
+ let state =
52
+ (await store.loadState(protocol, subjectId)) ??
53
+ createInitialState(subjectId)
54
+
55
+ if (
56
+ state.status === TaskStatus.Done ||
57
+ state.status === TaskStatus.Blocked ||
58
+ state.status === TaskStatus.Replan
59
+ ) {
60
+ return { status: state.status }
61
+ }
62
+
63
+ const artifactCache = new Map<string, Artifact>()
64
+ for (const [kind, artifactId] of Object.entries(state.artifacts)) {
65
+ const artifact = await store.loadArtifact(protocol, subjectId, artifactId)
66
+ if (artifact) {
67
+ artifactCache.set(kind, artifact)
68
+ }
69
+ }
70
+
71
+ const artifacts: TypedArtifactMap = {
72
+ get<T>(kind: string) {
73
+ return artifactCache.get(kind) as Artifact<T> | undefined
74
+ },
75
+ has(kind: string) {
76
+ return artifactCache.has(kind)
77
+ },
78
+ set(artifact: Artifact) {
79
+ artifactCache.set(artifact.kind, artifact)
80
+ },
81
+ }
82
+
83
+ if (state.status === TaskStatus.Suspended) {
84
+ state = { ...state, status: TaskStatus.Running }
85
+ await store.saveState(protocol, subjectId, state)
86
+ }
87
+
88
+ let current: null | string = state.currentPhase ?? program.entry
89
+
90
+ while (current) {
91
+ const node: undefined | WorkflowNode = program.nodes[current]
92
+ if (!node) {
93
+ throw new Error(`unknown node: ${current}`)
94
+ }
95
+
96
+ if (node.type === WorkflowNodeType.Gate) {
97
+ const ctx: WorkflowContext = { artifacts, config, state, subjectId }
98
+ const passed: boolean = await node.test(ctx)
99
+ const nextPhase: string = passed ? node.then : node.otherwise
100
+
101
+ await store.appendTransition(protocol, subjectId, {
102
+ nextPhase,
103
+ phase: current,
104
+ status: state.status,
105
+ timestamp: new Date().toISOString(),
106
+ resultKind: passed
107
+ ? KernelResultKind.GatePass
108
+ : KernelResultKind.GateFail,
109
+ })
110
+
111
+ current = nextPhase
112
+ continue
113
+ }
114
+
115
+ if (node.type === WorkflowNodeType.Branch) {
116
+ const ctx: WorkflowContext = { artifacts, config, state, subjectId }
117
+ const decision = await node.decide(ctx)
118
+ const nextPhase: string | undefined = node.paths[decision]
119
+ if (!nextPhase) {
120
+ throw new Error(`branch "${current}" has no path for "${decision}"`)
121
+ }
122
+
123
+ await store.appendTransition(protocol, subjectId, {
124
+ nextPhase,
125
+ phase: current,
126
+ resultKind: `branch.${decision}`,
127
+ status: state.status,
128
+ timestamp: new Date().toISOString(),
129
+ })
130
+
131
+ current = nextPhase
132
+ continue
133
+ }
134
+
135
+ const phaseCount = (state.phaseIterations[current] ?? 0) + 1
136
+ state = {
137
+ ...state,
138
+ currentPhase: current,
139
+ iteration: phaseCount,
140
+ phaseIterations: { ...state.phaseIterations, [current]: phaseCount },
141
+ status: TaskStatus.Running,
142
+ }
143
+ await store.saveState(protocol, subjectId, state)
144
+
145
+ let actionResult: { artifact?: Artifact; result: DomainResult }
146
+ try {
147
+ const ctx: WorkflowContext = { artifacts, config, state, subjectId }
148
+ actionResult = await node.run(ctx)
149
+ } catch (error) {
150
+ const reason = error instanceof Error ? error.message : String(error)
151
+ state = { ...state, failureReason: reason }
152
+
153
+ if (program.transitions[current]?.[KernelResultKind.Error]) {
154
+ actionResult = { result: { kind: KernelResultKind.Error } }
155
+ } else {
156
+ state = { ...state, status: TaskStatus.Blocked }
157
+ await store.saveState(protocol, subjectId, state)
158
+ await store.appendTransition(protocol, subjectId, {
159
+ nextPhase: null,
160
+ phase: current,
161
+ resultKind: KernelResultKind.Error,
162
+ status: TaskStatus.Blocked,
163
+ timestamp: new Date().toISOString(),
164
+ })
165
+ return { status: TaskStatus.Blocked }
166
+ }
167
+ }
168
+
169
+ if (actionResult.artifact) {
170
+ await store.saveArtifact(protocol, subjectId, actionResult.artifact)
171
+ artifacts.set(actionResult.artifact)
172
+ state = {
173
+ ...state,
174
+ artifacts: {
175
+ ...state.artifacts,
176
+ [actionResult.artifact.kind]: actionResult.artifact.id,
177
+ },
178
+ }
179
+ }
180
+
181
+ const transitionTable = program.transitions[current]
182
+ if (!transitionTable) {
183
+ throw new Error(`no transition table for "${current}"`)
184
+ }
185
+
186
+ const rule: TransitionRule | undefined =
187
+ transitionTable[actionResult.result.kind]
188
+ if (!rule) {
189
+ throw new Error(
190
+ `no transition rule for "${current}" -> "${actionResult.result.kind}"`,
191
+ )
192
+ }
193
+
194
+ const transition: Transition =
195
+ typeof rule === 'function'
196
+ ? rule({ result: actionResult.result, state })
197
+ : rule
198
+
199
+ state = {
200
+ ...state,
201
+ currentPhase: transition.nextPhase,
202
+ status: transition.status,
203
+ completedAt:
204
+ transition.status === TaskStatus.Done
205
+ ? new Date().toISOString()
206
+ : state.completedAt,
207
+ }
208
+ await store.saveState(protocol, subjectId, state)
209
+
210
+ await store.appendTransition(protocol, subjectId, {
211
+ nextPhase: transition.nextPhase,
212
+ phase: current,
213
+ resultKind: actionResult.result.kind,
214
+ status: transition.status,
215
+ timestamp: new Date().toISOString(),
216
+ })
217
+
218
+ if (state.status !== TaskStatus.Running) {
219
+ return { status: state.status }
220
+ }
221
+
222
+ current = transition.nextPhase
223
+ }
224
+
225
+ return { status: state.status }
226
+ }
@@ -0,0 +1,47 @@
1
+ export enum TaskStatus {
2
+ Blocked = 'blocked',
3
+ Done = 'done',
4
+ Queued = 'queued',
5
+ Replan = 'replan',
6
+ Running = 'running',
7
+ Suspended = 'suspended',
8
+ }
9
+
10
+ export interface TaskState {
11
+ artifacts: Record<string, string>
12
+ completedAt: null | string
13
+ currentPhase: null | string
14
+ failureReason: null | string
15
+ iteration: number
16
+ phaseIterations: Record<string, number>
17
+ status: TaskStatus
18
+ }
19
+
20
+ export interface TransitionRecord {
21
+ nextPhase: null | string
22
+ phase: string
23
+ resultKind: string
24
+ status: TaskStatus
25
+ timestamp: string
26
+ }
27
+
28
+ export interface Artifact<TPayload = unknown> {
29
+ id: string
30
+ kind: string
31
+ payload: TPayload
32
+ subjectId: string
33
+ timestamp: string
34
+ }
35
+
36
+ export function createInitialState(subjectId: string): TaskState {
37
+ void subjectId
38
+ return {
39
+ artifacts: {},
40
+ completedAt: null,
41
+ currentPhase: null,
42
+ failureReason: null,
43
+ iteration: 0,
44
+ phaseIterations: {},
45
+ status: TaskStatus.Queued,
46
+ }
47
+ }
@@ -0,0 +1,26 @@
1
+ import type { Artifact, TaskState, TransitionRecord } from './state'
2
+
3
+ export interface HarnessStore {
4
+ appendTransition: (
5
+ protocol: string,
6
+ subjectId: string,
7
+ record: TransitionRecord,
8
+ ) => Promise<void>
9
+ listArtifacts: (protocol: string, subjectId: string) => Promise<Artifact[]>
10
+ loadArtifact: (
11
+ protocol: string,
12
+ subjectId: string,
13
+ artifactId: string,
14
+ ) => Promise<Artifact | null>
15
+ loadState: (protocol: string, subjectId: string) => Promise<null | TaskState>
16
+ saveArtifact: (
17
+ protocol: string,
18
+ subjectId: string,
19
+ artifact: Artifact,
20
+ ) => Promise<void>
21
+ saveState: (
22
+ protocol: string,
23
+ subjectId: string,
24
+ state: TaskState,
25
+ ) => Promise<void>
26
+ }
@@ -0,0 +1,87 @@
1
+ import {
2
+ WorkflowNodeType,
3
+ type ActionNode,
4
+ type ActionRunFn,
5
+ type BranchDecideFn,
6
+ type BranchNode,
7
+ type GateNode,
8
+ type GateTestFn,
9
+ type TransitionRule,
10
+ type WorkflowNode,
11
+ type WorkflowProgram,
12
+ } from './workflow-program'
13
+
14
+ export function action<TConfig = unknown>(
15
+ name: string,
16
+ overrides?: { run?: ActionRunFn<TConfig> },
17
+ ): ActionNode<TConfig> {
18
+ return {
19
+ name,
20
+ type: WorkflowNodeType.Action,
21
+ run:
22
+ overrides?.run ??
23
+ (async () => ({ result: { kind: `${name}.completed` } })),
24
+ }
25
+ }
26
+
27
+ export function gate<TConfig = unknown>(
28
+ name: string,
29
+ input: { otherwise: string; test: GateTestFn<TConfig>; then: string },
30
+ ): GateNode<TConfig> {
31
+ return {
32
+ name,
33
+ otherwise: input.otherwise,
34
+ test: input.test,
35
+ then: input.then,
36
+ type: WorkflowNodeType.Gate,
37
+ }
38
+ }
39
+
40
+ export function branch<TConfig = unknown>(
41
+ name: string,
42
+ input: { decide: BranchDecideFn<TConfig>; paths: Record<string, string> },
43
+ ): BranchNode<TConfig> {
44
+ return {
45
+ name,
46
+ decide: input.decide,
47
+ paths: input.paths,
48
+ type: WorkflowNodeType.Branch,
49
+ }
50
+ }
51
+
52
+ export function sequence<TConfig = unknown>(
53
+ nodes: WorkflowNode<TConfig>[],
54
+ transitions: Record<string, Record<string, TransitionRule>>,
55
+ ): WorkflowProgram<TConfig> {
56
+ const nodeMap: Record<string, WorkflowNode<TConfig>> = {}
57
+ for (const node of nodes) {
58
+ nodeMap[node.name] = node
59
+ }
60
+
61
+ const nodeNames = new Set(Object.keys(nodeMap))
62
+
63
+ for (const node of nodes) {
64
+ if (node.type === WorkflowNodeType.Action && !(node.name in transitions)) {
65
+ throw new Error(`missing transition table for action node "${node.name}"`)
66
+ }
67
+ }
68
+
69
+ for (const rules of Object.values(transitions)) {
70
+ for (const rule of Object.values(rules)) {
71
+ if (typeof rule === 'function') {
72
+ continue
73
+ }
74
+ if (rule.nextPhase !== null && !nodeNames.has(rule.nextPhase)) {
75
+ throw new Error(
76
+ `transition references unknown node "${rule.nextPhase}"`,
77
+ )
78
+ }
79
+ }
80
+ }
81
+
82
+ return {
83
+ entry: nodes[0]!.name,
84
+ nodes: nodeMap,
85
+ transitions,
86
+ }
87
+ }
@@ -0,0 +1,86 @@
1
+ import type { Artifact, TaskState, TaskStatus } from './state'
2
+
3
+ export enum WorkflowNodeType {
4
+ Action = 'action',
5
+ Branch = 'branch',
6
+ Gate = 'gate',
7
+ }
8
+
9
+ export interface TypedArtifactMap {
10
+ get: <T = unknown>(kind: string) => Artifact<T> | undefined
11
+ has: (kind: string) => boolean
12
+ set: (artifact: Artifact) => void
13
+ }
14
+
15
+ export interface WorkflowContext<TConfig = unknown> {
16
+ artifacts: TypedArtifactMap
17
+ config: TConfig
18
+ state: TaskState
19
+ subjectId: string
20
+ }
21
+
22
+ export interface DomainResult<
23
+ TKind extends string = string,
24
+ TPayload = unknown,
25
+ > {
26
+ kind: TKind
27
+ payload?: TPayload
28
+ }
29
+
30
+ export interface ActionResult {
31
+ artifact?: Artifact
32
+ result: DomainResult
33
+ }
34
+
35
+ export type ActionRunFn<TConfig = unknown> = (
36
+ ctx: WorkflowContext<TConfig>,
37
+ ) => Promise<ActionResult>
38
+
39
+ export type GateTestFn<TConfig = unknown> = (
40
+ ctx: WorkflowContext<TConfig>,
41
+ ) => Promise<boolean>
42
+
43
+ export type BranchDecideFn<TConfig = unknown> = (
44
+ ctx: WorkflowContext<TConfig>,
45
+ ) => Promise<string>
46
+
47
+ export interface ActionNode<TConfig = unknown> {
48
+ name: string
49
+ run: ActionRunFn<TConfig>
50
+ type: WorkflowNodeType.Action
51
+ }
52
+
53
+ export interface GateNode<TConfig = unknown> {
54
+ name: string
55
+ otherwise: string
56
+ test: GateTestFn<TConfig>
57
+ then: string
58
+ type: WorkflowNodeType.Gate
59
+ }
60
+
61
+ export interface BranchNode<TConfig = unknown> {
62
+ decide: BranchDecideFn<TConfig>
63
+ name: string
64
+ paths: Record<string, string>
65
+ type: WorkflowNodeType.Branch
66
+ }
67
+
68
+ export type WorkflowNode<TConfig = unknown> =
69
+ | ActionNode<TConfig>
70
+ | BranchNode<TConfig>
71
+ | GateNode<TConfig>
72
+
73
+ export interface Transition {
74
+ nextPhase: null | string
75
+ status: TaskStatus
76
+ }
77
+
78
+ export type TransitionRule =
79
+ | ((input: { result: DomainResult; state: TaskState }) => Transition)
80
+ | Transition
81
+
82
+ export interface WorkflowProgram<TConfig = unknown> {
83
+ entry: string
84
+ nodes: Record<string, WorkflowNode<TConfig>>
85
+ transitions: Record<string, Record<string, TransitionRule>>
86
+ }
@@ -0,0 +1,17 @@
1
+ export interface AgentInvocation {
2
+ outputSchema: Record<string, unknown>
3
+ prompt: string
4
+ role: string
5
+ }
6
+
7
+ export interface AgentPort {
8
+ execute: (invocation: AgentInvocation) => Promise<unknown>
9
+ name: string
10
+ }
11
+
12
+ export function createRoleInvocation(
13
+ role: string,
14
+ input: Omit<AgentInvocation, 'role'>,
15
+ ): AgentInvocation {
16
+ return { ...input, role }
17
+ }
@@ -0,0 +1,23 @@
1
+ import type { PullRequestSnapshot } from '../core/runtime'
2
+
3
+ export interface CodeHostPort {
4
+ createPullRequest: (input: {
5
+ baseBranch: string
6
+ body: string
7
+ headBranch: string
8
+ title: string
9
+ }) => Promise<{ number: number; url: string }>
10
+ findMergedPullRequestByHeadBranch: (input: {
11
+ headBranch: string
12
+ }) => Promise<null | { mergeCommitSha: string; number: number }>
13
+ findOpenPullRequestByHeadBranch: (input: {
14
+ headBranch: string
15
+ }) => Promise<null | { number: number }>
16
+ getPullRequestSnapshot: (input: {
17
+ pullRequestNumber: number
18
+ }) => Promise<PullRequestSnapshot>
19
+ squashMergePullRequest: (input: {
20
+ pullRequestNumber: number
21
+ subject: string
22
+ }) => Promise<{ commitSha: string }>
23
+ }
@@ -0,0 +1,139 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+
4
+ import { TaskStatus, type Artifact } from '../harness/state'
5
+ import { action, sequence } from '../harness/workflow-builders'
6
+ import { writeJsonAtomic } from '../utils/fs'
7
+
8
+ import type { BatchStructuredOutputProvider } from '../batch/provider'
9
+ import type { WorkflowProgram } from '../harness/workflow-program'
10
+
11
+ export enum BatchPhase {
12
+ Persist = 'persist',
13
+ Prepare = 'prepare',
14
+ Process = 'process',
15
+ }
16
+
17
+ export enum BatchResult {
18
+ PersistCompleted = 'persist.completed',
19
+ PrepareCompleted = 'prepare.completed',
20
+ ProcessCompleted = 'process.completed',
21
+ ProcessRetryRequested = 'process.retry_requested',
22
+ }
23
+
24
+ export enum BatchArtifactKind {
25
+ PersistResult = 'persist_result',
26
+ PrepareResult = 'prepare_result',
27
+ ProcessResult = 'process_result',
28
+ }
29
+
30
+ export function createBatchProgram(deps: {
31
+ configDir: string
32
+ maxRetries: number
33
+ outputSchema: Record<string, unknown>
34
+ prompt: string
35
+ provider: BatchStructuredOutputProvider
36
+ results: Record<string, unknown>
37
+ resultsPath: string
38
+ validateOutput: (value: unknown) => void
39
+ }): WorkflowProgram {
40
+ return sequence(
41
+ [
42
+ action(BatchPhase.Prepare, {
43
+ async run(ctx) {
44
+ const content = await readFile(
45
+ path.join(deps.configDir, ctx.subjectId),
46
+ 'utf8',
47
+ )
48
+ const artifact: Artifact<{ content: string; filePath: string }> = {
49
+ id: `${encodeURIComponent(ctx.subjectId)}:${BatchArtifactKind.PrepareResult}`,
50
+ kind: BatchArtifactKind.PrepareResult,
51
+ payload: { content, filePath: ctx.subjectId },
52
+ subjectId: ctx.subjectId,
53
+ timestamp: new Date().toISOString(),
54
+ }
55
+ return {
56
+ artifact,
57
+ result: { kind: BatchResult.PrepareCompleted },
58
+ }
59
+ },
60
+ }),
61
+ action(BatchPhase.Process, {
62
+ async run(ctx) {
63
+ const prepareArtifact = ctx.artifacts.get<{
64
+ content: string
65
+ filePath: string
66
+ }>(BatchArtifactKind.PrepareResult)
67
+ try {
68
+ const output = await deps.provider.runFile({
69
+ content: prepareArtifact!.payload.content,
70
+ filePath: prepareArtifact!.payload.filePath,
71
+ outputSchema: deps.outputSchema,
72
+ prompt: deps.prompt,
73
+ })
74
+ deps.validateOutput(output)
75
+ const artifact: Artifact<{ output: unknown }> = {
76
+ id: `${encodeURIComponent(ctx.subjectId)}:${BatchArtifactKind.ProcessResult}`,
77
+ kind: BatchArtifactKind.ProcessResult,
78
+ payload: { output },
79
+ subjectId: ctx.subjectId,
80
+ timestamp: new Date().toISOString(),
81
+ }
82
+ return {
83
+ artifact,
84
+ result: { kind: BatchResult.ProcessCompleted },
85
+ }
86
+ } catch {
87
+ return {
88
+ result: { kind: BatchResult.ProcessRetryRequested },
89
+ }
90
+ }
91
+ },
92
+ }),
93
+ action(BatchPhase.Persist, {
94
+ async run(ctx) {
95
+ const processArtifact = ctx.artifacts.get<{ output: unknown }>(
96
+ BatchArtifactKind.ProcessResult,
97
+ )
98
+ deps.results[ctx.subjectId] = processArtifact!.payload.output
99
+ await writeJsonAtomic(deps.resultsPath, deps.results)
100
+ const artifact: Artifact<{ filePath: string; persisted: boolean }> = {
101
+ id: `${encodeURIComponent(ctx.subjectId)}:${BatchArtifactKind.PersistResult}`,
102
+ kind: BatchArtifactKind.PersistResult,
103
+ payload: { filePath: ctx.subjectId, persisted: true },
104
+ subjectId: ctx.subjectId,
105
+ timestamp: new Date().toISOString(),
106
+ }
107
+ return {
108
+ artifact,
109
+ result: { kind: BatchResult.PersistCompleted },
110
+ }
111
+ },
112
+ }),
113
+ ],
114
+ {
115
+ [BatchPhase.Persist]: {
116
+ [BatchResult.PersistCompleted]: {
117
+ nextPhase: null,
118
+ status: TaskStatus.Done,
119
+ },
120
+ },
121
+ [BatchPhase.Prepare]: {
122
+ [BatchResult.PrepareCompleted]: {
123
+ nextPhase: BatchPhase.Process,
124
+ status: TaskStatus.Running,
125
+ },
126
+ },
127
+ [BatchPhase.Process]: {
128
+ [BatchResult.ProcessCompleted]: {
129
+ nextPhase: BatchPhase.Persist,
130
+ status: TaskStatus.Running,
131
+ },
132
+ [BatchResult.ProcessRetryRequested]: (input) =>
133
+ input.state.iteration >= deps.maxRetries
134
+ ? { nextPhase: null, status: TaskStatus.Blocked }
135
+ : { nextPhase: BatchPhase.Process, status: TaskStatus.Suspended },
136
+ },
137
+ },
138
+ )
139
+ }