flowmind 1.5.2 → 1.5.4
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/CHANGELOG.md +14 -0
- package/bin/flowmind.js +85 -8
- package/core/adapters/mcp-adapter.js +26 -0
- package/core/adapters/workflow-adapter.js +26 -0
- package/core/ai/providers/mimo.js +1 -1
- package/core/component-registry.js +19 -1
- package/core/config-manager.js +7 -2
- package/core/index.js +153 -2
- package/core/mcp-http-client.js +63 -0
- package/core/providers/aliyun/dms-adapter.js +7 -35
- package/core/providers/aliyun/rds-query-adapter.js +70 -0
- package/core/providers/aliyun/redis-adapter.js +4 -20
- package/core/providers/aliyun/sls-adapter.js +3 -10
- package/core/providers/friday/flow-adapter.js +19 -30
- package/core/providers/friday/report-adapter.js +5 -25
- package/core/providers/yapi/yapi-adapter.js +6 -30
- package/core/providers/yuque/yuque-adapter.js +7 -35
- package/core/sdd-agent-sync.js +240 -16
- package/core/source-inference.js +324 -0
- package/package.json +1 -1
- package/skills/auto-flow/index.js +903 -74
- package/skills/data-logic-validation/index.js +133 -12
- package/skills/log-audit/index.js +94 -1
- package/skills/resource-bind/index.js +61 -18
- package/skills/yapi-sync-interface/index.js +146 -13
- package/skills/yuque-sync-design/index.js +130 -11
- package/tui/app.jsx +15 -5
- package/tui/components/ChatPanel.jsx +1 -1
- package/tui/format-result.js +43 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.5.4] - 2026-07-01
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- FlowMind now resolves deferred MCP invocations from skill outputs and executes them through the registered adapter when available
|
|
9
|
+
- Auto-flow, YApi, Yuque, and log audit skills now return concise execution summaries instead of raw MCP payload shapes
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Repeated TUI submissions are now serialized to avoid multi-input crashes during fast follow-up commands
|
|
13
|
+
- CLI startup now falls back to ASCII-friendly banners in managed terminal environments that can crash on full graphics rendering
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- `resource-bind` and related learned bindings now surface clearer resource summaries for follow-up skill reuse
|
|
17
|
+
- Result formatting now unwraps nested execution payloads so the TUI shows the actual output instead of intermediate envelopes
|
|
18
|
+
|
|
5
19
|
## [1.5.2] - 2026-07-01
|
|
6
20
|
|
|
7
21
|
### Added
|
package/bin/flowmind.js
CHANGED
|
@@ -75,7 +75,19 @@ async function initFlowMind(options = {}) {
|
|
|
75
75
|
/**
|
|
76
76
|
* Display banner
|
|
77
77
|
*/
|
|
78
|
-
function showBanner() {
|
|
78
|
+
function showBanner(asciiMode = false) {
|
|
79
|
+
if (asciiMode) {
|
|
80
|
+
console.log(chalk.cyan(`
|
|
81
|
+
+--------------------------------------------------+
|
|
82
|
+
| |
|
|
83
|
+
| FlowMind |
|
|
84
|
+
| The AI Agent That Learns How You Work |
|
|
85
|
+
| |
|
|
86
|
+
+--------------------------------------------------+
|
|
87
|
+
`));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
79
91
|
console.log(chalk.cyan(`
|
|
80
92
|
╔══════════════════════════════════════════════════╗
|
|
81
93
|
║ ║
|
|
@@ -89,7 +101,71 @@ function showBanner() {
|
|
|
89
101
|
/**
|
|
90
102
|
* Dragon Totem ASCII Art by level - Chinese Dragon (中国龙)
|
|
91
103
|
*/
|
|
92
|
-
function getDragonArt(level) {
|
|
104
|
+
function getDragonArt(level, asciiMode = false) {
|
|
105
|
+
if (asciiMode) {
|
|
106
|
+
const asciiArts = {
|
|
107
|
+
0: [
|
|
108
|
+
' /-----\\ ',
|
|
109
|
+
' / /-\\ \\ ',
|
|
110
|
+
' | | o | | ',
|
|
111
|
+
' | | | | ',
|
|
112
|
+
' | \\_/ | ',
|
|
113
|
+
' \\ / ',
|
|
114
|
+
' \\-----/ ',
|
|
115
|
+
' Dragon Egg '
|
|
116
|
+
],
|
|
117
|
+
1: [
|
|
118
|
+
' /--\\ ',
|
|
119
|
+
' /----/ \\---\\ ',
|
|
120
|
+
' / o \\/ \\ ',
|
|
121
|
+
' \\ /\\ /\\ / ',
|
|
122
|
+
' \\/\\/ \\/\\/ \\/ ',
|
|
123
|
+
' Hatchling Dragon '
|
|
124
|
+
],
|
|
125
|
+
2: [
|
|
126
|
+
' /-\\ /-\\ ',
|
|
127
|
+
' /----/ \\/ \\---\\ ',
|
|
128
|
+
' / o __ \\ ',
|
|
129
|
+
' \\ /--------\\ / ',
|
|
130
|
+
' \\---/ ////// \\/ ',
|
|
131
|
+
' \\_/ \\_/ ',
|
|
132
|
+
' Juvenile Dragon '
|
|
133
|
+
],
|
|
134
|
+
3: [
|
|
135
|
+
' /---\\ /---\\ ',
|
|
136
|
+
' /---/ \\/ \\---\\ ',
|
|
137
|
+
'/ o __ \\ ',
|
|
138
|
+
'| /----------\\ | ',
|
|
139
|
+
' \\--/ //////// \\-/ ',
|
|
140
|
+
' \\/ ////////// \\/ ',
|
|
141
|
+
' \\_/ \\_/ ',
|
|
142
|
+
' Adult Dragon '
|
|
143
|
+
],
|
|
144
|
+
4: [
|
|
145
|
+
' /---\\ /---\\ ',
|
|
146
|
+
'/---/ \\____/ \\---\\ ',
|
|
147
|
+
'| o __ | ',
|
|
148
|
+
'| /------------\\ | ',
|
|
149
|
+
' \\-/ //////////// \\-/ ',
|
|
150
|
+
' / //////////// \\ ',
|
|
151
|
+
' /_/ \\_\\ ',
|
|
152
|
+
' Elder Dragon '
|
|
153
|
+
],
|
|
154
|
+
5: [
|
|
155
|
+
' * /---\\ /---\\ * ',
|
|
156
|
+
'/-/ \\______/ \\-\\ ',
|
|
157
|
+
'| o __ | ',
|
|
158
|
+
'| /--------------\\ | ',
|
|
159
|
+
' \\-/ /////**///// \\-/ ',
|
|
160
|
+
' / ///// ///// \\ ',
|
|
161
|
+
' /_/ ** ** \\_\\ ',
|
|
162
|
+
' * \\______________/ * ',
|
|
163
|
+
' Ascended Dragon '
|
|
164
|
+
]
|
|
165
|
+
};
|
|
166
|
+
return asciiArts[level] || asciiArts[0];
|
|
167
|
+
}
|
|
168
|
+
|
|
93
169
|
const arts = {
|
|
94
170
|
0: [
|
|
95
171
|
' ╭─────╮ ',
|
|
@@ -192,11 +268,11 @@ function getHighlightColor(level) {
|
|
|
192
268
|
/**
|
|
193
269
|
* Render dragon totem with honor data
|
|
194
270
|
*/
|
|
195
|
-
function renderDragonTotem(honorData) {
|
|
271
|
+
function renderDragonTotem(honorData, asciiMode = false) {
|
|
196
272
|
const info = getLevelInfo(honorData.points);
|
|
197
273
|
const dragonColor = getDragonColor(info.level);
|
|
198
274
|
const highlightColor = getHighlightColor(info.level);
|
|
199
|
-
const art = getDragonArt(info.level);
|
|
275
|
+
const art = getDragonArt(info.level, asciiMode);
|
|
200
276
|
|
|
201
277
|
// Calculate max art line width for proper padding
|
|
202
278
|
const maxArtWidth = Math.max(...art.map(l => l.length));
|
|
@@ -210,7 +286,8 @@ function renderDragonTotem(honorData) {
|
|
|
210
286
|
|
|
211
287
|
console.log('');
|
|
212
288
|
console.log(chalk.cyan(' ┌' + border + '┐'));
|
|
213
|
-
|
|
289
|
+
const title = asciiMode ? ' Dragon Totem of Honor ' : ' 🐉 Dragon Totem of Honor ';
|
|
290
|
+
console.log(chalk.cyan(' │') + title + padRight(title, boxWidth - 2) + chalk.cyan('│'));
|
|
214
291
|
console.log(chalk.cyan(' ├' + border + '┤'));
|
|
215
292
|
|
|
216
293
|
for (const line of art) {
|
|
@@ -366,7 +443,7 @@ program
|
|
|
366
443
|
.description('Initialize FlowMind in current directory')
|
|
367
444
|
.option('--ai <provider>', 'Initialize with AI provider (openai/anthropic/glm/mimo/qwen/ernie/deepseek/ollama)')
|
|
368
445
|
.action(async (options) => {
|
|
369
|
-
showBanner();
|
|
446
|
+
showBanner(options.ascii || shouldUseAsciiUi());
|
|
370
447
|
|
|
371
448
|
const spinner = ora('Initializing FlowMind...').start();
|
|
372
449
|
|
|
@@ -836,7 +913,7 @@ program
|
|
|
836
913
|
} else if (options.publish) {
|
|
837
914
|
await publishHonor({ output: options.output, gist: options.gist });
|
|
838
915
|
} else {
|
|
839
|
-
renderDragonTotem(honorData);
|
|
916
|
+
renderDragonTotem(honorData, shouldUseAsciiUi());
|
|
840
917
|
console.log(chalk.gray(` ${getNextLevelHint(honorData.points)}`));
|
|
841
918
|
}
|
|
842
919
|
} catch (error) {
|
|
@@ -959,7 +1036,7 @@ program
|
|
|
959
1036
|
|
|
960
1037
|
// Interactive mode
|
|
961
1038
|
async function runInteractiveMode(fm) {
|
|
962
|
-
showBanner();
|
|
1039
|
+
showBanner(shouldUseAsciiUi());
|
|
963
1040
|
console.log(chalk.cyan('Interactive mode started. Type "exit" to quit.\n'));
|
|
964
1041
|
|
|
965
1042
|
try {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const BaseAdapter = require('./base-adapter');
|
|
7
|
+
const { callMcpTool } = require('../mcp-http-client');
|
|
7
8
|
|
|
8
9
|
class McpAdapter extends BaseAdapter {
|
|
9
10
|
constructor(providerName, config = {}) {
|
|
@@ -60,6 +61,31 @@ class McpAdapter extends BaseAdapter {
|
|
|
60
61
|
tools: this.getToolMappings()
|
|
61
62
|
};
|
|
62
63
|
}
|
|
64
|
+
|
|
65
|
+
getTransportConfig() {
|
|
66
|
+
return this.config.transport || this.config.mcpServerConfig || this.config.mcpTransport || {};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async callTool(tool, args = {}) {
|
|
70
|
+
const transport = this.getTransportConfig();
|
|
71
|
+
const serverName = this.mcpServer || this.providerName;
|
|
72
|
+
return callMcpTool({
|
|
73
|
+
url: transport.url || transport.endpoint || transport.baseUrl,
|
|
74
|
+
headers: transport.headers || {},
|
|
75
|
+
tool,
|
|
76
|
+
args,
|
|
77
|
+
timeoutMs: transport.timeoutMs || 60000,
|
|
78
|
+
serverName
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async callMcpTool(localName, args = {}) {
|
|
83
|
+
const tool = this.resolveTool(localName);
|
|
84
|
+
if (!tool) {
|
|
85
|
+
throw new Error(`Unknown MCP tool mapping: ${localName}`);
|
|
86
|
+
}
|
|
87
|
+
return this.callTool(tool, args);
|
|
88
|
+
}
|
|
63
89
|
}
|
|
64
90
|
|
|
65
91
|
module.exports = McpAdapter;
|
|
@@ -29,6 +29,32 @@ class WorkflowAdapter extends McpAdapter {
|
|
|
29
29
|
throw new Error('Subclasses must implement listPipelines()');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Query the current iterate metadata used by workflow lookup flows.
|
|
34
|
+
* @returns {Promise<object>}
|
|
35
|
+
*/
|
|
36
|
+
async getCurrentIterate() {
|
|
37
|
+
throw new Error('Subclasses must implement getCurrentIterate()');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* List deploy checklist records that contain service-to-pipeline mappings.
|
|
42
|
+
* @param {object} params
|
|
43
|
+
* @returns {Promise<object>}
|
|
44
|
+
*/
|
|
45
|
+
async listDeployChecklists(params) {
|
|
46
|
+
throw new Error('Subclasses must implement listDeployChecklists()');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Query historical flow orders that can be used as a fallback pipeline source.
|
|
51
|
+
* @param {object} params
|
|
52
|
+
* @returns {Promise<object>}
|
|
53
|
+
*/
|
|
54
|
+
async orderList(params) {
|
|
55
|
+
throw new Error('Subclasses must implement orderList()');
|
|
56
|
+
}
|
|
57
|
+
|
|
32
58
|
/**
|
|
33
59
|
* Start a pipeline run.
|
|
34
60
|
* @param {string} pipelineId
|
|
@@ -41,6 +41,10 @@ class ComponentRegistry {
|
|
|
41
41
|
const AliyunDmsAdapter = require('./providers/aliyun/dms-adapter');
|
|
42
42
|
return new AliyunDmsAdapter(this.getProviderConfig('databaseManager', 'aliyun-dms'));
|
|
43
43
|
});
|
|
44
|
+
this.registerFactory('aliyun-rds-query', () => {
|
|
45
|
+
const AliyunRdsQueryAdapter = require('./providers/aliyun/rds-query-adapter');
|
|
46
|
+
return new AliyunRdsQueryAdapter(this.getProviderConfig('databaseQuery', 'aliyun-rds-query'));
|
|
47
|
+
});
|
|
44
48
|
this.registerFactory('aliyun-redis', () => {
|
|
45
49
|
const AliyunRedisAdapter = require('./providers/aliyun/redis-adapter');
|
|
46
50
|
return new AliyunRedisAdapter(this.getProviderConfig('redisMonitor', 'aliyun-redis'));
|
|
@@ -79,7 +83,21 @@ class ComponentRegistry {
|
|
|
79
83
|
const components = this.config.get('components', {});
|
|
80
84
|
const typeConfig = components[componentType] || {};
|
|
81
85
|
const providers = typeConfig.providers || {};
|
|
82
|
-
|
|
86
|
+
const baseConfig = providers[providerName] || {};
|
|
87
|
+
const mcpServerName = McpCompatibility.getMcpServer(componentType, providerName);
|
|
88
|
+
const mcpServers = this.config.get('mcpServers', {});
|
|
89
|
+
const transport = mcpServerName ? mcpServers[mcpServerName] : null;
|
|
90
|
+
|
|
91
|
+
if (!transport) {
|
|
92
|
+
return baseConfig;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
...baseConfig,
|
|
97
|
+
transport: {
|
|
98
|
+
...transport
|
|
99
|
+
}
|
|
100
|
+
};
|
|
83
101
|
}
|
|
84
102
|
|
|
85
103
|
/**
|
package/core/config-manager.js
CHANGED
|
@@ -82,7 +82,10 @@ class ConfigManager {
|
|
|
82
82
|
if (await fs.pathExists(resourceConfigPath)) {
|
|
83
83
|
try {
|
|
84
84
|
const resourceConfig = await fs.readJson(resourceConfigPath);
|
|
85
|
-
this.mergeConfig({
|
|
85
|
+
this.mergeConfig({
|
|
86
|
+
resources: resourceConfig.resources || {},
|
|
87
|
+
mcpServers: resourceConfig.mcpServers || {}
|
|
88
|
+
});
|
|
86
89
|
} catch (error) {
|
|
87
90
|
console.warn('Failed to load resource config:', error.message);
|
|
88
91
|
}
|
|
@@ -216,7 +219,9 @@ class ConfigManager {
|
|
|
216
219
|
redis: { enabled: false },
|
|
217
220
|
logs: { enabled: false },
|
|
218
221
|
apiDocs: { enabled: false }
|
|
219
|
-
}
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
mcpServers: {}
|
|
220
225
|
};
|
|
221
226
|
}
|
|
222
227
|
|
package/core/index.js
CHANGED
|
@@ -11,6 +11,8 @@ const ConfigManager = require('./config-manager');
|
|
|
11
11
|
const ComponentRegistry = require('./component-registry');
|
|
12
12
|
const ModelManager = require('./ai/model-manager');
|
|
13
13
|
const eventBus = require('./event-bus');
|
|
14
|
+
const { autoSyncSddAgentToFlowMind } = require('./sdd-agent-sync');
|
|
15
|
+
const { inferSourceContext } = require('./source-inference');
|
|
14
16
|
|
|
15
17
|
class FlowMind {
|
|
16
18
|
constructor(options = {}) {
|
|
@@ -32,6 +34,7 @@ class FlowMind {
|
|
|
32
34
|
async init() {
|
|
33
35
|
if (this.initialized) return this;
|
|
34
36
|
|
|
37
|
+
await autoSyncSddAgentToFlowMind();
|
|
35
38
|
await this.config.load();
|
|
36
39
|
|
|
37
40
|
// Validate configuration
|
|
@@ -191,19 +194,30 @@ class FlowMind {
|
|
|
191
194
|
// Get learning rules for this skill
|
|
192
195
|
const learnings = await this.learning.getSkillLearnings(skill.name);
|
|
193
196
|
const resourceBinding = await this.learning.matchResourceBinding(input, skill.name);
|
|
197
|
+
let sourceInference = context.sourceInference || null;
|
|
198
|
+
if (!sourceInference) {
|
|
199
|
+
try {
|
|
200
|
+
sourceInference = await inferSourceContext(input);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
sourceInference = null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
194
205
|
|
|
195
206
|
// Apply learning rules to context
|
|
196
207
|
const enhancedContext = {
|
|
197
208
|
...context,
|
|
198
209
|
flowmind: context.flowmind || this,
|
|
210
|
+
componentRegistry: context.componentRegistry || this.components,
|
|
199
211
|
currentSkill: context.currentSkill || skill.name,
|
|
200
212
|
learnings: learnings,
|
|
201
213
|
preferences: await this.learning.getPreferences(skill.name),
|
|
202
|
-
resourceBinding
|
|
214
|
+
resourceBinding,
|
|
215
|
+
sourceInference
|
|
203
216
|
};
|
|
204
217
|
|
|
205
218
|
// Execute skill
|
|
206
219
|
const result = await skill.execute(input, enhancedContext);
|
|
220
|
+
const resolvedResult = await this.resolveDeferredMcpResult(result, skill, enhancedContext);
|
|
207
221
|
|
|
208
222
|
if (resourceBinding?.id) {
|
|
209
223
|
await this.learning.markResourceBindingUsed(resourceBinding.id, {
|
|
@@ -226,7 +240,144 @@ class FlowMind {
|
|
|
226
240
|
// Award honor point for skill use
|
|
227
241
|
await this.honor.award('skill_use', `Used skill: ${skill.name}`);
|
|
228
242
|
|
|
229
|
-
return
|
|
243
|
+
return resolvedResult;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async resolveDeferredMcpResult(result, skill, context = {}) {
|
|
247
|
+
const invocation = this.extractMcpInvocation(result);
|
|
248
|
+
if (!invocation) {
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const adapter = this.resolveDeferredAdapter(invocation, skill, context);
|
|
253
|
+
if (!adapter) {
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const executor = typeof adapter.callTool === 'function'
|
|
258
|
+
? adapter.callTool.bind(adapter)
|
|
259
|
+
: typeof adapter.callMcpTool === 'function'
|
|
260
|
+
? adapter.callMcpTool.bind(adapter)
|
|
261
|
+
: null;
|
|
262
|
+
|
|
263
|
+
if (!executor) {
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const execution = await executor(invocation.tool, invocation.args || {});
|
|
269
|
+
const resultData = result && typeof result === 'object' && !Array.isArray(result) && result.data && typeof result.data === 'object'
|
|
270
|
+
? result.data
|
|
271
|
+
: {};
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
...result,
|
|
275
|
+
data: {
|
|
276
|
+
...resultData,
|
|
277
|
+
execution,
|
|
278
|
+
mcpTool: invocation.tool,
|
|
279
|
+
mcpExecuted: true,
|
|
280
|
+
mcpServer: invocation.mcpServer || adapter.mcpServer || adapter.providerName
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
} catch (error) {
|
|
284
|
+
return {
|
|
285
|
+
...result,
|
|
286
|
+
type: result?.type || 'error',
|
|
287
|
+
success: false,
|
|
288
|
+
message: result?.message || error.message,
|
|
289
|
+
data: {
|
|
290
|
+
...(result && typeof result === 'object' && result.data && typeof result.data === 'object' ? result.data : {}),
|
|
291
|
+
mcpTool: invocation.tool,
|
|
292
|
+
mcpExecuted: false,
|
|
293
|
+
mcpError: error.message
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
extractMcpInvocation(result) {
|
|
300
|
+
if (!result || typeof result !== 'object') {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const candidates = [
|
|
305
|
+
result.data,
|
|
306
|
+
result
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
for (const candidate of candidates) {
|
|
310
|
+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const tool = candidate.mcpTool || candidate.tool;
|
|
315
|
+
if (!tool || typeof tool !== 'string') {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const args = candidate.args || candidate.params || {};
|
|
320
|
+
if (args && typeof args !== 'object') {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
tool,
|
|
326
|
+
args,
|
|
327
|
+
mcpServer: candidate.mcpServer || null
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
resolveDeferredAdapter(invocation, skill, context = {}) {
|
|
335
|
+
const registry = context.componentRegistry || this.components;
|
|
336
|
+
if (!registry) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const bindingComponentType = context.resourceBinding?.componentType || context.resolvedBinding?.componentType || null;
|
|
341
|
+
const skillComponentType = this.inferSkillComponentType(skill, context);
|
|
342
|
+
const componentTypes = [
|
|
343
|
+
bindingComponentType,
|
|
344
|
+
skillComponentType,
|
|
345
|
+
invocation.componentType
|
|
346
|
+
].filter(Boolean);
|
|
347
|
+
|
|
348
|
+
for (const componentType of componentTypes) {
|
|
349
|
+
const adapter = registry.getAdapter(componentType);
|
|
350
|
+
if (adapter) {
|
|
351
|
+
return adapter;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (typeof registry.getAll === 'function') {
|
|
356
|
+
const allAdapters = registry.getAll();
|
|
357
|
+
for (const entry of allAdapters) {
|
|
358
|
+
const adapter = registry.getAdapterByProvider?.(entry.type, entry.provider) || null;
|
|
359
|
+
if (adapter && (adapter.mcpServer === invocation.mcpServer || adapter.providerName === invocation.provider)) {
|
|
360
|
+
return adapter;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
inferSkillComponentType(skill, context = {}) {
|
|
369
|
+
const name = skill?.name || context.currentSkill || '';
|
|
370
|
+
const mapping = {
|
|
371
|
+
'log-audit': 'logService',
|
|
372
|
+
'sls-log-audit': 'logService',
|
|
373
|
+
'yapi-sync-interface': 'apiDoc',
|
|
374
|
+
'yuque-sync-design': 'knowledgeBase',
|
|
375
|
+
'data-logic-validation': 'databaseQuery',
|
|
376
|
+
'resource-bind': null,
|
|
377
|
+
'auto-flow': 'workflow'
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
return mapping[name] || null;
|
|
230
381
|
}
|
|
231
382
|
|
|
232
383
|
/**
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal JSON-RPC MCP HTTP client.
|
|
3
|
+
* Sends tools/call requests to an MCP gateway over HTTP.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
async function callMcpTool({ url, headers = {}, tool, args = {}, timeoutMs = 60000, serverName = 'mcp' }) {
|
|
7
|
+
if (!url) {
|
|
8
|
+
throw new Error(`MCP transport URL not configured for ${serverName}`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const requestId = Date.now();
|
|
12
|
+
const controller = new AbortController();
|
|
13
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch(url, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: {
|
|
19
|
+
'Content-Type': 'application/json',
|
|
20
|
+
'User-Agent': 'flowmind',
|
|
21
|
+
...headers
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
jsonrpc: '2.0',
|
|
25
|
+
id: requestId,
|
|
26
|
+
method: 'tools/call',
|
|
27
|
+
params: {
|
|
28
|
+
name: tool,
|
|
29
|
+
arguments: args
|
|
30
|
+
}
|
|
31
|
+
}),
|
|
32
|
+
signal: controller.signal
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const text = await response.text();
|
|
36
|
+
let payload = null;
|
|
37
|
+
|
|
38
|
+
if (text) {
|
|
39
|
+
try {
|
|
40
|
+
payload = JSON.parse(text);
|
|
41
|
+
} catch (parseError) {
|
|
42
|
+
throw new Error(`Invalid MCP response from ${serverName}: ${text.slice(0, 200)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
const errorMessage = payload?.error?.message || payload?.message || response.statusText;
|
|
48
|
+
throw new Error(`MCP request failed for ${serverName}: ${errorMessage}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (payload?.error) {
|
|
52
|
+
throw new Error(payload.error.message || `MCP tool call failed for ${serverName}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return payload?.result ?? payload ?? null;
|
|
56
|
+
} finally {
|
|
57
|
+
clearTimeout(timeout);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
callMcpTool
|
|
63
|
+
};
|
|
@@ -27,43 +27,23 @@ class AliyunDmsAdapter extends DatabaseManagerAdapter {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
async listInstances(params) {
|
|
30
|
-
return {
|
|
31
|
-
mcpServer: this.mcpServer,
|
|
32
|
-
tool: this.resolveTool('listInstances'),
|
|
33
|
-
params: params || {}
|
|
34
|
-
};
|
|
30
|
+
return this.callMcpTool('listInstances', params || {});
|
|
35
31
|
}
|
|
36
32
|
|
|
37
33
|
async executeScript(params) {
|
|
38
|
-
return
|
|
39
|
-
mcpServer: this.mcpServer,
|
|
40
|
-
tool: this.resolveTool('executeScript'),
|
|
41
|
-
params
|
|
42
|
-
};
|
|
34
|
+
return this.callMcpTool('executeScript', params);
|
|
43
35
|
}
|
|
44
36
|
|
|
45
37
|
async searchDatabase(searchKey) {
|
|
46
|
-
return {
|
|
47
|
-
mcpServer: this.mcpServer,
|
|
48
|
-
tool: this.resolveTool('searchDatabase'),
|
|
49
|
-
params: { search_key: searchKey }
|
|
50
|
-
};
|
|
38
|
+
return this.callMcpTool('searchDatabase', { search_key: searchKey });
|
|
51
39
|
}
|
|
52
40
|
|
|
53
41
|
async listTables(databaseId) {
|
|
54
|
-
return {
|
|
55
|
-
mcpServer: this.mcpServer,
|
|
56
|
-
tool: this.resolveTool('listTables'),
|
|
57
|
-
params: { database_id: databaseId }
|
|
58
|
-
};
|
|
42
|
+
return this.callMcpTool('listTables', { database_id: databaseId });
|
|
59
43
|
}
|
|
60
44
|
|
|
61
45
|
async getTableDetail(tableGuid) {
|
|
62
|
-
return {
|
|
63
|
-
mcpServer: this.mcpServer,
|
|
64
|
-
tool: this.resolveTool('getTableDetailInfo'),
|
|
65
|
-
params: { table_guid: tableGuid }
|
|
66
|
-
};
|
|
46
|
+
return this.callMcpTool('getTableDetailInfo', { table_guid: tableGuid });
|
|
67
47
|
}
|
|
68
48
|
|
|
69
49
|
/**
|
|
@@ -73,11 +53,7 @@ class AliyunDmsAdapter extends DatabaseManagerAdapter {
|
|
|
73
53
|
* @returns {Promise<object>}
|
|
74
54
|
*/
|
|
75
55
|
async generateSql(databaseId, question) {
|
|
76
|
-
return {
|
|
77
|
-
mcpServer: this.mcpServer,
|
|
78
|
-
tool: this.resolveTool('generateSql'),
|
|
79
|
-
params: { database_id: databaseId, question }
|
|
80
|
-
};
|
|
56
|
+
return this.callMcpTool('generateSql', { database_id: databaseId, question });
|
|
81
57
|
}
|
|
82
58
|
|
|
83
59
|
/**
|
|
@@ -87,11 +63,7 @@ class AliyunDmsAdapter extends DatabaseManagerAdapter {
|
|
|
87
63
|
* @returns {Promise<object>}
|
|
88
64
|
*/
|
|
89
65
|
async optimizeSql(databaseId, sql) {
|
|
90
|
-
return {
|
|
91
|
-
mcpServer: this.mcpServer,
|
|
92
|
-
tool: this.resolveTool('optimizeSql'),
|
|
93
|
-
params: { database_id: databaseId, sql }
|
|
94
|
-
};
|
|
66
|
+
return this.callMcpTool('optimizeSql', { database_id: databaseId, sql });
|
|
95
67
|
}
|
|
96
68
|
}
|
|
97
69
|
|