Skip to content

Commit 4bb5a49

Browse files
committed
feat(orchestrator): enhance task board UI and ensure plan persistence
- UI: Redesign ask_user interaction with InteractiveCard component. - UI: Render requirement docs as Markdown in TaskBoard. - UI: Show friendly plan names in EditorTabs. - Core: Auto-save task plans to disk on state updates in orchestratorSlice. - Refactor: Rename OptionCard to InteractiveCard and optimize imports. - Chore: Update tool configurations and prompt templates.
1 parent 0049d4b commit 4bb5a49

File tree

17 files changed

+1882
-367
lines changed

17 files changed

+1882
-367
lines changed
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/**
2+
* 执行调度器
3+
*
4+
* 职责:
5+
* - 确定可执行的任务(依赖已满足)
6+
* - 支持顺序和并行执行
7+
* - 处理任务失败和依赖传播
8+
*/
9+
10+
import { logger } from '@utils/Logger'
11+
import type {
12+
TaskPlan,
13+
OrchestratorTask,
14+
TaskExecutionResult,
15+
ExecutionStats,
16+
OrchestratorConfig,
17+
} from './types'
18+
import { DEFAULT_ORCHESTRATOR_CONFIG } from './types'
19+
20+
// ============================================
21+
// 执行调度器
22+
// ============================================
23+
24+
export class ExecutionScheduler {
25+
private config: OrchestratorConfig
26+
private runningTasks: Set<string> = new Set()
27+
private abortController: AbortController | null = null
28+
29+
constructor(config: Partial<OrchestratorConfig> = {}) {
30+
this.config = { ...DEFAULT_ORCHESTRATOR_CONFIG, ...config }
31+
}
32+
33+
// ============================================
34+
// 任务查询
35+
// ============================================
36+
37+
/**
38+
* 获取所有可执行的任务(依赖已满足且未执行)
39+
*/
40+
getExecutableTasks(plan: TaskPlan): OrchestratorTask[] {
41+
const executable: OrchestratorTask[] = []
42+
43+
for (const task of plan.tasks) {
44+
if (task.status !== 'pending') continue
45+
if (this.runningTasks.has(task.id)) continue
46+
47+
const depStatus = this.checkDependencies(task, plan)
48+
49+
if (depStatus === 'ready') {
50+
executable.push(task)
51+
} else if (depStatus === 'blocked') {
52+
// 依赖失败,自动跳过
53+
if (this.config.autoSkipOnDependencyFailure) {
54+
this.markTaskSkipped(task, 'Dependency failed or skipped')
55+
}
56+
}
57+
// depStatus === 'waiting' -> 继续等待
58+
}
59+
60+
return executable
61+
}
62+
63+
/**
64+
* 检查任务的依赖状态
65+
*/
66+
private checkDependencies(task: OrchestratorTask, plan: TaskPlan): 'ready' | 'waiting' | 'blocked' {
67+
if (task.dependencies.length === 0) return 'ready'
68+
69+
let allCompleted = true
70+
71+
for (const depId of task.dependencies) {
72+
const depTask = plan.tasks.find(t => t.id === depId)
73+
if (!depTask) {
74+
logger.agent.warn(`[Scheduler] Dependency not found: ${depId}`)
75+
continue
76+
}
77+
78+
if (depTask.status === 'failed' || depTask.status === 'skipped') {
79+
return 'blocked'
80+
}
81+
82+
if (depTask.status !== 'completed') {
83+
allCompleted = false
84+
}
85+
}
86+
87+
return allCompleted ? 'ready' : 'waiting'
88+
}
89+
90+
/**
91+
* 标记任务为跳过
92+
*/
93+
private markTaskSkipped(task: OrchestratorTask, reason: string): void {
94+
task.status = 'skipped'
95+
task.error = reason
96+
logger.agent.info(`[Scheduler] Task skipped: ${task.id} - ${reason}`)
97+
}
98+
99+
/**
100+
* 获取下一个待执行任务(顺序模式)
101+
*/
102+
getNextTask(plan: TaskPlan): OrchestratorTask | null {
103+
const executable = this.getExecutableTasks(plan)
104+
return executable[0] || null
105+
}
106+
107+
// ============================================
108+
// 执行控制
109+
// ============================================
110+
111+
/**
112+
* 开始执行
113+
*/
114+
start(): void {
115+
this.abortController = new AbortController()
116+
this.runningTasks.clear()
117+
}
118+
119+
/**
120+
* 停止执行
121+
*/
122+
stop(): void {
123+
if (this.abortController) {
124+
this.abortController.abort()
125+
this.abortController = null
126+
}
127+
this.runningTasks.clear()
128+
}
129+
130+
/**
131+
* 暂停执行
132+
*/
133+
pause(): void {
134+
this.abortController?.abort()
135+
}
136+
137+
/**
138+
* 恢复执行
139+
*/
140+
resume(): void {
141+
this.abortController = new AbortController()
142+
}
143+
144+
/**
145+
* 检查是否已中止
146+
*/
147+
get isAborted(): boolean {
148+
return this.abortController?.signal.aborted ?? true
149+
}
150+
151+
/**
152+
* 获取中止信号
153+
*/
154+
get abortSignal(): AbortSignal | undefined {
155+
return this.abortController?.signal
156+
}
157+
158+
// ============================================
159+
// 任务状态管理
160+
// ============================================
161+
162+
/**
163+
* 标记任务开始执行
164+
*/
165+
markTaskRunning(task: OrchestratorTask): void {
166+
task.status = 'running'
167+
task.startedAt = Date.now()
168+
this.runningTasks.add(task.id)
169+
}
170+
171+
/**
172+
* 标记任务完成
173+
*/
174+
markTaskCompleted(task: OrchestratorTask, output: string): TaskExecutionResult {
175+
const duration = Date.now() - (task.startedAt || Date.now())
176+
task.status = 'completed'
177+
task.output = output
178+
task.completedAt = Date.now()
179+
this.runningTasks.delete(task.id)
180+
181+
return { taskId: task.id, success: true, output, duration }
182+
}
183+
184+
/**
185+
* 标记任务失败
186+
*/
187+
markTaskFailed(task: OrchestratorTask, error: string): TaskExecutionResult {
188+
const duration = Date.now() - (task.startedAt || Date.now())
189+
task.status = 'failed'
190+
task.error = error
191+
task.completedAt = Date.now()
192+
this.runningTasks.delete(task.id)
193+
194+
return { taskId: task.id, success: false, output: '', error, duration }
195+
}
196+
197+
// ============================================
198+
// 进度统计
199+
// ============================================
200+
201+
/**
202+
* 检查计划是否完成
203+
*/
204+
isComplete(plan: TaskPlan): boolean {
205+
return plan.tasks.every(t =>
206+
t.status === 'completed' || t.status === 'failed' || t.status === 'skipped'
207+
)
208+
}
209+
210+
/**
211+
* 检查是否有任务正在运行
212+
*/
213+
hasRunningTasks(): boolean {
214+
return this.runningTasks.size > 0
215+
}
216+
217+
/**
218+
* 计算执行统计
219+
*/
220+
calculateStats(plan: TaskPlan, startedAt: number): ExecutionStats {
221+
const tasks = plan.tasks
222+
return {
223+
totalTasks: tasks.length,
224+
completedTasks: tasks.filter(t => t.status === 'completed').length,
225+
failedTasks: tasks.filter(t => t.status === 'failed').length,
226+
skippedTasks: tasks.filter(t => t.status === 'skipped').length,
227+
totalDuration: Date.now() - startedAt,
228+
startedAt,
229+
completedAt: this.isComplete(plan) ? Date.now() : undefined,
230+
}
231+
}
232+
233+
// ============================================
234+
// 并行执行支持
235+
// ============================================
236+
237+
/**
238+
* 获取可并行执行的任务批次
239+
*/
240+
getParallelBatch(plan: TaskPlan): OrchestratorTask[] {
241+
const executable = this.getExecutableTasks(plan)
242+
// 限制并发数
243+
return executable.slice(0, this.config.maxConcurrency)
244+
}
245+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Orchestrator 模块导出
3+
*/
4+
5+
// 类型
6+
export type {
7+
OrchestratorState,
8+
OrchestratorConfig,
9+
OrchestratorEvent,
10+
TaskPlan,
11+
OrchestratorTask,
12+
TaskStatus,
13+
ExecutionMode,
14+
PlanStatus,
15+
TaskExecutionContext,
16+
TaskExecutionResult,
17+
ExecutionStats,
18+
} from './types'
19+
20+
export { DEFAULT_ORCHESTRATOR_CONFIG } from './types'
21+
22+
// 调度器
23+
export { ExecutionScheduler } from './ExecutionScheduler'

0 commit comments

Comments
 (0)