flowmind 1.5.3 → 1.5.6

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 CHANGED
@@ -1,6 +1,36 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [1.5.6] - 2026-07-02
4
+
5
+ ### Added
6
+ - Added a `check-update` script and CLI command so already installed users can quickly see whether a newer npm release is available
7
+
8
+ ### Fixed
9
+ - TUI and dashboard now degrade gracefully on narrow or very small terminals instead of crashing on resize
10
+ - CLI TUI/dashboard startup now uses a shared Ink launcher with consistent stdin proxy cleanup
11
+ - Auto-flow remote checklist resolution now requires an exact service/pipeline descriptor match, so a neighboring service can no longer be started by index position alone
12
+ - Log audit now extracts quoted Chinese exception text as a query keyword, shows explicit no-records guidance, and surfaces raw MCP response summaries for TUI debugging
13
+ - Log audit now also tries to infer a keyword from natural-language log queries when the user forgets to add quotes
14
+ - Log audit and SLS log audit now share a natural-language parser for keyword, level, and time-window extraction
15
+ - Natural-language log queries now also infer service and project names when the user writes "项目" or "服务" in a loose sentence
16
+
17
+ ### Changed
18
+ - Sidebar, chat, status, and dashboard panels now switch to compact layouts when terminal width drops below the normal threshold
19
+ - Terminal resize handling now keeps the current session alive and shows a recovery hint instead of exiting on render errors
20
+
21
+ ## [1.5.4] - 2026-07-01
22
+
23
+ ### Added
24
+ - FlowMind now resolves deferred MCP invocations from skill outputs and executes them through the registered adapter when available
25
+ - Auto-flow, YApi, Yuque, and log audit skills now return concise execution summaries instead of raw MCP payload shapes
26
+
27
+ ### Fixed
28
+ - Repeated TUI submissions are now serialized to avoid multi-input crashes during fast follow-up commands
29
+ - CLI startup now falls back to ASCII-friendly banners in managed terminal environments that can crash on full graphics rendering
30
+
31
+ ### Changed
32
+ - `resource-bind` and related learned bindings now surface clearer resource summaries for follow-up skill reuse
33
+ - Result formatting now unwraps nested execution payloads so the TUI shows the actual output instead of intermediate envelopes
4
34
 
5
35
  ## [1.5.2] - 2026-07-01
6
36
 
package/README.md CHANGED
@@ -15,6 +15,8 @@
15
15
 
16
16
  </div>
17
17
 
18
+ Weekly downloads: 3,280
19
+
18
20
  ---
19
21
 
20
22
  ## What It Is
package/README_CN.md CHANGED
@@ -15,6 +15,8 @@
15
15
 
16
16
  </div>
17
17
 
18
+ 本周下载量:3,280 次
19
+
18
20
  ---
19
21
 
20
22
  ## 它是什么
package/bin/flowmind.js CHANGED
@@ -14,8 +14,10 @@ const path = require('path');
14
14
  const { execSync } = require('child_process');
15
15
  const FlowMind = require('../core');
16
16
  const HonorEngine = require('../core/honor-engine');
17
+ const { main: runUpdateCheck } = require('../scripts/check-update');
18
+ const { launchInkApp } = require('../core/cli-ink');
17
19
  const { syncSddAgentToFlowMind } = require('../core/sdd-agent-sync');
18
- const { detectManagedCliHost, shouldUseAsciiUi, shouldProxyInkStdin, getNestedTuiGuardMessage } = require('../core/utils');
20
+ const { detectManagedCliHost, shouldUseAsciiUi, getNestedTuiGuardMessage } = require('../core/utils');
19
21
  const { isExitCommand } = require('../tui/ui');
20
22
 
21
23
  /**
@@ -75,7 +77,19 @@ async function initFlowMind(options = {}) {
75
77
  /**
76
78
  * Display banner
77
79
  */
