task-while 0.0.2 → 0.0.4
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/README.md +34 -34
- package/package.json +2 -2
- package/src/adapters/fs/harness-store.ts +84 -0
- package/src/agents/claude.ts +159 -9
- package/src/agents/codex.ts +68 -4
- package/src/agents/event-log.ts +160 -15
- package/src/batch/discovery.ts +1 -1
- package/src/commands/batch.ts +152 -155
- package/src/commands/run-branch-helpers.ts +81 -0
- package/src/commands/run-providers.ts +77 -0
- package/src/commands/run.ts +121 -177
- package/src/core/create-runtime-ports.ts +118 -0
- package/src/core/runtime.ts +15 -36
- package/src/harness/in-memory-store.ts +45 -0
- package/src/harness/kernel.ts +226 -0
- package/src/harness/state.ts +47 -0
- package/src/harness/store.ts +26 -0
- package/src/harness/workflow-builders.ts +87 -0
- package/src/harness/workflow-program.ts +86 -0
- package/src/ports/agent.ts +17 -0
- package/src/ports/code-host.ts +23 -0
- package/src/programs/batch.ts +139 -0
- package/src/programs/run-direct.ts +209 -0
- package/src/programs/run-pr-transitions.ts +81 -0
- package/src/programs/run-pr.ts +290 -0
- package/src/programs/shared-steps.ts +252 -0
- package/src/schedulers/scheduler.ts +208 -0
- package/src/session/session.ts +127 -0
- package/src/workflow/config.ts +15 -0
- package/src/core/engine-helpers.ts +0 -114
- package/src/core/engine-outcomes.ts +0 -166
- package/src/core/engine.ts +0 -223
- package/src/core/orchestrator-helpers.ts +0 -52
- package/src/core/orchestrator-integrate-resume.ts +0 -149
- package/src/core/orchestrator-review-resume.ts +0 -228
- package/src/core/orchestrator-task-attempt.ts +0 -257
- package/src/core/orchestrator.ts +0 -99
- package/src/runtime/fs-runtime.ts +0 -209
- package/src/workflow/direct-preset.ts +0 -44
- package/src/workflow/preset.ts +0 -86
- package/src/workflow/pull-request-preset.ts +0 -312
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
PendingTaskState,
|
|
3
|
-
ReviewFinding,
|
|
4
|
-
ReviewOutput,
|
|
5
|
-
ReviewVerdict,
|
|
6
|
-
TaskGraph,
|
|
7
|
-
TaskState,
|
|
8
|
-
TaskTopologyEntry,
|
|
9
|
-
WorkflowState,
|
|
10
|
-
} from '../types'
|
|
11
|
-
|
|
12
|
-
export function cloneState(state: WorkflowState): WorkflowState {
|
|
13
|
-
return structuredClone(state)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function createBaseTaskState(): PendingTaskState {
|
|
17
|
-
return {
|
|
18
|
-
attempt: 0,
|
|
19
|
-
generation: 1,
|
|
20
|
-
invalidatedBy: null,
|
|
21
|
-
lastFindings: [],
|
|
22
|
-
status: 'pending',
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function getTask(
|
|
27
|
-
graph: TaskGraph,
|
|
28
|
-
taskHandle: string,
|
|
29
|
-
): TaskTopologyEntry {
|
|
30
|
-
const task = graph.tasks.find((item) => item.handle === taskHandle)
|
|
31
|
-
if (!task) {
|
|
32
|
-
throw new Error(`Unknown task: ${taskHandle}`)
|
|
33
|
-
}
|
|
34
|
-
return task
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function getTaskState(
|
|
38
|
-
state: WorkflowState,
|
|
39
|
-
taskHandle: string,
|
|
40
|
-
): TaskState {
|
|
41
|
-
const taskState = state.tasks[taskHandle]
|
|
42
|
-
if (!taskState) {
|
|
43
|
-
throw new Error(`Missing state for task ${taskHandle}`)
|
|
44
|
-
}
|
|
45
|
-
return taskState
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function canStartTask(
|
|
49
|
-
graph: TaskGraph,
|
|
50
|
-
state: WorkflowState,
|
|
51
|
-
taskHandle: string,
|
|
52
|
-
) {
|
|
53
|
-
const task = getTask(graph, taskHandle)
|
|
54
|
-
return task.dependsOn.every(
|
|
55
|
-
(dependency) => state.tasks[dependency]?.status === 'done',
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function getMaxIterations(graph: TaskGraph) {
|
|
60
|
-
return graph.maxIterations
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function withReviewMetadata(
|
|
64
|
-
taskState: TaskState,
|
|
65
|
-
input: WithReviewMetadataInput,
|
|
66
|
-
) {
|
|
67
|
-
const next = {
|
|
68
|
-
attempt: taskState.attempt,
|
|
69
|
-
generation: taskState.generation,
|
|
70
|
-
invalidatedBy: taskState.invalidatedBy,
|
|
71
|
-
lastFindings: taskState.lastFindings,
|
|
72
|
-
...(taskState.lastReviewVerdict
|
|
73
|
-
? { lastReviewVerdict: taskState.lastReviewVerdict }
|
|
74
|
-
: {}),
|
|
75
|
-
}
|
|
76
|
-
if (input.findings) {
|
|
77
|
-
next.lastFindings = input.findings
|
|
78
|
-
}
|
|
79
|
-
if (input.reviewVerdict) {
|
|
80
|
-
next.lastReviewVerdict = input.reviewVerdict
|
|
81
|
-
}
|
|
82
|
-
return next
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export interface WithReviewMetadataInput {
|
|
86
|
-
findings?: ReviewFinding[]
|
|
87
|
-
reviewVerdict?: ReviewVerdict
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface ZeroGateInput {
|
|
91
|
-
review: ReviewOutput
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function shouldPassZeroGate(input: ZeroGateInput) {
|
|
95
|
-
return (
|
|
96
|
-
input.review.verdict === 'pass' &&
|
|
97
|
-
input.review.findings.length === 0 &&
|
|
98
|
-
input.review.acceptanceChecks.every((check) => check.status === 'pass')
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function collectDescendants(
|
|
103
|
-
graph: TaskGraph,
|
|
104
|
-
taskHandle: string,
|
|
105
|
-
seen = new Set<string>(),
|
|
106
|
-
) {
|
|
107
|
-
for (const task of graph.tasks) {
|
|
108
|
-
if (task.dependsOn.includes(taskHandle) && !seen.has(task.handle)) {
|
|
109
|
-
seen.add(task.handle)
|
|
110
|
-
collectDescendants(graph, task.handle, seen)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return seen
|
|
114
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
cloneState,
|
|
3
|
-
getMaxIterations,
|
|
4
|
-
getTaskState,
|
|
5
|
-
shouldPassZeroGate,
|
|
6
|
-
withReviewMetadata,
|
|
7
|
-
} from './engine-helpers'
|
|
8
|
-
|
|
9
|
-
import type {
|
|
10
|
-
FinalReport,
|
|
11
|
-
ReviewOutput,
|
|
12
|
-
TaskGraph,
|
|
13
|
-
WorkflowState,
|
|
14
|
-
} from '../types'
|
|
15
|
-
|
|
16
|
-
export function recordReviewApproved(
|
|
17
|
-
state: WorkflowState,
|
|
18
|
-
taskHandle: string,
|
|
19
|
-
review: ReviewOutput,
|
|
20
|
-
): WorkflowState {
|
|
21
|
-
const next = cloneState(state)
|
|
22
|
-
const taskState = getTaskState(next, taskHandle)
|
|
23
|
-
if (taskState.status !== 'running' || taskState.stage !== 'review') {
|
|
24
|
-
throw new Error(`Task ${taskHandle} is not reviewing`)
|
|
25
|
-
}
|
|
26
|
-
if (review.verdict !== 'pass') {
|
|
27
|
-
throw new Error(`Task ${taskHandle} review is not approved`)
|
|
28
|
-
}
|
|
29
|
-
next.tasks[taskHandle] = {
|
|
30
|
-
...withReviewMetadata(taskState, {
|
|
31
|
-
findings: review.findings,
|
|
32
|
-
reviewVerdict: review.verdict,
|
|
33
|
-
}),
|
|
34
|
-
stage: 'integrate',
|
|
35
|
-
status: 'running',
|
|
36
|
-
}
|
|
37
|
-
return next
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function recordIntegrateResult(
|
|
41
|
-
_graph: TaskGraph,
|
|
42
|
-
state: WorkflowState,
|
|
43
|
-
taskHandle: string,
|
|
44
|
-
input: RecordIntegrateResultInput,
|
|
45
|
-
): WorkflowState {
|
|
46
|
-
const next = cloneState(state)
|
|
47
|
-
const taskState = getTaskState(next, taskHandle)
|
|
48
|
-
if (taskState.status !== 'running' || taskState.stage !== 'integrate') {
|
|
49
|
-
throw new Error(`Task ${taskHandle} is not integrating`)
|
|
50
|
-
}
|
|
51
|
-
if (!shouldPassZeroGate(input)) {
|
|
52
|
-
throw new Error(
|
|
53
|
-
`Task ${taskHandle} integration requires an approved review`,
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
next.currentTaskHandle = null
|
|
57
|
-
next.tasks[taskHandle] = {
|
|
58
|
-
...withReviewMetadata(taskState, {
|
|
59
|
-
findings: input.review.findings,
|
|
60
|
-
reviewVerdict: input.review.verdict,
|
|
61
|
-
}),
|
|
62
|
-
commitSha: input.commitSha,
|
|
63
|
-
status: 'done',
|
|
64
|
-
}
|
|
65
|
-
return next
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface RecordIntegrateResultInput {
|
|
69
|
-
commitSha: string
|
|
70
|
-
review: ReviewOutput
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function recordCommitFailure(
|
|
74
|
-
graph: TaskGraph,
|
|
75
|
-
state: WorkflowState,
|
|
76
|
-
taskHandle: string,
|
|
77
|
-
reason: string,
|
|
78
|
-
): WorkflowState {
|
|
79
|
-
const next = cloneState(state)
|
|
80
|
-
const taskState = getTaskState(next, taskHandle)
|
|
81
|
-
next.currentTaskHandle = null
|
|
82
|
-
const metadata = withReviewMetadata(taskState, {
|
|
83
|
-
reviewVerdict: 'pass',
|
|
84
|
-
})
|
|
85
|
-
next.tasks[taskHandle] =
|
|
86
|
-
taskState.attempt >= getMaxIterations(graph)
|
|
87
|
-
? {
|
|
88
|
-
...metadata,
|
|
89
|
-
reason,
|
|
90
|
-
status: 'blocked',
|
|
91
|
-
}
|
|
92
|
-
: {
|
|
93
|
-
...metadata,
|
|
94
|
-
status: 'rework',
|
|
95
|
-
}
|
|
96
|
-
return next
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function recordReviewFailure(
|
|
100
|
-
graph: TaskGraph,
|
|
101
|
-
state: WorkflowState,
|
|
102
|
-
taskHandle: string,
|
|
103
|
-
reason: string,
|
|
104
|
-
): WorkflowState {
|
|
105
|
-
const next = cloneState(state)
|
|
106
|
-
const taskState = getTaskState(next, taskHandle)
|
|
107
|
-
next.currentTaskHandle = null
|
|
108
|
-
next.tasks[taskHandle] =
|
|
109
|
-
taskState.attempt >= getMaxIterations(graph)
|
|
110
|
-
? {
|
|
111
|
-
...withReviewMetadata(taskState, {}),
|
|
112
|
-
reason,
|
|
113
|
-
status: 'blocked',
|
|
114
|
-
}
|
|
115
|
-
: {
|
|
116
|
-
...withReviewMetadata(taskState, {}),
|
|
117
|
-
status: 'rework',
|
|
118
|
-
}
|
|
119
|
-
return next
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function buildReport(
|
|
123
|
-
graph: TaskGraph,
|
|
124
|
-
state: WorkflowState,
|
|
125
|
-
generatedAt: string,
|
|
126
|
-
): FinalReport {
|
|
127
|
-
const tasks = graph.tasks.map((task) => {
|
|
128
|
-
const taskState = getTaskState(state, task.handle)
|
|
129
|
-
return {
|
|
130
|
-
attempt: taskState.attempt,
|
|
131
|
-
generation: taskState.generation,
|
|
132
|
-
taskHandle: task.handle,
|
|
133
|
-
...('commitSha' in taskState ? { commitSha: taskState.commitSha } : {}),
|
|
134
|
-
...(taskState.lastReviewVerdict
|
|
135
|
-
? { lastReviewVerdict: taskState.lastReviewVerdict }
|
|
136
|
-
: {}),
|
|
137
|
-
...('reason' in taskState ? { reason: taskState.reason } : {}),
|
|
138
|
-
status: taskState.status,
|
|
139
|
-
}
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
const blockedTasks = tasks.filter((task) => task.status === 'blocked').length
|
|
143
|
-
const completedTasks = tasks.filter((task) => task.status === 'done').length
|
|
144
|
-
const replanTasks = tasks.filter((task) => task.status === 'replan').length
|
|
145
|
-
const finalStatus =
|
|
146
|
-
replanTasks > 0
|
|
147
|
-
? 'replan_required'
|
|
148
|
-
: blockedTasks > 0
|
|
149
|
-
? 'blocked'
|
|
150
|
-
: completedTasks === tasks.length
|
|
151
|
-
? 'completed'
|
|
152
|
-
: 'in_progress'
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
featureId: graph.featureId,
|
|
156
|
-
generatedAt,
|
|
157
|
-
tasks,
|
|
158
|
-
summary: {
|
|
159
|
-
blockedTasks,
|
|
160
|
-
completedTasks,
|
|
161
|
-
finalStatus,
|
|
162
|
-
replanTasks,
|
|
163
|
-
totalTasks: tasks.length,
|
|
164
|
-
},
|
|
165
|
-
}
|
|
166
|
-
}
|
package/src/core/engine.ts
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
canStartTask,
|
|
3
|
-
cloneState,
|
|
4
|
-
createBaseTaskState,
|
|
5
|
-
getMaxIterations,
|
|
6
|
-
getTaskState,
|
|
7
|
-
shouldPassZeroGate,
|
|
8
|
-
withReviewMetadata,
|
|
9
|
-
} from './engine-helpers'
|
|
10
|
-
|
|
11
|
-
import type { ReviewOutput, TaskGraph, WorkflowState } from '../types'
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
buildReport,
|
|
15
|
-
recordCommitFailure,
|
|
16
|
-
recordIntegrateResult,
|
|
17
|
-
recordReviewApproved,
|
|
18
|
-
recordReviewFailure,
|
|
19
|
-
} from './engine-outcomes'
|
|
20
|
-
|
|
21
|
-
export function createInitialWorkflowState(graph: TaskGraph): WorkflowState {
|
|
22
|
-
return {
|
|
23
|
-
currentTaskHandle: null,
|
|
24
|
-
featureId: graph.featureId,
|
|
25
|
-
tasks: Object.fromEntries(
|
|
26
|
-
graph.tasks.map((task) => [task.handle, createBaseTaskState()]),
|
|
27
|
-
),
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function alignStateWithGraph(
|
|
32
|
-
graph: TaskGraph,
|
|
33
|
-
state: WorkflowState,
|
|
34
|
-
options?: AlignStateWithGraphOptions,
|
|
35
|
-
): WorkflowState {
|
|
36
|
-
const next = cloneState(state)
|
|
37
|
-
const alignedTasks = Object.fromEntries(
|
|
38
|
-
graph.tasks.map((task) => {
|
|
39
|
-
const existing = next.tasks[task.handle] ?? createBaseTaskState()
|
|
40
|
-
if (existing.status === 'running') {
|
|
41
|
-
if (
|
|
42
|
-
(options?.preserveRunningReview && existing.stage === 'review') ||
|
|
43
|
-
(options?.preserveRunningIntegrate && existing.stage === 'integrate')
|
|
44
|
-
) {
|
|
45
|
-
return [task.handle, existing]
|
|
46
|
-
}
|
|
47
|
-
return [
|
|
48
|
-
task.handle,
|
|
49
|
-
{
|
|
50
|
-
attempt: existing.attempt,
|
|
51
|
-
generation: existing.generation,
|
|
52
|
-
invalidatedBy: existing.invalidatedBy,
|
|
53
|
-
lastFindings: existing.lastFindings,
|
|
54
|
-
...(existing.lastReviewVerdict
|
|
55
|
-
? { lastReviewVerdict: existing.lastReviewVerdict }
|
|
56
|
-
: {}),
|
|
57
|
-
status: 'rework' as const,
|
|
58
|
-
},
|
|
59
|
-
]
|
|
60
|
-
}
|
|
61
|
-
return [task.handle, existing]
|
|
62
|
-
}),
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
featureId: graph.featureId,
|
|
67
|
-
tasks: alignedTasks,
|
|
68
|
-
currentTaskHandle:
|
|
69
|
-
next.currentTaskHandle && alignedTasks[next.currentTaskHandle]
|
|
70
|
-
? next.currentTaskHandle
|
|
71
|
-
: null,
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function selectNextRunnableTask(
|
|
76
|
-
graph: TaskGraph,
|
|
77
|
-
state: WorkflowState,
|
|
78
|
-
): null | string {
|
|
79
|
-
return (
|
|
80
|
-
graph.tasks.find((task) => {
|
|
81
|
-
const taskState = state.tasks[task.handle]
|
|
82
|
-
if (!taskState) {
|
|
83
|
-
return false
|
|
84
|
-
}
|
|
85
|
-
if (taskState.status === 'rework') {
|
|
86
|
-
return true
|
|
87
|
-
}
|
|
88
|
-
return (
|
|
89
|
-
taskState.status === 'pending' &&
|
|
90
|
-
canStartTask(graph, state, task.handle)
|
|
91
|
-
)
|
|
92
|
-
})?.handle ?? null
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function startAttempt(
|
|
97
|
-
graph: TaskGraph,
|
|
98
|
-
state: WorkflowState,
|
|
99
|
-
taskHandle: string,
|
|
100
|
-
): WorkflowState {
|
|
101
|
-
const taskState = getTaskState(state, taskHandle)
|
|
102
|
-
if (taskState.status !== 'pending' && taskState.status !== 'rework') {
|
|
103
|
-
throw new Error(`Task ${taskHandle} is not runnable`)
|
|
104
|
-
}
|
|
105
|
-
if (
|
|
106
|
-
taskState.status === 'pending' &&
|
|
107
|
-
!canStartTask(graph, state, taskHandle)
|
|
108
|
-
) {
|
|
109
|
-
throw new Error(`Task ${taskHandle} dependencies are not completed`)
|
|
110
|
-
}
|
|
111
|
-
const next = cloneState(state)
|
|
112
|
-
const current = getTaskState(next, taskHandle)
|
|
113
|
-
next.currentTaskHandle = taskHandle
|
|
114
|
-
next.tasks[taskHandle] = {
|
|
115
|
-
...current,
|
|
116
|
-
attempt: current.attempt + 1,
|
|
117
|
-
invalidatedBy: null,
|
|
118
|
-
stage: 'implement',
|
|
119
|
-
status: 'running',
|
|
120
|
-
}
|
|
121
|
-
return next
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function recordImplementSuccess(
|
|
125
|
-
state: WorkflowState,
|
|
126
|
-
taskHandle: string,
|
|
127
|
-
): WorkflowState {
|
|
128
|
-
const next = cloneState(state)
|
|
129
|
-
const taskState = getTaskState(next, taskHandle)
|
|
130
|
-
if (taskState.status !== 'running' || taskState.stage !== 'implement') {
|
|
131
|
-
throw new Error(`Task ${taskHandle} is not implementing`)
|
|
132
|
-
}
|
|
133
|
-
next.tasks[taskHandle] = {
|
|
134
|
-
...taskState,
|
|
135
|
-
stage: 'review',
|
|
136
|
-
status: 'running',
|
|
137
|
-
}
|
|
138
|
-
return next
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function recordImplementFailure(
|
|
142
|
-
graph: TaskGraph,
|
|
143
|
-
state: WorkflowState,
|
|
144
|
-
taskHandle: string,
|
|
145
|
-
reason: string,
|
|
146
|
-
): WorkflowState {
|
|
147
|
-
const next = cloneState(state)
|
|
148
|
-
const taskState = getTaskState(next, taskHandle)
|
|
149
|
-
next.currentTaskHandle = null
|
|
150
|
-
next.tasks[taskHandle] =
|
|
151
|
-
taskState.attempt >= getMaxIterations(graph)
|
|
152
|
-
? {
|
|
153
|
-
...withReviewMetadata(taskState, {}),
|
|
154
|
-
reason,
|
|
155
|
-
status: 'blocked',
|
|
156
|
-
}
|
|
157
|
-
: {
|
|
158
|
-
...withReviewMetadata(taskState, {}),
|
|
159
|
-
status: 'rework',
|
|
160
|
-
}
|
|
161
|
-
return next
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export function recordReviewResult(
|
|
165
|
-
graph: TaskGraph,
|
|
166
|
-
state: WorkflowState,
|
|
167
|
-
taskHandle: string,
|
|
168
|
-
input: RecordReviewResultInput,
|
|
169
|
-
): WorkflowState {
|
|
170
|
-
const next = cloneState(state)
|
|
171
|
-
const taskState = getTaskState(next, taskHandle)
|
|
172
|
-
next.currentTaskHandle = null
|
|
173
|
-
const metadata = withReviewMetadata(taskState, {
|
|
174
|
-
findings: input.review.findings,
|
|
175
|
-
reviewVerdict: input.review.verdict,
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
if (shouldPassZeroGate(input)) {
|
|
179
|
-
throw new Error(
|
|
180
|
-
`Task ${taskHandle} requires integrate result after approved review`,
|
|
181
|
-
)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (input.review.verdict === 'replan') {
|
|
185
|
-
next.tasks[taskHandle] = {
|
|
186
|
-
...metadata,
|
|
187
|
-
reason: input.review.summary,
|
|
188
|
-
status: 'replan',
|
|
189
|
-
}
|
|
190
|
-
return next
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (input.review.verdict === 'blocked') {
|
|
194
|
-
next.tasks[taskHandle] = {
|
|
195
|
-
...metadata,
|
|
196
|
-
reason: input.review.summary,
|
|
197
|
-
status: 'blocked',
|
|
198
|
-
}
|
|
199
|
-
return next
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
next.tasks[taskHandle] =
|
|
203
|
-
taskState.attempt >= getMaxIterations(graph)
|
|
204
|
-
? {
|
|
205
|
-
...metadata,
|
|
206
|
-
reason: input.review.summary,
|
|
207
|
-
status: 'blocked',
|
|
208
|
-
}
|
|
209
|
-
: {
|
|
210
|
-
...metadata,
|
|
211
|
-
status: 'rework',
|
|
212
|
-
}
|
|
213
|
-
return next
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export interface AlignStateWithGraphOptions {
|
|
217
|
-
preserveRunningIntegrate?: boolean
|
|
218
|
-
preserveRunningReview?: boolean
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export interface RecordReviewResultInput {
|
|
222
|
-
review: ReviewOutput
|
|
223
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { buildReport } from './engine'
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
ImplementArtifact,
|
|
5
|
-
ReviewArtifact,
|
|
6
|
-
TaskGraph,
|
|
7
|
-
WorkflowEvent,
|
|
8
|
-
WorkflowState,
|
|
9
|
-
} from '../types'
|
|
10
|
-
import type { OrchestratorRuntime } from './runtime'
|
|
11
|
-
|
|
12
|
-
export function now() {
|
|
13
|
-
return new Date().toISOString()
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function persistState(
|
|
17
|
-
runtime: OrchestratorRuntime,
|
|
18
|
-
graph: TaskGraph,
|
|
19
|
-
state: WorkflowState,
|
|
20
|
-
) {
|
|
21
|
-
await runtime.store.saveState(state)
|
|
22
|
-
const report = buildReport(graph, state, now())
|
|
23
|
-
await runtime.store.saveReport(report)
|
|
24
|
-
return report
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function appendEvent(
|
|
28
|
-
runtime: OrchestratorRuntime,
|
|
29
|
-
event: WorkflowEvent,
|
|
30
|
-
) {
|
|
31
|
-
await runtime.store.appendEvent(event)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function persistCommittedArtifacts(
|
|
35
|
-
runtime: OrchestratorRuntime,
|
|
36
|
-
input: PersistCommittedArtifactsInput,
|
|
37
|
-
) {
|
|
38
|
-
await runtime.store.saveImplementArtifact({
|
|
39
|
-
...input.implementArtifact,
|
|
40
|
-
commitSha: input.commitSha,
|
|
41
|
-
})
|
|
42
|
-
await runtime.store.saveReviewArtifact({
|
|
43
|
-
...input.reviewArtifact,
|
|
44
|
-
commitSha: input.commitSha,
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface PersistCommittedArtifactsInput {
|
|
49
|
-
commitSha: string
|
|
50
|
-
implementArtifact: ImplementArtifact
|
|
51
|
-
reviewArtifact: ReviewArtifact
|
|
52
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isPullRequestWorkflowPreset,
|
|
3
|
-
type WorkflowRuntime,
|
|
4
|
-
} from '../workflow/preset'
|
|
5
|
-
import { recordCommitFailure, recordIntegrateResult } from './engine'
|
|
6
|
-
import {
|
|
7
|
-
appendEvent,
|
|
8
|
-
now,
|
|
9
|
-
persistCommittedArtifacts,
|
|
10
|
-
persistState,
|
|
11
|
-
} from './orchestrator-helpers'
|
|
12
|
-
|
|
13
|
-
import type {
|
|
14
|
-
FinalReport,
|
|
15
|
-
IntegrateArtifact,
|
|
16
|
-
TaskGraph,
|
|
17
|
-
WorkflowState,
|
|
18
|
-
} from '../types'
|
|
19
|
-
import type { OrchestratorRuntime } from './runtime'
|
|
20
|
-
|
|
21
|
-
export interface ResumePullRequestIntegrateInput {
|
|
22
|
-
graph: TaskGraph
|
|
23
|
-
runtime: OrchestratorRuntime
|
|
24
|
-
state: WorkflowState
|
|
25
|
-
workflow: WorkflowRuntime
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface ResumePullRequestIntegrateResult {
|
|
29
|
-
report: FinalReport
|
|
30
|
-
state: WorkflowState
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export async function resumePullRequestIntegrate(
|
|
34
|
-
input: ResumePullRequestIntegrateInput,
|
|
35
|
-
): Promise<null | ResumePullRequestIntegrateResult> {
|
|
36
|
-
if (
|
|
37
|
-
!isPullRequestWorkflowPreset(input.workflow.preset) ||
|
|
38
|
-
!input.state.currentTaskHandle
|
|
39
|
-
) {
|
|
40
|
-
return null
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const taskHandle = input.state.currentTaskHandle
|
|
44
|
-
const taskState = input.state.tasks[taskHandle]
|
|
45
|
-
if (taskState?.status !== 'running' || taskState.stage !== 'integrate') {
|
|
46
|
-
return null
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const artifactKey = {
|
|
50
|
-
attempt: taskState.attempt,
|
|
51
|
-
generation: taskState.generation,
|
|
52
|
-
taskHandle,
|
|
53
|
-
}
|
|
54
|
-
const [implementArtifact, reviewArtifact] = await Promise.all([
|
|
55
|
-
input.runtime.store.loadImplementArtifact(artifactKey),
|
|
56
|
-
input.runtime.store.loadReviewArtifact(artifactKey),
|
|
57
|
-
])
|
|
58
|
-
|
|
59
|
-
if (!implementArtifact || !reviewArtifact) {
|
|
60
|
-
const reason = `Cannot resume integrate for ${taskHandle} without persisted implement and review artifacts`
|
|
61
|
-
const nextState = recordCommitFailure(
|
|
62
|
-
input.graph,
|
|
63
|
-
input.state,
|
|
64
|
-
taskHandle,
|
|
65
|
-
reason,
|
|
66
|
-
)
|
|
67
|
-
await appendEvent(input.runtime, {
|
|
68
|
-
attempt: taskState.attempt,
|
|
69
|
-
detail: reason,
|
|
70
|
-
generation: taskState.generation,
|
|
71
|
-
taskHandle,
|
|
72
|
-
timestamp: now(),
|
|
73
|
-
type: 'integrate_failed',
|
|
74
|
-
})
|
|
75
|
-
const report = await persistState(input.runtime, input.graph, nextState)
|
|
76
|
-
return {
|
|
77
|
-
report,
|
|
78
|
-
state: nextState,
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const commitMessage = input.runtime.taskSource.buildCommitSubject(taskHandle)
|
|
83
|
-
|
|
84
|
-
let integrateResult
|
|
85
|
-
try {
|
|
86
|
-
integrateResult = await input.workflow.preset.integrate({
|
|
87
|
-
commitMessage,
|
|
88
|
-
runtime: input.runtime,
|
|
89
|
-
taskHandle,
|
|
90
|
-
})
|
|
91
|
-
} catch (error) {
|
|
92
|
-
const reason = error instanceof Error ? error.message : String(error)
|
|
93
|
-
const nextState = recordCommitFailure(
|
|
94
|
-
input.graph,
|
|
95
|
-
input.state,
|
|
96
|
-
taskHandle,
|
|
97
|
-
reason,
|
|
98
|
-
)
|
|
99
|
-
await appendEvent(input.runtime, {
|
|
100
|
-
attempt: taskState.attempt,
|
|
101
|
-
detail: reason,
|
|
102
|
-
generation: taskState.generation,
|
|
103
|
-
taskHandle,
|
|
104
|
-
timestamp: now(),
|
|
105
|
-
type: 'integrate_failed',
|
|
106
|
-
})
|
|
107
|
-
const report = await persistState(input.runtime, input.graph, nextState)
|
|
108
|
-
return {
|
|
109
|
-
report,
|
|
110
|
-
state: nextState,
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const integrateArtifact: IntegrateArtifact = {
|
|
115
|
-
attempt: taskState.attempt,
|
|
116
|
-
createdAt: now(),
|
|
117
|
-
generation: taskState.generation,
|
|
118
|
-
result: integrateResult.result,
|
|
119
|
-
taskHandle,
|
|
120
|
-
}
|
|
121
|
-
const nextState = recordIntegrateResult(
|
|
122
|
-
input.graph,
|
|
123
|
-
input.state,
|
|
124
|
-
taskHandle,
|
|
125
|
-
{
|
|
126
|
-
commitSha: integrateResult.result.commitSha,
|
|
127
|
-
review: reviewArtifact.result,
|
|
128
|
-
},
|
|
129
|
-
)
|
|
130
|
-
const report = await persistState(input.runtime, input.graph, nextState)
|
|
131
|
-
await appendEvent(input.runtime, {
|
|
132
|
-
attempt: taskState.attempt,
|
|
133
|
-
detail: integrateResult.result.summary,
|
|
134
|
-
generation: taskState.generation,
|
|
135
|
-
taskHandle,
|
|
136
|
-
timestamp: now(),
|
|
137
|
-
type: 'integrate_completed',
|
|
138
|
-
})
|
|
139
|
-
await input.runtime.store.saveIntegrateArtifact(integrateArtifact)
|
|
140
|
-
await persistCommittedArtifacts(input.runtime, {
|
|
141
|
-
commitSha: integrateResult.result.commitSha,
|
|
142
|
-
implementArtifact,
|
|
143
|
-
reviewArtifact,
|
|
144
|
-
})
|
|
145
|
-
return {
|
|
146
|
-
report,
|
|
147
|
-
state: nextState,
|
|
148
|
-
}
|
|
149
|
-
}
|