133 lines
5.9 KiB
JavaScript
133 lines
5.9 KiB
JavaScript
const { spawn } = require('child_process');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
// BackgroundReview — post-task self-improvement evaluation
|
|
// Forks a lightweight sub-agent to analyze completed work and
|
|
// decide if any memory or skill updates are warranted.
|
|
class BackgroundReview {
|
|
constructor(config) {
|
|
this.config = config || {};
|
|
this.reviewDir = config.reviewDir || path.join(__dirname, 'reviews');
|
|
this.enabled = config.enabled !== false;
|
|
this.minResultLength = config.minResultLength || 50;
|
|
}
|
|
|
|
async review(sessionId, task, result, memoryProvider) {
|
|
if (!this.enabled) return;
|
|
if (!result) return;
|
|
|
|
var output = '';
|
|
if (typeof result === 'string') output = result;
|
|
else if (result.output) output = typeof result.output === 'string' ? result.output : JSON.stringify(result.output);
|
|
else if (result.summary) output = result.summary;
|
|
else output = JSON.stringify(result);
|
|
|
|
if (!output || output.length < this.minResultLength) return;
|
|
|
|
// 1. Check for style/preference signals
|
|
var styleSignals = this._detectStyleSignals(task, output);
|
|
if (styleSignals.length && memoryProvider) {
|
|
for (var i = 0; i < styleSignals.length; i++) {
|
|
var sig = styleSignals[i];
|
|
memoryProvider.addEntry('preference', sig.title, sig.content, ['style', 'preference']);
|
|
}
|
|
}
|
|
|
|
// 2. Check for knowledge/skill signals
|
|
var skillSignals = this._detectSkillSignals(task, output);
|
|
if (skillSignals.length && memoryProvider) {
|
|
for (var i = 0; i < skillSignals.length; i++) {
|
|
var sig = skillSignals[i];
|
|
memoryProvider.addEntry('skill_signal', sig.title, sig.content, ['skill', sig.category || 'general']);
|
|
}
|
|
}
|
|
|
|
// 3. Log review report
|
|
this._logReview(sessionId, {
|
|
styleSignals: styleSignals.length,
|
|
skillSignals: skillSignals.length,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
return {
|
|
styleSignals: styleSignals.length,
|
|
skillSignals: skillSignals.length
|
|
};
|
|
}
|
|
|
|
// Detect user preference/style signals from task output
|
|
_detectStyleSignals(task, output) {
|
|
var signals = [];
|
|
var taskStr = typeof task === 'string' ? task : (task && task.task ? (typeof task.task === 'string' ? task.task : task.task.name || '') : '');
|
|
|
|
// Look for explicit style corrections in output
|
|
var stylePatterns = [
|
|
{ pattern: /别啰嗦|简洁|简短|直接点|说重点/i, title: '偏好简洁回复', content: '用户偏好简洁直接的回复风格,避免啰嗦和冗余解释', category: 'style' },
|
|
{ pattern: /不要问|别问|直接做|别废话/i, title: '偏好行动而非询问', content: '用户希望直接执行任务,避免多余的确认性提问', category: 'style' },
|
|
{ pattern: /用中文|说中文/i, title: '偏好中文回复', content: '用户偏好使用中文进行交流', category: 'language' },
|
|
{ pattern: /格式|排版|对齐|美观/i, title: '关注输出格式', content: '用户关注输出格式和排版美观度', category: 'style' },
|
|
{ pattern: /不要[格格格式]式|别用[格格格式]式|换个格式/i, title: '格式偏好调整', content: '用户对特定输出格式有偏好,需要调整回复格式', category: 'style' },
|
|
];
|
|
|
|
for (var i = 0; i < stylePatterns.length; i++) {
|
|
if (stylePatterns[i].pattern.test(taskStr) || stylePatterns[i].pattern.test(output)) {
|
|
signals.push(stylePatterns[i]);
|
|
}
|
|
}
|
|
|
|
return signals;
|
|
}
|
|
|
|
// Detect reusable knowledge/skill signals from task output
|
|
_detectSkillSignals(task, output) {
|
|
var signals = [];
|
|
var combined = (typeof task === 'string' ? task : JSON.stringify(task)) + ' ' + output;
|
|
|
|
// Look for knowledge that should be saved
|
|
var knowledgePatterns = [
|
|
{ pattern: /架构|设计模式|最佳实践|方案设计/i, title: '架构知识', content: '任务产生了架构设计或技术方案', category: 'architecture' },
|
|
{ pattern: /工作流|流程|步骤|指南/i, title: '工作流知识', content: '任务产生了可复用的工作流或操作步骤', category: 'workflow' },
|
|
{ pattern: /配置|部署|安装|docker/i, title: '部署知识', content: '任务涉及环境配置或部署流程', category: 'devops' },
|
|
{ pattern: /API|接口|路由|端点/i, title: 'API知识', content: '任务涉及API设计和接口规范', category: 'api' },
|
|
{ pattern: /代码|实现|函数|模块/i, title: '代码实现', content: '任务涉及具体代码实现', category: 'code' },
|
|
{ pattern: /测试|调试|修复|bug/i, title: '调试知识', content: '任务涉及问题排查或bug修复', category: 'debug' },
|
|
];
|
|
|
|
for (var i = 0; i < knowledgePatterns.length; i++) {
|
|
if (knowledgePatterns[i].pattern.test(combined)) {
|
|
signals.push(knowledgePatterns[i]);
|
|
}
|
|
}
|
|
|
|
return signals;
|
|
}
|
|
|
|
_logReview(sessionId, report) {
|
|
if (!fs.existsSync(this.reviewDir)) fs.mkdirSync(this.reviewDir, { recursive: true });
|
|
var logFile = path.join(this.reviewDir, 'reviews.jsonl');
|
|
var line = JSON.stringify({ sessionId: sessionId, report: report, ts: new Date().toISOString() }) + '\n';
|
|
try { fs.appendFileSync(logFile, line, 'utf8'); } catch (e) {}
|
|
}
|
|
|
|
getReviewStats() {
|
|
var logFile = path.join(this.reviewDir, 'reviews.jsonl');
|
|
if (!fs.existsSync(logFile)) return { total: 0, styleSignals: 0, skillSignals: 0 };
|
|
try {
|
|
var lines = fs.readFileSync(logFile, 'utf8').split('\n').filter(Boolean);
|
|
var stats = { total: lines.length, styleSignals: 0, skillSignals: 0 };
|
|
for (var i = 0; i < lines.length; i++) {
|
|
try {
|
|
var entry = JSON.parse(lines[i]);
|
|
if (entry.report) {
|
|
stats.styleSignals += entry.report.styleSignals || 0;
|
|
stats.skillSignals += entry.report.skillSignals || 0;
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
return stats;
|
|
} catch (e) { return { total: 0, styleSignals: 0, skillSignals: 0 }; }
|
|
}
|
|
}
|
|
|
|
module.exports = BackgroundReview; |