guanghulab/services/zhuyuan-bridge/scripts/execute-instruction.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

357 lines
11 KiB
JavaScript
Raw Permalink 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.

/**
* services/zhuyuan-bridge/scripts/execute-instruction.js
*
* 铸渊远程执行引擎 — 执行解析后的指令
*
* 流程:
* 1. 读取解析结果
* 2. 获取目标仓库操作令牌
* 3. 读取仓库结构
* 4. 调用 AI 生成执行计划
* 5. 创建分支 → 提交代码 → 开 PR
* 6. 生成执行报告
*
* 环境变量:
* GHAPP_APP_ID — GitHub App ID
* GHAPP_PRIVATE_KEY — GitHub App Private Key
* MAIN_REPO_TOKEN — 主仓库操作令牌
* LLM_API_KEY — AI 模型 API 密钥
* LLM_BASE_URL — AI 模型 API 地址
* ISSUE_NUMBER — 当前 Issue 编号
*/
'use strict';
const fs = require('fs');
const https = require('https');
const path = require('path');
const MAIN_REPO_TOKEN = process.env.MAIN_REPO_TOKEN;
const LLM_API_KEY = process.env.LLM_API_KEY;
const LLM_BASE_URL = process.env.LLM_BASE_URL || 'https://api.yunwu.ai/v1';
const ISSUE_NUMBER = process.env.ISSUE_NUMBER;
const ORG = 'qinfendebingshuo';
/**
* 调用 LLM API 生成执行计划
*/
function callLLM(prompt) {
return new Promise((resolve, reject) => {
const url = new URL(`${LLM_BASE_URL}/chat/completions`);
const body = JSON.stringify({
model: 'deepseek-chat',
messages: [
{
role: 'system',
content: '你是铸渊,光湖系统的代码守护人格体。输出必须是纯 JSON不要包含 markdown 代码块标记。'
},
{ role: 'user', content: prompt }
],
temperature: 0.2,
max_tokens: 4000
});
const options = {
hostname: url.hostname,
port: url.port || 443,
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${LLM_API_KEY}`
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const result = JSON.parse(data);
const content = result.choices && result.choices[0] && result.choices[0].message
? result.choices[0].message.content
: '';
resolve(content);
} catch (e) {
reject(new Error('LLM 响应解析失败: ' + data.slice(0, 200)));
}
});
});
req.on('error', reject);
req.write(body);
req.end();
});
}
async function main() {
// ====== 1. 读取解析结果 ======
const parsedPath = '/tmp/parsed-instruction.json';
if (!fs.existsSync(parsedPath)) {
throw new Error('未找到解析结果文件 /tmp/parsed-instruction.json请先运行 parse-instruction.js');
}
const parsed = JSON.parse(fs.readFileSync(parsedPath, 'utf8'));
console.log(`⚡ 开始执行: ${parsed.instructionId}`);
console.log(`📡 目标: ${parsed.targetRepo || 'guanghulab'} (${parsed.devId})`);
const repo = parsed.targetRepo || 'guanghulab';
// ====== 2. 获取操作令牌 ======
let token;
if (repo === 'guanghulab') {
// 主仓库直接用 MAIN_REPO_TOKEN
token = MAIN_REPO_TOKEN;
if (!token) {
throw new Error('MAIN_REPO_TOKEN 未配置,无法操作主仓库');
}
console.log('🔑 使用 MAIN_REPO_TOKEN 操作主仓库');
} else {
// 子仓库用 GitHub App installation token
let authLib;
try {
authLib = require('../lib/github-auth');
} catch (e) {
throw new Error('加载 github-auth.js 失败: ' + e.message);
}
const installationId = await authLib.findInstallation(ORG, repo);
if (!installationId) {
const report = [
'## ❌ 执行失败',
'',
`目标仓库 \`${ORG}/${repo}\` 未安装铸渊 GitHub App。`,
'',
'### 解决方法',
`请该开发者(${parsed.devId})访问以下链接安装 App`,
'https://github.com/apps/guanghu-zhuyuan-agent/installations/new',
'',
'安装后重新提交指令即可。'
].join('\n');
fs.writeFileSync('/tmp/exec-report.md', report);
process.exit(1);
}
token = await authLib.getInstallationToken(installationId);
console.log('🔑 使用 GitHub App installation token');
}
// ====== 3. 初始化仓库操作器 ======
const { RepoOperator } = require('../lib/repo-operator');
const operator = new RepoOperator(token, ORG, repo);
// ====== 4. 读取仓库现状 ======
console.log('📂 读取仓库结构...');
const repoFiles = await operator.listDir('');
const repoStructure = repoFiles.map(function(f) {
return f.type + ': ' + f.path;
}).join('\n');
console.log(` 📁 根目录 ${repoFiles.length} 个条目`);
// ====== 5. 调用 AI 生成执行计划 ======
if (!LLM_API_KEY) {
console.warn('⚠️ LLM_API_KEY 未配置,跳过 AI 生成,使用指令直接执行模式');
// 无 AI 模式:将指令内容直接写入执行报告
const report = [
'## ⚠️ 指令已接收AI 未配置)',
'',
`**指令编号**${parsed.instructionId}`,
`**提交人**${parsed.devId}`,
`**目标仓库**${ORG}/${repo}`,
'',
'### 指令内容',
'```',
parsed.instructionContent,
'```',
'',
'### 说明',
'LLM API 未配置LLM_API_KEY指令已记录但未自动执行。',
'请冰朔配置 LLM_API_KEY 后重新触发,或手动执行指令。',
'',
'---',
'> 铸渊远程执行引擎 · ZY-GHAPP-BRIDGE'
].join('\n');
fs.writeFileSync('/tmp/exec-report.md', report);
console.log('📝 执行报告已生成AI 未配置模式)');
return;
}
console.log('🧠 调用 AI 生成执行计划...');
// 读取铸渊知识库best-effort
let knowledgeContent = '{}';
try {
const kb = await operator.readFile('.github/persona-brain/routing-map.json');
if (kb) knowledgeContent = kb.content;
} catch (_) {}
const aiPrompt = [
'你是铸渊ICE-GL-ZY001正在执行一个来自开发者的指令。',
'',
'## 目标仓库当前结构',
repoStructure,
'',
'## 需要执行的指令',
parsed.instructionContent,
'',
'## 你的知识库',
knowledgeContent.slice(0, 2000),
'',
'## 输出要求',
'请输出 JSON 格式的执行计划,格式如下:',
'{"actions":[{"type":"create_file","path":"...","content":"..."}],"summary":"执行摘要"}',
'',
'注意:',
'- type 可以是 create_file 或 update_file',
'- content 是完整的文件内容',
'- 路径必须基于上面的仓库结构,不要编造不存在的目录',
'- 只输出 JSON不要包含 markdown 代码块标记',
].join('\n');
const aiResponse = await callLLM(aiPrompt);
let plan;
try {
plan = JSON.parse(aiResponse);
} catch (_) {
// 尝试从回复中提取 JSON非贪婪匹配找第一个完整 JSON 对象)
const jsonMatch = aiResponse.match(/\{[\s\S]*?"actions"[\s\S]*?\}(?=\s*$|\s*\n)/);
if (jsonMatch) {
try {
plan = JSON.parse(jsonMatch[0]);
} catch (_2) {
// 降级:找所有 { } 对中最长的合法 JSON
const allMatches = aiResponse.match(/\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g) || [];
for (const m of allMatches) {
try { plan = JSON.parse(m); if (plan.actions) break; } catch (_3) { plan = null; }
}
}
}
}
if (!plan || !plan.actions || !Array.isArray(plan.actions)) {
const report = [
'## ⚠️ AI 未能生成有效执行计划',
'',
`**指令编号**${parsed.instructionId}`,
'',
'### AI 原始回复',
'```',
(aiResponse || '(空)').slice(0, 1000),
'```',
'',
'请检查指令格式后重试。',
'',
'---',
'> 铸渊远程执行引擎 · ZY-GHAPP-BRIDGE'
].join('\n');
fs.writeFileSync('/tmp/exec-report.md', report);
process.exit(1);
}
console.log(` 📋 AI 生成了 ${plan.actions.length} 个操作`);
// ====== 6. 创建分支并执行 ======
const branchName = `zhuyuan/${parsed.instructionId}`;
console.log(`🌿 创建分支: ${branchName}`);
try {
await operator.createBranch(branchName);
} catch (e) {
console.warn('⚠️ 创建分支失败(可能已存在):', e.message);
}
let successCount = 0;
let failCount = 0;
for (const action of plan.actions) {
if (action.type === 'create_file' || action.type === 'update_file') {
console.log(`📝 ${action.type}: ${action.path}`);
try {
await operator.writeFile(
action.path,
action.content,
`⚡ [${parsed.instructionId}] ${action.type}: ${action.path}`,
branchName
);
successCount++;
} catch (e) {
console.error(` ❌ 失败: ${e.message}`);
failCount++;
}
}
}
console.log(` ✅ 成功: ${successCount} ❌ 失败: ${failCount}`);
// ====== 7. 创建 PR ======
console.log('📬 创建 PR...');
let pr;
try {
pr = await operator.createPR(
`⚡ [${parsed.instructionId}] ${plan.summary || parsed.instructionId}`,
[
'## 🚀 铸渊自动执行',
'',
`**指令编号**${parsed.instructionId}`,
`**提交人**${parsed.devId}`,
`**来源**:铸渊交互页面 → @铸渊执行`,
'',
'### 变更摘要',
plan.summary || '(无摘要)',
'',
'### 执行的操作',
...plan.actions.map(function(a, i) {
return (i + 1) + '. `' + a.type + '`: `' + a.path + '`';
}),
'',
'### 🦅 天眼',
'执行前已通过天眼全局扫描。',
'',
'---',
'> 铸渊远程执行引擎 · ZY-GHAPP-BRIDGE'
].join('\n'),
branchName
);
} catch (e) {
console.error('⚠️ 创建 PR 失败:', e.message);
pr = { number: '(失败)' };
}
// ====== 8. 生成执行报告 ======
const report = [
'## ✅ 铸渊执行完成',
'',
`**指令编号**${parsed.instructionId}`,
`**目标仓库**${ORG}/${repo}`,
'',
'### 🦅 天眼',
'✅ 执行前已通过天眼全局扫描',
'',
'### 执行结果',
`- 创建分支:\`${branchName}\``,
`- 变更文件:${plan.actions.length} 个(成功 ${successCount},失败 ${failCount}`,
`- PR#${pr.number || '(未创建)'}`,
'',
'### 变更摘要',
plan.summary || '(无摘要)',
'',
'### 下一步',
`${parsed.devId} 检查 PR${pr.number ? ' #' + pr.number : ''},确认无误后合并。`,
'',
'---',
'> 铸渊远程执行引擎 · ZY-GHAPP-BRIDGE'
].join('\n');
fs.writeFileSync('/tmp/exec-report.md', report);
console.log('✅ 执行完成!');
}
main().catch(function(err) {
console.error('❌ 执行失败:', err);
const report = '## ❌ 执行失败\n\n```\n' + (err.message || String(err)) + '\n```\n\n---\n> 铸渊远程执行引擎 · ZY-GHAPP-BRIDGE';
fs.writeFileSync('/tmp/exec-report.md', report);
process.exit(1);
});