flowmind 1.4.8 → 1.5.1

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.
@@ -1,20 +1,8 @@
1
1
  const React = require('react');
2
2
  const { Box, Text } = require('ink');
3
+ const { LEVEL_COLORS, LEVEL_NAMES, LEVEL_STATES, getBorderStyle, getDragonArt } = require('../../tui/ui');
3
4
 
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 }) {
5
+ function DragonPanel({ flowmind, asciiMode = false }) {
18
6
  const [honorData, setHonorData] = React.useState({ points: 0, level: 0, stats: {} });
19
7
 
20
8
  React.useEffect(() => {
@@ -28,7 +16,7 @@ function DragonPanel({ flowmind }) {
28
16
  }, [flowmind]);
29
17
 
30
18
  const level = honorData.level || 0;
31
- const art = DRAGON_ARTS[level] || DRAGON_ARTS[0];
19
+ const art = getDragonArt(level, { asciiMode });
32
20
  const color = LEVEL_COLORS[level] || 'gray';
33
21
  const levelName = LEVEL_NAMES[level] || 'Unknown';
34
22
  const state = LEVEL_STATES[level] || 'unknown';
@@ -37,7 +25,7 @@ function DragonPanel({ flowmind }) {
37
25
  const pointsToNext = nextPoints !== null ? nextPoints - honorData.points : 0;
38
26
 
39
27
  return (
40
- React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'cyan', paddingX: 1, flexGrow: 1 },
28
+ React.createElement(Box, { flexDirection: 'column', borderStyle: getBorderStyle(asciiMode), borderColor: 'cyan', paddingX: 1, flexGrow: 1 },
41
29
  React.createElement(Text, { bold: true, color: 'cyan' }, 'Dragon Totem'),
42
30
  React.createElement(Box, { flexDirection: 'row', marginTop: 1 },
43
31
  React.createElement(Box, { flexDirection: 'column' },
@@ -1,7 +1,8 @@
1
1
  const React = require('react');
2
2
  const { Box, Text } = require('ink');
3
+ const { getBorderStyle } = require('../../tui/ui');
3
4
 
4
- function McpStatusBar({ eventBus }) {
5
+ function McpStatusBar({ eventBus, asciiMode = false }) {
5
6
  const [toolCount, setToolCount] = React.useState(0);
6
7
  const [lastCall, setLastCall] = React.useState(null);
7
8
  const [serverState, setServerState] = React.useState('running');
@@ -19,7 +20,7 @@ function McpStatusBar({ eventBus }) {
19
20
  const formatTime = (ts) => ts ? new Date(ts).toTimeString().substring(0, 8) : 'none';
20
21
 
21
22
  return (
22
- React.createElement(Box, { borderStyle: 'single', borderColor: 'gray', paddingX: 1, justifyContent: 'space-between' },
23
+ React.createElement(Box, { borderStyle: getBorderStyle(asciiMode), borderColor: 'gray', paddingX: 1, justifyContent: 'space-between' },
23
24
  React.createElement(Text, null,
24
25
  React.createElement(Text, { color: 'gray' }, 'MCP Server: '),
25
26
  React.createElement(Text, { color: 'green' }, serverState)
@@ -1,9 +1,8 @@
1
1
  const React = require('react');
2
2
  const { Box, Text } = require('ink');
3
+ const { LEVEL_NAMES, getBorderStyle, getProgressBar } = require('../../tui/ui');
3
4
 
4
- const LEVEL_NAMES = ['Egg', 'Hatchling', 'Juvenile', 'Adult', 'Elder', 'Ascended'];
5
-
6
- function StatsRow({ flowmind }) {
5
+ function StatsRow({ flowmind, asciiMode = false }) {
7
6
  const [honorData, setHonorData] = React.useState({ points: 0, level: 0, stats: {} });
8
7
  const [learningStats, setLearningStats] = React.useState({ totalRecords: 0, byType: {} });
9
8
  const [aiStatus, setAiStatus] = React.useState({ initialized: false, defaultProvider: 'none' });
@@ -22,12 +21,11 @@ function StatsRow({ flowmind }) {
22
21
 
23
22
  const barWidth = 16;
24
23
  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);
24
+ const progressBar = getProgressBar(barWidth, progress, asciiMode);
27
25
 
28
26
  return (
29
27
  React.createElement(Box, { flexDirection: 'row' },
30
- React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'yellow', paddingX: 1, width: '33%' },
28
+ React.createElement(Box, { flexDirection: 'column', borderStyle: getBorderStyle(asciiMode), borderColor: 'yellow', paddingX: 1, width: '33%' },
31
29
  React.createElement(Text, { bold: true, color: 'yellow' }, 'Honor'),
32
30
  React.createElement(Text, null,
33
31
  React.createElement(Text, { color: 'yellow' }, LEVEL_NAMES[honorData.level] || 'Egg'),
@@ -36,7 +34,7 @@ function StatsRow({ flowmind }) {
36
34
  React.createElement(Text, { color: 'green' }, progressBar),
37
35
  React.createElement(Text, { color: 'gray' }, honorData.points + '/100 pts')
38
36
  ),
39
- React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'cyan', paddingX: 1, width: '33%' },
37
+ React.createElement(Box, { flexDirection: 'column', borderStyle: getBorderStyle(asciiMode), borderColor: 'cyan', paddingX: 1, width: '33%' },
40
38
  React.createElement(Text, { bold: true, color: 'cyan' }, 'Learning'),
41
39
  React.createElement(Text, null,
42
40
  React.createElement(Text, { color: 'white' }, '' + (learningStats.totalRecords || 0)),
@@ -50,7 +48,7 @@ function StatsRow({ flowmind }) {
50
48
  React.createElement(Text, { color: aiStatus.initialized ? 'green' : 'red' }, aiStatus.initialized ? 'ok' : 'off')
51
49
  )
52
50
  ),
53
- React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'blue', paddingX: 1, width: '33%' },
51
+ React.createElement(Box, { flexDirection: 'column', borderStyle: getBorderStyle(asciiMode), borderColor: 'blue', paddingX: 1, width: '33%' },
54
52
  React.createElement(Text, { bold: true, color: 'blue' }, 'Components'),
55
53
  React.createElement(Text, { color: 'gray' }, 'Registry loaded'),
56
54
  React.createElement(Text, null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowmind",
3
- "version": "1.4.8",
3
+ "version": "1.5.1",
4
4
  "description": "Memory and workflow automation for MCP, Codex, and Claude Code. Reuse repeatable developer operations through skills and explicit feedback.",
5
5
  "main": "core/index.js",
6
6
  "bin": {
@@ -38,7 +38,7 @@
38
38
  "license": "MIT",
39
39
  "repository": {
40
40
  "type": "git",
41
- "url": "https://github.com/Eleven-M/flowmind.git"
41
+ "url": "git+https://github.com/Eleven-M/flowmind.git"
42
42
  },
43
43
  "bugs": {
44
44
  "url": "https://github.com/Eleven-M/flowmind/issues"
@@ -1,11 +1,8 @@
1
1
  /**
2
2
  * Auto Flow Skill
3
- * Define, execute, and manage complex multi-step workflows
3
+ * Route deployment and workflow requests to the configured workflow MCP adapter.
4
4
  */
5
5
 
6
- const fs = require('fs-extra');
7
- const path = require('path');
8
-
9
6
  const BUILTIN_WORKFLOWS = {
10
7
  'dev-workflow': {
11
8
  name: 'Development Workflow',
@@ -28,69 +25,95 @@ const BUILTIN_WORKFLOWS = {
28
25
  }
29
26
  };
30
27
 
28
+ const GENERIC_SERVICE_TOKENS = new Set([
29
+ 'flowmind', 'auto', 'flow', 'workflow', 'workflows', 'pipeline', 'pipelines',
30
+ 'deploy', 'deployment', 'run', 'start', 'status', 'list', 'show',
31
+ 'service', 'services', 'skill', 'skills', 'prod', 'gray', 'uat', 'test'
32
+ ]);
33
+
34
+ const TOOL_NAMES = {
35
+ listPipelines: 'flowListPipelines',
36
+ startPipelineRun: 'flowStartPipelineRun',
37
+ startBatchPipelineRun: 'flowStartBatchPipelineRun',
38
+ getPipelineRun: 'flowGetPipelineRun',
39
+ listPipelineRuns: 'flowListPipelineRuns'
40
+ };
41
+
31
42
  module.exports = {
32
- canHandle(input, context) {
43
+ canHandle(input) {
33
44
  if (!input) return false;
34
- return /自动流程|auto.*flow|工作流|workflow|执行流程|run.*flow/i.test(input);
45
+ if (/绑定|bind|保存|记住/i.test(input) && /(mcp|token|地址|host|endpoint|url)/i.test(input)) {
46
+ return false;
47
+ }
48
+ return /自动化|自动部署|auto.*flow|工作流|workflow|pipeline|部署|发布|上线|执行流程|运行流程/i.test(input);
35
49
  },
36
50
 
37
51
  async execute(input, context) {
38
52
  const params = parseFlowParams(input);
53
+ const workflow = createWorkflowClient(context);
39
54
 
40
- if (params.action === 'list') {
55
+ if (params.action === 'define') {
41
56
  return {
42
57
  type: 'result',
43
58
  skill: 'auto-flow',
44
- message: `Available workflows: ${Object.keys(BUILTIN_WORKFLOWS).join(', ')}`,
45
- data: { workflows: Object.entries(BUILTIN_WORKFLOWS).map(([k, v]) => ({ id: k, name: v.name, steps: v.steps.length })) },
59
+ message: 'Define a workflow in YAML format',
60
+ data: {
61
+ format: 'YAML workflow definition',
62
+ example: {
63
+ name: 'My Workflow',
64
+ steps: [
65
+ { id: 'step1', action: 'run-command', command: 'echo hello' },
66
+ { id: 'step2', skill: 'code-review', depends_on: ['step1'] }
67
+ ]
68
+ }
69
+ },
46
70
  input,
47
71
  timestamp: new Date().toISOString()
48
72
  };
49
73
  }
50
74
 
51
- if (params.action === 'run') {
52
- const workflow = BUILTIN_WORKFLOWS[params.workflow];
53
- if (!workflow) {
54
- return {
55
- type: 'error', skill: 'auto-flow',
56
- message: `Workflow not found: ${params.workflow}. Available: ${Object.keys(BUILTIN_WORKFLOWS).join(', ')}`,
57
- input, timestamp: new Date().toISOString()
58
- };
59
- }
75
+ if (!workflow.client) {
76
+ return buildNoAdapterResult(input, params);
77
+ }
78
+
79
+ if (params.action === 'status') {
80
+ return executeStatus(workflow, params, input);
81
+ }
60
82
 
83
+ if (params.action === 'deploy' || params.action === 'run') {
84
+ return executeDeploy(workflow, params, input);
85
+ }
86
+
87
+ if (params.action === 'list' || params.serviceNames.length > 0) {
88
+ const execution = await workflow.client.listPipelines(buildListPipelineParams(params));
61
89
  return {
62
90
  type: 'result',
63
91
  skill: 'auto-flow',
64
- message: `Starting workflow: ${workflow.name} (${workflow.steps.length} steps)`,
92
+ message: params.serviceNames.length > 0
93
+ ? `Resolved workflow query for: ${params.serviceNames.join(', ')}`
94
+ : 'Listing available deployment pipelines',
65
95
  data: {
66
- workflow: params.workflow,
67
- steps: workflow.steps.map(s => ({
68
- id: s.id,
69
- name: s.name || s.action || s.skill,
70
- depends_on: s.depends_on || [],
71
- status: 'pending'
72
- })),
73
- status: 'started'
96
+ action: 'list',
97
+ provider: workflow.provider,
98
+ binding: workflow.binding,
99
+ filters: buildListPipelineParams(params),
100
+ execution
74
101
  },
75
102
  input,
76
103
  timestamp: new Date().toISOString()
77
104
  };
78
105
  }
79
106
 
80
- if (params.action === 'define') {
107
+ if (params.workflow && BUILTIN_WORKFLOWS[params.workflow]) {
108
+ const workflowDef = BUILTIN_WORKFLOWS[params.workflow];
81
109
  return {
82
110
  type: 'result',
83
111
  skill: 'auto-flow',
84
- message: 'Define a workflow in YAML format',
112
+ message: `Workflow ready: ${workflowDef.name}`,
85
113
  data: {
86
- format: 'YAML workflow definition',
87
- example: {
88
- name: 'My Workflow',
89
- steps: [
90
- { id: 'step1', action: 'run-command', command: 'echo hello' },
91
- { id: 'step2', skill: 'code-review', depends_on: ['step1'] }
92
- ]
93
- }
114
+ workflow: params.workflow,
115
+ steps: workflowDef.steps,
116
+ provider: workflow.provider
94
117
  },
95
118
  input,
96
119
  timestamp: new Date().toISOString()
@@ -100,10 +123,17 @@ module.exports = {
100
123
  return {
101
124
  type: 'result',
102
125
  skill: 'auto-flow',
103
- message: 'Auto Flow. Available actions: list, run, define',
126
+ message: 'Auto Flow. Available actions: deploy, status, list, define',
104
127
  data: {
105
- actions: ['list - List workflows', 'run <name> - Run workflow', 'define - Define new workflow'],
106
- builtinWorkflows: Object.keys(BUILTIN_WORKFLOWS)
128
+ actions: [
129
+ 'deploy - Start pipeline run by service/pipeline',
130
+ 'status - Query pipeline run status',
131
+ 'list - Search available pipelines',
132
+ 'define - Define a custom workflow'
133
+ ],
134
+ builtinWorkflows: Object.keys(BUILTIN_WORKFLOWS),
135
+ provider: workflow.provider,
136
+ binding: workflow.binding
107
137
  },
108
138
  input,
109
139
  timestamp: new Date().toISOString()
@@ -111,14 +141,259 @@ module.exports = {
111
141
  }
112
142
  };
113
143
 
144
+ function createWorkflowClient(context) {
145
+ const adapter = context.componentRegistry?.getAdapter('workflow');
146
+ if (adapter) {
147
+ return {
148
+ client: adapter,
149
+ provider: adapter.providerName,
150
+ binding: context.resourceBinding?.componentType === 'workflow' ? context.resourceBinding : null
151
+ };
152
+ }
153
+
154
+ const binding = context.resourceBinding?.componentType === 'workflow'
155
+ ? context.resourceBinding
156
+ : null;
157
+
158
+ if (!binding?.mcpServer) {
159
+ return { client: null, provider: null, binding: null };
160
+ }
161
+
162
+ return {
163
+ client: {
164
+ providerName: binding.provider || 'workflow-binding',
165
+ async listPipelines(params) {
166
+ return { mcpServer: binding.mcpServer, tool: TOOL_NAMES.listPipelines, params };
167
+ },
168
+ async startPipelineRun(pipelineId) {
169
+ return { mcpServer: binding.mcpServer, tool: TOOL_NAMES.startPipelineRun, params: { pipelineId } };
170
+ },
171
+ async startBatchPipelineRun(params) {
172
+ return { mcpServer: binding.mcpServer, tool: TOOL_NAMES.startBatchPipelineRun, params };
173
+ },
174
+ async getPipelineRun(pipelineId, runId) {
175
+ return { mcpServer: binding.mcpServer, tool: TOOL_NAMES.getPipelineRun, params: { pipelineId, pipelineRunId: runId } };
176
+ },
177
+ async listPipelineRuns(pipelineId, params) {
178
+ return { mcpServer: binding.mcpServer, tool: TOOL_NAMES.listPipelineRuns, params: { pipelineId, ...(params || {}) } };
179
+ }
180
+ },
181
+ provider: binding.provider || 'workflow-binding',
182
+ binding
183
+ };
184
+ }
185
+
186
+ function buildNoAdapterResult(input, params) {
187
+ if (params.workflow && BUILTIN_WORKFLOWS[params.workflow]) {
188
+ const workflow = BUILTIN_WORKFLOWS[params.workflow];
189
+ return {
190
+ type: 'result',
191
+ skill: 'auto-flow',
192
+ message: `Workflow ready: ${workflow.name}`,
193
+ data: {
194
+ workflow: params.workflow,
195
+ steps: workflow.steps
196
+ },
197
+ input,
198
+ timestamp: new Date().toISOString()
199
+ };
200
+ }
201
+
202
+ return {
203
+ type: 'result',
204
+ skill: 'auto-flow',
205
+ message: 'Workflow service not configured. Connect friday-auto-flow first.',
206
+ data: {
207
+ params,
208
+ hint: 'Run `flowmind resource` to review current bindings, then save one like: `flowmind "绑定发布业务 mcp=friday-auto-flow token=xxx env=uat"`'
209
+ },
210
+ input,
211
+ timestamp: new Date().toISOString()
212
+ };
213
+ }
214
+
215
+ async function executeDeploy(workflow, params, input) {
216
+ if (params.pipelineId) {
217
+ const execution = await workflow.client.startPipelineRun(params.pipelineId);
218
+ return {
219
+ type: 'result',
220
+ skill: 'auto-flow',
221
+ message: `Starting pipeline ${params.pipelineId}`,
222
+ data: {
223
+ action: 'deploy',
224
+ pipelineId: params.pipelineId,
225
+ environment: params.environment,
226
+ provider: workflow.provider,
227
+ binding: workflow.binding,
228
+ execution
229
+ },
230
+ input,
231
+ timestamp: new Date().toISOString()
232
+ };
233
+ }
234
+
235
+ const listParams = buildListPipelineParams(params);
236
+ const resolveExecution = await workflow.client.listPipelines(listParams);
237
+ const batchParams = buildBatchRunParams(params);
238
+ const execution = await workflow.client.startBatchPipelineRun(batchParams);
239
+
240
+ return {
241
+ type: 'result',
242
+ skill: 'auto-flow',
243
+ message: params.serviceNames.length > 0
244
+ ? `Prepared deployment for: ${params.serviceNames.join(', ')}`
245
+ : `Prepared workflow deployment${params.workflow ? `: ${params.workflow}` : ''}`,
246
+ data: {
247
+ action: 'deploy',
248
+ services: params.serviceNames,
249
+ workflow: params.workflow,
250
+ environment: params.environment,
251
+ provider: workflow.provider,
252
+ binding: workflow.binding,
253
+ resolution: {
254
+ filters: listParams,
255
+ execution: resolveExecution
256
+ },
257
+ execution
258
+ },
259
+ input,
260
+ timestamp: new Date().toISOString()
261
+ };
262
+ }
263
+
264
+ async function executeStatus(workflow, params, input) {
265
+ let execution;
266
+ if (params.pipelineId && params.runId) {
267
+ execution = await workflow.client.getPipelineRun(params.pipelineId, params.runId);
268
+ } else if (params.pipelineId) {
269
+ execution = await workflow.client.listPipelineRuns(params.pipelineId, {
270
+ env: params.environment
271
+ });
272
+ } else {
273
+ execution = await workflow.client.listPipelines(buildListPipelineParams(params));
274
+ }
275
+
276
+ return {
277
+ type: 'result',
278
+ skill: 'auto-flow',
279
+ message: params.runId
280
+ ? `Querying run status for ${params.runId}`
281
+ : 'Querying pipeline status',
282
+ data: {
283
+ action: 'status',
284
+ services: params.serviceNames,
285
+ pipelineId: params.pipelineId,
286
+ runId: params.runId,
287
+ environment: params.environment,
288
+ provider: workflow.provider,
289
+ binding: workflow.binding,
290
+ execution
291
+ },
292
+ input,
293
+ timestamp: new Date().toISOString()
294
+ };
295
+ }
296
+
114
297
  function parseFlowParams(input) {
115
- const params = {};
116
- if (/列表|list/i.test(input)) params.action = 'list';
117
- if (/执行|run|start|运行/i.test(input)) params.action = 'run';
118
- if (/定义|define|创建|create/i.test(input)) params.action = 'define';
298
+ const params = {
299
+ action: null,
300
+ workflow: null,
301
+ serviceNames: [],
302
+ environment: null,
303
+ pipelineId: null,
304
+ runId: null
305
+ };
119
306
 
120
- const workflowMatch = input.match(/(?:workflow|流程|工作流)\s*[:=]?\s*(\S+)/i);
307
+ if (/列表|list|查看.*流程|查看.*pipeline|查询.*流程|搜索.*流程|show|find|search/i.test(input)) {
308
+ params.action = 'list';
309
+ }
310
+ if (/状态|status|进度|运行记录|run record|record/i.test(input)) {
311
+ params.action = 'status';
312
+ }
313
+ if (/定义|define|创建流程|create.*workflow/i.test(input)) {
314
+ params.action = 'define';
315
+ }
316
+ if (/部署|发布|上线|deploy|release/i.test(input)) {
317
+ params.action = 'deploy';
318
+ } else if (/执行|run|start|运行/i.test(input)) {
319
+ params.action = 'run';
320
+ }
321
+
322
+ const workflowMatch = input.match(/(?:workflow|流程|工作流)\s*[:=]?\s*([A-Za-z0-9._-]+)/i);
121
323
  if (workflowMatch) params.workflow = workflowMatch[1];
122
324
 
325
+ const pipelineIdMatch = input.match(/(?:pipelineId|pipeline_id|pipeline)\s*[:=]?\s*([A-Za-z0-9._-]+)/i);
326
+ if (pipelineIdMatch) params.pipelineId = pipelineIdMatch[1];
327
+
328
+ const runIdMatch = input.match(/(?:runId|run_id|pipelineRunId|运行id)\s*[:=]?\s*([A-Za-z0-9._-]+)/i);
329
+ if (runIdMatch) params.runId = runIdMatch[1];
330
+
331
+ const envMatch = input.match(/(?:环境|env|environment)\s*[:=]?\s*(test|uat|gray|prod|dev|sit|staging|production|测试|预发|灰度|生产)/i);
332
+ if (envMatch) {
333
+ params.environment = normalizeEnvironment(envMatch[1]);
334
+ } else {
335
+ const inferredEnv = inferEnvironmentFromText(input);
336
+ if (inferredEnv) params.environment = inferredEnv;
337
+ }
338
+
339
+ params.serviceNames = extractServiceNames(input);
340
+
341
+ if (!params.workflow && /deploy-workflow|dev-workflow/i.test(input)) {
342
+ params.workflow = input.match(/deploy-workflow|dev-workflow/i)[0];
343
+ }
344
+
345
+ if (!params.action && params.serviceNames.length > 0) {
346
+ params.action = 'list';
347
+ }
348
+
123
349
  return params;
124
350
  }
351
+
352
+ function buildListPipelineParams(params) {
353
+ return compactObject({
354
+ keyword: params.serviceNames.join(',') || params.workflow || undefined,
355
+ env: params.environment,
356
+ services: params.serviceNames.length > 0 ? params.serviceNames : undefined,
357
+ pipelineId: params.pipelineId
358
+ });
359
+ }
360
+
361
+ function buildBatchRunParams(params) {
362
+ return compactObject({
363
+ env: params.environment,
364
+ services: params.serviceNames.length > 0 ? params.serviceNames : undefined,
365
+ pipelineNames: params.serviceNames.length > 0 ? params.serviceNames : undefined,
366
+ workflow: params.workflow,
367
+ pipelineId: params.pipelineId
368
+ });
369
+ }
370
+
371
+ function extractServiceNames(input) {
372
+ const rawTokens = input.match(/[A-Za-z][A-Za-z0-9._-]{2,}/g) || [];
373
+ return [...new Set(rawTokens.filter((token) => {
374
+ const normalized = token.toLowerCase();
375
+ if (GENERIC_SERVICE_TOKENS.has(normalized)) return false;
376
+ return normalized.includes('-') || normalized.includes('_') || normalized.endsWith('service');
377
+ }))];
378
+ }
379
+
380
+ function inferEnvironmentFromText(input) {
381
+ if (/生产|prod|production/i.test(input)) return 'prod';
382
+ if (/灰度|gray/i.test(input)) return 'gray';
383
+ if (/预发|uat/i.test(input)) return 'uat';
384
+ if (/测试|test|dev|sit/i.test(input)) return 'test';
385
+ return null;
386
+ }
387
+
388
+ function normalizeEnvironment(value) {
389
+ const normalized = String(value).toLowerCase();
390
+ if (['生产', 'prod', 'production'].includes(normalized)) return 'prod';
391
+ if (['灰度', 'gray'].includes(normalized)) return 'gray';
392
+ if (['预发', 'uat', 'staging'].includes(normalized)) return 'uat';
393
+ if (['测试', 'test', 'dev', 'sit'].includes(normalized)) return 'test';
394
+ return normalized;
395
+ }
396
+
397
+ function compactObject(value) {
398
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== null && item !== ''));
399
+ }
@@ -12,6 +12,7 @@ module.exports = {
12
12
  async execute(input, context) {
13
13
  const registry = context.componentRegistry;
14
14
  const params = parseValidationParams(input);
15
+ const learnedBinding = context.resourceBinding;
15
16
 
16
17
  // Check MCP component availability
17
18
  const dbManager = registry?.getAdapter('databaseManager');
@@ -25,7 +26,7 @@ module.exports = {
25
26
  };
26
27
 
27
28
  if (params.action === 'sql') {
28
- if (!dbQuery) {
29
+ if (!dbQuery && !learnedBinding) {
29
30
  return {
30
31
  type: 'result',
31
32
  skill: 'data-logic-validation',
@@ -44,7 +45,8 @@ module.exports = {
44
45
  action: 'validate_sql',
45
46
  query: params.query,
46
47
  mcpTool: 'mcpQueryExec',
47
- availableComponents
48
+ availableComponents,
49
+ binding: learnedBinding
48
50
  },
49
51
  input,
50
52
  timestamp: new Date().toISOString()
@@ -52,7 +54,7 @@ module.exports = {
52
54
  }
53
55
 
54
56
  if (params.action === 'redis') {
55
- if (!redisMonitor) {
57
+ if (!redisMonitor && !learnedBinding) {
56
58
  return {
57
59
  type: 'result',
58
60
  skill: 'data-logic-validation',
@@ -71,7 +73,8 @@ module.exports = {
71
73
  action: 'validate_redis',
72
74
  key: params.key,
73
75
  mcpTools: ['mcpRedisKeyGet', 'mcpRedisKeyInfo', 'mcpRedisKeys'],
74
- availableComponents
76
+ availableComponents,
77
+ binding: learnedBinding
75
78
  },
76
79
  input,
77
80
  timestamp: new Date().toISOString()
@@ -85,7 +88,8 @@ module.exports = {
85
88
  data: {
86
89
  actions: ['sql - Validate SQL queries', 'redis - Validate Redis operations'],
87
90
  availableComponents,
88
- requiredMCP: ['databaseManager', 'databaseQuery', 'redisMonitor']
91
+ requiredMCP: ['databaseManager', 'databaseQuery', 'redisMonitor'],
92
+ binding: learnedBinding
89
93
  },
90
94
  input,
91
95
  timestamp: new Date().toISOString()
@@ -29,6 +29,11 @@ Manage database, Redis, API, and other external resource connections.
29
29
  - Authentication management
30
30
  - Request/response handling
31
31
 
32
+ ### 🧠 Business Binding Memory
33
+ - Save MCP server, address, token, namespace, project, and similar connection fields by business
34
+ - Reuse learned bindings automatically when the same business appears again
35
+ - Keep binding records in local learning files so users do not need to re-enter the same setup repeatedly
36
+
32
37
  ## Trigger Patterns
33
38
 
34
39
  ```
@@ -102,6 +107,7 @@ This skill supports FlowMind learning:
102
107
  - **Connection Method**: Learns preferred connection method
103
108
  - **Default Database**: Learns frequently used databases
104
109
  - **Query Style**: Learns query formatting preferences
110
+ - **Business Resource Binding**: Learns MCP/address/token configuration by business context
105
111
 
106
112
  ```
107
113
  User: "用 source_id 连接"
@@ -111,6 +117,20 @@ User: [Next connection]
111
117
  FlowMind: [Uses source_id automatically]
112
118
  ```
113
119
 
120
+ ### Example 3: Business Binding
121
+
122
+ ```
123
+ User: 绑定零售业务 mcp=yapi-mcp 地址=https://yapi.example.com token=xxx project=retail
124
+
125
+ FlowMind:
126
+ ✓ Saved learned binding for business: 零售
127
+
128
+ User: 同步零售业务接口到 YApi
129
+
130
+ FlowMind:
131
+ [Reuses learned YApi MCP/address/project binding automatically]
132
+ ```
133
+
114
134
  ## Examples
115
135
 
116
136
  ### Example 1: Database Connection
@@ -219,4 +239,4 @@ FlowMind:
219
239
  - Passwords are encrypted at rest
220
240
  - Connections use TLS when available
221
241
  - Credentials are never logged
222
- - Connection pooling prevents exhaustion
242
+ - Connection pooling prevents exhaustion