282 lines
7.6 KiB
JavaScript
282 lines
7.6 KiB
JavaScript
/**
|
||
* 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
|
||
};
|