guanghulab/glada/task-receiver.js

282 lines
7.6 KiB
JavaScript
Raw Permalink Normal View History

/**
* GLADA · 任务接收器 · task-receiver.js
*
* 负责
* 1. 监听任务队列中的 glada-task 类型任务
* 2. 解析 CAB 任务规格bridge/chat-to-agent/pending/
* 3. 验证任务格式和授权
* 4. 将合法任务传递给执行循环
*
* 版权国作登字-2026-A-00037559
* 签发铸渊 · ICE-GL-ZY001
*/
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
const PENDING_DIR = path.join(ROOT, 'bridge', 'chat-to-agent', 'pending');
const COMPLETED_DIR = path.join(ROOT, 'bridge', 'chat-to-agent', 'completed');
const GLADA_QUEUE_DIR = path.join(ROOT, 'glada', 'queue');
/**
* 扫描 pending 目录获取所有待执行的任务规格
* @returns {Object[]} 任务规格列表
*/
function scanPendingTasks() {
const tasks = [];
if (!fs.existsSync(PENDING_DIR)) {
return tasks;
}
const files = fs.readdirSync(PENDING_DIR)
.filter(f => f.endsWith('.json'))
.sort();
for (const file of files) {
try {
const filePath = path.join(PENDING_DIR, file);
const spec = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
tasks.push({ file, filePath, spec });
} catch (err) {
console.error(`[GLADA-Receiver] ⚠️ 无法解析任务文件: ${file} - ${err.message}`);
}
}
return tasks;
}
/**
* 扫描 GLADA 本地队列
* @returns {Object[]} 本地队列中的任务
*/
function scanLocalQueue() {
const tasks = [];
if (!fs.existsSync(GLADA_QUEUE_DIR)) {
fs.mkdirSync(GLADA_QUEUE_DIR, { recursive: true });
return tasks;
}
const files = fs.readdirSync(GLADA_QUEUE_DIR)
.filter(f => f.endsWith('.json'))
.sort();
for (const file of files) {
try {
const filePath = path.join(GLADA_QUEUE_DIR, file);
const task = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
if (task.status === 'pending') {
tasks.push({ file, filePath, ...task });
}
} catch (err) {
console.error(`[GLADA-Receiver] ⚠️ 无法解析本地队列文件: ${file} - ${err.message}`);
}
}
return tasks;
}
/**
* 验证任务规格
* @param {Object} spec - CAB 任务规格
* @returns {{ valid: boolean, errors: string[] }}
*/
function validateTaskSpec(spec) {
const errors = [];
if (!spec.task_id) {
errors.push('缺少 task_id');
}
if (!spec.authorization || spec.authorization.sovereign !== '冰朔 · TCS-0002∞') {
errors.push('授权不合法sovereignty 必须为 "冰朔 · TCS-0002∞"');
}
if (!spec.development_plan || !spec.development_plan.title) {
errors.push('缺少开发计划标题 (development_plan.title)');
}
if (!spec.development_plan || !Array.isArray(spec.development_plan.steps) || spec.development_plan.steps.length === 0) {
errors.push('开发步骤为空 (development_plan.steps)');
}
return { valid: errors.length === 0, errors };
}
/**
* CAB 任务规格转换为 GLADA 执行任务
* @param {Object} spec - CAB 任务规格
* @returns {Object} GLADA 执行任务
*/
function convertToGladaTask(spec) {
const now = new Date().toISOString();
return {
glada_task_id: `GLADA-${spec.task_id}`,
source_task_id: spec.task_id,
status: 'pending',
created_at: now,
updated_at: now,
// 开发计划
plan: {
title: spec.development_plan.title,
description: spec.development_plan.description || '',
steps: spec.development_plan.steps.map((step, i) => ({
step_id: i + 1,
description: typeof step === 'string' ? step : step.description || step.title || `步骤 ${i + 1}`,
status: 'pending',
started_at: null,
completed_at: null,
result: null,
files_changed: [],
reasoning: null
})),
priority: spec.development_plan.priority || 'normal'
},
// 架构上下文
architecture: spec.architecture || {},
// 约束
constraints: spec.constraints || {
no_touch_files: [],
required_tests: true,
deploy_after: false,
max_files_changed: 20
},
// 推理上下文
reasoning_context: spec.reasoning_context || {},
// 执行记录
execution_log: [],
// 完成信息
completion: {
completed_at: null,
total_files_changed: [],
test_results: null,
git_branch: null,
git_commits: [],
notification_sent: false
}
};
}
/**
* 接收下一个待执行任务
* 优先级本地队列 > CAB pending 目录
* @returns {Object|null} GLADA 执行任务或 null
*/
function receiveNextTask() {
// 1. 先检查本地队列
const localTasks = scanLocalQueue();
if (localTasks.length > 0) {
const task = localTasks[0];
console.log(`[GLADA-Receiver] 📥 从本地队列取出任务: ${task.glada_task_id}`);
return task;
}
// 2. 再检查 CAB pending 目录
const pendingTasks = scanPendingTasks();
if (pendingTasks.length > 0) {
const { file, filePath, spec } = pendingTasks[0];
// 验证
const validation = validateTaskSpec(spec);
if (!validation.valid) {
console.error(`[GLADA-Receiver] ❌ 任务 ${file} 验证失败:`, validation.errors);
return null;
}
// 转换为 GLADA 任务
const gladaTask = convertToGladaTask(spec);
// 保存到本地队列
fs.mkdirSync(GLADA_QUEUE_DIR, { recursive: true });
const queueFile = path.join(GLADA_QUEUE_DIR, `${gladaTask.glada_task_id}.json`);
fs.writeFileSync(queueFile, JSON.stringify(gladaTask, null, 2), 'utf-8');
console.log(`[GLADA-Receiver] 📥 接收CAB任务: ${spec.task_id}${gladaTask.glada_task_id}`);
return gladaTask;
}
return null;
}
/**
* 更新任务状态
* @param {string} gladaTaskId - GLADA 任务 ID
* @param {Object} updates - 要更新的字段
*/
function updateTask(gladaTaskId, updates) {
const filePath = path.join(GLADA_QUEUE_DIR, `${gladaTaskId}.json`);
if (!fs.existsSync(filePath)) {
console.error(`[GLADA-Receiver] ⚠️ 任务文件不存在: ${gladaTaskId}`);
return false;
}
const task = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
Object.assign(task, updates, { updated_at: new Date().toISOString() });
fs.writeFileSync(filePath, JSON.stringify(task, null, 2), 'utf-8');
return true;
}
/**
* 将任务标记为已完成并归档
* @param {string} gladaTaskId - GLADA 任务 ID
* @param {Object} completionData - 完成数据
*/
function archiveTask(gladaTaskId, completionData) {
const filePath = path.join(GLADA_QUEUE_DIR, `${gladaTaskId}.json`);
if (!fs.existsSync(filePath)) return false;
const task = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
task.status = 'completed';
task.completion = {
...task.completion,
...completionData,
completed_at: new Date().toISOString()
};
// 归档到完成目录
const archiveDir = path.join(GLADA_QUEUE_DIR, 'completed');
fs.mkdirSync(archiveDir, { recursive: true });
fs.writeFileSync(
path.join(archiveDir, `${gladaTaskId}.json`),
JSON.stringify(task, null, 2),
'utf-8'
);
// 从队列移除
fs.unlinkSync(filePath);
// 同时归档 CAB 源文件
if (task.source_task_id) {
const cabPendingFile = path.join(PENDING_DIR, `${task.source_task_id}.json`);
if (fs.existsSync(cabPendingFile)) {
fs.mkdirSync(COMPLETED_DIR, { recursive: true });
fs.renameSync(cabPendingFile, path.join(COMPLETED_DIR, `${task.source_task_id}.json`));
}
}
console.log(`[GLADA-Receiver] ✅ 任务已归档: ${gladaTaskId}`);
return true;
}
module.exports = {
scanPendingTasks,
scanLocalQueue,
validateTaskSpec,
convertToGladaTask,
receiveNextTask,
updateTask,
archiveTask,
GLADA_QUEUE_DIR
};