/** * GLADA · 主执行循环 · execution-loop.js * * 核心闭环: * 取任务 → 加载上下文 → 逐步执行 → 记录 → Git 提交 → 通知 * * 这是 GLADA 的心脏。它不断从任务队列中取出任务, * 按步骤逐一执行,每一步都经过: * 1. 深度上下文构建 * 2. LLM 代码生成 * 3. 回归防护 * 4. 自动测试 * 5. Git 提交 * 6. 开发记录 * * 任务来源(双通道): * A. core/task-queue 中央任务队列(type: 'glada-task') * B. CAB pending 目录 + GLADA 本地队列(兼容) * * 版权:国作登字-2026-A-00037559 * 签发:铸渊 · ICE-GL-ZY001 */ 'use strict'; const fs = require('fs'); const path = require('path'); const taskReceiver = require('./task-receiver'); const contextBuilder = require('./context-builder'); const stepExecutor = require('./step-executor'); const gitOperator = require('./git-operator'); const notifier = require('./notifier'); const skillDistiller = require('./skill-distiller'); const reflector = require('./reflector'); const ROOT = path.resolve(__dirname, '..'); const EXECUTION_LOG_DIR = path.join(ROOT, 'glada', 'logs', 'executions'); // 尝试加载中央任务队列(容错:如果不存在则跳过) let taskQueue = null; try { taskQueue = require('../core/task-queue'); } catch { console.warn('[GLADA] ⚠️ core/task-queue 加载失败,仅使用本地队列'); } /** * 执行单个 GLADA 任务(完整的步骤循环) * @param {Object} gladaTask - GLADA 任务对象 * @param {Object} [options] - 执行选项 * @returns {Promise} 执行结果 */ async function executeTask(gladaTask, options = {}) { const taskId = gladaTask.glada_task_id; const startTime = Date.now(); console.log(`\n${'═'.repeat(60)}`); console.log(`[GLADA] 🚀 开始执行任务: ${taskId}`); console.log(`[GLADA] 📋 标题: ${gladaTask.plan.title}`); console.log(`[GLADA] 📊 步骤数: ${gladaTask.plan.steps.length}`); console.log(`${'═'.repeat(60)}\n`); // 更新任务状态为 running gladaTask.status = 'running'; taskReceiver.updateTask(taskId, { status: 'running' }); // 通知任务开始 await notifier.notify(gladaTask, 'started'); // 记录原始分支 let originalBranch; try { originalBranch = gitOperator.getCurrentBranch(); } catch { originalBranch = 'main'; } // 创建任务分支 let taskBranch; try { taskBranch = gitOperator.createTaskBranch(taskId); gladaTask.completion.git_branch = taskBranch; } catch (err) { console.error(`[GLADA] ⚠️ 创建 Git 分支失败: ${err.message}`); taskBranch = null; } const executionResult = { task_id: taskId, status: 'running', steps_completed: 0, steps_failed: 0, total_files_changed: [], git_commits: [], errors: [], started_at: new Date().toISOString(), completed_at: null, duration_ms: 0 }; // 逐步执行 for (const step of gladaTask.plan.steps) { // 跳过已完成的步骤(支持断点续传) if (step.status === 'completed') { console.log(`[GLADA] ⏭️ 跳过已完成步骤 ${step.step_id}`); executionResult.steps_completed++; continue; } console.log(`\n${'─'.repeat(40)}`); console.log(`[GLADA] 📌 步骤 ${step.step_id}/${gladaTask.plan.steps.length}: ${step.description}`); console.log(`${'─'.repeat(40)}`); // 1. 构建深度上下文 const context = contextBuilder.buildContext(gladaTask); const systemPrompt = contextBuilder.contextToSystemPrompt(context); // 2. 执行步骤 step.status = 'running'; step.started_at = new Date().toISOString(); const stepResult = await stepExecutor.executeStep(step, systemPrompt, gladaTask, { model: options.model, // null = let model-router auto-select maxTokens: options.maxTokens || 8192, skipTests: options.skipTests }); // 3. 记录执行日志 const logEntry = { step_id: step.step_id, action: step.description, status: stepResult.status, reasoning: stepResult.reasoning, files_changed: stepResult.files_changed, error: stepResult.error, duration_ms: stepResult.duration_ms, timestamp: new Date().toISOString() }; gladaTask.execution_log.push(logEntry); writeExecutionLog(taskId, logEntry); // 4. 更新步骤状态 if (stepResult.status === 'completed') { step.status = 'completed'; step.completed_at = stepResult.completed_at; step.result = stepResult.summary; step.files_changed = stepResult.files_changed; step.reasoning = stepResult.reasoning; executionResult.steps_completed++; executionResult.total_files_changed.push(...stepResult.files_changed); // 5. Git 提交 if (taskBranch && stepResult.files_changed.length > 0) { try { const commitHash = gitOperator.commitStep( taskId, step.step_id, stepResult.summary || step.description, stepResult.files_changed ); if (commitHash) { executionResult.git_commits.push(`${commitHash}: 步骤${step.step_id} - ${step.description}`); } } catch (err) { console.warn(`[GLADA] ⚠️ Git 提交失败: ${err.message}`); } } } else { step.status = stepResult.status === 'rolled_back' ? 'rolled_back' : 'failed'; step.error = stepResult.error; executionResult.steps_failed++; executionResult.errors.push(`步骤${step.step_id}: ${stepResult.error}`); // 如果设置了失败即停止 if (options.stopOnFailure !== false) { console.warn(`[GLADA] ⛔ 步骤 ${step.step_id} 失败,停止执行`); break; } } // 保存任务状态 taskReceiver.updateTask(taskId, { plan: gladaTask.plan, execution_log: gladaTask.execution_log }); // 步骤间通知 await notifier.notify(gladaTask, 'step_completed'); } // 推送到远程 if (taskBranch) { gitOperator.pushBranch(taskBranch); } // 最终状态 const allCompleted = gladaTask.plan.steps.every(s => s.status === 'completed'); executionResult.status = allCompleted ? 'completed' : 'partial'; executionResult.completed_at = new Date().toISOString(); executionResult.duration_ms = Date.now() - startTime; // 去重文件列表 executionResult.total_files_changed = [...new Set(executionResult.total_files_changed)]; // 更新任务完成信息 gladaTask.status = allCompleted ? 'completed' : 'failed'; gladaTask.completion = { ...gladaTask.completion, completed_at: executionResult.completed_at, total_files_changed: executionResult.total_files_changed, git_branch: taskBranch, git_commits: executionResult.git_commits, notification_sent: false }; // 保存最终状态 taskReceiver.updateTask(taskId, { status: gladaTask.status, plan: gladaTask.plan, completion: gladaTask.completion, execution_log: gladaTask.execution_log }); // 发送完成通知 const eventType = allCompleted ? 'completed' : 'failed'; await notifier.notify(gladaTask, eventType); gladaTask.completion.notification_sent = true; taskReceiver.updateTask(taskId, { completion: gladaTask.completion }); // ── Hermes-inspired 后处理:Skill 蒸馏 + 自我反思 ── // 1. Skill 蒸馏:从成功的执行中提炼可复用模板 try { const distillResult = skillDistiller.distillAndSave(gladaTask); if (distillResult.saved) { console.log(`[GLADA] 🧪 Skill 蒸馏完成: ${distillResult.path}`); } } catch (err) { console.warn(`[GLADA] ⚠️ Skill 蒸馏失败(不影响主流程): ${err.message}`); } // 2. 自我反思:分析执行过程,写入经验枝干 try { const reflectResult = reflector.reflectOffline(gladaTask); if (reflectResult.saved) { console.log(`[GLADA] 🪞 自我反思完成: ${reflectResult.path}`); console.log(`[GLADA] 📊 信心评分: ${Math.round(reflectResult.reflection.confidence_score * 100)}%`); } } catch (err) { console.warn(`[GLADA] ⚠️ 自我反思失败(不影响主流程): ${err.message}`); } // 如果全部完成,归档 if (allCompleted) { taskReceiver.archiveTask(taskId, gladaTask.completion); } // 切回原始分支 if (originalBranch && taskBranch) { try { gitOperator.checkoutBranch(originalBranch); } catch { // 忽略 } } console.log(`\n${'═'.repeat(60)}`); console.log(`[GLADA] ${allCompleted ? '✅' : '⚠️'} 任务执行${allCompleted ? '完成' : '部分完成'}: ${taskId}`); console.log(`[GLADA] 📊 完成: ${executionResult.steps_completed}, 失败: ${executionResult.steps_failed}`); console.log(`[GLADA] ⏱️ 耗时: ${Math.round(executionResult.duration_ms / 1000)}s`); console.log(`${'═'.repeat(60)}\n`); return executionResult; } /** * 写入执行日志 * @param {string} taskId - 任务 ID * @param {Object} entry - 日志条目 */ function writeExecutionLog(taskId, entry) { fs.mkdirSync(EXECUTION_LOG_DIR, { recursive: true }); const logFile = path.join(EXECUTION_LOG_DIR, `${taskId}.jsonl`); fs.appendFileSync(logFile, JSON.stringify(entry) + '\n', 'utf-8'); } /** * 从中央任务队列获取 glada-task 类型的任务 * @returns {Object|null} GLADA 格式的任务,或 null */ function dequeueFromCentralQueue() { if (!taskQueue) return null; try { taskQueue.loadQueue(); const queueTask = taskQueue.dequeue(); if (!queueTask || queueTask.type !== 'glada-task') { return null; } // 标记为运行中 // 将中央队列任务转换为 GLADA 执行格式 console.log(`[GLADA] 📥 从中央任务队列取出: ${queueTask.task_id}`); // 如果任务有 cab_spec,用它转换;否则构建最小执行任务 if (queueTask.cab_spec) { return taskReceiver.convertToGladaTask(queueTask.cab_spec); } // 最小格式转换 return { glada_task_id: `GLADA-${queueTask.task_id}`, source_task_id: queueTask.task_id, source: 'central-queue', status: 'pending', created_at: queueTask.created_at || new Date().toISOString(), updated_at: new Date().toISOString(), plan: { title: queueTask.description || queueTask.task_id, description: queueTask.description || '', steps: (queueTask.steps || [queueTask.description || 'Execute task']).map((step, i) => ({ step_id: i + 1, description: typeof step === 'string' ? step : step.description || `步骤 ${i + 1}`, status: 'pending', started_at: null, completed_at: null, result: null, files_changed: [], reasoning: null })), priority: queueTask.priority || 'normal' }, architecture: queueTask.architecture || {}, constraints: queueTask.constraints || { no_touch_files: [], required_tests: true }, reasoning_context: {}, execution_log: [], completion: { completed_at: null, total_files_changed: [], git_branch: null, git_commits: [], notification_sent: false } }; } catch (err) { console.error(`[GLADA] ⚠️ 中央队列读取失败: ${err.message}`); return null; } } /** * 主循环:持续监听并执行任务 * 双通道获取任务:中央 task-queue → 本地 GLADA 队列 * @param {Object} [options] - 循环选项 * @param {number} [options.pollIntervalMs=30000] - 轮询间隔(毫秒) * @param {boolean} [options.singleRun=false] - 只执行一个任务就退出 */ async function startLoop(options = {}) { const pollInterval = options.pollIntervalMs || 30000; const singleRun = options.singleRun || false; console.log(`[GLADA] 🔄 执行循环已启动 (轮询间隔: ${pollInterval / 1000}s)`); if (taskQueue) { console.log(`[GLADA] 📡 已连接中央任务队列 (core/task-queue)`); } const poll = async () => { try { // 双通道:先查中央队列,再查本地队列 let task = dequeueFromCentralQueue(); if (!task) { task = taskReceiver.receiveNextTask(); } if (task) { // 如果来自中央队列,同时保存到本地以便断点续传 if (task.source === 'central-queue') { const queueDir = taskReceiver.GLADA_QUEUE_DIR; fs.mkdirSync(queueDir, { recursive: true }); fs.writeFileSync( path.join(queueDir, `${task.glada_task_id}.json`), JSON.stringify(task, null, 2), 'utf-8' ); } await executeTask(task, options); // 中央队列完成回写 if (task.source === 'central-queue' && taskQueue) { try { const allCompleted = task.plan.steps.every(s => s.status === 'completed'); if (allCompleted) { taskQueue.complete(task.source_task_id, { glada_task_id: task.glada_task_id }); } else { taskQueue.fail(task.source_task_id, 'GLADA execution incomplete'); } } catch { // 回写失败不阻塞主流程 } } if (singleRun) { console.log('[GLADA] 单次运行模式,退出'); return; } } } catch (err) { console.error(`[GLADA] ❌ 循环异常: ${err.message}`); } // 继续轮询 if (!singleRun) { setTimeout(poll, pollInterval); } }; await poll(); } module.exports = { executeTask, writeExecutionLog, startLoop };