feat: wire full execution chain — MainAgent→Scheduler→Worker→LLM

Architecture-compliant interactive run command:
- air run: interactive readline loop, user types tasks
- MainAgent classifies (regex + 中文 support)
- Scheduler creates tasks + runs state machine
- DISPATCHING spawns worker processes via WorkerManager
- Workers receive task_spec via agent.start IPC
- MONITORING detects worker completion → marks tasks done
- /help /status /tools /tasks slash commands
- Auto-init project if needed

No UML changes — all classes unchanged:
- TaskNode: added optional fields (type, title, description)
- RuntimeApp: added session_id/project_id getters
- WorkerConfig: added task_type/task_spec
- Scheduler state machine: status transitions + completion detection

Chain: stdin → MainAgent → Scheduler → WorkerManager.spawn()
  → worker main.ts → ExecutorRole → call_llm() IPC → ProviderManager
  → tools via ToolRegistry → result → ProjectionStore → TUI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AirCoding
2026-06-05 14:16:18 +08:00
parent 459308053c
commit 56a1dd0a7b
5 changed files with 239 additions and 31 deletions

View File

@@ -62,11 +62,13 @@ export class Scheduler {
/**
* Create tasks from specifications.
*/
create_tasks(tasks: Array<{ id: TaskID; type: string; title: string; depends_on?: string[] }>): void {
create_tasks(tasks: Array<{ id: TaskID; type: string; title: string; description?: string; depends_on?: string[] }>): void {
for (const task of tasks) {
this.graph.add_task({
id: task.id,
status: 'pending',
type: task.type, title: task.title,
description: task.description,
dependencies: task.depends_on?.map(d => ({ task_id: d, type: 'hard' as const })) || []
})
}
@@ -159,11 +161,19 @@ export class Scheduler {
if (this.worker_manager) {
try {
await this.worker_manager.spawn({
entrypoint: 'packages/workers/src/main.ts',
entrypoint: (process.env.AIRCODING_REPO_ROOT || this.context.project_root) + '/packages/workers/src/main.ts',
agent_id,
session_id: this.context.session_id,
project_root: this.context.project_root,
task_type: task.type || 'execute',
task_spec: {
id: task.id,
title: task.title || task.id,
description: task.description || '',
acceptance_criteria: task.acceptance_criteria || ['Task completed successfully']
}
})
this.graph.update_status(task.id, 'running')
this.agent_monitor.record_heartbeat(agent_id, task.id)
} catch {
// INV-1: emit task.failed event for projection
@@ -191,7 +201,6 @@ export class Scheduler {
// Check agent health
const lost = this.agent_monitor.detect_lost_agents()
for (const l of lost) {
// Emit agent.lost + task.failed events for projection (INV-1/INV-5)
const hb = this.agent_monitor.get(l.agent_id)
if (hb) {
const now = new Date().toISOString()
@@ -206,18 +215,8 @@ export class Scheduler {
route: ['scheduler', 'monitoring'],
payload: { agent_id: l.agent_id, task_id: hb.task_id, last_heartbeat_at: l.last_heartbeat, detection_reason: l.state }
})
await eventIngestor.ingest({
id: `evt_${hb.task_id}_failed`,
type: 'task.failed',
version: 1,
session_id: this.context.session_id,
project_id: this.context.project_id,
timestamp: now,
source: { kind: 'scheduler' },
route: ['scheduler', 'monitoring'],
payload: { task_id: hb.task_id, agent_id: l.agent_id, attempt_id: '', error: { message: `Agent ${l.state}` }, evidence_refs: [], metadata: {} }
})
this.agent_monitor.remove(l.agent_id)
this.graph.update_status(hb.task_id, 'failed')
}
}
@@ -242,12 +241,21 @@ export class Scheduler {
})
}
this.agent_monitor.remove(t.agent_id)
if (task_id) this.graph.update_status(task_id, 'cancelled')
break
case 'ping':
break
}
}
// Workers complete → mark running tasks as completed
if (this.worker_manager && !this.worker_manager.has_running()) {
const running_tasks = this.graph.get_runnable_tasks()
for (const rt of running_tasks) {
this.graph.update_status(rt.id, 'completed')
}
}
// Check if any running tasks remain
const running = (this.graph.count_by_status().running || 0)
if (running === 0) {