guanghulab/glada/execution-loop.js

432 lines
13 KiB
JavaScript
Raw Normal View History

/**
* 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<Object>} 执行结果
*/
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
};