guanghulab/glada/task-receiver.js
2026-05-10 13:12:44 +08:00

282 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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
};