flowmind 1.0.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README_CN.md +248 -0
- package/bin/flowmind.js +638 -2
- package/config/ai-config.example.json +64 -0
- package/config/claude-mcp-config.example.json +12 -0
- package/core/ai/base-model.js +70 -0
- package/core/ai/index.js +29 -0
- package/core/ai/model-manager.js +320 -0
- package/core/ai/prompts/extraction.js +38 -0
- package/core/ai/prompts/index.js +11 -0
- package/core/ai/prompts/intent.js +43 -0
- package/core/ai/prompts/learning.js +46 -0
- package/core/ai/prompts/selection.js +38 -0
- package/core/ai/prompts/summary.js +35 -0
- package/core/ai/providers/anthropic.js +93 -0
- package/core/ai/providers/deepseek.js +80 -0
- package/core/ai/providers/ernie.js +111 -0
- package/core/ai/providers/glm.js +80 -0
- package/core/ai/providers/mimo.js +80 -0
- package/core/ai/providers/ollama.js +147 -0
- package/core/ai/providers/openai.js +82 -0
- package/core/ai/providers/qwen.js +80 -0
- package/core/event-bus.js +17 -0
- package/core/honor-engine.js +255 -0
- package/core/index.js +115 -13
- package/core/learning-engine.js +29 -1
- package/core/skill-loader.js +31 -9
- package/dashboard/app.jsx +29 -0
- package/dashboard/components/ActivityFeed.jsx +86 -0
- package/dashboard/components/DragonPanel.jsx +67 -0
- package/dashboard/components/McpStatusBar.jsx +43 -0
- package/dashboard/components/StatsRow.jsx +65 -0
- package/mcp/server.js +328 -0
- package/package.json +19 -7
- package/tui/app.jsx +69 -0
- package/tui/components/ChatPanel.jsx +72 -0
- package/tui/components/DragonTotem.jsx +108 -0
- package/tui/components/ResultPanel.jsx +33 -0
- package/tui/components/Sidebar.jsx +70 -0
- package/tui/components/StatusBar.jsx +49 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qwen Provider - 阿里云通义千问模型接入
|
|
3
|
+
* 支持 Qwen-Turbo、Qwen-Plus、Qwen-Max 等模型
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const BaseModel = require('../base-model');
|
|
7
|
+
|
|
8
|
+
class QwenProvider extends BaseModel {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
super('qwen', config);
|
|
11
|
+
this.apiKey = config.apiKey || process.env.DASHSCOPE_API_KEY;
|
|
12
|
+
this.model = config.model || 'qwen-turbo';
|
|
13
|
+
this.baseUrl = config.baseUrl || 'https://dashscope.aliyuncs.com/compatible-mode/v1';
|
|
14
|
+
this.temperature = config.temperature ?? 0.3;
|
|
15
|
+
this.maxTokens = config.maxTokens ?? 2000;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async init() {
|
|
19
|
+
if (!this.apiKey) {
|
|
20
|
+
throw new Error('DashScope API key is required. Set DASHSCOPE_API_KEY environment variable or provide in config.');
|
|
21
|
+
}
|
|
22
|
+
this.initialized = true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
validateConfig() {
|
|
26
|
+
return !!this.apiKey;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async chat(messages, options = {}) {
|
|
30
|
+
if (!this.initialized) {
|
|
31
|
+
await this.init();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
model: options.model || this.model,
|
|
42
|
+
messages: messages,
|
|
43
|
+
temperature: options.temperature ?? this.temperature,
|
|
44
|
+
max_tokens: options.maxTokens ?? this.maxTokens
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const error = await response.text();
|
|
50
|
+
throw new Error(`Qwen API error: ${response.status} - ${error}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
return data.choices[0].message.content;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async complete(prompt, options = {}) {
|
|
58
|
+
return this.chat([{ role: 'user', content: prompt }], options);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async isAvailable() {
|
|
62
|
+
try {
|
|
63
|
+
if (!this.apiKey) return false;
|
|
64
|
+
return true;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getInfo() {
|
|
71
|
+
return {
|
|
72
|
+
...super.getInfo(),
|
|
73
|
+
model: this.model,
|
|
74
|
+
baseUrl: this.baseUrl,
|
|
75
|
+
provider: 'Alibaba Cloud'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = QwenProvider;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlowMind Event Bus
|
|
3
|
+
* Shared singleton EventEmitter for cross-module event communication.
|
|
4
|
+
* Enables real-time monitoring (dashboard) and event-driven TUI updates.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const EventEmitter = require('events');
|
|
8
|
+
|
|
9
|
+
class FlowMindEventBus extends EventEmitter {
|
|
10
|
+
constructor() {
|
|
11
|
+
super();
|
|
12
|
+
this.setMaxListeners(50);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Singleton — shared across all modules
|
|
17
|
+
module.exports = new FlowMindEventBus();
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlowMind Honor Engine
|
|
3
|
+
* Tracks honor points earned through usage and manages dragon totem levels
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs-extra');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const eventBus = require('./event-bus');
|
|
9
|
+
|
|
10
|
+
const HONOR_POINTS = {
|
|
11
|
+
init: 1,
|
|
12
|
+
skill_use: 1,
|
|
13
|
+
new_skill: 2,
|
|
14
|
+
learning: 3
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const DRAGON_LEVELS = [
|
|
18
|
+
{ level: 0, minPoints: 0, name: 'Egg', state: 'dormant' },
|
|
19
|
+
{ level: 1, minPoints: 1, name: 'Hatchling', state: 'awakening' },
|
|
20
|
+
{ level: 2, minPoints: 10, name: 'Juvenile', state: 'growing' },
|
|
21
|
+
{ level: 3, minPoints: 30, name: 'Adult', state: 'soaring' },
|
|
22
|
+
{ level: 4, minPoints: 60, name: 'Elder', state: 'wise' },
|
|
23
|
+
{ level: 5, minPoints: 100, name: 'Ascended', state: 'transcendent' }
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
class HonorEngine {
|
|
27
|
+
constructor(config) {
|
|
28
|
+
this.config = config;
|
|
29
|
+
this.honorPath = path.join(
|
|
30
|
+
config.get('storagePath') || path.join(process.env.HOME || process.env.USERPROFILE, '.flowmind'),
|
|
31
|
+
'honor.json'
|
|
32
|
+
);
|
|
33
|
+
this.skillsPath = config.get('skills.path', path.join(__dirname, '..', 'skills'));
|
|
34
|
+
this.data = null;
|
|
35
|
+
this.initialized = false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Initialize honor engine
|
|
40
|
+
*/
|
|
41
|
+
async init() {
|
|
42
|
+
try {
|
|
43
|
+
if (await fs.pathExists(this.honorPath)) {
|
|
44
|
+
this.data = await fs.readJson(this.honorPath);
|
|
45
|
+
} else {
|
|
46
|
+
this.data = this.createDefaultData();
|
|
47
|
+
// Seed knownSkills by scanning skills/ directory
|
|
48
|
+
await this.seedKnownSkills();
|
|
49
|
+
await this.save();
|
|
50
|
+
}
|
|
51
|
+
this.initialized = true;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
// Non-blocking: create default data in memory
|
|
54
|
+
this.data = this.createDefaultData();
|
|
55
|
+
this.initialized = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create default honor data structure
|
|
61
|
+
*/
|
|
62
|
+
createDefaultData() {
|
|
63
|
+
return {
|
|
64
|
+
version: '1.0',
|
|
65
|
+
points: 0,
|
|
66
|
+
level: 0,
|
|
67
|
+
history: [],
|
|
68
|
+
knownSkills: [],
|
|
69
|
+
stats: {
|
|
70
|
+
initCount: 0,
|
|
71
|
+
skillUseCount: 0,
|
|
72
|
+
newSkillCount: 0,
|
|
73
|
+
learningCount: 0
|
|
74
|
+
},
|
|
75
|
+
lastUpdated: new Date().toISOString()
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Seed knownSkills by scanning skills/ directory
|
|
81
|
+
*/
|
|
82
|
+
async seedKnownSkills() {
|
|
83
|
+
try {
|
|
84
|
+
if (await fs.pathExists(this.skillsPath)) {
|
|
85
|
+
const entries = await fs.readdir(this.skillsPath);
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
const fullPath = path.join(this.skillsPath, entry);
|
|
88
|
+
const stat = await fs.stat(fullPath);
|
|
89
|
+
if (stat.isDirectory()) {
|
|
90
|
+
this.data.knownSkills.push(entry);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
// Non-blocking
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Award honor points for an action
|
|
101
|
+
*/
|
|
102
|
+
async award(action, description) {
|
|
103
|
+
if (!this.initialized) await this.init();
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const points = HONOR_POINTS[action] || 0;
|
|
107
|
+
if (points === 0) return;
|
|
108
|
+
|
|
109
|
+
// Check init action: only award once
|
|
110
|
+
if (action === 'init' && this.data.stats.initCount > 0) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Award points
|
|
115
|
+
this.data.points += points;
|
|
116
|
+
this.data.level = this.getLevel(this.data.points);
|
|
117
|
+
|
|
118
|
+
// Update stats
|
|
119
|
+
const statKey = `${action}Count`;
|
|
120
|
+
if (this.data.stats[statKey] !== undefined) {
|
|
121
|
+
this.data.stats[statKey]++;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Add to history
|
|
125
|
+
this.data.history.push({
|
|
126
|
+
action,
|
|
127
|
+
points,
|
|
128
|
+
description,
|
|
129
|
+
timestamp: new Date().toISOString()
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Keep history to last 100 entries
|
|
133
|
+
if (this.data.history.length > 100) {
|
|
134
|
+
this.data.history = this.data.history.slice(-100);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.data.lastUpdated = new Date().toISOString();
|
|
138
|
+
await this.save();
|
|
139
|
+
|
|
140
|
+
// Emit honor event for TUI/dashboard monitoring
|
|
141
|
+
eventBus.emit('honor:awarded', {
|
|
142
|
+
action,
|
|
143
|
+
points,
|
|
144
|
+
description,
|
|
145
|
+
level: this.data.level,
|
|
146
|
+
levelName: this.getLevelName(this.data.level),
|
|
147
|
+
total: this.data.points,
|
|
148
|
+
timestamp: this.data.lastUpdated
|
|
149
|
+
});
|
|
150
|
+
} catch (error) {
|
|
151
|
+
// Non-blocking
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check if a skill name is already known
|
|
157
|
+
*/
|
|
158
|
+
isKnownSkill(name) {
|
|
159
|
+
return this.data && this.data.knownSkills.includes(name);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Add a skill to the known list
|
|
164
|
+
*/
|
|
165
|
+
async addKnownSkill(name) {
|
|
166
|
+
if (!this.initialized) await this.init();
|
|
167
|
+
try {
|
|
168
|
+
if (!this.data.knownSkills.includes(name)) {
|
|
169
|
+
this.data.knownSkills.push(name);
|
|
170
|
+
await this.save();
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
// Non-blocking
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get the honor data
|
|
179
|
+
*/
|
|
180
|
+
getData() {
|
|
181
|
+
return this.data || this.createDefaultData();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get level for a given point total
|
|
186
|
+
*/
|
|
187
|
+
getLevel(points) {
|
|
188
|
+
let level = 0;
|
|
189
|
+
for (const tier of DRAGON_LEVELS) {
|
|
190
|
+
if (points >= tier.minPoints) {
|
|
191
|
+
level = tier.level;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return level;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get level name for a given level number
|
|
199
|
+
*/
|
|
200
|
+
getLevelName(level) {
|
|
201
|
+
const tier = DRAGON_LEVELS.find(t => t.level === level);
|
|
202
|
+
return tier ? tier.name : 'Unknown';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get level info including next level hint
|
|
207
|
+
*/
|
|
208
|
+
getLevelInfo(points) {
|
|
209
|
+
const level = this.getLevel(points);
|
|
210
|
+
const currentTier = DRAGON_LEVELS.find(t => t.level === level);
|
|
211
|
+
const nextTier = DRAGON_LEVELS.find(t => t.level === level + 1);
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
level,
|
|
215
|
+
name: currentTier.name,
|
|
216
|
+
state: currentTier.state,
|
|
217
|
+
points,
|
|
218
|
+
nextLevel: nextTier ? nextTier.level : null,
|
|
219
|
+
nextLevelName: nextTier ? nextTier.name : null,
|
|
220
|
+
pointsToNext: nextTier ? nextTier.minPoints - points : 0
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Export honor data for publishing
|
|
226
|
+
*/
|
|
227
|
+
exportForPublish() {
|
|
228
|
+
const info = this.getLevelInfo(this.data.points);
|
|
229
|
+
return {
|
|
230
|
+
version: this.data.version,
|
|
231
|
+
points: this.data.points,
|
|
232
|
+
level: info.level,
|
|
233
|
+
levelName: info.name,
|
|
234
|
+
state: info.state,
|
|
235
|
+
stats: this.data.stats,
|
|
236
|
+
knownSkillsCount: this.data.knownSkills.length,
|
|
237
|
+
recentHistory: this.data.history.slice(-20),
|
|
238
|
+
exportedAt: new Date().toISOString()
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Save honor data to disk
|
|
244
|
+
*/
|
|
245
|
+
async save() {
|
|
246
|
+
try {
|
|
247
|
+
await fs.ensureDir(path.dirname(this.honorPath));
|
|
248
|
+
await fs.writeJson(this.honorPath, this.data, { spaces: 2 });
|
|
249
|
+
} catch (error) {
|
|
250
|
+
// Non-blocking
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
module.exports = HonorEngine;
|
package/core/index.js
CHANGED
|
@@ -5,17 +5,22 @@
|
|
|
5
5
|
|
|
6
6
|
const SkillLoader = require('./skill-loader');
|
|
7
7
|
const LearningEngine = require('./learning-engine');
|
|
8
|
+
const HonorEngine = require('./honor-engine');
|
|
8
9
|
const SceneMatcher = require('./scene-matcher');
|
|
9
10
|
const ConfigManager = require('./config-manager');
|
|
10
11
|
const ComponentRegistry = require('./component-registry');
|
|
12
|
+
const ModelManager = require('./ai/model-manager');
|
|
13
|
+
const eventBus = require('./event-bus');
|
|
11
14
|
|
|
12
15
|
class FlowMind {
|
|
13
16
|
constructor(options = {}) {
|
|
14
17
|
this.config = new ConfigManager(options.configPath);
|
|
15
|
-
this.
|
|
18
|
+
this.honor = new HonorEngine(this.config);
|
|
19
|
+
this.learning = new LearningEngine(this.config, this.honor);
|
|
16
20
|
this.matcher = new SceneMatcher(this.config, this.learning);
|
|
17
21
|
this.components = new ComponentRegistry(this.config);
|
|
18
|
-
this.skills = new SkillLoader(this.config, this.learning, this.components);
|
|
22
|
+
this.skills = new SkillLoader(this.config, this.learning, this.components, this.honor);
|
|
23
|
+
this.ai = new ModelManager(options.ai || {});
|
|
19
24
|
this.initialized = false;
|
|
20
25
|
}
|
|
21
26
|
|
|
@@ -28,10 +33,18 @@ class FlowMind {
|
|
|
28
33
|
await this.config.load();
|
|
29
34
|
await this.components.init();
|
|
30
35
|
await this.components.initAll();
|
|
36
|
+
await this.honor.init();
|
|
31
37
|
await this.learning.init();
|
|
32
38
|
await this.skills.loadAll();
|
|
33
39
|
await this.matcher.loadScenes();
|
|
34
40
|
|
|
41
|
+
// Initialize AI model manager
|
|
42
|
+
try {
|
|
43
|
+
await this.ai.init();
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.warn(`AI model initialization failed: ${error.message}. Falling back to rule-based engine.`);
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
this.initialized = true;
|
|
36
49
|
return this;
|
|
37
50
|
}
|
|
@@ -46,36 +59,87 @@ class FlowMind {
|
|
|
46
59
|
|
|
47
60
|
const startTime = Date.now();
|
|
48
61
|
|
|
62
|
+
eventBus.emit('process:start', { input, timestamp: new Date().toISOString() });
|
|
63
|
+
|
|
49
64
|
try {
|
|
50
|
-
// 1.
|
|
51
|
-
const
|
|
65
|
+
// 1. AI Intent Understanding (if available)
|
|
66
|
+
const intent = await this.ai.understandIntent(input, context);
|
|
67
|
+
|
|
68
|
+
// 2. Check for learning patterns (corrections, feedback)
|
|
69
|
+
// Use AI to analyze learning feedback if available
|
|
70
|
+
const aiLearningResult = await this.ai.analyzeLearningFeedback(input, context);
|
|
71
|
+
const learningResult = aiLearningResult?.isLearning
|
|
72
|
+
? aiLearningResult
|
|
73
|
+
: await this.learning.detectLearning(input, context);
|
|
52
74
|
if (learningResult) {
|
|
53
75
|
return this.formatLearningResponse(learningResult);
|
|
54
76
|
}
|
|
55
77
|
|
|
56
|
-
//
|
|
57
|
-
const sceneMatch = await this.matcher.match(input);
|
|
78
|
+
// 3. Check scene mappings (with AI intent if available)
|
|
79
|
+
const sceneMatch = await this.matcher.match(input, intent);
|
|
58
80
|
if (sceneMatch && sceneMatch.confidence >= 0.7) {
|
|
59
81
|
return this.executeSceneWorkflow(sceneMatch, input, context);
|
|
60
82
|
}
|
|
61
83
|
|
|
62
|
-
//
|
|
63
|
-
|
|
84
|
+
// 4. Select skill (AI-assisted if available)
|
|
85
|
+
let skill = null;
|
|
86
|
+
const candidates = await this.skills.getCandidates(input, context);
|
|
87
|
+
|
|
88
|
+
if (candidates.length > 0) {
|
|
89
|
+
// Use AI to select skill if available
|
|
90
|
+
const aiSelection = await this.ai.selectSkill(input, candidates);
|
|
91
|
+
if (aiSelection && aiSelection.selectedSkill) {
|
|
92
|
+
skill = this.skills.get(aiSelection.selectedSkill);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Fallback to rule-based selection
|
|
97
|
+
if (!skill) {
|
|
98
|
+
skill = await this.skills.select(input, context);
|
|
99
|
+
}
|
|
100
|
+
|
|
64
101
|
if (!skill) {
|
|
65
102
|
return this.formatError('No matching skill found', input);
|
|
66
103
|
}
|
|
67
104
|
|
|
68
|
-
//
|
|
69
|
-
const
|
|
105
|
+
// 5. Extract parameters using AI (if available)
|
|
106
|
+
const extractedParams = await this.ai.extractParameters(input, skill.name);
|
|
107
|
+
|
|
108
|
+
// 6. Execute with learning applied
|
|
109
|
+
const enhancedContext = {
|
|
110
|
+
...context,
|
|
111
|
+
...extractedParams,
|
|
112
|
+
intent: intent
|
|
113
|
+
};
|
|
114
|
+
const result = await this.executeWithLearning(skill, input, enhancedContext);
|
|
115
|
+
|
|
116
|
+
// 7. Generate AI summary (if available)
|
|
117
|
+
const summary = await this.ai.summarizeResult(result, {
|
|
118
|
+
skill: skill.name,
|
|
119
|
+
intent: intent
|
|
120
|
+
});
|
|
70
121
|
|
|
71
|
-
//
|
|
72
|
-
|
|
122
|
+
// 8. Format and return
|
|
123
|
+
const formatted = this.formatResult(summary || result, {
|
|
73
124
|
skill: skill.name,
|
|
74
125
|
duration: Date.now() - startTime,
|
|
75
|
-
sceneMatch: sceneMatch
|
|
126
|
+
sceneMatch: sceneMatch,
|
|
127
|
+
intent: intent,
|
|
128
|
+
aiEnhanced: !!summary
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
eventBus.emit('process:complete', {
|
|
132
|
+
input,
|
|
133
|
+
skill: skill.name,
|
|
134
|
+
duration: formatted.metadata.duration,
|
|
135
|
+
success: true,
|
|
136
|
+
timestamp: formatted.timestamp
|
|
76
137
|
});
|
|
77
138
|
|
|
139
|
+
return formatted;
|
|
140
|
+
|
|
78
141
|
} catch (error) {
|
|
142
|
+
eventBus.emit('process:error', { input, error: error.message, timestamp: new Date().toISOString() });
|
|
79
143
|
return this.formatError(error.message, input);
|
|
80
144
|
}
|
|
81
145
|
}
|
|
@@ -84,6 +148,8 @@ class FlowMind {
|
|
|
84
148
|
* Execute skill with learning applied
|
|
85
149
|
*/
|
|
86
150
|
async executeWithLearning(skill, input, context) {
|
|
151
|
+
const skillStartTime = Date.now();
|
|
152
|
+
|
|
87
153
|
// Get learning rules for this skill
|
|
88
154
|
const learnings = await this.learning.getSkillLearnings(skill.name);
|
|
89
155
|
|
|
@@ -97,6 +163,19 @@ class FlowMind {
|
|
|
97
163
|
// Execute skill
|
|
98
164
|
const result = await skill.execute(input, enhancedContext);
|
|
99
165
|
|
|
166
|
+
const duration = Date.now() - skillStartTime;
|
|
167
|
+
|
|
168
|
+
// Emit skill execution event
|
|
169
|
+
eventBus.emit('skill:executed', {
|
|
170
|
+
name: skill.name,
|
|
171
|
+
duration,
|
|
172
|
+
success: true,
|
|
173
|
+
timestamp: new Date().toISOString()
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Award honor point for skill use
|
|
177
|
+
await this.honor.award('skill_use', `Used skill: ${skill.name}`);
|
|
178
|
+
|
|
100
179
|
return result;
|
|
101
180
|
}
|
|
102
181
|
|
|
@@ -180,6 +259,13 @@ class FlowMind {
|
|
|
180
259
|
return this.learning.getStats();
|
|
181
260
|
}
|
|
182
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Get honor data
|
|
264
|
+
*/
|
|
265
|
+
getHonorData() {
|
|
266
|
+
return this.honor.getData();
|
|
267
|
+
}
|
|
268
|
+
|
|
183
269
|
/**
|
|
184
270
|
* Get the component registry
|
|
185
271
|
* @returns {ComponentRegistry}
|
|
@@ -205,6 +291,22 @@ class FlowMind {
|
|
|
205
291
|
return this.components.getStatus();
|
|
206
292
|
}
|
|
207
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Get AI model status
|
|
296
|
+
* @returns {object}
|
|
297
|
+
*/
|
|
298
|
+
getAIStatus() {
|
|
299
|
+
return this.ai.getStatus();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Check if AI is available
|
|
304
|
+
* @returns {boolean}
|
|
305
|
+
*/
|
|
306
|
+
hasAI() {
|
|
307
|
+
return this.ai.hasAvailableProvider();
|
|
308
|
+
}
|
|
309
|
+
|
|
208
310
|
/**
|
|
209
311
|
* Export learnings
|
|
210
312
|
*/
|
package/core/learning-engine.js
CHANGED
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
const fs = require('fs-extra');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const { v4: uuidv4 } = require('uuid');
|
|
9
|
+
const eventBus = require('./event-bus');
|
|
9
10
|
|
|
10
11
|
class LearningEngine {
|
|
11
|
-
constructor(config) {
|
|
12
|
+
constructor(config, honorEngine = null) {
|
|
12
13
|
this.config = config;
|
|
14
|
+
this.honorEngine = honorEngine;
|
|
13
15
|
this.learningPath = config.get('learning.storagePath', '~/.flowmind/learning');
|
|
14
16
|
this.records = {};
|
|
15
17
|
this.skillBindings = {};
|
|
@@ -170,6 +172,13 @@ class LearningEngine {
|
|
|
170
172
|
// Update stats
|
|
171
173
|
await this.updateStats('correction', record.skill);
|
|
172
174
|
|
|
175
|
+
eventBus.emit('learning:recorded', {
|
|
176
|
+
type: 'correction',
|
|
177
|
+
skill: record.skill,
|
|
178
|
+
recordId: record.id,
|
|
179
|
+
severity: record.severity
|
|
180
|
+
});
|
|
181
|
+
|
|
173
182
|
return {
|
|
174
183
|
type: 'correction',
|
|
175
184
|
record: record,
|
|
@@ -204,6 +213,13 @@ class LearningEngine {
|
|
|
204
213
|
// Update stats
|
|
205
214
|
await this.updateStats('scene_mapping', 'global');
|
|
206
215
|
|
|
216
|
+
eventBus.emit('learning:recorded', {
|
|
217
|
+
type: 'scene_mapping',
|
|
218
|
+
skill: 'global',
|
|
219
|
+
recordId: record.id,
|
|
220
|
+
keywords: record.keywords
|
|
221
|
+
});
|
|
222
|
+
|
|
207
223
|
return {
|
|
208
224
|
type: 'scene_mapping',
|
|
209
225
|
record: record,
|
|
@@ -232,6 +248,13 @@ class LearningEngine {
|
|
|
232
248
|
// Update stats
|
|
233
249
|
await this.updateStats('preference', record.skill);
|
|
234
250
|
|
|
251
|
+
eventBus.emit('learning:recorded', {
|
|
252
|
+
type: 'preference',
|
|
253
|
+
skill: record.skill,
|
|
254
|
+
recordId: record.id,
|
|
255
|
+
preferenceType: record.preferenceType
|
|
256
|
+
});
|
|
257
|
+
|
|
235
258
|
return {
|
|
236
259
|
type: 'preference',
|
|
237
260
|
record: record,
|
|
@@ -398,6 +421,11 @@ class LearningEngine {
|
|
|
398
421
|
|
|
399
422
|
const statsPath = path.join(this.expandPath(this.learningPath), 'stats.json');
|
|
400
423
|
await fs.writeJson(statsPath, this.stats, { spaces: 2 });
|
|
424
|
+
|
|
425
|
+
// Award honor points for learning
|
|
426
|
+
if (this.honorEngine) {
|
|
427
|
+
await this.honorEngine.award('learning', `Learned: ${type} for ${skill}`);
|
|
428
|
+
}
|
|
401
429
|
}
|
|
402
430
|
|
|
403
431
|
/**
|
package/core/skill-loader.js
CHANGED
|
@@ -7,10 +7,11 @@ const fs = require('fs-extra');
|
|
|
7
7
|
const path = require('path');
|
|
8
8
|
|
|
9
9
|
class SkillLoader {
|
|
10
|
-
constructor(config, learning, componentRegistry = null) {
|
|
10
|
+
constructor(config, learning, componentRegistry = null, honorEngine = null) {
|
|
11
11
|
this.config = config;
|
|
12
12
|
this.learning = learning;
|
|
13
13
|
this.componentRegistry = componentRegistry;
|
|
14
|
+
this.honorEngine = honorEngine;
|
|
14
15
|
this.skills = new Map();
|
|
15
16
|
this.skillPath = config.get('skills.path', path.join(__dirname, '..', 'skills'));
|
|
16
17
|
}
|
|
@@ -68,6 +69,13 @@ class SkillLoader {
|
|
|
68
69
|
};
|
|
69
70
|
|
|
70
71
|
this.skills.set(name, skill);
|
|
72
|
+
|
|
73
|
+
// Award honor points for new (previously unknown) skills
|
|
74
|
+
if (this.honorEngine && !this.honorEngine.isKnownSkill(name)) {
|
|
75
|
+
await this.honorEngine.award('new_skill', `New skill loaded: ${name}`);
|
|
76
|
+
await this.honorEngine.addKnownSkill(name);
|
|
77
|
+
}
|
|
78
|
+
|
|
71
79
|
return skill;
|
|
72
80
|
} catch (error) {
|
|
73
81
|
console.error(`Failed to load skill ${name}:`, error.message);
|
|
@@ -225,6 +233,23 @@ class SkillLoader {
|
|
|
225
233
|
* Select best skill for input
|
|
226
234
|
*/
|
|
227
235
|
async select(input, context = {}) {
|
|
236
|
+
const candidates = await this.getCandidates(input, context);
|
|
237
|
+
|
|
238
|
+
if (candidates.length === 0) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Sort by score (highest first)
|
|
243
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
244
|
+
|
|
245
|
+
return candidates[0].skill;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get candidate skills for input
|
|
250
|
+
* Returns skills that can handle the input with their scores
|
|
251
|
+
*/
|
|
252
|
+
async getCandidates(input, context = {}) {
|
|
228
253
|
const candidates = [];
|
|
229
254
|
|
|
230
255
|
for (const [name, skill] of this.skills) {
|
|
@@ -232,7 +257,11 @@ class SkillLoader {
|
|
|
232
257
|
const canHandle = await skill.canHandle(input, context);
|
|
233
258
|
if (canHandle) {
|
|
234
259
|
candidates.push({
|
|
260
|
+
name: skill.name,
|
|
235
261
|
skill: skill,
|
|
262
|
+
description: skill.definition?.description || '',
|
|
263
|
+
triggers: skill.definition?.triggers || [],
|
|
264
|
+
category: skill.definition?.category || skill.definition?.metadata?.category || '',
|
|
236
265
|
score: this.calculateSkillScore(skill, input, context)
|
|
237
266
|
});
|
|
238
267
|
}
|
|
@@ -241,14 +270,7 @@ class SkillLoader {
|
|
|
241
270
|
}
|
|
242
271
|
}
|
|
243
272
|
|
|
244
|
-
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Sort by score (highest first)
|
|
249
|
-
candidates.sort((a, b) => b.score - a.score);
|
|
250
|
-
|
|
251
|
-
return candidates[0].skill;
|
|
273
|
+
return candidates;
|
|
252
274
|
}
|
|
253
275
|
|
|
254
276
|
/**
|