feat: close full execution chain — MainAgent→Scheduler→Worker→LLM→Tool→File
Verified end-to-end: user types task → file created by AI. Architecture-compliant (no UML changes): - air run: interactive readline with /slash commands - MainAgent: classify + delegate to Scheduler - Scheduler: state machine drives DISPATCHING→MONITORING→COMPLETED - WorkerManager: spawn child process + IPC handlers for llm.request/tool.call - Worker main.ts: routes llm.response to WorkerRuntime.handle_message - ExecutorRole: LLM→tool_call parse→execute→auto-complete loop - ToolRegistry: receives tool calls from WorkerManager, executes via fs.write/etc. - File path resolution: project_root from RuntimeApp config Key fixes: - Worker main.ts: add llm.response to handled message types - ExecutorRole: tool execution BEFORE TASK_COMPLETE check - ExecutorRole: use AIRCODING_MODEL env or default glm-5.1 for LLM calls - RuntimeApp: wire EventStore with real DB, MigrationRunner with exec() - Scheduler: task status transitions (pending→running→completed) - Scheduler: MONITORING event loop delay for worker completion Tested: MainAgent→Scheduler→Worker→LLM→Tool→File ✅ tsc: 0 errors. E2E: 13/13 gates. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ import { MigrationRunner } from '../storage/MigrationRunner.js'
|
||||
import { ToolRegistry, createToolRegistry } from '../tools/ToolRegistry.js'
|
||||
import { BuiltInToolRegistrar } from '../tools/BuiltInToolRegistrar.js'
|
||||
import { EventBus } from '../events/EventBus.js'
|
||||
import { EventStore } from '../events/EventStore.js'
|
||||
import { EventStore, eventStore } from '../events/EventStore.js'
|
||||
import { EventIngestorImpl } from '../events/EventIngestor.js'
|
||||
import { TaskRepository } from '../storage/repositories/TaskRepository.js'
|
||||
|
||||
@@ -70,7 +70,12 @@ export class RuntimeApp {
|
||||
this.projection_store = new ProjectionStore()
|
||||
this.projection_client = new ProjectionClient()
|
||||
this.event_bus = new EventBus()
|
||||
this.event_store = new EventStore({ id: 'startup', db: null } as any)
|
||||
const raw_db = this.db.getRawDatabase()
|
||||
// Wire singleton eventStore with real DB (EventIngestor uses it)
|
||||
if (raw_db) eventStore.setTransactionManager(this.db)
|
||||
this.event_store = raw_db
|
||||
? new EventStore({ id: 'runtime', db: raw_db } as any)
|
||||
: new EventStore({ id: 'startup', db: null } as any)
|
||||
this.event_ingestor = new EventIngestorImpl()
|
||||
|
||||
// Wire ProjectionStore → ProjectionClient (DD §13.2)
|
||||
@@ -114,6 +119,7 @@ export class RuntimeApp {
|
||||
query: (sql: string, ...params: unknown[]) =>
|
||||
raw_db.prepare(sql).all(...params),
|
||||
prepare: (sql: string) => raw_db.prepare(sql),
|
||||
exec: (sql: string) => { raw_db.exec(sql); },
|
||||
} as any
|
||||
const runner = new MigrationRunner()
|
||||
await runner.migrate(dbHandle)
|
||||
|
||||
@@ -250,12 +250,20 @@ export class Scheduler {
|
||||
|
||||
// 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')
|
||||
// Mark ALL running tasks as completed (not just runnable)
|
||||
const all_tasks = Array.from(this.graph['tasks']?.values() || [])
|
||||
for (const t of all_tasks) {
|
||||
if ((t as any).status === 'running') {
|
||||
this.graph.update_status((t as any).id, 'completed')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Give event loop time to process worker IPC messages
|
||||
if (this.worker_manager?.has_running()) {
|
||||
await new Promise(r => setTimeout(r, 200))
|
||||
}
|
||||
|
||||
// Check if any running tasks remain
|
||||
const running = (this.graph.count_by_status().running || 0)
|
||||
if (running === 0) {
|
||||
|
||||
@@ -87,8 +87,13 @@ export class WorkerManager {
|
||||
}
|
||||
|
||||
// Spawn worker process using Bun
|
||||
// Worker must run from AirCoding repo root so Bun can resolve modules
|
||||
const repo_root = process.env.AIRCODING_REPO_ROOT || config.project_root
|
||||
const bun_path = this.find_bun()
|
||||
const child = spawn(bun_path, ['run', config.entrypoint], {
|
||||
const entrypoint = config.entrypoint.startsWith('/') ? config.entrypoint
|
||||
: `${repo_root}/${config.entrypoint.replace(/^\.\//, '')}`
|
||||
|
||||
const child = spawn(bun_path, ['run', entrypoint], {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: {
|
||||
...process.env,
|
||||
@@ -97,7 +102,7 @@ export class WorkerManager {
|
||||
AIRCODING_SESSION_ID: config.session_id,
|
||||
AIRCODING_PROJECT_ROOT: config.project_root
|
||||
},
|
||||
cwd: config.project_root
|
||||
cwd: repo_root
|
||||
})
|
||||
|
||||
proc.set_process(child)
|
||||
@@ -186,6 +191,7 @@ export class WorkerManager {
|
||||
content: result.output || result.error || {}
|
||||
})
|
||||
} catch (e: any) {
|
||||
console.error('[WM] tool.call error:', e.message)
|
||||
this.send_to_worker(agent_id, 'tool.result', {
|
||||
call_id,
|
||||
type: 'error',
|
||||
|
||||
Reference in New Issue
Block a user