flowmind 1.0.0 → 1.1.0

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.
@@ -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;
package/core/index.js CHANGED
@@ -8,6 +8,7 @@ const LearningEngine = require('./learning-engine');
8
8
  const SceneMatcher = require('./scene-matcher');
9
9
  const ConfigManager = require('./config-manager');
10
10
  const ComponentRegistry = require('./component-registry');
11
+ const ModelManager = require('./ai/model-manager');
11
12
 
12
13
  class FlowMind {
13
14
  constructor(options = {}) {
@@ -16,6 +17,7 @@ class FlowMind {
16
17
  this.matcher = new SceneMatcher(this.config, this.learning);
17
18
  this.components = new ComponentRegistry(this.config);
18
19
  this.skills = new SkillLoader(this.config, this.learning, this.components);
20
+ this.ai = new ModelManager(options.ai || {});
19
21
  this.initialized = false;
20
22
  }
21
23
 
@@ -32,6 +34,13 @@ class FlowMind {
32
34
  await this.skills.loadAll();
33
35
  await this.matcher.loadScenes();
34
36
 
37
+ // Initialize AI model manager
38
+ try {
39
+ await this.ai.init();
40
+ } catch (error) {
41
+ console.warn(`AI model initialization failed: ${error.message}. Falling back to rule-based engine.`);
42
+ }
43
+
35
44
  this.initialized = true;
36
45
  return this;
37
46
  }
@@ -47,32 +56,70 @@ class FlowMind {
47
56
  const startTime = Date.now();
48
57
 
49
58
  try {
50
- // 1. Check for learning patterns (corrections, feedback)
51
- const learningResult = await this.learning.detectLearning(input, context);
59
+ // 1. AI Intent Understanding (if available)
60
+ const intent = await this.ai.understandIntent(input, context);
61
+
62
+ // 2. Check for learning patterns (corrections, feedback)
63
+ // Use AI to analyze learning feedback if available
64
+ const aiLearningResult = await this.ai.analyzeLearningFeedback(input, context);
65
+ const learningResult = aiLearningResult?.isLearning
66
+ ? aiLearningResult
67
+ : await this.learning.detectLearning(input, context);
52
68
  if (learningResult) {
53
69
  return this.formatLearningResponse(learningResult);
54
70
  }
55
71
 
56
- // 2. Check scene mappings
57
- const sceneMatch = await this.matcher.match(input);
72
+ // 3. Check scene mappings (with AI intent if available)
73
+ const sceneMatch = await this.matcher.match(input, intent);
58
74
  if (sceneMatch && sceneMatch.confidence >= 0.7) {
59
75
  return this.executeSceneWorkflow(sceneMatch, input, context);
60
76
  }
61
77
 
62
- // 3. Select and execute skill
63
- const skill = await this.skills.select(input, context);
78
+ // 4. Select skill (AI-assisted if available)
79
+ let skill = null;
80
+ const candidates = await this.skills.getCandidates(input, context);
81
+
82
+ if (candidates.length > 0) {
83
+ // Use AI to select skill if available
84
+ const aiSelection = await this.ai.selectSkill(input, candidates);
85
+ if (aiSelection && aiSelection.selectedSkill) {
86
+ skill = this.skills.get(aiSelection.selectedSkill);
87
+ }
88
+ }
89
+
90
+ // Fallback to rule-based selection
91
+ if (!skill) {
92
+ skill = await this.skills.select(input, context);
93
+ }
94
+
64
95
  if (!skill) {
65
96
  return this.formatError('No matching skill found', input);
66
97
  }
67
98
 
68
- // 4. Execute with learning applied
69
- const result = await this.executeWithLearning(skill, input, context);
99
+ // 5. Extract parameters using AI (if available)
100
+ const extractedParams = await this.ai.extractParameters(input, skill.name);
101
+
102
+ // 6. Execute with learning applied
103
+ const enhancedContext = {
104
+ ...context,
105
+ ...extractedParams,
106
+ intent: intent
107
+ };
108
+ const result = await this.executeWithLearning(skill, input, enhancedContext);
70
109
 
71
- // 5. Format and return
72
- return this.formatResult(result, {
110
+ // 7. Generate AI summary (if available)
111
+ const summary = await this.ai.summarizeResult(result, {
112
+ skill: skill.name,
113
+ intent: intent
114
+ });
115
+
116
+ // 8. Format and return
117
+ return this.formatResult(summary || result, {
73
118
  skill: skill.name,
74
119
  duration: Date.now() - startTime,
75
- sceneMatch: sceneMatch
120
+ sceneMatch: sceneMatch,
121
+ intent: intent,
122
+ aiEnhanced: !!summary
76
123
  });
77
124
 
78
125
  } catch (error) {
@@ -205,6 +252,22 @@ class FlowMind {
205
252
  return this.components.getStatus();
206
253
  }
207
254
 
255
+ /**
256
+ * Get AI model status
257
+ * @returns {object}
258
+ */
259
+ getAIStatus() {
260
+ return this.ai.getStatus();
261
+ }
262
+
263
+ /**
264
+ * Check if AI is available
265
+ * @returns {boolean}
266
+ */
267
+ hasAI() {
268
+ return this.ai.hasAvailableProvider();
269
+ }
270
+
208
271
  /**
209
272
  * Export learnings
210
273
  */
@@ -41,14 +41,14 @@ const McpServerMapping = Object.freeze({
41
41
  },
42
42
 
43
43
  // API Documentation
44
- 'aomi-yapi-mcp': {
44
+ 'yapi-mcp': {
45
45
  type: ComponentType.API_DOC,
46
46
  provider: 'yapi',
47
47
  description: 'YApi API documentation management'
48
48
  },
49
49
 
50
50
  // Knowledge Base
51
- 'aomi-yuque-mcp': {
51
+ 'yuque-mcp': {
52
52
  type: ComponentType.KNOWLEDGE_BASE,
53
53
  provider: 'yuque',
54
54
  description: 'Yuque knowledge base management'
@@ -77,8 +77,8 @@ const ProviderToMcp = Object.freeze({
77
77
  'databaseManager:aliyun-dms': 'aliyun-dms-mcp-server',
78
78
  'databaseQuery:aliyun-rds-query': 'friday-rds-redis-query',
79
79
  'redisMonitor:aliyun-redis': 'friday-aliyun-sz-rds-redis',
80
- 'apiDoc:yapi': 'aomi-yapi-mcp',
81
- 'knowledgeBase:yuque': 'aomi-yuque-mcp',
80
+ 'apiDoc:yapi': 'yapi-mcp',
81
+ 'knowledgeBase:yuque': 'yuque-mcp',
82
82
  'workflow:friday-flow': 'friday-auto-flow',
83
83
  'report:friday-report': 'friday-auto-report'
84
84
  });
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * YApi API Documentation Adapter
3
- * Wraps the aomi-yapi-mcp MCP server for YApi platform management
3
+ * Wraps the yapi-mcp MCP server for YApi platform management
4
4
  */
5
5
 
6
6
  const ApiDocAdapter = require('../../adapters/api-doc-adapter');
@@ -24,7 +24,7 @@ class YapiAdapter extends ApiDocAdapter {
24
24
  }
25
25
 
26
26
  get mcpServer() {
27
- return 'aomi-yapi-mcp';
27
+ return 'yapi-mcp';
28
28
  }
29
29
 
30
30
  async searchApis(params) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Yuque Knowledge Base Adapter
3
- * Wraps the aomi-yuque-mcp MCP server for Yuque platform management
3
+ * Wraps the yuque-mcp MCP server for Yuque platform management
4
4
  */
5
5
 
6
6
  const KnowledgeBaseAdapter = require('../../adapters/knowledge-base-adapter');
@@ -23,7 +23,7 @@ class YuqueAdapter extends KnowledgeBaseAdapter {
23
23
  }
24
24
 
25
25
  get mcpServer() {
26
- return 'aomi-yuque-mcp';
26
+ return 'yuque-mcp';
27
27
  }
28
28
 
29
29
  async getRepos(params) {
@@ -96,10 +96,35 @@ class SkillLoader {
96
96
 
97
97
  // Parse YAML-like frontmatter
98
98
  const lines = frontmatter.split('\n');
99
+ let currentKey = null;
100
+ let currentObj = null;
101
+
99
102
  for (const line of lines) {
103
+ // Top-level key: value
100
104
  const match = line.match(/^(\w+):\s*(.+)$/);
101
105
  if (match) {
102
- definition[match[1]] = match[2].replace(/^["']|["']$/g, '');
106
+ const key = match[1];
107
+ const value = match[2].replace(/^["']|["']$/g, '');
108
+ definition[key] = value;
109
+ currentKey = key;
110
+ currentObj = null;
111
+ continue;
112
+ }
113
+
114
+ // Nested object (e.g., metadata:)
115
+ const nestedMatch = line.match(/^(\w+):\s*$/);
116
+ if (nestedMatch) {
117
+ currentKey = nestedMatch[1];
118
+ definition[currentKey] = {};
119
+ currentObj = definition[currentKey];
120
+ continue;
121
+ }
122
+
123
+ // Nested key: value (indented)
124
+ const nestedValueMatch = line.match(/^\s+(\w+):\s*(.+)$/);
125
+ if (nestedValueMatch && currentObj) {
126
+ currentObj[nestedValueMatch[1]] = nestedValueMatch[2].replace(/^["']|["']$/g, '');
127
+ continue;
103
128
  }
104
129
  }
105
130
 
@@ -114,7 +139,7 @@ class SkillLoader {
114
139
  }
115
140
 
116
141
  // Extract trigger patterns
117
- const triggerMatch = content.match(/## Trigger Conditions\n([\s\S]*?)(?=\n##|$)/);
142
+ const triggerMatch = content.match(/## Trigger Patterns\n([\s\S]*?)(?=\n##|$)/);
118
143
  if (triggerMatch) {
119
144
  definition.triggers = this.extractTriggers(triggerMatch[1]);
120
145
  }
@@ -200,6 +225,23 @@ class SkillLoader {
200
225
  * Select best skill for input
201
226
  */
202
227
  async select(input, context = {}) {
228
+ const candidates = await this.getCandidates(input, context);
229
+
230
+ if (candidates.length === 0) {
231
+ return null;
232
+ }
233
+
234
+ // Sort by score (highest first)
235
+ candidates.sort((a, b) => b.score - a.score);
236
+
237
+ return candidates[0].skill;
238
+ }
239
+
240
+ /**
241
+ * Get candidate skills for input
242
+ * Returns skills that can handle the input with their scores
243
+ */
244
+ async getCandidates(input, context = {}) {
203
245
  const candidates = [];
204
246
 
205
247
  for (const [name, skill] of this.skills) {
@@ -207,7 +249,11 @@ class SkillLoader {
207
249
  const canHandle = await skill.canHandle(input, context);
208
250
  if (canHandle) {
209
251
  candidates.push({
252
+ name: skill.name,
210
253
  skill: skill,
254
+ description: skill.definition?.description || '',
255
+ triggers: skill.definition?.triggers || [],
256
+ category: skill.definition?.category || skill.definition?.metadata?.category || '',
211
257
  score: this.calculateSkillScore(skill, input, context)
212
258
  });
213
259
  }
@@ -216,14 +262,7 @@ class SkillLoader {
216
262
  }
217
263
  }
218
264
 
219
- if (candidates.length === 0) {
220
- return null;
221
- }
222
-
223
- // Sort by score (highest first)
224
- candidates.sort((a, b) => b.score - a.score);
225
-
226
- return candidates[0].skill;
265
+ return candidates;
227
266
  }
228
267
 
229
268
  /**
@@ -266,7 +305,10 @@ class SkillLoader {
266
305
  return Array.from(this.skills.values()).map(s => ({
267
306
  name: s.name,
268
307
  description: s.definition?.description,
269
- category: s.definition?.category
308
+ category: s.definition?.category || s.definition?.metadata?.category,
309
+ version: s.definition?.version || s.definition?.metadata?.version,
310
+ author: s.definition?.author || s.definition?.metadata?.author,
311
+ path: s.path
270
312
  }));
271
313
  }
272
314
 
package/mcp/server.js ADDED
@@ -0,0 +1,313 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * FlowMind MCP Server
5
+ * 让 Claude/Codex 可以直接调用 FlowMind 内部流程
6
+ */
7
+
8
+ const FlowMind = require('../core');
9
+
10
+ // MCP Server 实现
11
+ class FlowMindMCPServer {
12
+ constructor() {
13
+ this.flowmind = null;
14
+ this.initialized = false;
15
+ }
16
+
17
+ async init() {
18
+ if (this.initialized) return;
19
+
20
+ this.flowmind = new FlowMind();
21
+ await this.flowmind.init();
22
+ this.initialized = true;
23
+ }
24
+
25
+ /**
26
+ * 获取所有可用工具
27
+ */
28
+ getTools() {
29
+ const skills = this.flowmind.skills.list();
30
+ const tools = [];
31
+
32
+ // 添加核心工具
33
+ tools.push({
34
+ name: 'flowmind_process',
35
+ description: 'Process a request using FlowMind AI agent. This is the main entry point for using FlowMind.',
36
+ inputSchema: {
37
+ type: 'object',
38
+ properties: {
39
+ input: {
40
+ type: 'string',
41
+ description: 'The request to process'
42
+ },
43
+ context: {
44
+ type: 'object',
45
+ description: 'Optional context for the request'
46
+ }
47
+ },
48
+ required: ['input']
49
+ }
50
+ });
51
+
52
+ tools.push({
53
+ name: 'flowmind_list_skills',
54
+ description: 'List all available FlowMind skills',
55
+ inputSchema: {
56
+ type: 'object',
57
+ properties: {}
58
+ }
59
+ });
60
+
61
+ tools.push({
62
+ name: 'flowmind_get_skill',
63
+ description: 'Get detailed information about a specific skill',
64
+ inputSchema: {
65
+ type: 'object',
66
+ properties: {
67
+ name: {
68
+ type: 'string',
69
+ description: 'Skill name'
70
+ }
71
+ },
72
+ required: ['name']
73
+ }
74
+ });
75
+
76
+ tools.push({
77
+ name: 'flowmind_ai_status',
78
+ description: 'Get AI model status and configuration',
79
+ inputSchema: {
80
+ type: 'object',
81
+ properties: {}
82
+ }
83
+ });
84
+
85
+ tools.push({
86
+ name: 'flowmind_learning_stats',
87
+ description: 'Get learning statistics',
88
+ inputSchema: {
89
+ type: 'object',
90
+ properties: {}
91
+ }
92
+ });
93
+
94
+ // 添加每个技能作为独立工具
95
+ for (const skill of skills) {
96
+ tools.push({
97
+ name: `flowmind_skill_${skill.name}`,
98
+ description: skill.description || `Execute ${skill.name} skill`,
99
+ inputSchema: {
100
+ type: 'object',
101
+ properties: {
102
+ input: {
103
+ type: 'string',
104
+ description: 'Input for the skill'
105
+ },
106
+ context: {
107
+ type: 'object',
108
+ description: 'Optional context'
109
+ }
110
+ },
111
+ required: ['input']
112
+ }
113
+ });
114
+ }
115
+
116
+ return tools;
117
+ }
118
+
119
+ /**
120
+ * 调用工具
121
+ */
122
+ async callTool(name, args) {
123
+ await this.init();
124
+
125
+ try {
126
+ // 核心工具
127
+ if (name === 'flowmind_process') {
128
+ const result = await this.flowmind.process(args.input, args.context || {});
129
+ return {
130
+ content: [{
131
+ type: 'text',
132
+ text: JSON.stringify(result, null, 2)
133
+ }]
134
+ };
135
+ }
136
+
137
+ if (name === 'flowmind_list_skills') {
138
+ const skills = this.flowmind.skills.list();
139
+ return {
140
+ content: [{
141
+ type: 'text',
142
+ text: JSON.stringify({ skills }, null, 2)
143
+ }]
144
+ };
145
+ }
146
+
147
+ if (name === 'flowmind_get_skill') {
148
+ const skill = this.flowmind.skills.get(args.name);
149
+ if (!skill) {
150
+ return {
151
+ content: [{
152
+ type: 'text',
153
+ text: JSON.stringify({ error: `Skill not found: ${args.name}` })
154
+ }],
155
+ isError: true
156
+ };
157
+ }
158
+ return {
159
+ content: [{
160
+ type: 'text',
161
+ text: JSON.stringify({
162
+ name: skill.name,
163
+ description: skill.definition?.description,
164
+ category: skill.definition?.category,
165
+ triggers: skill.definition?.triggers,
166
+ componentDependencies: skill.definition?.componentDependencies
167
+ }, null, 2)
168
+ }]
169
+ };
170
+ }
171
+
172
+ if (name === 'flowmind_ai_status') {
173
+ const status = this.flowmind.getAIStatus();
174
+ return {
175
+ content: [{
176
+ type: 'text',
177
+ text: JSON.stringify(status, null, 2)
178
+ }]
179
+ };
180
+ }
181
+
182
+ if (name === 'flowmind_learning_stats') {
183
+ const stats = await this.flowmind.getStats();
184
+ return {
185
+ content: [{
186
+ type: 'text',
187
+ text: JSON.stringify(stats, null, 2)
188
+ }]
189
+ };
190
+ }
191
+
192
+ // 技能工具
193
+ if (name.startsWith('flowmind_skill_')) {
194
+ const skillName = name.replace('flowmind_skill_', '');
195
+ const skill = this.flowmind.skills.get(skillName);
196
+
197
+ if (!skill) {
198
+ return {
199
+ content: [{
200
+ type: 'text',
201
+ text: JSON.stringify({ error: `Skill not found: ${skillName}` })
202
+ }],
203
+ isError: true
204
+ };
205
+ }
206
+
207
+ const result = await this.flowmind.executeWithLearning(skill, args.input, args.context || {});
208
+ return {
209
+ content: [{
210
+ type: 'text',
211
+ text: JSON.stringify(result, null, 2)
212
+ }]
213
+ };
214
+ }
215
+
216
+ return {
217
+ content: [{
218
+ type: 'text',
219
+ text: JSON.stringify({ error: `Unknown tool: ${name}` })
220
+ }],
221
+ isError: true
222
+ };
223
+
224
+ } catch (error) {
225
+ return {
226
+ content: [{
227
+ type: 'text',
228
+ text: JSON.stringify({ error: error.message })
229
+ }],
230
+ isError: true
231
+ };
232
+ }
233
+ }
234
+ }
235
+
236
+ // 启动 MCP Server
237
+ async function main() {
238
+ const server = new FlowMindMCPServer();
239
+
240
+ // 读取 stdin,写入 stdout
241
+ const readline = require('readline');
242
+ const rl = readline.createInterface({
243
+ input: process.stdin,
244
+ output: process.stdout,
245
+ terminal: false
246
+ });
247
+
248
+ rl.on('line', async (line) => {
249
+ try {
250
+ const request = JSON.parse(line);
251
+ let response;
252
+
253
+ if (request.method === 'initialize') {
254
+ response = {
255
+ jsonrpc: '2.0',
256
+ id: request.id,
257
+ result: {
258
+ protocolVersion: '2024-11-05',
259
+ capabilities: {
260
+ tools: {}
261
+ },
262
+ serverInfo: {
263
+ name: 'flowmind',
264
+ version: '1.0.1'
265
+ }
266
+ }
267
+ };
268
+ } else if (request.method === 'tools/list') {
269
+ await server.init();
270
+ const tools = server.getTools();
271
+ response = {
272
+ jsonrpc: '2.0',
273
+ id: request.id,
274
+ result: { tools }
275
+ };
276
+ } else if (request.method === 'tools/call') {
277
+ const result = await server.callTool(request.params.name, request.params.arguments || {});
278
+ response = {
279
+ jsonrpc: '2.0',
280
+ id: request.id,
281
+ result
282
+ };
283
+ } else {
284
+ response = {
285
+ jsonrpc: '2.0',
286
+ id: request.id,
287
+ error: {
288
+ code: -32601,
289
+ message: `Method not found: ${request.method}`
290
+ }
291
+ };
292
+ }
293
+
294
+ process.stdout.write(JSON.stringify(response) + '\n');
295
+ } catch (error) {
296
+ const response = {
297
+ jsonrpc: '2.0',
298
+ id: null,
299
+ error: {
300
+ code: -32700,
301
+ message: 'Parse error'
302
+ }
303
+ };
304
+ process.stdout.write(JSON.stringify(response) + '\n');
305
+ }
306
+ });
307
+
308
+ // 初始化
309
+ await server.init();
310
+ console.error('FlowMind MCP Server started');
311
+ }
312
+
313
+ main().catch(console.error);