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,29 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const { Box, useApp, useInput } = require('ink');
|
|
3
|
+
const ActivityFeed = require('./components/ActivityFeed.jsx');
|
|
4
|
+
const StatsRow = require('./components/StatsRow.jsx');
|
|
5
|
+
const DragonPanel = require('./components/DragonPanel.jsx');
|
|
6
|
+
const McpStatusBar = require('./components/McpStatusBar.jsx');
|
|
7
|
+
|
|
8
|
+
function DashboardApp({ flowmind, eventBus }) {
|
|
9
|
+
const { exit } = useApp();
|
|
10
|
+
|
|
11
|
+
useInput((input, key) => {
|
|
12
|
+
if (key.ctrl && input === 'c') exit();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
React.createElement(Box, { flexDirection: 'column', width: '100%', height: '100%' },
|
|
17
|
+
React.createElement(Box, { flexDirection: 'row', flexGrow: 1 },
|
|
18
|
+
React.createElement(ActivityFeed, { eventBus: eventBus }),
|
|
19
|
+
React.createElement(Box, { flexDirection: 'column', width: '60%', flexGrow: 1 },
|
|
20
|
+
React.createElement(StatsRow, { flowmind: flowmind }),
|
|
21
|
+
React.createElement(DragonPanel, { flowmind: flowmind })
|
|
22
|
+
)
|
|
23
|
+
),
|
|
24
|
+
React.createElement(McpStatusBar, { eventBus: eventBus })
|
|
25
|
+
)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = DashboardApp;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const { Box, Text } = require('ink');
|
|
3
|
+
|
|
4
|
+
const EVENT_COLORS = {
|
|
5
|
+
'skill:executed': 'green',
|
|
6
|
+
'honor:awarded': 'yellow',
|
|
7
|
+
'learning:recorded': 'cyan',
|
|
8
|
+
'mcp:tool_called': 'magenta',
|
|
9
|
+
'process:start': 'blue',
|
|
10
|
+
'process:complete': 'green',
|
|
11
|
+
'process:error': 'red',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function formatTime(timestamp) {
|
|
15
|
+
if (!timestamp) return '??:??';
|
|
16
|
+
return new Date(timestamp).toTimeString().substring(0, 8);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function formatEvent(event) {
|
|
20
|
+
switch (event.type) {
|
|
21
|
+
case 'skill:executed':
|
|
22
|
+
return 'skill:' + (event.data?.name || '?') + ' ' + (event.data?.success ? '\u2713' : '\u2717');
|
|
23
|
+
case 'honor:awarded':
|
|
24
|
+
return 'honor +' + (event.data?.points || 0) + ' (' + (event.data?.description || '') + ')';
|
|
25
|
+
case 'learning:recorded':
|
|
26
|
+
return 'learning:' + (event.data?.type || '?') + ' ' + (event.data?.skill || '');
|
|
27
|
+
case 'mcp:tool_called':
|
|
28
|
+
return 'MCP:' + (event.data?.tool || '?') + ' ' + (event.data?.success ? '\u2713' : '\u2717') + ' ' + (event.data?.duration || 0) + 'ms';
|
|
29
|
+
case 'process:start':
|
|
30
|
+
return 'process: ' + (event.data?.input?.substring(0, 30) || '?') + '...';
|
|
31
|
+
case 'process:complete':
|
|
32
|
+
return 'done:' + (event.data?.skill || '?') + ' ' + (event.data?.duration || 0) + 'ms';
|
|
33
|
+
case 'process:error':
|
|
34
|
+
return 'error: ' + (event.data?.error?.substring(0, 40) || '?');
|
|
35
|
+
default:
|
|
36
|
+
return event.type || 'unknown';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function ActivityFeed({ eventBus }) {
|
|
41
|
+
const [events, setEvents] = React.useState([]);
|
|
42
|
+
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
if (!eventBus) return;
|
|
45
|
+
|
|
46
|
+
const handler = (eventType) => (data) => {
|
|
47
|
+
setEvents(prev => {
|
|
48
|
+
const next = [...prev, { type: eventType, data, timestamp: data.timestamp || new Date().toISOString() }];
|
|
49
|
+
if (next.length > 100) next.shift();
|
|
50
|
+
return next;
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const eventTypes = ['skill:executed', 'honor:awarded', 'learning:recorded', 'mcp:tool_called', 'process:start', 'process:complete', 'process:error'];
|
|
55
|
+
const handlers = eventTypes.map(type => {
|
|
56
|
+
const h = handler(type);
|
|
57
|
+
eventBus.on(type, h);
|
|
58
|
+
return { type, handler: h };
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return () => {
|
|
62
|
+
for (const { type, handler: h } of handlers) {
|
|
63
|
+
eventBus.removeListener(type, h);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}, [eventBus]);
|
|
67
|
+
|
|
68
|
+
const displayEvents = events.slice(-30);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'green', paddingX: 1, width: '40%' },
|
|
72
|
+
React.createElement(Text, { bold: true, color: 'green' }, 'Activity Feed'),
|
|
73
|
+
React.createElement(Box, { flexDirection: 'column', marginTop: 1, overflow: 'hidden' },
|
|
74
|
+
displayEvents.length === 0 && React.createElement(Text, { color: 'gray' }, 'Waiting for events...'),
|
|
75
|
+
displayEvents.map((event, i) =>
|
|
76
|
+
React.createElement(Text, { key: i },
|
|
77
|
+
React.createElement(Text, { color: 'gray' }, formatTime(event.timestamp) + ' '),
|
|
78
|
+
React.createElement(Text, { color: EVENT_COLORS[event.type] || 'white' }, formatEvent(event))
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = ActivityFeed;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const { Box, Text } = require('ink');
|
|
3
|
+
|
|
4
|
+
const DRAGON_ARTS = {
|
|
5
|
+
0: [' ╭─────╮ ',' ╱ ╭─╮ ╲ ',' │ │ │ │ ',' │ │ ◎ │ │ ',' │ ╰─╯ │ ',' ╲ ╱ ',' ╰─────╯ '],
|
|
6
|
+
1: [' ╭──╮ ',' ╭────╯ ╰───╮ ',' ╱ ◎ ╰─╯ ╲ ',' ╱ ▽ ╲ ',' ╲ ╱╲ ╱╲ ╱ ',' ╲╱╱ ╲╱╱ ╲╱╲╱ '],
|
|
7
|
+
2: [' ╭─╮ ╭─╮ ',' ╭────╯ ╰──╯ ╰───╮ ',' ╱ ◎ ╰──╯ ╲ ',' ╱ ╭────────╮ ╲ ',' ╲ ╱ ╱╱╱╱╱╱╱╱ ╲ ╱ ',' ╲───╯ ╱╱╱╱╱╱╱╱╱╱ ╰──╱ ',' ╰─╯ ╰─╯ '],
|
|
8
|
+
3: [' ╭───╮ ╭───╮ ',' ╭───╯ ╰──╯ ╰───╮ ',' ╱ ◎ ╰───╯ ╲ ','│ ╭──────────╮ │ ','│ ╱ ╱╱╱╱╱╱╱╱╱╱ ╲ │ ',' ╲──╯ ╱╱╱╱╱╱╱╱╱╱╱╱ ╰───╯ ',' ╲ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╱ ',' ╲╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ',' ╰───╯ ╰───╯ '],
|
|
9
|
+
4: [' ╭───╮ ╭───╮ ','╭───╯ ╰──────╯ ╰───╮ ','│ ◎ ╰───╯ │ ','│ ╭────────────╮ │ ','│ ╱ ╱╱╱╱╱╱╱╱╱╱╱╱ ╲ │ ',' ╲───╯ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╰──╯ ',' ╲ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╲ ',' ╲─╯╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╰─╲ ',' ╲╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ',' ╰───╯ ╰───╯ '],
|
|
10
|
+
5: [' ★ ╭───╮ ╭───╮ ★ ','╭─╯ ╰──╯ ╰──╯ ╰─╮ ','│ ◎ ╰───╯ │ ','│ ╭──────────────╮ │ ','│ ╱ ★╱╱╱╱╱╱╱╱╱╱★╱╱ ╲ │ ',' ╲────╯ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╰───╯ ',' ╲ ╱╱╱╱★╱╱╱╱╱╱╱╱★╱╱╱╱╱ ╲ ',' ╲──╯╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╰──╲ ',' ╲─╯╱╱╱★╱╱╱╱╱╱╱╱★╱╱╱╱╱╰──╲ ',' ★ ╲╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ★ ',' ╰───╯ ╰───╯ '],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const LEVEL_NAMES = ['Egg', 'Hatchling', 'Juvenile', 'Adult', 'Elder', 'Ascended'];
|
|
14
|
+
const LEVEL_STATES = ['dormant', 'awakening', 'growing', 'soaring', 'wise', 'transcendent'];
|
|
15
|
+
const LEVEL_COLORS = ['gray', 'cyan', 'cyan', 'cyanBright', 'cyanBright', 'cyanBright'];
|
|
16
|
+
|
|
17
|
+
function DragonPanel({ flowmind }) {
|
|
18
|
+
const [honorData, setHonorData] = React.useState({ points: 0, level: 0, stats: {} });
|
|
19
|
+
|
|
20
|
+
React.useEffect(() => {
|
|
21
|
+
if (!flowmind) return;
|
|
22
|
+
const refresh = () => {
|
|
23
|
+
try { setHonorData(flowmind.getHonorData()); } catch (e) { /* ignore */ }
|
|
24
|
+
};
|
|
25
|
+
refresh();
|
|
26
|
+
const interval = setInterval(refresh, 5000);
|
|
27
|
+
return () => clearInterval(interval);
|
|
28
|
+
}, [flowmind]);
|
|
29
|
+
|
|
30
|
+
const level = honorData.level || 0;
|
|
31
|
+
const art = DRAGON_ARTS[level] || DRAGON_ARTS[0];
|
|
32
|
+
const color = LEVEL_COLORS[level] || 'gray';
|
|
33
|
+
const levelName = LEVEL_NAMES[level] || 'Unknown';
|
|
34
|
+
const state = LEVEL_STATES[level] || 'unknown';
|
|
35
|
+
const nextLevelPoints = [1, 10, 30, 60, 100];
|
|
36
|
+
const nextPoints = nextLevelPoints[level] || null;
|
|
37
|
+
const pointsToNext = nextPoints !== null ? nextPoints - honorData.points : 0;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'cyan', paddingX: 1, flexGrow: 1 },
|
|
41
|
+
React.createElement(Text, { bold: true, color: 'cyan' }, 'Dragon Totem'),
|
|
42
|
+
React.createElement(Box, { flexDirection: 'row', marginTop: 1 },
|
|
43
|
+
React.createElement(Box, { flexDirection: 'column' },
|
|
44
|
+
art.map((line, i) => React.createElement(Text, { key: i, color: color }, line))
|
|
45
|
+
),
|
|
46
|
+
React.createElement(Box, { flexDirection: 'column', marginLeft: 3, justifyContent: 'center' },
|
|
47
|
+
React.createElement(Text, null,
|
|
48
|
+
React.createElement(Text, { color: 'yellow', bold: true }, 'Lv' + level),
|
|
49
|
+
React.createElement(Text, { color: 'white' }, ' ' + levelName)
|
|
50
|
+
),
|
|
51
|
+
React.createElement(Text, { color: 'gray' }, 'State: ' + state),
|
|
52
|
+
React.createElement(Text, null,
|
|
53
|
+
React.createElement(Text, { color: 'yellow' }, '' + honorData.points),
|
|
54
|
+
React.createElement(Text, { color: 'gray' }, ' points')
|
|
55
|
+
),
|
|
56
|
+
pointsToNext > 0 && React.createElement(Text, { color: 'gray' }, pointsToNext + ' to next'),
|
|
57
|
+
React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
|
|
58
|
+
React.createElement(Text, { color: 'gray' }, 'Skills: ' + (honorData.stats?.skillUseCount || 0)),
|
|
59
|
+
React.createElement(Text, { color: 'gray' }, 'Learnings: ' + (honorData.stats?.learningCount || 0))
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = DragonPanel;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const { Box, Text } = require('ink');
|
|
3
|
+
|
|
4
|
+
function McpStatusBar({ eventBus }) {
|
|
5
|
+
const [toolCount, setToolCount] = React.useState(0);
|
|
6
|
+
const [lastCall, setLastCall] = React.useState(null);
|
|
7
|
+
const [serverState, setServerState] = React.useState('running');
|
|
8
|
+
|
|
9
|
+
React.useEffect(() => {
|
|
10
|
+
if (!eventBus) return;
|
|
11
|
+
const handler = (data) => {
|
|
12
|
+
setToolCount(prev => prev + 1);
|
|
13
|
+
setLastCall(data.timestamp || new Date().toISOString());
|
|
14
|
+
};
|
|
15
|
+
eventBus.on('mcp:tool_called', handler);
|
|
16
|
+
return () => { eventBus.removeListener('mcp:tool_called', handler); };
|
|
17
|
+
}, [eventBus]);
|
|
18
|
+
|
|
19
|
+
const formatTime = (ts) => ts ? new Date(ts).toTimeString().substring(0, 8) : 'none';
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
React.createElement(Box, { borderStyle: 'single', borderColor: 'gray', paddingX: 1, justifyContent: 'space-between' },
|
|
23
|
+
React.createElement(Text, null,
|
|
24
|
+
React.createElement(Text, { color: 'gray' }, 'MCP Server: '),
|
|
25
|
+
React.createElement(Text, { color: 'green' }, serverState)
|
|
26
|
+
),
|
|
27
|
+
React.createElement(Text, null,
|
|
28
|
+
React.createElement(Text, { color: 'gray' }, 'Port: '),
|
|
29
|
+
React.createElement(Text, { color: 'white' }, 'stdin/stdout')
|
|
30
|
+
),
|
|
31
|
+
React.createElement(Text, null,
|
|
32
|
+
React.createElement(Text, { color: 'gray' }, 'Tool calls: '),
|
|
33
|
+
React.createElement(Text, { color: 'white' }, '' + toolCount)
|
|
34
|
+
),
|
|
35
|
+
React.createElement(Text, null,
|
|
36
|
+
React.createElement(Text, { color: 'gray' }, 'Last call: '),
|
|
37
|
+
React.createElement(Text, { color: 'white' }, formatTime(lastCall))
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = McpStatusBar;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const { Box, Text } = require('ink');
|
|
3
|
+
|
|
4
|
+
const LEVEL_NAMES = ['Egg', 'Hatchling', 'Juvenile', 'Adult', 'Elder', 'Ascended'];
|
|
5
|
+
|
|
6
|
+
function StatsRow({ flowmind }) {
|
|
7
|
+
const [honorData, setHonorData] = React.useState({ points: 0, level: 0, stats: {} });
|
|
8
|
+
const [learningStats, setLearningStats] = React.useState({ totalRecords: 0, byType: {} });
|
|
9
|
+
const [aiStatus, setAiStatus] = React.useState({ initialized: false, defaultProvider: 'none' });
|
|
10
|
+
|
|
11
|
+
React.useEffect(() => {
|
|
12
|
+
if (!flowmind) return;
|
|
13
|
+
const refresh = () => {
|
|
14
|
+
try { setHonorData(flowmind.getHonorData()); } catch (e) { /* ignore */ }
|
|
15
|
+
try { flowmind.getStats().then(s => setLearningStats(s)); } catch (e) { /* ignore */ }
|
|
16
|
+
try { setAiStatus(flowmind.getAIStatus()); } catch (e) { /* ignore */ }
|
|
17
|
+
};
|
|
18
|
+
refresh();
|
|
19
|
+
const interval = setInterval(refresh, 5000);
|
|
20
|
+
return () => clearInterval(interval);
|
|
21
|
+
}, [flowmind]);
|
|
22
|
+
|
|
23
|
+
const barWidth = 16;
|
|
24
|
+
const progress = honorData.points > 0 ? Math.min(1, honorData.points / 100) : 0;
|
|
25
|
+
const filled = Math.round(progress * barWidth);
|
|
26
|
+
const progressBar = '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
React.createElement(Box, { flexDirection: 'row' },
|
|
30
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'yellow', paddingX: 1, width: '33%' },
|
|
31
|
+
React.createElement(Text, { bold: true, color: 'yellow' }, 'Honor'),
|
|
32
|
+
React.createElement(Text, null,
|
|
33
|
+
React.createElement(Text, { color: 'yellow' }, LEVEL_NAMES[honorData.level] || 'Egg'),
|
|
34
|
+
React.createElement(Text, { color: 'gray' }, ' Lv' + honorData.level)
|
|
35
|
+
),
|
|
36
|
+
React.createElement(Text, { color: 'green' }, progressBar),
|
|
37
|
+
React.createElement(Text, { color: 'gray' }, honorData.points + '/100 pts')
|
|
38
|
+
),
|
|
39
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'cyan', paddingX: 1, width: '33%' },
|
|
40
|
+
React.createElement(Text, { bold: true, color: 'cyan' }, 'Learning'),
|
|
41
|
+
React.createElement(Text, null,
|
|
42
|
+
React.createElement(Text, { color: 'white' }, '' + (learningStats.totalRecords || 0)),
|
|
43
|
+
React.createElement(Text, { color: 'gray' }, ' records')
|
|
44
|
+
),
|
|
45
|
+
Object.entries(learningStats.byType || {}).map(([type, count]) =>
|
|
46
|
+
React.createElement(Text, { key: type, color: 'gray' }, ' ' + type + ': ' + count)
|
|
47
|
+
),
|
|
48
|
+
React.createElement(Text, null,
|
|
49
|
+
React.createElement(Text, { color: 'gray' }, 'AI: '),
|
|
50
|
+
React.createElement(Text, { color: aiStatus.initialized ? 'green' : 'red' }, aiStatus.initialized ? 'ok' : 'off')
|
|
51
|
+
)
|
|
52
|
+
),
|
|
53
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'blue', paddingX: 1, width: '33%' },
|
|
54
|
+
React.createElement(Text, { bold: true, color: 'blue' }, 'Components'),
|
|
55
|
+
React.createElement(Text, { color: 'gray' }, 'Registry loaded'),
|
|
56
|
+
React.createElement(Text, null,
|
|
57
|
+
React.createElement(Text, { color: 'gray' }, 'Provider: '),
|
|
58
|
+
React.createElement(Text, { color: aiStatus.initialized ? 'green' : 'red' }, aiStatus.defaultProvider || 'none')
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = StatsRow;
|
package/mcp/server.js
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FlowMind MCP Server
|
|
5
|
+
* 让 Claude/Codex 可以直接调用 FlowMind 内部流程
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const FlowMind = require('../core');
|
|
9
|
+
const eventBus = require('../core/event-bus');
|
|
10
|
+
|
|
11
|
+
// MCP Server 实现
|
|
12
|
+
class FlowMindMCPServer {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.flowmind = null;
|
|
15
|
+
this.initialized = false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async init() {
|
|
19
|
+
if (this.initialized) return;
|
|
20
|
+
|
|
21
|
+
this.flowmind = new FlowMind();
|
|
22
|
+
await this.flowmind.init();
|
|
23
|
+
this.initialized = true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 获取所有可用工具
|
|
28
|
+
*/
|
|
29
|
+
getTools() {
|
|
30
|
+
const skills = this.flowmind.skills.list();
|
|
31
|
+
const tools = [];
|
|
32
|
+
|
|
33
|
+
// 添加核心工具
|
|
34
|
+
tools.push({
|
|
35
|
+
name: 'flowmind_process',
|
|
36
|
+
description: 'Process a request using FlowMind AI agent. This is the main entry point for using FlowMind.',
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
input: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
description: 'The request to process'
|
|
43
|
+
},
|
|
44
|
+
context: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
description: 'Optional context for the request'
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
required: ['input']
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
tools.push({
|
|
54
|
+
name: 'flowmind_list_skills',
|
|
55
|
+
description: 'List all available FlowMind skills',
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
tools.push({
|
|
63
|
+
name: 'flowmind_get_skill',
|
|
64
|
+
description: 'Get detailed information about a specific skill',
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
name: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: 'Skill name'
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
required: ['name']
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
tools.push({
|
|
78
|
+
name: 'flowmind_ai_status',
|
|
79
|
+
description: 'Get AI model status and configuration',
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
properties: {}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
tools.push({
|
|
87
|
+
name: 'flowmind_learning_stats',
|
|
88
|
+
description: 'Get learning statistics',
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// 添加每个技能作为独立工具
|
|
96
|
+
for (const skill of skills) {
|
|
97
|
+
tools.push({
|
|
98
|
+
name: `flowmind_skill_${skill.name}`,
|
|
99
|
+
description: skill.description || `Execute ${skill.name} skill`,
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
input: {
|
|
104
|
+
type: 'string',
|
|
105
|
+
description: 'Input for the skill'
|
|
106
|
+
},
|
|
107
|
+
context: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
description: 'Optional context'
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
required: ['input']
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return tools;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 调用工具
|
|
122
|
+
*/
|
|
123
|
+
async callTool(name, args) {
|
|
124
|
+
await this.init();
|
|
125
|
+
|
|
126
|
+
const callStart = Date.now();
|
|
127
|
+
let result;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
// 核心工具
|
|
131
|
+
if (name === 'flowmind_process') {
|
|
132
|
+
const data = await this.flowmind.process(args.input, args.context || {});
|
|
133
|
+
result = {
|
|
134
|
+
content: [{
|
|
135
|
+
type: 'text',
|
|
136
|
+
text: JSON.stringify(data, null, 2)
|
|
137
|
+
}]
|
|
138
|
+
};
|
|
139
|
+
} else if (name === 'flowmind_list_skills') {
|
|
140
|
+
const skills = this.flowmind.skills.list();
|
|
141
|
+
result = {
|
|
142
|
+
content: [{
|
|
143
|
+
type: 'text',
|
|
144
|
+
text: JSON.stringify({ skills }, null, 2)
|
|
145
|
+
}]
|
|
146
|
+
};
|
|
147
|
+
} else if (name === 'flowmind_get_skill') {
|
|
148
|
+
const skill = this.flowmind.skills.get(args.name);
|
|
149
|
+
if (!skill) {
|
|
150
|
+
result = {
|
|
151
|
+
content: [{
|
|
152
|
+
type: 'text',
|
|
153
|
+
text: JSON.stringify({ error: `Skill not found: ${args.name}` })
|
|
154
|
+
}],
|
|
155
|
+
isError: true
|
|
156
|
+
};
|
|
157
|
+
} else {
|
|
158
|
+
result = {
|
|
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
|
+
} else if (name === 'flowmind_ai_status') {
|
|
172
|
+
const status = this.flowmind.getAIStatus();
|
|
173
|
+
result = {
|
|
174
|
+
content: [{
|
|
175
|
+
type: 'text',
|
|
176
|
+
text: JSON.stringify(status, null, 2)
|
|
177
|
+
}]
|
|
178
|
+
};
|
|
179
|
+
} else if (name === 'flowmind_learning_stats') {
|
|
180
|
+
const stats = await this.flowmind.getStats();
|
|
181
|
+
result = {
|
|
182
|
+
content: [{
|
|
183
|
+
type: 'text',
|
|
184
|
+
text: JSON.stringify(stats, null, 2)
|
|
185
|
+
}]
|
|
186
|
+
};
|
|
187
|
+
} else if (name.startsWith('flowmind_skill_')) {
|
|
188
|
+
// 技能工具
|
|
189
|
+
const skillName = name.replace('flowmind_skill_', '');
|
|
190
|
+
const skill = this.flowmind.skills.get(skillName);
|
|
191
|
+
|
|
192
|
+
if (!skill) {
|
|
193
|
+
result = {
|
|
194
|
+
content: [{
|
|
195
|
+
type: 'text',
|
|
196
|
+
text: JSON.stringify({ error: `Skill not found: ${skillName}` })
|
|
197
|
+
}],
|
|
198
|
+
isError: true
|
|
199
|
+
};
|
|
200
|
+
} else {
|
|
201
|
+
const data = await this.flowmind.executeWithLearning(skill, args.input, args.context || {});
|
|
202
|
+
result = {
|
|
203
|
+
content: [{
|
|
204
|
+
type: 'text',
|
|
205
|
+
text: JSON.stringify(data, null, 2)
|
|
206
|
+
}]
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
result = {
|
|
211
|
+
content: [{
|
|
212
|
+
type: 'text',
|
|
213
|
+
text: JSON.stringify({ error: `Unknown tool: ${name}` })
|
|
214
|
+
}],
|
|
215
|
+
isError: true
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Emit success event
|
|
220
|
+
eventBus.emit('mcp:tool_called', {
|
|
221
|
+
tool: name,
|
|
222
|
+
args,
|
|
223
|
+
duration: Date.now() - callStart,
|
|
224
|
+
success: !result.isError,
|
|
225
|
+
timestamp: new Date().toISOString()
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
return result;
|
|
229
|
+
|
|
230
|
+
} catch (error) {
|
|
231
|
+
eventBus.emit('mcp:tool_called', {
|
|
232
|
+
tool: name,
|
|
233
|
+
args,
|
|
234
|
+
duration: Date.now() - callStart,
|
|
235
|
+
success: false,
|
|
236
|
+
error: error.message,
|
|
237
|
+
timestamp: new Date().toISOString()
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
content: [{
|
|
242
|
+
type: 'text',
|
|
243
|
+
text: JSON.stringify({ error: error.message })
|
|
244
|
+
}],
|
|
245
|
+
isError: true
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 启动 MCP Server
|
|
252
|
+
async function main() {
|
|
253
|
+
const server = new FlowMindMCPServer();
|
|
254
|
+
|
|
255
|
+
// 读取 stdin,写入 stdout
|
|
256
|
+
const readline = require('readline');
|
|
257
|
+
const rl = readline.createInterface({
|
|
258
|
+
input: process.stdin,
|
|
259
|
+
output: process.stdout,
|
|
260
|
+
terminal: false
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
rl.on('line', async (line) => {
|
|
264
|
+
try {
|
|
265
|
+
const request = JSON.parse(line);
|
|
266
|
+
let response;
|
|
267
|
+
|
|
268
|
+
if (request.method === 'initialize') {
|
|
269
|
+
response = {
|
|
270
|
+
jsonrpc: '2.0',
|
|
271
|
+
id: request.id,
|
|
272
|
+
result: {
|
|
273
|
+
protocolVersion: '2024-11-05',
|
|
274
|
+
capabilities: {
|
|
275
|
+
tools: {}
|
|
276
|
+
},
|
|
277
|
+
serverInfo: {
|
|
278
|
+
name: 'flowmind',
|
|
279
|
+
version: '1.0.1'
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
} else if (request.method === 'tools/list') {
|
|
284
|
+
await server.init();
|
|
285
|
+
const tools = server.getTools();
|
|
286
|
+
response = {
|
|
287
|
+
jsonrpc: '2.0',
|
|
288
|
+
id: request.id,
|
|
289
|
+
result: { tools }
|
|
290
|
+
};
|
|
291
|
+
} else if (request.method === 'tools/call') {
|
|
292
|
+
const result = await server.callTool(request.params.name, request.params.arguments || {});
|
|
293
|
+
response = {
|
|
294
|
+
jsonrpc: '2.0',
|
|
295
|
+
id: request.id,
|
|
296
|
+
result
|
|
297
|
+
};
|
|
298
|
+
} else {
|
|
299
|
+
response = {
|
|
300
|
+
jsonrpc: '2.0',
|
|
301
|
+
id: request.id,
|
|
302
|
+
error: {
|
|
303
|
+
code: -32601,
|
|
304
|
+
message: `Method not found: ${request.method}`
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
310
|
+
} catch (error) {
|
|
311
|
+
const response = {
|
|
312
|
+
jsonrpc: '2.0',
|
|
313
|
+
id: null,
|
|
314
|
+
error: {
|
|
315
|
+
code: -32700,
|
|
316
|
+
message: 'Parse error'
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// 初始化
|
|
324
|
+
await server.init();
|
|
325
|
+
console.error('FlowMind MCP Server started');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
main().catch(console.error);
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowmind",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "The AI Agent That Learns How You Work - Stop repeating yourself, FlowMind learns your workflows and applies them automatically.",
|
|
5
5
|
"main": "core/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"flowmind": "./bin/flowmind.js"
|
|
7
|
+
"flowmind": "./bin/flowmind.js",
|
|
8
|
+
"flowmind-mcp": "./mcp/server.js"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
10
11
|
"start": "node core/index.js",
|
|
12
|
+
"mcp": "node mcp/server.js",
|
|
11
13
|
"test": "jest",
|
|
12
14
|
"test:coverage": "jest --coverage",
|
|
13
15
|
"lint": "eslint .",
|
|
@@ -21,7 +23,10 @@
|
|
|
21
23
|
"learning-system",
|
|
22
24
|
"code-review",
|
|
23
25
|
"log-analysis",
|
|
24
|
-
"devops"
|
|
26
|
+
"devops",
|
|
27
|
+
"mcp-server",
|
|
28
|
+
"claude-integration",
|
|
29
|
+
"codex-integration"
|
|
25
30
|
],
|
|
26
31
|
"author": "FlowMind Technologies",
|
|
27
32
|
"license": "MIT",
|
|
@@ -36,7 +41,10 @@
|
|
|
36
41
|
"files": [
|
|
37
42
|
"bin/",
|
|
38
43
|
"core/",
|
|
44
|
+
"mcp/",
|
|
39
45
|
"skills/",
|
|
46
|
+
"tui/",
|
|
47
|
+
"dashboard/",
|
|
40
48
|
"scripts/",
|
|
41
49
|
"templates/",
|
|
42
50
|
"config/",
|
|
@@ -50,16 +58,20 @@
|
|
|
50
58
|
"node": ">=18.0.0"
|
|
51
59
|
},
|
|
52
60
|
"dependencies": {
|
|
53
|
-
"commander": "^11.0.0",
|
|
54
61
|
"chalk": "^4.1.2",
|
|
55
|
-
"
|
|
56
|
-
"inquirer": "^8.2.6",
|
|
62
|
+
"commander": "^11.0.0",
|
|
57
63
|
"fs-extra": "^11.1.1",
|
|
64
|
+
"ink": "^3.2.0",
|
|
65
|
+
"ink-spinner": "^4.0.3",
|
|
66
|
+
"ink-text-input": "^4.0.3",
|
|
67
|
+
"inquirer": "^8.2.6",
|
|
68
|
+
"ora": "^5.4.1",
|
|
69
|
+
"react": "^18.3.1",
|
|
58
70
|
"uuid": "^9.0.0"
|
|
59
71
|
},
|
|
60
72
|
"devDependencies": {
|
|
61
|
-
"jest": "^29.7.0",
|
|
62
73
|
"eslint": "^8.50.0",
|
|
74
|
+
"jest": "^29.7.0",
|
|
63
75
|
"nodemon": "^3.0.1",
|
|
64
76
|
"webpack": "^5.88.0",
|
|
65
77
|
"webpack-cli": "^5.1.4"
|