78
- function showBanner() {
80
+ function showBanner(asciiMode = false) {
81
+ if (asciiMode) {
82
+ console.log(chalk.cyan(`
83
+ +--------------------------------------------------+
84
+ | |
85
+ | FlowMind |
86
+ | The AI Agent That Learns How You Work |
87
+ | |
88
+ +--------------------------------------------------+
89
+ `));
90
+ return;
91
+ }
92
+
79
93
  console.log(chalk.cyan(`
80
94
  ╔══════════════════════════════════════════════════╗
81
95
  ║ ║
@@ -89,7 +103,71 @@ function showBanner() {
89
103
  /**
90
104
  * Dragon Totem ASCII Art by level - Chinese Dragon (中国龙)
91
105
  */
92
- function getDragonArt(level) {
106
+ function getDragonArt(level, asciiMode = false) {
107
+ if (asciiMode) {
108
+ const asciiArts = {
109
+ 0: [
110
+ ' /-----\\ ',
111
+ ' / /-\\ \\ ',
112
+ ' | | o | | ',
113
+ ' | | | | ',
114
+ ' | \\_/ | ',
115
+ ' \\ / ',
116
+ ' \\-----/ ',
117
+ ' Dragon Egg '
118
+ ],
119
+ 1: [
120
+ ' /--\\ ',
121
+ ' /----/ \\---\\ ',
122
+ ' / o \\/ \\ ',
123
+ ' \\ /\\ /\\ / ',
124
+ ' \\/\\/ \\/\\/ \\/ ',
125
+ ' Hatchling Dragon '
126
+ ],
127
+ 2: [
128
+ ' /-\\ /-\\ ',
129
+ ' /----/ \\/ \\---\\ ',
130
+ ' / o __ \\ ',
131
+ ' \\ /--------\\ / ',
132
+ ' \\---/ ////// \\/ ',
133
+ ' \\_/ \\_/ ',
134
+ ' Juvenile Dragon '
135
+ ],
136
+ 3: [
137
+ ' /---\\ /---\\ ',
138
+ ' /---/ \\/ \\---\\ ',
139
+ '/ o __ \\ ',
140
+ '| /----------\\ | ',
141
+ ' \\--/ //////// \\-/ ',
142
+ ' \\/ ////////// \\/ ',
143
+ ' \\_/ \\_/ ',
144
+ ' Adult Dragon '
145
+ ],
146
+ 4: [
147
+ ' /---\\ /---\\ ',
148
+ '/---/ \\____/ \\---\\ ',
149
+ '| o __ | ',
150
+ '| /------------\\ | ',
151
+ ' \\-/ //////////// \\-/ ',
152
+ ' / //////////// \\ ',
153
+ ' /_/ \\_\\ ',
154
+ ' Elder Dragon '
155
+ ],
156
+ 5: [
157
+ ' * /---\\ /---\\ * ',
158
+ '/-/ \\______/ \\-\\ ',
159
+ '| o __ | ',
160
+ '| /--------------\\ | ',
161
+ ' \\-/ /////**///// \\-/ ',
162
+ ' / ///// ///// \\ ',
163
+ ' /_/ ** ** \\_\\ ',
164
+ ' * \\______________/ * ',
165
+ ' Ascended Dragon '
166
+ ]
167
+ };
168
+ return asciiArts[level] || asciiArts[0];
169
+ }
170
+
93
171
  const arts = {
94
172
  0: [
95
173
  ' ╭─────╮ ',
@@ -192,11 +270,11 @@ function getHighlightColor(level) {
192
270
  /**
193
271
  * Render dragon totem with honor data
194
272
  */
195
- function renderDragonTotem(honorData) {
273
+ function renderDragonTotem(honorData, asciiMode = false) {
196
274
  const info = getLevelInfo(honorData.points);
197
275
  const dragonColor = getDragonColor(info.level);
198
276
  const highlightColor = getHighlightColor(info.level);
199
- const art = getDragonArt(info.level);
277
+ const art = getDragonArt(info.level, asciiMode);
200
278
 
201
279
  // Calculate max art line width for proper padding
202
280
  const maxArtWidth = Math.max(...art.map(l => l.length));
@@ -210,7 +288,8 @@ function renderDragonTotem(honorData) {
210
288
 
211
289
  console.log('');
212
290
  console.log(chalk.cyan(' ┌' + border + '┐'));
213
- console.log(chalk.cyan(' │') + ' 🐉 Dragon Totem of Honor ' + padRight(' 🐉 Dragon Totem of Honor ', boxWidth - 2) + chalk.cyan('│'));
291
+ const title = asciiMode ? ' Dragon Totem of Honor ' : ' 🐉 Dragon Totem of Honor ';
292
+ console.log(chalk.cyan(' │') + title + padRight(title, boxWidth - 2) + chalk.cyan('│'));
214
293
  console.log(chalk.cyan(' ├' + border + '┤'));
215
294
 
216
295
  for (const line of art) {
@@ -366,7 +445,7 @@ program
366
445
  .description('Initialize FlowMind in current directory')
367
446
  .option('--ai <provider>', 'Initialize with AI provider (openai/anthropic/glm/mimo/qwen/ernie/deepseek/ollama)')
368
447
  .action(async (options) => {
369
- showBanner();
448
+ showBanner(options.ascii || shouldUseAsciiUi());
370
449
 
371
450
  const spinner = ora('Initializing FlowMind...').start();
372
451
 
@@ -836,7 +915,7 @@ program
836
915
  } else if (options.publish) {
837
916
  await publishHonor({ output: options.output, gist: options.gist });
838
917
  } else {
839
- renderDragonTotem(honorData);
918
+ renderDragonTotem(honorData, shouldUseAsciiUi());
840
919
  console.log(chalk.gray(` ${getNextLevelHint(honorData.points)}`));
841
920
  }
842
921
  } catch (error) {
@@ -959,7 +1038,7 @@ program
959
1038
 
960
1039
  // Interactive mode
961
1040
  async function runInteractiveMode(fm) {
962
- showBanner();
1041
+ showBanner(shouldUseAsciiUi());
963
1042
  console.log(chalk.cyan('Interactive mode started. Type "exit" to quit.\n'));
964
1043
 
965
1044
  try {
@@ -1529,8 +1608,6 @@ program
1529
1608
  .description('Launch enhanced TUI with split panels, skill browser, and dragon display')
1530
1609
  .option('--ascii', 'Force ASCII-safe terminal rendering')
1531
1610
  .action(async (options) => {
1532
- let stdinWrapper = null;
1533
- let stdinForwarder = null;
1534
1611
  try {
1535
1612
  const managedHost = detectManagedCliHost();
1536
1613
  if (managedHost) {
@@ -1540,71 +1617,21 @@ program
1540
1617
  return;
1541
1618
  }
1542
1619
 
1543
- // Register .jsx extension for CJS
1544
- require('module')._extensions['.jsx'] = require('module')._extensions['.js'];
1545
-
1546
1620
  const React = require('react');
1547
- const { render } = require('ink');
1548
- const { PassThrough } = require('stream');
1549
1621
  const App = require('../tui/app.jsx');
1550
1622
  const asciiMode = options.ascii || shouldUseAsciiUi();
1551
1623
 
1552
1624
  const fm = await initFlowMind();
1553
-
1554
- const realStdin = process.stdin;
1555
- let inkStdin = realStdin;
1556
-
1557
- // Only proxy stdin when the host stream cannot satisfy Ink's raw-mode requirements.
1558
- if (shouldProxyInkStdin(realStdin)) {
1559
- stdinWrapper = new PassThrough();
1560
- stdinWrapper.isTTY = true;
1561
- stdinWrapper.isRaw = false;
1562
- stdinWrapper.setRawMode = (mode) => {
1563
- try {
1564
- if (realStdin.setRawMode) {
1565
- realStdin.setRawMode(mode);
1566
- }
1567
- } catch (e) {
1568
- // Suppress raw mode errors in non-TTY environments
1569
- }
1570
- return stdinWrapper;
1571
- };
1572
- // Forward real stdin data to the wrapper (store reference for cleanup)
1573
- if (realStdin.readable) {
1574
- stdinForwarder = (chunk) => {
1575
- if (!stdinWrapper.destroyed) {
1576
- try {
1577
- stdinWrapper.write(chunk);
1578
- } catch (e) {
1579
- // Ignore write-after-destroy errors
1580
- }
1581
- }
1582
- };
1583
- realStdin.on('data', stdinForwarder);
1584
- }
1585
-
1586
- inkStdin = stdinWrapper;
1587
- }
1588
-
1589
- const { unmount, waitUntilExit } = render(
1590
- React.createElement(App, { flowmind: fm, asciiMode: asciiMode }),
1591
- { stdin: inkStdin }
1592
- );
1593
- await waitUntilExit();
1594
- unmount();
1625
+ await launchInkApp({
1626
+ element: React.createElement(App, { flowmind: fm, asciiMode: asciiMode }),
1627
+ stdin: process.stdin
1628
+ });
1595
1629
  } catch (error) {
1596
1630
  console.error(chalk.red('TUI Error:'), error.message);
1597
1631
  if (error.message.includes('Cannot find module')) {
1598
1632
  console.log(chalk.yellow('Try running: npm install ink@3 react ink-text-input ink-spinner'));
1599
1633
  }
1600
1634
  } finally {
1601
- // Clean up stdin listener to prevent leak
1602
- if (stdinForwarder) {
1603
- process.stdin.removeListener('data', stdinForwarder);
1604
- }
1605
- if (stdinWrapper && !stdinWrapper.destroyed) {
1606
- stdinWrapper.destroy();
1607
- }
1608
1635
  restoreTerminal();
1609
1636
  }
1610
1637
  });
@@ -1615,8 +1642,6 @@ program
1615
1642
  .description('Launch real-time monitoring dashboard for MCP activity and events')
1616
1643
  .option('--ascii', 'Force ASCII-safe terminal rendering')
1617
1644
  .action(async (options) => {
1618
- let stdinWrapper = null;
1619
- let stdinForwarder = null;
1620
1645
  try {
1621
1646
  const managedHost = detectManagedCliHost();
1622
1647
  if (managedHost) {
@@ -1626,68 +1651,22 @@ program
1626
1651
  return;
1627
1652
  }
1628
1653
 
1629
- // Register .jsx extension for CJS
1630
- require('module')._extensions['.jsx'] = require('module')._extensions['.js'];
1631
-
1632
1654
  const React = require('react');
1633
- const { render } = require('ink');
1634
- const { PassThrough } = require('stream');
1635
1655
  const DashboardApp = require('../dashboard/app.jsx');
1636
1656
  const eventBus = require('../core/event-bus');
1637
1657
  const asciiMode = options.ascii || shouldUseAsciiUi();
1638
1658
 
1639
1659
  const fm = await initFlowMind();
1640
-
1641
- const realStdin = process.stdin;
1642
- let inkStdin = realStdin;
1643
-
1644
- // Only proxy stdin when the host stream cannot satisfy Ink's raw-mode requirements.
1645
- if (shouldProxyInkStdin(realStdin)) {
1646
- stdinWrapper = new PassThrough();
1647
- stdinWrapper.isTTY = true;
1648
- stdinWrapper.isRaw = false;
1649
- stdinWrapper.setRawMode = (mode) => {
1650
- try {
1651
- if (realStdin.setRawMode) {
1652
- realStdin.setRawMode(mode);
1653
- }
1654
- } catch (e) {
1655
- // Suppress raw mode errors in non-TTY environments
1656
- }
1657
- return stdinWrapper;
1658
- };
1659
- if (realStdin.readable) {
1660
- stdinForwarder = (chunk) => {
1661
- if (!stdinWrapper.destroyed) {
1662
- try {
1663
- stdinWrapper.write(chunk);
1664
- } catch (e) { /* ignore write-after-destroy */ }
1665
- }
1666
- };
1667
- realStdin.on('data', stdinForwarder);
1668
- }
1669
-
1670
- inkStdin = stdinWrapper;
1671
- }
1672
-
1673
- const { unmount, waitUntilExit } = render(
1674
- React.createElement(DashboardApp, { flowmind: fm, eventBus, asciiMode: asciiMode }),
1675
- { stdin: inkStdin }
1676
- );
1677
- await waitUntilExit();
1678
- unmount();
1660
+ await launchInkApp({
1661
+ element: React.createElement(DashboardApp, { flowmind: fm, eventBus, asciiMode: asciiMode }),
1662
+ stdin: process.stdin
1663
+ });
1679
1664
  } catch (error) {
1680
1665
  console.error(chalk.red('Dashboard Error:'), error.message);
1681
1666
  if (error.message.includes('Cannot find module')) {
1682
1667
  console.log(chalk.yellow('Try running: npm install ink@3 react'));
1683
1668
  }
1684
1669
  } finally {
1685
- if (stdinForwarder) {
1686
- process.stdin.removeListener('data', stdinForwarder);
1687
- }
1688
- if (stdinWrapper && !stdinWrapper.destroyed) {
1689
- stdinWrapper.destroy();
1690
- }
1691
1670
  restoreTerminal();
1692
1671
  }
1693
1672
  });
@@ -1813,10 +1792,17 @@ program
1813
1792
  }
1814
1793
  });
1815
1794
 
1795
+ program
1796
+ .command('check-update')
1797
+ .description('Check whether a newer FlowMind version is available')
1798
+ .action(async () => {
1799
+ await runUpdateCheck();
1800
+ });
1801
+
1816
1802
  // Parse arguments
1817
1803
  const knownCommands = new Set([
1818
1804
  'init', 'process', 'p', 'learn', 'scenes', 'skills', 'skill', 'resource', 'res',
1819
- 'stats', 'honor', 'config', 'ai', 'tui', 'dashboard', 'doctor', 'update', 'help'
1805
+ 'stats', 'honor', 'config', 'ai', 'tui', 'dashboard', 'doctor', 'update', 'check-update', 'help'
1820
1806
  ]);
1821
1807
  const cliArgs = process.argv.slice(2);
1822
1808
 
@@ -66,15 +66,10 @@ class McpAdapter extends BaseAdapter {
66
66
  return this.config.transport || this.config.mcpServerConfig || this.config.mcpTransport || {};
67
67
  }
68
68
 
69
- async callMcpTool(localName, args = {}) {
70
- const tool = this.resolveTool(localName);
71
- if (!tool) {
72
- throw new Error(`Unknown MCP tool mapping: ${localName}`);
73
- }
74
-
69
+ async callTool(tool, args = {}) {
75
70
  const transport = this.getTransportConfig();
76
71
  const serverName = this.mcpServer || this.providerName;
77
- const response = await callMcpTool({
72
+ return callMcpTool({
78
73
  url: transport.url || transport.endpoint || transport.baseUrl,
79
74
  headers: transport.headers || {},
80
75
  tool,
@@ -82,8 +77,14 @@ class McpAdapter extends BaseAdapter {
82
77
  timeoutMs: transport.timeoutMs || 60000,
83
78
  serverName
84
79
  });
80
+ }
85
81
 
86
- return response;
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);
87
88
  }
88
89
  }
89
90
 
@@ -82,7 +82,7 @@ class MiMoProvider extends BaseModel {
82
82
  ...super.getInfo(),
83
83
  model: this.model,
84
84
  baseUrl: this.baseUrl,
85
- provider: 'Xiaomi'
85
+ provider: 'MiMo'
86
86
  };
87
87
  }
88
88
  }
@@ -0,0 +1,79 @@
1
+ const { PassThrough } = require('stream');
2
+ const { shouldProxyInkStdin } = require('./utils');
3
+
4
+ function registerJsxExtension() {
5
+ require('module')._extensions['.jsx'] = require('module')._extensions['.js'];
6
+ }
7
+
8
+ function createInkStdinProxy(realStdin) {
9
+ if (!realStdin) {
10
+ return { stdin: null, wrapper: null, forwarder: null };
11
+ }
12
+
13
+ const wrapper = new PassThrough();
14
+ wrapper.isTTY = true;
15
+ wrapper.isRaw = false;
16
+ wrapper.setRawMode = (mode) => {
17
+ try {
18
+ if (realStdin.setRawMode) {
19
+ realStdin.setRawMode(mode);
20
+ }
21
+ } catch (error) {
22
+ // Ignore raw mode failures in wrapped environments.
23
+ }
24
+ return wrapper;
25
+ };
26
+
27
+ let forwarder = null;
28
+ if (realStdin.readable) {
29
+ forwarder = (chunk) => {
30
+ if (!wrapper.destroyed) {
31
+ try {
32
+ wrapper.write(chunk);
33
+ } catch (error) {
34
+ // Ignore write-after-destroy errors while shutting down.
35
+ }
36
+ }
37
+ };
38
+ realStdin.on('data', forwarder);
39
+ }
40
+
41
+ return { stdin: wrapper, wrapper, forwarder };
42
+ }
43
+
44
+ function cleanupInkStdinProxy(realStdin, proxy) {
45
+ if (!proxy) return;
46
+
47
+ if (proxy.forwarder && realStdin?.removeListener) {
48
+ realStdin.removeListener('data', proxy.forwarder);
49
+ }
50
+
51
+ if (proxy.wrapper && !proxy.wrapper.destroyed) {
52
+ proxy.wrapper.destroy();
53
+ }
54
+ }
55
+
56
+ async function launchInkApp({ element, stdin = process.stdin }) {
57
+ registerJsxExtension();
58
+ const { render } = require('ink');
59
+ const proxy = shouldProxyInkStdin(stdin) ? createInkStdinProxy(stdin) : { stdin: null, wrapper: null, forwarder: null };
60
+ let unmount = null;
61
+
62
+ try {
63
+ const rendered = render(element, { stdin: proxy.stdin || stdin });
64
+ unmount = rendered.unmount;
65
+ await rendered.waitUntilExit();
66
+ } finally {
67
+ if (typeof unmount === 'function') {
68
+ unmount();
69
+ }
70
+ cleanupInkStdinProxy(stdin, proxy);
71
+ }
72
+ }
73
+
74
+ module.exports = {
75
+ cleanupInkStdinProxy,
76
+ createInkStdinProxy,
77
+ launchInkApp,
78
+ registerJsxExtension
79
+ };
package/core/index.js CHANGED
@@ -217,6 +217,7 @@ class FlowMind {
217
217
 
218
218
  // Execute skill
219
219
  const result = await skill.execute(input, enhancedContext);
220
+ const resolvedResult = await this.resolveDeferredMcpResult(result, skill, enhancedContext);
220
221
 
221
222
  if (resourceBinding?.id) {
222
223
  await this.learning.markResourceBindingUsed(resourceBinding.id, {
@@ -239,7 +240,144 @@ class FlowMind {
239
240
  // Award honor point for skill use
240
241
  await this.honor.award('skill_use', `Used skill: ${skill.name}`);
241
242
 
242
- return result;
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;
243
381
  }
244
382
 
245
383
  /**