415 lines
13 KiB
JavaScript
Raw Normal View History

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