task-while 0.0.1
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 +21 -0
- package/README.md +322 -0
- package/bin/task-while.mjs +22 -0
- package/package.json +72 -0
- package/src/agents/claude.ts +175 -0
- package/src/agents/codex.ts +231 -0
- package/src/agents/provider-options.ts +45 -0
- package/src/agents/types.ts +69 -0
- package/src/batch/config.ts +109 -0
- package/src/batch/discovery.ts +35 -0
- package/src/batch/provider.ts +79 -0
- package/src/commands/batch.ts +266 -0
- package/src/commands/run.ts +270 -0
- package/src/core/engine-helpers.ts +114 -0
- package/src/core/engine-outcomes.ts +166 -0
- package/src/core/engine.ts +223 -0
- package/src/core/orchestrator-helpers.ts +52 -0
- package/src/core/orchestrator-integrate-resume.ts +149 -0
- package/src/core/orchestrator-review-resume.ts +228 -0
- package/src/core/orchestrator-task-attempt.ts +257 -0
- package/src/core/orchestrator.ts +99 -0
- package/src/core/runtime.ts +175 -0
- package/src/core/task-topology.ts +85 -0
- package/src/index.ts +121 -0
- package/src/prompts/implementer.ts +18 -0
- package/src/prompts/reviewer.ts +26 -0
- package/src/runtime/fs-runtime.ts +209 -0
- package/src/runtime/git.ts +137 -0
- package/src/runtime/github-pr-snapshot-decode.ts +307 -0
- package/src/runtime/github-pr-snapshot-queries.ts +137 -0
- package/src/runtime/github-pr-snapshot.ts +139 -0
- package/src/runtime/github.ts +232 -0
- package/src/runtime/path-layout.ts +13 -0
- package/src/runtime/workspace-resolver.ts +125 -0
- package/src/schema/index.ts +127 -0
- package/src/schema/model.ts +233 -0
- package/src/schema/shared.ts +93 -0
- package/src/task-sources/openspec/cli-json.ts +79 -0
- package/src/task-sources/openspec/context-files.ts +121 -0
- package/src/task-sources/openspec/parse-tasks-md.ts +57 -0
- package/src/task-sources/openspec/session.ts +235 -0
- package/src/task-sources/openspec/source.ts +59 -0
- package/src/task-sources/registry.ts +22 -0
- package/src/task-sources/spec-kit/parse-tasks-md.ts +48 -0
- package/src/task-sources/spec-kit/session.ts +174 -0
- package/src/task-sources/spec-kit/source.ts +30 -0
- package/src/task-sources/types.ts +47 -0
- package/src/types.ts +29 -0
- package/src/utils/fs.ts +31 -0
- package/src/workflow/config.ts +127 -0
- package/src/workflow/direct-preset.ts +44 -0
- package/src/workflow/finalize-task-checkbox.ts +24 -0
- package/src/workflow/preset.ts +86 -0
- package/src/workflow/pull-request-preset.ts +312 -0
- package/src/workflow/remote-reviewer.ts +243 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isPullRequestWorkflowPreset,
|
|
3
|
+
type WorkflowRuntime,
|
|
4
|
+
} from '../workflow/preset'
|
|
5
|
+
import {
|
|
6
|
+
recordCommitFailure,
|
|
7
|
+
recordIntegrateResult,
|
|
8
|
+
recordReviewApproved,
|
|
9
|
+
recordReviewFailure,
|
|
10
|
+
recordReviewResult,
|
|
11
|
+
} from './engine'
|
|
12
|
+
import { shouldPassZeroGate } from './engine-helpers'
|
|
13
|
+
import {
|
|
14
|
+
appendEvent,
|
|
15
|
+
now,
|
|
16
|
+
persistCommittedArtifacts,
|
|
17
|
+
persistState,
|
|
18
|
+
} from './orchestrator-helpers'
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
FinalReport,
|
|
22
|
+
IntegrateArtifact,
|
|
23
|
+
ReviewArtifact,
|
|
24
|
+
TaskGraph,
|
|
25
|
+
WorkflowState,
|
|
26
|
+
} from '../types'
|
|
27
|
+
import type { OrchestratorRuntime } from './runtime'
|
|
28
|
+
|
|
29
|
+
export interface ResumePullRequestReviewInput {
|
|
30
|
+
graph: TaskGraph
|
|
31
|
+
runtime: OrchestratorRuntime
|
|
32
|
+
state: WorkflowState
|
|
33
|
+
workflow: WorkflowRuntime
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ResumePullRequestReviewResult {
|
|
37
|
+
report: FinalReport
|
|
38
|
+
state: WorkflowState
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function resumePullRequestReview(
|
|
42
|
+
input: ResumePullRequestReviewInput,
|
|
43
|
+
): Promise<null | ResumePullRequestReviewResult> {
|
|
44
|
+
const preset = input.workflow.preset
|
|
45
|
+
if (!isPullRequestWorkflowPreset(preset) || !input.state.currentTaskHandle) {
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const taskHandle = input.state.currentTaskHandle
|
|
50
|
+
const taskState = input.state.tasks[taskHandle]
|
|
51
|
+
if (taskState?.status !== 'running' || taskState.stage !== 'review') {
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const artifactKey = {
|
|
56
|
+
attempt: taskState.attempt,
|
|
57
|
+
generation: taskState.generation,
|
|
58
|
+
taskHandle,
|
|
59
|
+
}
|
|
60
|
+
const implementArtifact =
|
|
61
|
+
await input.runtime.store.loadImplementArtifact(artifactKey)
|
|
62
|
+
|
|
63
|
+
if (!implementArtifact) {
|
|
64
|
+
const reason = `Cannot resume review for ${taskHandle} without a persisted implement artifact`
|
|
65
|
+
const nextState = recordReviewFailure(
|
|
66
|
+
input.graph,
|
|
67
|
+
input.state,
|
|
68
|
+
taskHandle,
|
|
69
|
+
reason,
|
|
70
|
+
)
|
|
71
|
+
await appendEvent(input.runtime, {
|
|
72
|
+
attempt: taskState.attempt,
|
|
73
|
+
detail: reason,
|
|
74
|
+
generation: taskState.generation,
|
|
75
|
+
taskHandle,
|
|
76
|
+
timestamp: now(),
|
|
77
|
+
type: 'review_failed',
|
|
78
|
+
})
|
|
79
|
+
const report = await persistState(input.runtime, input.graph, nextState)
|
|
80
|
+
return {
|
|
81
|
+
report,
|
|
82
|
+
state: nextState,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const commitMessage = input.runtime.taskSource.buildCommitSubject(taskHandle)
|
|
87
|
+
let review
|
|
88
|
+
let reviewPhaseKind: 'approved' | 'rejected'
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const completionCriteria =
|
|
92
|
+
await input.runtime.taskSource.getCompletionCriteria(taskHandle)
|
|
93
|
+
const reviewPhase = await preset.review({
|
|
94
|
+
attempt: taskState.attempt,
|
|
95
|
+
commitMessage,
|
|
96
|
+
completionCriteria,
|
|
97
|
+
runtime: input.runtime,
|
|
98
|
+
taskHandle,
|
|
99
|
+
})
|
|
100
|
+
reviewPhaseKind = reviewPhase.kind
|
|
101
|
+
review = reviewPhase.review
|
|
102
|
+
if (review.taskHandle !== taskHandle) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Review taskHandle mismatch: expected ${taskHandle}, received ${review.taskHandle}`,
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
109
|
+
const nextState = recordReviewFailure(
|
|
110
|
+
input.graph,
|
|
111
|
+
input.state,
|
|
112
|
+
taskHandle,
|
|
113
|
+
reason,
|
|
114
|
+
)
|
|
115
|
+
await appendEvent(input.runtime, {
|
|
116
|
+
attempt: taskState.attempt,
|
|
117
|
+
detail: reason,
|
|
118
|
+
generation: taskState.generation,
|
|
119
|
+
taskHandle,
|
|
120
|
+
timestamp: now(),
|
|
121
|
+
type: 'review_failed',
|
|
122
|
+
})
|
|
123
|
+
const report = await persistState(input.runtime, input.graph, nextState)
|
|
124
|
+
return {
|
|
125
|
+
report,
|
|
126
|
+
state: nextState,
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const reviewArtifact: ReviewArtifact = {
|
|
131
|
+
attempt: taskState.attempt,
|
|
132
|
+
createdAt: now(),
|
|
133
|
+
generation: taskState.generation,
|
|
134
|
+
result: review,
|
|
135
|
+
taskHandle,
|
|
136
|
+
}
|
|
137
|
+
await input.runtime.store.saveReviewArtifact(reviewArtifact)
|
|
138
|
+
await appendEvent(input.runtime, {
|
|
139
|
+
attempt: taskState.attempt,
|
|
140
|
+
detail: review.summary,
|
|
141
|
+
generation: taskState.generation,
|
|
142
|
+
taskHandle,
|
|
143
|
+
timestamp: now(),
|
|
144
|
+
type: 'review_completed',
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
if (reviewPhaseKind === 'approved' && shouldPassZeroGate({ review })) {
|
|
148
|
+
let nextState = recordReviewApproved(input.state, taskHandle, review)
|
|
149
|
+
await appendEvent(input.runtime, {
|
|
150
|
+
attempt: taskState.attempt,
|
|
151
|
+
generation: taskState.generation,
|
|
152
|
+
taskHandle,
|
|
153
|
+
timestamp: now(),
|
|
154
|
+
type: 'integrate_started',
|
|
155
|
+
})
|
|
156
|
+
let report = await persistState(input.runtime, input.graph, nextState)
|
|
157
|
+
|
|
158
|
+
let integrateResult
|
|
159
|
+
try {
|
|
160
|
+
integrateResult = await input.workflow.preset.integrate({
|
|
161
|
+
commitMessage,
|
|
162
|
+
runtime: input.runtime,
|
|
163
|
+
taskHandle,
|
|
164
|
+
})
|
|
165
|
+
} catch (error) {
|
|
166
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
167
|
+
nextState = recordCommitFailure(
|
|
168
|
+
input.graph,
|
|
169
|
+
nextState,
|
|
170
|
+
taskHandle,
|
|
171
|
+
reason,
|
|
172
|
+
)
|
|
173
|
+
await appendEvent(input.runtime, {
|
|
174
|
+
attempt: taskState.attempt,
|
|
175
|
+
detail: reason,
|
|
176
|
+
generation: taskState.generation,
|
|
177
|
+
taskHandle,
|
|
178
|
+
timestamp: now(),
|
|
179
|
+
type: 'integrate_failed',
|
|
180
|
+
})
|
|
181
|
+
report = await persistState(input.runtime, input.graph, nextState)
|
|
182
|
+
return {
|
|
183
|
+
report,
|
|
184
|
+
state: nextState,
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const integrateArtifact: IntegrateArtifact = {
|
|
189
|
+
attempt: taskState.attempt,
|
|
190
|
+
createdAt: now(),
|
|
191
|
+
generation: taskState.generation,
|
|
192
|
+
result: integrateResult.result,
|
|
193
|
+
taskHandle,
|
|
194
|
+
}
|
|
195
|
+
nextState = recordIntegrateResult(input.graph, nextState, taskHandle, {
|
|
196
|
+
commitSha: integrateResult.result.commitSha,
|
|
197
|
+
review,
|
|
198
|
+
})
|
|
199
|
+
report = await persistState(input.runtime, input.graph, nextState)
|
|
200
|
+
await appendEvent(input.runtime, {
|
|
201
|
+
attempt: taskState.attempt,
|
|
202
|
+
detail: integrateResult.result.summary,
|
|
203
|
+
generation: taskState.generation,
|
|
204
|
+
taskHandle,
|
|
205
|
+
timestamp: now(),
|
|
206
|
+
type: 'integrate_completed',
|
|
207
|
+
})
|
|
208
|
+
await input.runtime.store.saveIntegrateArtifact(integrateArtifact)
|
|
209
|
+
await persistCommittedArtifacts(input.runtime, {
|
|
210
|
+
commitSha: integrateResult.result.commitSha,
|
|
211
|
+
implementArtifact,
|
|
212
|
+
reviewArtifact,
|
|
213
|
+
})
|
|
214
|
+
return {
|
|
215
|
+
report,
|
|
216
|
+
state: nextState,
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const nextState = recordReviewResult(input.graph, input.state, taskHandle, {
|
|
221
|
+
review,
|
|
222
|
+
})
|
|
223
|
+
const report = await persistState(input.runtime, input.graph, nextState)
|
|
224
|
+
return {
|
|
225
|
+
report,
|
|
226
|
+
state: nextState,
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import {
|
|
2
|
+
recordCommitFailure,
|
|
3
|
+
recordImplementFailure,
|
|
4
|
+
recordImplementSuccess,
|
|
5
|
+
recordIntegrateResult,
|
|
6
|
+
recordReviewApproved,
|
|
7
|
+
recordReviewFailure,
|
|
8
|
+
recordReviewResult,
|
|
9
|
+
startAttempt,
|
|
10
|
+
} from './engine'
|
|
11
|
+
import { shouldPassZeroGate } from './engine-helpers'
|
|
12
|
+
import {
|
|
13
|
+
appendEvent,
|
|
14
|
+
now,
|
|
15
|
+
persistCommittedArtifacts,
|
|
16
|
+
persistState,
|
|
17
|
+
} from './orchestrator-helpers'
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
FinalReport,
|
|
21
|
+
ImplementArtifact,
|
|
22
|
+
IntegrateArtifact,
|
|
23
|
+
ReviewArtifact,
|
|
24
|
+
TaskGraph,
|
|
25
|
+
WorkflowState,
|
|
26
|
+
} from '../types'
|
|
27
|
+
import type { ReviewPhaseResult, WorkflowRuntime } from '../workflow/preset'
|
|
28
|
+
import type { OrchestratorRuntime } from './runtime'
|
|
29
|
+
|
|
30
|
+
export interface ExecuteTaskAttemptInput {
|
|
31
|
+
graph: TaskGraph
|
|
32
|
+
runtime: OrchestratorRuntime
|
|
33
|
+
state: WorkflowState
|
|
34
|
+
taskHandle: string
|
|
35
|
+
workflow: WorkflowRuntime
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ExecuteTaskAttemptResult {
|
|
39
|
+
report: FinalReport
|
|
40
|
+
state: WorkflowState
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function executeTaskAttempt(
|
|
44
|
+
input: ExecuteTaskAttemptInput,
|
|
45
|
+
): Promise<ExecuteTaskAttemptResult> {
|
|
46
|
+
let state = startAttempt(input.graph, input.state, input.taskHandle)
|
|
47
|
+
await appendEvent(input.runtime, {
|
|
48
|
+
attempt: state.tasks[input.taskHandle]!.attempt,
|
|
49
|
+
generation: state.tasks[input.taskHandle]!.generation,
|
|
50
|
+
taskHandle: input.taskHandle,
|
|
51
|
+
timestamp: now(),
|
|
52
|
+
type: 'attempt_started',
|
|
53
|
+
})
|
|
54
|
+
let report = await persistState(input.runtime, input.graph, state)
|
|
55
|
+
const taskState = state.tasks[input.taskHandle]!
|
|
56
|
+
const taskHandle = input.taskHandle
|
|
57
|
+
const commitMessage = input.runtime.taskSource.buildCommitSubject(taskHandle)
|
|
58
|
+
let implementArtifact: ImplementArtifact | null = null
|
|
59
|
+
let reviewArtifact: null | ReviewArtifact = null
|
|
60
|
+
let implement
|
|
61
|
+
try {
|
|
62
|
+
const prompt = await input.runtime.taskSource.buildImplementPrompt({
|
|
63
|
+
attempt: taskState.attempt,
|
|
64
|
+
generation: taskState.generation,
|
|
65
|
+
lastFindings: taskState.lastFindings,
|
|
66
|
+
taskHandle,
|
|
67
|
+
})
|
|
68
|
+
implement = await input.workflow.roles.implementer.implement({
|
|
69
|
+
attempt: taskState.attempt,
|
|
70
|
+
generation: taskState.generation,
|
|
71
|
+
lastFindings: taskState.lastFindings,
|
|
72
|
+
prompt,
|
|
73
|
+
taskHandle,
|
|
74
|
+
})
|
|
75
|
+
if (implement.taskHandle !== taskHandle) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Implement taskHandle mismatch: expected ${taskHandle}, received ${implement.taskHandle}`,
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
82
|
+
state = recordImplementFailure(input.graph, state, input.taskHandle, reason)
|
|
83
|
+
await appendEvent(input.runtime, {
|
|
84
|
+
attempt: taskState.attempt,
|
|
85
|
+
detail: reason,
|
|
86
|
+
generation: taskState.generation,
|
|
87
|
+
taskHandle: input.taskHandle,
|
|
88
|
+
timestamp: now(),
|
|
89
|
+
type: 'implement_failed',
|
|
90
|
+
})
|
|
91
|
+
report = await persistState(input.runtime, input.graph, state)
|
|
92
|
+
return { report, state }
|
|
93
|
+
}
|
|
94
|
+
implementArtifact = {
|
|
95
|
+
attempt: taskState.attempt,
|
|
96
|
+
createdAt: now(),
|
|
97
|
+
generation: taskState.generation,
|
|
98
|
+
result: implement,
|
|
99
|
+
taskHandle,
|
|
100
|
+
}
|
|
101
|
+
await input.runtime.store.saveImplementArtifact(implementArtifact)
|
|
102
|
+
state = recordImplementSuccess(state, input.taskHandle)
|
|
103
|
+
await appendEvent(input.runtime, {
|
|
104
|
+
attempt: taskState.attempt,
|
|
105
|
+
generation: taskState.generation,
|
|
106
|
+
taskHandle: input.taskHandle,
|
|
107
|
+
timestamp: now(),
|
|
108
|
+
type: 'implement_succeeded',
|
|
109
|
+
})
|
|
110
|
+
await appendEvent(input.runtime, {
|
|
111
|
+
attempt: taskState.attempt,
|
|
112
|
+
generation: taskState.generation,
|
|
113
|
+
taskHandle: input.taskHandle,
|
|
114
|
+
timestamp: now(),
|
|
115
|
+
type: 'review_started',
|
|
116
|
+
})
|
|
117
|
+
report = await persistState(input.runtime, input.graph, state)
|
|
118
|
+
let review
|
|
119
|
+
let reviewPhaseKind: 'approved' | 'rejected'
|
|
120
|
+
try {
|
|
121
|
+
let reviewPhase: ReviewPhaseResult
|
|
122
|
+
if (input.workflow.preset.mode === 'direct') {
|
|
123
|
+
const actualChangedFiles =
|
|
124
|
+
await input.runtime.git.getChangedFilesSinceHead()
|
|
125
|
+
const prompt = await input.runtime.taskSource.buildReviewPrompt({
|
|
126
|
+
actualChangedFiles,
|
|
127
|
+
attempt: taskState.attempt,
|
|
128
|
+
generation: taskState.generation,
|
|
129
|
+
implement,
|
|
130
|
+
lastFindings: taskState.lastFindings,
|
|
131
|
+
taskHandle,
|
|
132
|
+
})
|
|
133
|
+
reviewPhase = await input.workflow.preset.review({
|
|
134
|
+
actualChangedFiles,
|
|
135
|
+
attempt: taskState.attempt,
|
|
136
|
+
commitMessage,
|
|
137
|
+
generation: taskState.generation,
|
|
138
|
+
implement,
|
|
139
|
+
lastFindings: taskState.lastFindings,
|
|
140
|
+
prompt,
|
|
141
|
+
taskHandle,
|
|
142
|
+
})
|
|
143
|
+
} else {
|
|
144
|
+
const completionCriteria =
|
|
145
|
+
await input.runtime.taskSource.getCompletionCriteria(taskHandle)
|
|
146
|
+
reviewPhase = await input.workflow.preset.review({
|
|
147
|
+
attempt: taskState.attempt,
|
|
148
|
+
commitMessage,
|
|
149
|
+
completionCriteria,
|
|
150
|
+
runtime: input.runtime,
|
|
151
|
+
taskHandle,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
reviewPhaseKind = reviewPhase.kind
|
|
155
|
+
review = reviewPhase.review
|
|
156
|
+
if (review.taskHandle !== taskHandle) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Review taskHandle mismatch: expected ${taskHandle}, received ${review.taskHandle}`,
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
163
|
+
state = recordReviewFailure(input.graph, state, input.taskHandle, reason)
|
|
164
|
+
await appendEvent(input.runtime, {
|
|
165
|
+
attempt: taskState.attempt,
|
|
166
|
+
detail: reason,
|
|
167
|
+
generation: taskState.generation,
|
|
168
|
+
taskHandle: input.taskHandle,
|
|
169
|
+
timestamp: now(),
|
|
170
|
+
type: 'review_failed',
|
|
171
|
+
})
|
|
172
|
+
report = await persistState(input.runtime, input.graph, state)
|
|
173
|
+
return { report, state }
|
|
174
|
+
}
|
|
175
|
+
reviewArtifact = {
|
|
176
|
+
attempt: taskState.attempt,
|
|
177
|
+
createdAt: now(),
|
|
178
|
+
generation: taskState.generation,
|
|
179
|
+
result: review,
|
|
180
|
+
taskHandle,
|
|
181
|
+
}
|
|
182
|
+
await input.runtime.store.saveReviewArtifact(reviewArtifact)
|
|
183
|
+
await appendEvent(input.runtime, {
|
|
184
|
+
attempt: taskState.attempt,
|
|
185
|
+
detail: review.summary,
|
|
186
|
+
generation: taskState.generation,
|
|
187
|
+
taskHandle: input.taskHandle,
|
|
188
|
+
timestamp: now(),
|
|
189
|
+
type: 'review_completed',
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
if (reviewPhaseKind === 'approved' && shouldPassZeroGate({ review })) {
|
|
193
|
+
state = recordReviewApproved(state, input.taskHandle, review)
|
|
194
|
+
await appendEvent(input.runtime, {
|
|
195
|
+
attempt: taskState.attempt,
|
|
196
|
+
generation: taskState.generation,
|
|
197
|
+
taskHandle: input.taskHandle,
|
|
198
|
+
timestamp: now(),
|
|
199
|
+
type: 'integrate_started',
|
|
200
|
+
})
|
|
201
|
+
report = await persistState(input.runtime, input.graph, state)
|
|
202
|
+
|
|
203
|
+
let integrateResult
|
|
204
|
+
try {
|
|
205
|
+
integrateResult = await input.workflow.preset.integrate({
|
|
206
|
+
commitMessage,
|
|
207
|
+
runtime: input.runtime,
|
|
208
|
+
taskHandle,
|
|
209
|
+
})
|
|
210
|
+
} catch (error) {
|
|
211
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
212
|
+
state = recordCommitFailure(input.graph, state, input.taskHandle, reason)
|
|
213
|
+
await appendEvent(input.runtime, {
|
|
214
|
+
attempt: taskState.attempt,
|
|
215
|
+
detail: reason,
|
|
216
|
+
generation: taskState.generation,
|
|
217
|
+
taskHandle: input.taskHandle,
|
|
218
|
+
timestamp: now(),
|
|
219
|
+
type: 'integrate_failed',
|
|
220
|
+
})
|
|
221
|
+
report = await persistState(input.runtime, input.graph, state)
|
|
222
|
+
return { report, state }
|
|
223
|
+
}
|
|
224
|
+
const integrateArtifact: IntegrateArtifact = {
|
|
225
|
+
attempt: taskState.attempt,
|
|
226
|
+
createdAt: now(),
|
|
227
|
+
generation: taskState.generation,
|
|
228
|
+
result: integrateResult.result,
|
|
229
|
+
taskHandle,
|
|
230
|
+
}
|
|
231
|
+
state = recordIntegrateResult(input.graph, state, taskHandle, {
|
|
232
|
+
commitSha: integrateResult.result.commitSha,
|
|
233
|
+
review,
|
|
234
|
+
})
|
|
235
|
+
report = await persistState(input.runtime, input.graph, state)
|
|
236
|
+
await appendEvent(input.runtime, {
|
|
237
|
+
attempt: taskState.attempt,
|
|
238
|
+
detail: integrateResult.result.summary,
|
|
239
|
+
generation: taskState.generation,
|
|
240
|
+
taskHandle: input.taskHandle,
|
|
241
|
+
timestamp: now(),
|
|
242
|
+
type: 'integrate_completed',
|
|
243
|
+
})
|
|
244
|
+
await input.runtime.store.saveIntegrateArtifact(integrateArtifact)
|
|
245
|
+
await persistCommittedArtifacts(input.runtime, {
|
|
246
|
+
commitSha: integrateResult.result.commitSha,
|
|
247
|
+
implementArtifact,
|
|
248
|
+
reviewArtifact,
|
|
249
|
+
})
|
|
250
|
+
return { report, state }
|
|
251
|
+
}
|
|
252
|
+
state = recordReviewResult(input.graph, state, input.taskHandle, {
|
|
253
|
+
review,
|
|
254
|
+
})
|
|
255
|
+
report = await persistState(input.runtime, input.graph, state)
|
|
256
|
+
return { report, state }
|
|
257
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
alignStateWithGraph,
|
|
3
|
+
createInitialWorkflowState,
|
|
4
|
+
selectNextRunnableTask,
|
|
5
|
+
} from './engine'
|
|
6
|
+
import { persistState } from './orchestrator-helpers'
|
|
7
|
+
import { resumePullRequestIntegrate } from './orchestrator-integrate-resume'
|
|
8
|
+
import { resumePullRequestReview } from './orchestrator-review-resume'
|
|
9
|
+
import { executeTaskAttempt } from './orchestrator-task-attempt'
|
|
10
|
+
|
|
11
|
+
import type { FinalReport, TaskGraph, WorkflowState } from '../types'
|
|
12
|
+
import type { WorkflowRuntime } from '../workflow/preset'
|
|
13
|
+
import type { OrchestratorRuntime } from './runtime'
|
|
14
|
+
|
|
15
|
+
export interface WorkflowRunResult {
|
|
16
|
+
state: WorkflowState
|
|
17
|
+
summary: FinalReport['summary']
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RunWorkflowInput {
|
|
21
|
+
graph: TaskGraph
|
|
22
|
+
runtime: OrchestratorRuntime
|
|
23
|
+
untilTaskHandle?: string
|
|
24
|
+
workflow: WorkflowRuntime
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function runWorkflow(
|
|
28
|
+
input: RunWorkflowInput,
|
|
29
|
+
): Promise<WorkflowRunResult> {
|
|
30
|
+
const workflow = input.workflow
|
|
31
|
+
const isPullRequestMode = workflow.preset.mode === 'pull-request'
|
|
32
|
+
await input.runtime.store.saveGraph(input.graph)
|
|
33
|
+
const storedState = await input.runtime.store.loadState()
|
|
34
|
+
let state = alignStateWithGraph(
|
|
35
|
+
input.graph,
|
|
36
|
+
storedState ?? createInitialWorkflowState(input.graph),
|
|
37
|
+
{
|
|
38
|
+
preserveRunningIntegrate: isPullRequestMode,
|
|
39
|
+
preserveRunningReview: isPullRequestMode,
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
let report = await persistState(input.runtime, input.graph, state)
|
|
43
|
+
|
|
44
|
+
while (
|
|
45
|
+
report.summary.finalStatus !== 'blocked' &&
|
|
46
|
+
report.summary.finalStatus !== 'replan_required'
|
|
47
|
+
) {
|
|
48
|
+
if (
|
|
49
|
+
input.untilTaskHandle &&
|
|
50
|
+
state.tasks[input.untilTaskHandle]?.status === 'done'
|
|
51
|
+
) {
|
|
52
|
+
break
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const resumedIntegrate = await resumePullRequestIntegrate({
|
|
56
|
+
graph: input.graph,
|
|
57
|
+
runtime: input.runtime,
|
|
58
|
+
state,
|
|
59
|
+
workflow,
|
|
60
|
+
})
|
|
61
|
+
if (resumedIntegrate) {
|
|
62
|
+
report = resumedIntegrate.report
|
|
63
|
+
state = resumedIntegrate.state
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const resumedReview = await resumePullRequestReview({
|
|
68
|
+
graph: input.graph,
|
|
69
|
+
runtime: input.runtime,
|
|
70
|
+
state,
|
|
71
|
+
workflow,
|
|
72
|
+
})
|
|
73
|
+
if (resumedReview) {
|
|
74
|
+
report = resumedReview.report
|
|
75
|
+
state = resumedReview.state
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const task = selectNextRunnableTask(input.graph, state)
|
|
80
|
+
if (!task) {
|
|
81
|
+
break
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const next = await executeTaskAttempt({
|
|
85
|
+
graph: input.graph,
|
|
86
|
+
runtime: input.runtime,
|
|
87
|
+
state,
|
|
88
|
+
taskHandle: task,
|
|
89
|
+
workflow,
|
|
90
|
+
})
|
|
91
|
+
report = next.report
|
|
92
|
+
state = next.state
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
state,
|
|
97
|
+
summary: report.summary,
|
|
98
|
+
}
|
|
99
|
+
}
|