flowmind 1.4.7 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +34 -880
- package/README_CN.md +33 -1130
- package/bin/flowmind.js +219 -55
- package/core/adapters/workflow-adapter.js +9 -0
- package/core/config-manager.js +8 -1
- package/core/index.js +33 -17
- package/core/learning-engine.js +406 -0
- package/core/providers/friday/flow-adapter.js +8 -0
- package/core/scene-matcher.js +5 -1
- package/core/sdd-agent-sync.js +467 -0
- package/core/skill-loader.js +51 -10
- package/core/utils.js +55 -1
- package/dashboard/app.jsx +5 -5
- package/dashboard/components/ActivityFeed.jsx +7 -6
- package/dashboard/components/DragonPanel.jsx +4 -16
- package/dashboard/components/McpStatusBar.jsx +3 -2
- package/dashboard/components/StatsRow.jsx +6 -8
- package/docs/guide.md +151 -0
- package/docs/guide.zh-CN.md +151 -0
- package/package.json +4 -2
- package/skills/auto-flow/index.js +320 -45
- package/skills/data-logic-validation/index.js +9 -5
- package/skills/resource-bind/SKILL.md +21 -1
- package/skills/resource-bind/index.js +206 -14
- package/skills/yapi-sync-interface/index.js +14 -7
- package/skills/yuque-sync-design/index.js +15 -8
- package/tui/app.jsx +44 -28
- package/tui/components/ChatPanel.jsx +55 -43
- package/tui/components/DragonTotem.jsx +4 -73
- package/tui/components/Sidebar.jsx +7 -7
- package/tui/components/StatusBar.jsx +5 -6
- package/tui/format-result.js +164 -0
- package/tui/ui.js +186 -0
package/bin/flowmind.js
CHANGED
|
@@ -14,6 +14,9 @@ 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 { syncSddAgentToFlowMind } = require('../core/sdd-agent-sync');
|
|
18
|
+
const { detectManagedCliHost, shouldUseAsciiUi, shouldProxyInkStdin, getNestedTuiGuardMessage } = require('../core/utils');
|
|
19
|
+
const { isExitCommand } = require('../tui/ui');
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* Restore terminal to sane state (cancel raw mode, show cursor, reset colors)
|
|
@@ -714,19 +717,32 @@ program
|
|
|
714
717
|
program
|
|
715
718
|
.command('resource')
|
|
716
719
|
.alias('res')
|
|
717
|
-
.description('Manage local resource files')
|
|
720
|
+
.description('Manage resource connections, learned bindings, and local resource files')
|
|
718
721
|
.option('-l, --list [dir]', 'List resource directory')
|
|
719
722
|
.option('-s, --show <file>', 'Show file content')
|
|
720
723
|
.option('-e, --edit <file>', 'Edit file')
|
|
721
724
|
.option('-c, --config', 'Show resource configuration')
|
|
725
|
+
.option('--sync-sdd-agent [dir]', 'Sync local SDD-Agent resources and scenes into FlowMind')
|
|
722
726
|
.option('-j, --json', 'Output as JSON (for tool integration)')
|
|
723
727
|
.action(async (options) => {
|
|
724
728
|
try {
|
|
725
729
|
const fm = await initFlowMind();
|
|
726
730
|
const resourceConfig = fm.config.get('resources', {});
|
|
727
731
|
const configDir = path.join(process.env.HOME || process.env.USERPROFILE, '.flowmind');
|
|
732
|
+
const componentStatus = fm.getComponentStatus ? fm.getComponentStatus() : {};
|
|
733
|
+
const bindings = await fm.learning.listResourceBindings();
|
|
728
734
|
|
|
729
|
-
if (options.
|
|
735
|
+
if (options.syncSddAgent !== undefined) {
|
|
736
|
+
const summary = await syncSddAgentToFlowMind({
|
|
737
|
+
sourceDir: typeof options.syncSddAgent === 'string' ? options.syncSddAgent : undefined
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
if (options.json) {
|
|
741
|
+
console.log(JSON.stringify({ synced: summary }, null, 2));
|
|
742
|
+
} else {
|
|
743
|
+
displayResourceSyncSummary(summary);
|
|
744
|
+
}
|
|
745
|
+
} else if (options.config) {
|
|
730
746
|
// Show resource configuration
|
|
731
747
|
if (options.json) {
|
|
732
748
|
console.log(JSON.stringify({ resources: resourceConfig }, null, 2));
|
|
@@ -752,7 +768,7 @@ program
|
|
|
752
768
|
// Edit file
|
|
753
769
|
const filePath = resolveResourcePath(options.edit, configDir);
|
|
754
770
|
await openInEditor(filePath);
|
|
755
|
-
} else {
|
|
771
|
+
} else if (options.list !== undefined) {
|
|
756
772
|
// List directory (default)
|
|
757
773
|
const targetDir = options.list && typeof options.list === 'string'
|
|
758
774
|
? resolveResourcePath(options.list, configDir)
|
|
@@ -768,6 +784,20 @@ program
|
|
|
768
784
|
} else {
|
|
769
785
|
console.error(chalk.red(`Directory not found: ${targetDir}`));
|
|
770
786
|
}
|
|
787
|
+
} else if (options.json) {
|
|
788
|
+
console.log(JSON.stringify({
|
|
789
|
+
resources: resourceConfig,
|
|
790
|
+
componentStatus,
|
|
791
|
+
bindings,
|
|
792
|
+
examples: getResourceSetupExamples()
|
|
793
|
+
}, null, 2));
|
|
794
|
+
} else {
|
|
795
|
+
displayResourceOverview({
|
|
796
|
+
resourceConfig,
|
|
797
|
+
componentStatus,
|
|
798
|
+
bindings,
|
|
799
|
+
configDir
|
|
800
|
+
});
|
|
771
801
|
}
|
|
772
802
|
} catch (error) {
|
|
773
803
|
console.error(chalk.red('Error:'), error.message);
|
|
@@ -951,7 +981,7 @@ async function runInteractiveMode(fm) {
|
|
|
951
981
|
break;
|
|
952
982
|
}
|
|
953
983
|
|
|
954
|
-
if (
|
|
984
|
+
if (isExitCommand(input)) {
|
|
955
985
|
console.log(chalk.cyan('\nGoodbye! FlowMind will remember your preferences. 👋\n'));
|
|
956
986
|
break;
|
|
957
987
|
}
|
|
@@ -1337,6 +1367,99 @@ function displayResourceConfig(config) {
|
|
|
1337
1367
|
console.log(chalk.cyan('└─────────────────────────────────────────────────────┘'));
|
|
1338
1368
|
}
|
|
1339
1369
|
|
|
1370
|
+
function getResourceSetupExamples() {
|
|
1371
|
+
return [
|
|
1372
|
+
'flowmind resource',
|
|
1373
|
+
'flowmind resource --config',
|
|
1374
|
+
'flowmind "绑定零售业务 mcp=yapi-mcp 地址=https://yapi.example.com token=xxx project=retail"',
|
|
1375
|
+
'flowmind "绑定发布业务 mcp=friday-auto-flow token=xxx env=uat"',
|
|
1376
|
+
'flowmind "同步零售业务接口到 YApi"',
|
|
1377
|
+
'flowmind "部署零售服务到 UAT"'
|
|
1378
|
+
];
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
function maskSecret(value) {
|
|
1382
|
+
if (!value) return null;
|
|
1383
|
+
const stringValue = String(value);
|
|
1384
|
+
if (stringValue.length <= 6) return '***';
|
|
1385
|
+
return `${stringValue.slice(0, 3)}***${stringValue.slice(-2)}`;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
function describeConnection(binding) {
|
|
1389
|
+
const parts = [];
|
|
1390
|
+
if (binding.mcpServer) parts.push(`mcp=${binding.mcpServer}`);
|
|
1391
|
+
if (binding.connection?.address) parts.push(`address=${binding.connection.address}`);
|
|
1392
|
+
if (binding.connection?.project) parts.push(`project=${binding.connection.project}`);
|
|
1393
|
+
if (binding.connection?.namespace) parts.push(`namespace=${binding.connection.namespace}`);
|
|
1394
|
+
if (binding.connection?.env) parts.push(`env=${binding.connection.env}`);
|
|
1395
|
+
if (binding.connection?.token) parts.push(`token=${maskSecret(binding.connection.token)}`);
|
|
1396
|
+
return parts;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
function displayResourceOverview({ resourceConfig, componentStatus, bindings, configDir }) {
|
|
1400
|
+
const configuredResources = Object.keys(resourceConfig || {});
|
|
1401
|
+
const activeComponents = Object.entries(componentStatus || {}).filter(([, value]) => value?.active);
|
|
1402
|
+
|
|
1403
|
+
console.log(chalk.cyan('\nFlowMind Resource Overview'));
|
|
1404
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
1405
|
+
console.log(chalk.white(`Config dir: ${configDir}`));
|
|
1406
|
+
console.log(chalk.white(`Configured resources: ${configuredResources.length}`));
|
|
1407
|
+
console.log(chalk.white(`Active components: ${activeComponents.length}/${Object.keys(componentStatus || {}).length}`));
|
|
1408
|
+
console.log(chalk.white(`Learned bindings: ${bindings.length}`));
|
|
1409
|
+
|
|
1410
|
+
console.log(chalk.cyan('\nActive Components'));
|
|
1411
|
+
if (activeComponents.length === 0) {
|
|
1412
|
+
console.log(chalk.gray(' (none)'));
|
|
1413
|
+
} else {
|
|
1414
|
+
for (const [name, info] of activeComponents) {
|
|
1415
|
+
const provider = info.provider ? ` provider=${info.provider}` : '';
|
|
1416
|
+
console.log(chalk.white(` - ${name}${provider}`));
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
console.log(chalk.cyan('\nLearned Bindings'));
|
|
1421
|
+
if (bindings.length === 0) {
|
|
1422
|
+
console.log(chalk.gray(' (none)'));
|
|
1423
|
+
console.log(chalk.gray(' Save one with natural language, for example:'));
|
|
1424
|
+
console.log(chalk.white(' flowmind "绑定零售业务 mcp=yapi-mcp 地址=https://yapi.example.com token=xxx project=retail"'));
|
|
1425
|
+
} else {
|
|
1426
|
+
for (const binding of bindings.slice(0, 8)) {
|
|
1427
|
+
const label = `${binding.business || 'default'} [${binding.componentType || 'unknown'}]`;
|
|
1428
|
+
console.log(chalk.white(` - ${label}`));
|
|
1429
|
+
const details = describeConnection(binding);
|
|
1430
|
+
if (details.length > 0) {
|
|
1431
|
+
console.log(chalk.gray(` ${details.join(' ')}`));
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
if (bindings.length > 8) {
|
|
1435
|
+
console.log(chalk.gray(` ... and ${bindings.length - 8} more`));
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
console.log(chalk.cyan('\nCommon Commands'));
|
|
1440
|
+
for (const example of getResourceSetupExamples()) {
|
|
1441
|
+
console.log(chalk.white(` ${example}`));
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
console.log(chalk.cyan('\nNext Step'));
|
|
1445
|
+
console.log(chalk.white(' For deployment workflows, bind `friday-auto-flow` first, then run your deploy request in natural language.'));
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function displayResourceSyncSummary(summary) {
|
|
1449
|
+
console.log(chalk.cyan('\nFlowMind Sync Complete'));
|
|
1450
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
1451
|
+
console.log(chalk.white(`Source: ${summary.sourceDir}`));
|
|
1452
|
+
console.log(chalk.white(`Target: ${summary.targetDir}`));
|
|
1453
|
+
console.log(chalk.white(`Bindings imported: ${summary.counts.importedBindings}`));
|
|
1454
|
+
console.log(chalk.white(`Scenes imported: ${summary.counts.importedScenes}`));
|
|
1455
|
+
console.log(chalk.white(`Components synced: ${summary.counts.components}`));
|
|
1456
|
+
console.log(chalk.gray('\nLocal files updated:'));
|
|
1457
|
+
console.log(chalk.white(` ${summary.files.resourceConfig}`));
|
|
1458
|
+
console.log(chalk.white(` ${summary.files.componentConfig}`));
|
|
1459
|
+
console.log(chalk.white(` ${summary.files.resourceBindings}`));
|
|
1460
|
+
console.log(chalk.white(` ${summary.files.scenes}`));
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1340
1463
|
function displayAIStatus(status) {
|
|
1341
1464
|
console.log(chalk.cyan('\n┌─────────────────────────────────────────────────────┐'));
|
|
1342
1465
|
console.log(chalk.cyan('│ AI Model Status │'));
|
|
@@ -1404,10 +1527,19 @@ async function openInEditor(filePath) {
|
|
|
1404
1527
|
program
|
|
1405
1528
|
.command('tui')
|
|
1406
1529
|
.description('Launch enhanced TUI with split panels, skill browser, and dragon display')
|
|
1407
|
-
.
|
|
1530
|
+
.option('--ascii', 'Force ASCII-safe terminal rendering')
|
|
1531
|
+
.action(async (options) => {
|
|
1408
1532
|
let stdinWrapper = null;
|
|
1409
1533
|
let stdinForwarder = null;
|
|
1410
1534
|
try {
|
|
1535
|
+
const managedHost = detectManagedCliHost();
|
|
1536
|
+
if (managedHost) {
|
|
1537
|
+
console.error(chalk.red(`TUI is not supported inside ${managedHost.name}.`));
|
|
1538
|
+
console.log(chalk.yellow(getNestedTuiGuardMessage(managedHost)));
|
|
1539
|
+
process.exitCode = 1;
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1411
1543
|
// Register .jsx extension for CJS
|
|
1412
1544
|
require('module')._extensions['.jsx'] = require('module')._extensions['.js'];
|
|
1413
1545
|
|
|
@@ -1415,42 +1547,48 @@ program
|
|
|
1415
1547
|
const { render } = require('ink');
|
|
1416
1548
|
const { PassThrough } = require('stream');
|
|
1417
1549
|
const App = require('../tui/app.jsx');
|
|
1550
|
+
const asciiMode = options.ascii || shouldUseAsciiUi();
|
|
1418
1551
|
|
|
1419
1552
|
const fm = await initFlowMind();
|
|
1420
1553
|
|
|
1421
|
-
// Create a stdin wrapper to handle non-TTY environments (e.g., piped stdin).
|
|
1422
|
-
// Ink v3's useInput hook calls setRawMode(true) which throws if stdin is not a TTY.
|
|
1423
1554
|
const realStdin = process.stdin;
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
return stdinWrapper;
|
|
1436
|
-
};
|
|
1437
|
-
// Forward real stdin data to the wrapper (store reference for cleanup)
|
|
1438
|
-
if (realStdin.readable) {
|
|
1439
|
-
stdinForwarder = (chunk) => {
|
|
1440
|
-
if (!stdinWrapper.destroyed) {
|
|
1441
|
-
try {
|
|
1442
|
-
stdinWrapper.write(chunk);
|
|
1443
|
-
} catch (e) {
|
|
1444
|
-
// Ignore write-after-destroy errors
|
|
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);
|
|
1445
1566
|
}
|
|
1567
|
+
} catch (e) {
|
|
1568
|
+
// Suppress raw mode errors in non-TTY environments
|
|
1446
1569
|
}
|
|
1570
|
+
return stdinWrapper;
|
|
1447
1571
|
};
|
|
1448
|
-
|
|
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;
|
|
1449
1587
|
}
|
|
1450
1588
|
|
|
1451
1589
|
const { unmount, waitUntilExit } = render(
|
|
1452
|
-
React.createElement(App, { flowmind: fm }),
|
|
1453
|
-
{ stdin:
|
|
1590
|
+
React.createElement(App, { flowmind: fm, asciiMode: asciiMode }),
|
|
1591
|
+
{ stdin: inkStdin }
|
|
1454
1592
|
);
|
|
1455
1593
|
await waitUntilExit();
|
|
1456
1594
|
unmount();
|
|
@@ -1475,10 +1613,19 @@ program
|
|
|
1475
1613
|
program
|
|
1476
1614
|
.command('dashboard')
|
|
1477
1615
|
.description('Launch real-time monitoring dashboard for MCP activity and events')
|
|
1478
|
-
.
|
|
1616
|
+
.option('--ascii', 'Force ASCII-safe terminal rendering')
|
|
1617
|
+
.action(async (options) => {
|
|
1479
1618
|
let stdinWrapper = null;
|
|
1480
1619
|
let stdinForwarder = null;
|
|
1481
1620
|
try {
|
|
1621
|
+
const managedHost = detectManagedCliHost();
|
|
1622
|
+
if (managedHost) {
|
|
1623
|
+
console.error(chalk.red(`Dashboard is not supported inside ${managedHost.name}.`));
|
|
1624
|
+
console.log(chalk.yellow(getNestedTuiGuardMessage(managedHost)));
|
|
1625
|
+
process.exitCode = 1;
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1482
1629
|
// Register .jsx extension for CJS
|
|
1483
1630
|
require('module')._extensions['.jsx'] = require('module')._extensions['.js'];
|
|
1484
1631
|
|
|
@@ -1487,38 +1634,45 @@ program
|
|
|
1487
1634
|
const { PassThrough } = require('stream');
|
|
1488
1635
|
const DashboardApp = require('../dashboard/app.jsx');
|
|
1489
1636
|
const eventBus = require('../core/event-bus');
|
|
1637
|
+
const asciiMode = options.ascii || shouldUseAsciiUi();
|
|
1490
1638
|
|
|
1491
1639
|
const fm = await initFlowMind();
|
|
1492
1640
|
|
|
1493
|
-
// Create a stdin wrapper to handle non-TTY environments
|
|
1494
1641
|
const realStdin = process.stdin;
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
stdinForwarder = (chunk) => {
|
|
1510
|
-
if (!stdinWrapper.destroyed) {
|
|
1511
|
-
try {
|
|
1512
|
-
stdinWrapper.write(chunk);
|
|
1513
|
-
} catch (e) { /* ignore write-after-destroy */ }
|
|
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
|
|
1514
1656
|
}
|
|
1657
|
+
return stdinWrapper;
|
|
1515
1658
|
};
|
|
1516
|
-
realStdin.
|
|
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;
|
|
1517
1671
|
}
|
|
1518
1672
|
|
|
1519
1673
|
const { unmount, waitUntilExit } = render(
|
|
1520
|
-
React.createElement(DashboardApp, { flowmind: fm, eventBus }),
|
|
1521
|
-
{ stdin:
|
|
1674
|
+
React.createElement(DashboardApp, { flowmind: fm, eventBus, asciiMode: asciiMode }),
|
|
1675
|
+
{ stdin: inkStdin }
|
|
1522
1676
|
);
|
|
1523
1677
|
await waitUntilExit();
|
|
1524
1678
|
unmount();
|
|
@@ -1660,6 +1814,16 @@ program
|
|
|
1660
1814
|
});
|
|
1661
1815
|
|
|
1662
1816
|
// Parse arguments
|
|
1817
|
+
const knownCommands = new Set([
|
|
1818
|
+
'init', 'process', 'p', 'learn', 'scenes', 'skills', 'skill', 'resource', 'res',
|
|
1819
|
+
'stats', 'honor', 'config', 'ai', 'tui', 'dashboard', 'doctor', 'update', 'help'
|
|
1820
|
+
]);
|
|
1821
|
+
const cliArgs = process.argv.slice(2);
|
|
1822
|
+
|
|
1823
|
+
if (cliArgs.length > 0 && !cliArgs[0].startsWith('-') && !knownCommands.has(cliArgs[0])) {
|
|
1824
|
+
process.argv.splice(2, 0, 'process');
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1663
1827
|
program.parse(process.argv);
|
|
1664
1828
|
|
|
1665
1829
|
// Default to interactive mode if no command specified
|
|
@@ -38,6 +38,15 @@ class WorkflowAdapter extends McpAdapter {
|
|
|
38
38
|
throw new Error('Subclasses must implement startPipelineRun()');
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Start a batch pipeline run.
|
|
43
|
+
* @param {object} params
|
|
44
|
+
* @returns {Promise<object>}
|
|
45
|
+
*/
|
|
46
|
+
async startBatchPipelineRun(params) {
|
|
47
|
+
throw new Error('Subclasses must implement startBatchPipelineRun()');
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
/**
|
|
42
51
|
* Get pipeline run status.
|
|
43
52
|
* @param {string} pipelineId
|
package/core/config-manager.js
CHANGED
|
@@ -19,6 +19,8 @@ class ConfigManager {
|
|
|
19
19
|
* Load configuration
|
|
20
20
|
*/
|
|
21
21
|
async load() {
|
|
22
|
+
this.config = this.deepMerge({}, this.defaults);
|
|
23
|
+
|
|
22
24
|
// Try to load from specified path
|
|
23
25
|
if (this.configPath) {
|
|
24
26
|
await this.loadFromFile(this.configPath);
|
|
@@ -181,7 +183,12 @@ class ConfigManager {
|
|
|
181
183
|
autoApply: true,
|
|
182
184
|
confidenceThreshold: 0.7,
|
|
183
185
|
maxRecordsPerSkill: 100,
|
|
184
|
-
storagePath: path.join(this.getHomeDir(), '.flowmind', 'learning')
|
|
186
|
+
storagePath: path.join(this.getHomeDir(), '.flowmind', 'learning'),
|
|
187
|
+
autoPromote: {
|
|
188
|
+
enabled: true,
|
|
189
|
+
resourceBindingMinUses: 3,
|
|
190
|
+
sceneMinUses: 2
|
|
191
|
+
}
|
|
185
192
|
},
|
|
186
193
|
|
|
187
194
|
// Scene mapping settings
|
package/core/index.js
CHANGED
|
@@ -190,6 +190,7 @@ class FlowMind {
|
|
|
190
190
|
|
|
191
191
|
// Get learning rules for this skill
|
|
192
192
|
const learnings = await this.learning.getSkillLearnings(skill.name);
|
|
193
|
+
const resourceBinding = await this.learning.matchResourceBinding(input, skill.name);
|
|
193
194
|
|
|
194
195
|
// Apply learning rules to context
|
|
195
196
|
const enhancedContext = {
|
|
@@ -197,12 +198,21 @@ class FlowMind {
|
|
|
197
198
|
flowmind: context.flowmind || this,
|
|
198
199
|
currentSkill: context.currentSkill || skill.name,
|
|
199
200
|
learnings: learnings,
|
|
200
|
-
preferences: await this.learning.getPreferences(skill.name)
|
|
201
|
+
preferences: await this.learning.getPreferences(skill.name),
|
|
202
|
+
resourceBinding
|
|
201
203
|
};
|
|
202
204
|
|
|
203
205
|
// Execute skill
|
|
204
206
|
const result = await skill.execute(input, enhancedContext);
|
|
205
207
|
|
|
208
|
+
if (resourceBinding?.id) {
|
|
209
|
+
await this.learning.markResourceBindingUsed(resourceBinding.id, {
|
|
210
|
+
input,
|
|
211
|
+
skillName: skill.name,
|
|
212
|
+
currentSkill: enhancedContext.currentSkill
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
206
216
|
const duration = Date.now() - skillStartTime;
|
|
207
217
|
|
|
208
218
|
// Emit skill execution event
|
|
@@ -226,23 +236,29 @@ class FlowMind {
|
|
|
226
236
|
const { scene, params } = sceneMatch;
|
|
227
237
|
const results = [];
|
|
228
238
|
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
239
|
+
try {
|
|
240
|
+
for (const step of scene.workflow.skills) {
|
|
241
|
+
const skill = this.skills.get(step.skill);
|
|
242
|
+
if (!skill) continue;
|
|
243
|
+
|
|
244
|
+
const stepContext = {
|
|
245
|
+
...context,
|
|
246
|
+
params: { ...step.params, ...params },
|
|
247
|
+
previousResults: results,
|
|
248
|
+
flowmind: this,
|
|
249
|
+
currentSkill: step.skill
|
|
250
|
+
};
|
|
240
251
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
252
|
+
const result = await this.executeWithLearning(skill, input, stepContext);
|
|
253
|
+
results.push({
|
|
254
|
+
skill: step.skill,
|
|
255
|
+
result: result
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
await this.matcher.updateSceneStats(scene.id, true, { input });
|
|
259
|
+
} catch (error) {
|
|
260
|
+
await this.matcher.updateSceneStats(scene.id, false, { input });
|
|
261
|
+
throw error;
|
|
246
262
|
}
|
|
247
263
|
|
|
248
264
|
return {
|