/** * ═══════════════════════════════════════════════════════════ * AGE OS · MCP 工具: 跨层开发任务 * ═══════════════════════════════════════════════════════════ * * 签发: 铸渊 · ICE-GL-ZY001 * 版权: 国作登字-2026-A-00037559 * * 五层架构核心桥接: * L1 网站聊天层 → L2 MCP工具层 → L3 霜砚Agent层 → L4 副驾驶 → L5 铸渊审查 * * 本模块是 L2 MCP工具层 的开发任务管理接口。 * Notion端人格体(霜砚等)通过这些工具提交开发请求, * 铸渊通过GitHub Actions自动执行,副驾驶完成代码开发。 * * 工具清单 (7个): * submitDevTask — 提交新开发任务 (Notion人格体Agent调用) * getDevTask — 获取任务详情 * listDevTasks — 列出开发任务 (支持筛选) * updateDevTaskStatus — 更新任务状态 * logDevTaskAction — 记录任务日志 * getDevTaskStats — 获取开发统计 * getDevTaskLogs — 获取任务操作日志 */ 'use strict'; const db = require('../db'); // ─── 任务ID生成 ────────────────────────────────── function generateTaskId() { const now = new Date(); const y = now.getFullYear(); const m = String(now.getMonth() + 1).padStart(2, '0'); const d = String(now.getDate()).padStart(2, '0'); const dateStr = '' + y + m + d; // 序号通过数据库查询确定 return { dateStr, prefix: 'CAB-' + dateStr }; } /** * submitDevTask — 提交新开发任务 * * 这是最关键的工具。Notion端的人格体Agent调用此工具, * 就能向铸渊发起开发请求。 * * input: * persona_id: string — 提交者人格体ID (SY001/YC001/CX001/ZY001) * submitted_by: string — 提交者名称 (霜砚/映川/晨曦/铸渊) * agent_id: string — Agent编号 (AG-SY-WEB-001) [可选] * title: string — 任务标题 * description: string — 任务描述 [可选] * priority: string — 优先级 (low/normal/high/urgent) [可选] * scope: string — 范围 (tiny/small/medium/large) [可选] * steps: string[] — 开发步骤列表 * architecture: object — 架构决策 [可选] * constraints: object — 约束条件 [可选] * reasoning: object — 推理上下文 [可选] */ async function submitDevTask(input) { const { persona_id, submitted_by, agent_id, title, description, priority, scope, steps, architecture, constraints, reasoning } = input; if (!submitted_by) throw new Error('缺少 submitted_by (提交者名称)'); if (!title) throw new Error('缺少 title (任务标题)'); if (!steps || !Array.isArray(steps) || steps.length === 0) { throw new Error('缺少 steps (开发步骤列表, 至少一步)'); } // 生成任务ID const { prefix } = generateTaskId(); const seqResult = await db.query( "SELECT COUNT(*) AS cnt FROM dev_tasks WHERE task_id LIKE $1", [prefix + '%'] ); const seq = String(parseInt(seqResult.rows[0].cnt, 10) + 1).padStart(3, '0'); const taskId = prefix + '-' + seq; // 格式化步骤 const formattedSteps = steps.map(function(s, i) { if (typeof s === 'string') { return { index: i + 1, step: s, status: 'pending' }; } return { index: i + 1, ...s, status: s.status || 'pending' }; }); const result = await db.query( `INSERT INTO dev_tasks ( task_id, persona_id, submitted_by, agent_id, title, description, priority, scope, status, steps, architecture, constraints, reasoning ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'pending', $9, $10, $11, $12) RETURNING *`, [ taskId, persona_id || null, submitted_by, agent_id || null, title, description || '', priority || 'normal', scope || 'small', JSON.stringify(formattedSteps), JSON.stringify(architecture || {}), JSON.stringify(constraints || { required_tests: true, deploy_after: false, max_files_changed: 20 }), JSON.stringify(reasoning || {}) ] ); // 记录提交日志 await db.query( `INSERT INTO dev_task_logs (task_id, action, actor, actor_type, message, data) VALUES ($1, 'submit', $2, $3, $4, $5)`, [ taskId, submitted_by, agent_id ? 'agent' : 'persona', '开发任务已提交: ' + title, JSON.stringify({ persona_id: persona_id, agent_id: agent_id, steps_count: formattedSteps.length, priority: priority || 'normal' }) ] ); return { task_id: taskId, status: 'pending', title: title, submitted_by: submitted_by, steps_count: formattedSteps.length, next_step: '任务已提交。铸渊将通过 CAB Bridge 创建 GitHub Issue,副驾驶将执行开发。', cab_bridge: { pending_path: 'bridge/chat-to-agent/pending/' + taskId + '.json', workflow: 'copilot-dev-bridge.yml', review_workflow: 'shuangyan-dev-review.yml' } }; } /** * getDevTask — 获取任务详情 * * input: * task_id: string — 任务ID (CAB-YYYYMMDD-NNN) */ async function getDevTask(input) { var task_id = input.task_id; if (!task_id) throw new Error('缺少 task_id'); var result = await db.query( 'SELECT * FROM dev_tasks WHERE task_id = $1', [task_id] ); if (result.rows.length === 0) { throw new Error('任务不存在: ' + task_id); } var task = result.rows[0]; // 获取最近日志 var logsResult = await db.query( 'SELECT * FROM dev_task_logs WHERE task_id = $1 ORDER BY created_at DESC LIMIT 10', [task_id] ); return { ...task, recent_logs: logsResult.rows }; } /** * listDevTasks — 列出开发任务 * * input: * status: string — 按状态筛选 (pending/authorized/in_progress/review/completed/failed) [可选] * persona_id: string — 按人格体筛选 [可选] * limit: number — 返回数量 (默认20, 最大100) [可选] * offset: number — 偏移量 [可选] */ async function listDevTasks(input) { var conditions = []; var params = []; var idx = 1; if (input.status) { conditions.push('status = $' + idx); params.push(input.status); idx++; } if (input.persona_id) { conditions.push('persona_id = $' + idx); params.push(input.persona_id); idx++; } var limit = Math.min(parseInt(input.limit, 10) || 20, 100); var offset = parseInt(input.offset, 10) || 0; var where = conditions.length > 0 ? ' WHERE ' + conditions.join(' AND ') : ''; var sql = 'SELECT task_id, persona_id, submitted_by, agent_id, title, status, priority, scope, ' + 'issue_number, pr_number, created_at, updated_at, completed_at ' + 'FROM dev_tasks' + where + ' ORDER BY created_at DESC LIMIT $' + idx + ' OFFSET $' + (idx + 1); params.push(limit, offset); var result = await db.query(sql, params); // 获取统计 var statsResult = await db.query('SELECT * FROM dev_task_stats'); return { tasks: result.rows, total: result.rows.length, stats: statsResult.rows[0] || {}, pagination: { limit: limit, offset: offset } }; } /** * updateDevTaskStatus — 更新任务状态 * * input: * task_id: string — 任务ID * status: string — 新状态 (authorized/in_progress/review/completed/failed/cancelled) * actor: string — 操作者名称 * actor_type: string — 操作者类型 (persona/agent/human/system/workflow) [可选] * message: string — 状态变更说明 [可选] * issue_number: number — GitHub Issue编号 [可选] * pr_number: number — PR编号 [可选] * result_summary: string — 结果摘要 (完成时) [可选] * files_changed: string[] — 变更文件列表 (完成时) [可选] */ async function updateDevTaskStatus(input) { var task_id = input.task_id; var status = input.status; var actor = input.actor; if (!task_id) throw new Error('缺少 task_id'); if (!status) throw new Error('缺少 status'); if (!actor) throw new Error('缺少 actor'); // 构建更新字段 var sets = ['status = $1']; var params = [status]; var idx = 2; if (input.issue_number != null) { sets.push('issue_number = $' + idx); params.push(input.issue_number); idx++; } if (input.pr_number != null) { sets.push('pr_number = $' + idx); params.push(input.pr_number); idx++; } if (input.result_summary) { sets.push('result_summary = $' + idx); params.push(input.result_summary); idx++; } if (input.files_changed) { sets.push('files_changed = $' + idx); params.push(JSON.stringify(input.files_changed)); idx++; } // 设置 app.current_actor 供触发器使用 await db.query("SELECT set_config('app.current_actor', $1, true)", [actor]); await db.query("SELECT set_config('app.current_actor_type', $1, true)", [input.actor_type || 'system']); params.push(task_id); var result = await db.query( 'UPDATE dev_tasks SET ' + sets.join(', ') + ' WHERE task_id = $' + idx + ' RETURNING *', params ); if (result.rows.length === 0) { throw new Error('任务不存在: ' + task_id); } // 手动记录详细日志 (触发器只记录状态变更) if (input.message) { await db.query( 'INSERT INTO dev_task_logs (task_id, action, actor, actor_type, message, data) VALUES ($1, $2, $3, $4, $5, $6)', [ task_id, 'status_update', actor, input.actor_type || 'system', input.message, JSON.stringify({ new_status: status, issue_number: input.issue_number, pr_number: input.pr_number }) ] ); } return { task_id: task_id, status: status, updated: true, task: result.rows[0] }; } /** * logDevTaskAction — 记录任务操作日志 * * input: * task_id: string — 任务ID * action: string — 操作类型 (progress/comment/review/step_complete) * actor: string — 操作者 * actor_type: string — 操作者类型 [可选] * message: string — 日志内容 * data: object — 附加数据 [可选] */ async function logDevTaskAction(input) { var task_id = input.task_id; if (!task_id) throw new Error('缺少 task_id'); if (!input.action) throw new Error('缺少 action'); if (!input.actor) throw new Error('缺少 actor'); // 验证任务存在 var check = await db.query('SELECT task_id FROM dev_tasks WHERE task_id = $1', [task_id]); if (check.rows.length === 0) { throw new Error('任务不存在: ' + task_id); } await db.query( 'INSERT INTO dev_task_logs (task_id, action, actor, actor_type, message, data) VALUES ($1, $2, $3, $4, $5, $6)', [ task_id, input.action, input.actor, input.actor_type || 'system', input.message || '', JSON.stringify(input.data || {}) ] ); return { task_id: task_id, logged: true, action: input.action }; } /** * getDevTaskStats — 获取开发任务统计 * * input: (无必需参数) */ async function getDevTaskStats() { var stats = await db.query('SELECT * FROM dev_task_stats'); var activity = await db.query('SELECT * FROM persona_dev_activity ORDER BY last_task_at DESC'); // 最近完成的任务 var recentCompleted = await db.query( 'SELECT task_id, title, submitted_by, completed_at FROM dev_tasks WHERE status = $1 ORDER BY completed_at DESC LIMIT 5', ['completed'] ); // 当前活跃任务 var activeTasks = await db.query( "SELECT task_id, title, submitted_by, status, priority FROM dev_tasks WHERE status IN ('pending', 'authorized', 'in_progress', 'review') ORDER BY priority DESC, created_at ASC" ); return { overview: stats.rows[0] || {}, persona_activity: activity.rows, active_tasks: activeTasks.rows, recent_completed: recentCompleted.rows }; } /** * getDevTaskLogs — 获取任务操作日志 * * input: * task_id: string — 任务ID [可选, 不填返回全局最近日志] * limit: number — 返回数量 (默认20) [可选] */ async function getDevTaskLogs(input) { var limit = Math.min(parseInt(input.limit, 10) || 20, 100); var sql, params; if (input.task_id) { sql = 'SELECT * FROM dev_task_logs WHERE task_id = $1 ORDER BY created_at DESC LIMIT $2'; params = [input.task_id, limit]; } else { sql = 'SELECT * FROM dev_task_logs ORDER BY created_at DESC LIMIT $1'; params = [limit]; } var result = await db.query(sql, params); return { logs: result.rows, total: result.rows.length }; } module.exports = { submitDevTask, getDevTask, listDevTasks, updateDevTaskStatus, logDevTaskAction, getDevTaskStats, getDevTaskLogs };