skill-task-manager.js
4.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/**
* SkillTaskManager — Skill 创建/迭代流水线的检查点管理器
*
* 每个 Skill 任务在 skill 目录下写入 .innovator_task.json,
* 记录流水线步骤的完成状态。
* 支持两种 pipeline:
* - create: Hunt → Digest → SkillCreate (3 步)
* - feedback: ReHunt → ReDigest → Evolve → Publish (4 步)
*/
const fs = require('fs');
const path = require('path');
const TASK_FILE = '.innovator_task.json';
const PIPELINE_STEPS = ['hunt', 'digest', 'skill_create'];
const FEEDBACK_PIPELINE_STEPS = ['re_hunt', 're_digest', 'evolve', 'publish'];
const EVOLUTION_PIPELINE_STEPS = ['perceive', 'plan', 'hunt', 'digest', 'evolve', 'evaluate', 'reflect', 'publish'];
/**
* 初始化任务文件
* @param {string} skillDir - Skill 目录绝对路径
* @param {object} meta - { skillName, domain, scope, description, keywords, source, jobId, feedback, pipelineType }
*/
function initTask(skillDir, meta = {}) {
fs.mkdirSync(skillDir, { recursive: true });
const pipelineType = meta.pipelineType || 'create';
const steps = pipelineType === 'evolution'
? EVOLUTION_PIPELINE_STEPS
: pipelineType === 'feedback'
? FEEDBACK_PIPELINE_STEPS
: PIPELINE_STEPS;
const task = {
skillName: meta.skillName || path.basename(skillDir),
domain: meta.domain || '',
scope: meta.scope || '',
description: meta.description || '',
keywords: meta.keywords || [],
source: meta.source || 'unknown',
jobId: meta.jobId || null,
pipelineType,
feedback: meta.feedback || null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
pipeline: steps.map(step => ({ step, status: 'pending', completedAt: null })),
};
_write(skillDir, task);
return task;
}
/**
* 更新步骤状态
* @param {string} skillDir
* @param {string} step - 'hunt' | 'digest' | 'skill_create'
* @param {string} status - 'running' | 'done' | 'failed'
* @param {string} [error] - 失败原因
*/
function updateStep(skillDir, step, status, error) {
const task = getTask(skillDir);
if (!task) return null;
const entry = task.pipeline.find(p => p.step === step);
if (!entry) return null;
entry.status = status;
if (status === 'done') entry.completedAt = new Date().toISOString();
if (status === 'failed') entry.error = error || 'unknown';
task.updatedAt = new Date().toISOString();
_write(skillDir, task);
return task;
}
/**
* 读取任务状态
* @param {string} skillDir
* @returns {object|null}
*/
function getTask(skillDir) {
const filePath = path.join(skillDir, TASK_FILE);
if (!fs.existsSync(filePath)) return null;
try {
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
} catch {
return null;
}
}
/**
* 获取第一个未完成的步骤
* @param {string} skillDir
* @returns {string|null} step name or null if all done
*/
function getNextPendingStep(skillDir) {
const task = getTask(skillDir);
if (!task) return null;
const pending = task.pipeline.find(p => p.status === 'pending' || p.status === 'running');
return pending ? pending.step : null;
}
/**
* 扫描所有 Skill 目录,返回有未完成任务的列表
* @param {string} skillsDir - skills/ 根目录
* @returns {Array<{skillDir, skillName, nextStep, task}>}
*/
function findIncompleteTasks(skillsDir) {
const results = [];
if (!fs.existsSync(skillsDir)) return results;
const dirs = fs.readdirSync(skillsDir, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => d.name);
for (const dirName of dirs) {
const skillDir = path.join(skillsDir, dirName);
const task = getTask(skillDir);
if (!task) continue;
const nextStep = getNextPendingStep(skillDir);
if (nextStep) {
results.push({
skillDir,
skillName: task.skillName || dirName,
nextStep,
task,
});
}
}
return results;
}
/**
* 检查任务是否全部完成
* @param {string} skillDir
* @returns {boolean}
*/
function isCompleted(skillDir) {
return getNextPendingStep(skillDir) === null && getTask(skillDir) !== null;
}
/** 内部写入 */
function _write(skillDir, task) {
const filePath = path.join(skillDir, TASK_FILE);
fs.writeFileSync(filePath, JSON.stringify(task, null, 2) + '\n', 'utf-8');
}
module.exports = {
initTask,
updateStep,
getTask,
getNextPendingStep,
findIncompleteTasks,
isCompleted,
PIPELINE_STEPS,
FEEDBACK_PIPELINE_STEPS,
EVOLUTION_PIPELINE_STEPS,
TASK_FILE,
};