@synergenius/flow-weaver 0.10.8 → 0.10.10
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/dist/cli/commands/describe.d.ts +2 -2
- package/dist/cli/commands/describe.js +6 -0
- package/dist/cli/commands/diagram.d.ts +2 -2
- package/dist/cli/commands/diagram.js +13 -6
- package/dist/cli/commands/mcp-setup.d.ts +45 -0
- package/dist/cli/commands/mcp-setup.js +374 -0
- package/dist/cli/flow-weaver.mjs +1626 -734
- package/dist/cli/index.js +32 -15
- package/dist/diagram/ascii-renderer.d.ts +13 -0
- package/dist/diagram/ascii-renderer.js +528 -0
- package/dist/diagram/index.d.ts +4 -0
- package/dist/diagram/index.js +26 -0
- package/dist/diagram/types.d.ts +1 -1
- package/dist/mcp/editor-connection.d.ts +11 -9
- package/dist/mcp/editor-connection.js +29 -12
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/prompts.d.ts +3 -0
- package/dist/mcp/prompts.js +68 -0
- package/dist/mcp/resources.d.ts +3 -3
- package/dist/mcp/resources.js +5 -5
- package/dist/mcp/server.js +5 -5
- package/dist/mcp/tools-diagram.d.ts +2 -1
- package/dist/mcp/tools-diagram.js +50 -14
- package/dist/mcp/tools-editor.js +22 -22
- package/dist/mcp/tools-query.js +2 -2
- package/dist/mcp/types.d.ts +3 -3
- package/dist/parser.d.ts +6 -0
- package/dist/parser.js +59 -25
- package/package.json +1 -1
package/dist/mcp/tools-editor.js
CHANGED
|
@@ -4,8 +4,8 @@ import { executeWorkflowFromFile } from './workflow-executor.js';
|
|
|
4
4
|
import { AgentChannel } from './agent-channel.js';
|
|
5
5
|
import { storePendingRun, getPendingRun, removePendingRun, listPendingRuns } from './run-registry.js';
|
|
6
6
|
/**
|
|
7
|
-
* Unwrap
|
|
8
|
-
*
|
|
7
|
+
* Unwrap Studio ack responses to flatten double-nested results.
|
|
8
|
+
* Studio returns { requestId, success, result: { actualData } },
|
|
9
9
|
* we extract the `result` field to avoid double-nesting in MCP output.
|
|
10
10
|
*/
|
|
11
11
|
function unwrapAckResult(ack) {
|
|
@@ -15,25 +15,25 @@ function unwrapAckResult(ack) {
|
|
|
15
15
|
return ack;
|
|
16
16
|
}
|
|
17
17
|
export function registerEditorTools(mcp, connection, buffer) {
|
|
18
|
-
mcp.tool('fw_check_events', 'Get buffered
|
|
18
|
+
mcp.tool('fw_check_events', 'Get buffered Studio events. Returns and clears the event buffer unless peek=true.', { peek: z.boolean().optional().describe('If true, read events without clearing the buffer') }, async (args) => {
|
|
19
19
|
const events = args.peek ? buffer.peek() : buffer.drain();
|
|
20
20
|
return makeToolResult(events);
|
|
21
21
|
});
|
|
22
|
-
mcp.tool('fw_get_state', 'Get the current
|
|
22
|
+
mcp.tool('fw_get_state', 'Get the current Studio/workflow state from Flow Weaver.', {}, async () => {
|
|
23
23
|
if (!connection.isConnected) {
|
|
24
|
-
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to
|
|
24
|
+
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to Studio. Is Studio running?');
|
|
25
25
|
}
|
|
26
26
|
const result = await connection.sendCommand('get-state', {});
|
|
27
27
|
return makeToolResult(unwrapAckResult(result));
|
|
28
28
|
});
|
|
29
|
-
mcp.tool('fw_focus_node', 'Select and center a node in
|
|
29
|
+
mcp.tool('fw_focus_node', 'Select and center a node in Flow Weaver Studio.', { nodeId: z.string().describe('The ID of the node to focus') }, async (args) => {
|
|
30
30
|
if (!connection.isConnected) {
|
|
31
|
-
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to
|
|
31
|
+
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to Studio. Is Studio running?');
|
|
32
32
|
}
|
|
33
33
|
const result = await connection.sendCommand('focus-node', { nodeId: args.nodeId });
|
|
34
34
|
return makeToolResult(unwrapAckResult(result));
|
|
35
35
|
});
|
|
36
|
-
mcp.tool('fw_add_node', 'Add a new node to the workflow in
|
|
36
|
+
mcp.tool('fw_add_node', 'Add a new node to the workflow in Flow Weaver Studio.', {
|
|
37
37
|
nodeTypeName: z.string().describe('The name of the node type to add'),
|
|
38
38
|
nodeTypeDefinition: z
|
|
39
39
|
.record(z.unknown())
|
|
@@ -41,7 +41,7 @@ export function registerEditorTools(mcp, connection, buffer) {
|
|
|
41
41
|
.describe('Optional node type definition object'),
|
|
42
42
|
}, async (args) => {
|
|
43
43
|
if (!connection.isConnected) {
|
|
44
|
-
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to
|
|
44
|
+
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to Studio. Is Studio running?');
|
|
45
45
|
}
|
|
46
46
|
const params = { nodeTypeName: args.nodeTypeName };
|
|
47
47
|
if (args.nodeTypeDefinition) {
|
|
@@ -50,19 +50,19 @@ export function registerEditorTools(mcp, connection, buffer) {
|
|
|
50
50
|
const result = await connection.sendCommand('add-node', params);
|
|
51
51
|
return makeToolResult(unwrapAckResult(result));
|
|
52
52
|
});
|
|
53
|
-
mcp.tool('fw_open_workflow', 'Open a workflow file in
|
|
53
|
+
mcp.tool('fw_open_workflow', 'Open a workflow file in Flow Weaver Studio.', { filePath: z.string().describe('The path to the workflow file to open') }, async (args) => {
|
|
54
54
|
if (!connection.isConnected) {
|
|
55
|
-
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to
|
|
55
|
+
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to Studio. Is Studio running?');
|
|
56
56
|
}
|
|
57
57
|
const result = await connection.sendCommand('open-workflow', { filePath: args.filePath });
|
|
58
58
|
return makeToolResult(unwrapAckResult(result));
|
|
59
59
|
});
|
|
60
|
-
mcp.tool('fw_send_command', 'Send a generic command to
|
|
60
|
+
mcp.tool('fw_send_command', 'Send a generic command to Flow Weaver Studio.', {
|
|
61
61
|
action: z.string().describe('The command action name'),
|
|
62
62
|
params: z.record(z.unknown()).optional().describe('Optional parameters for the command'),
|
|
63
63
|
}, async (args) => {
|
|
64
64
|
if (!connection.isConnected) {
|
|
65
|
-
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to
|
|
65
|
+
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to Studio. Is Studio running?');
|
|
66
66
|
}
|
|
67
67
|
const result = await connection.sendCommand(args.action, args.params ?? {});
|
|
68
68
|
return makeToolResult(unwrapAckResult(result));
|
|
@@ -76,7 +76,7 @@ export function registerEditorTools(mcp, connection, buffer) {
|
|
|
76
76
|
.describe('Array of commands to execute as a batch'),
|
|
77
77
|
}, async (args) => {
|
|
78
78
|
if (!connection.isConnected) {
|
|
79
|
-
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to
|
|
79
|
+
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to Studio. Is Studio running?');
|
|
80
80
|
}
|
|
81
81
|
const result = await connection.sendBatch(args.commands);
|
|
82
82
|
return makeToolResult(unwrapAckResult(result));
|
|
@@ -84,7 +84,7 @@ export function registerEditorTools(mcp, connection, buffer) {
|
|
|
84
84
|
// --- New tools ---
|
|
85
85
|
mcp.tool('fw_remove_node', 'Remove a node and its connections from the workflow.', { nodeName: z.string().describe('The name/ID of the node to remove') }, async (args) => {
|
|
86
86
|
if (!connection.isConnected) {
|
|
87
|
-
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to
|
|
87
|
+
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to Studio. Is Studio running?');
|
|
88
88
|
}
|
|
89
89
|
const result = await connection.sendCommand('remove-node', { nodeName: args.nodeName });
|
|
90
90
|
return makeToolResult(unwrapAckResult(result));
|
|
@@ -101,7 +101,7 @@ export function registerEditorTools(mcp, connection, buffer) {
|
|
|
101
101
|
.describe('Connection specification'),
|
|
102
102
|
}, async (args) => {
|
|
103
103
|
if (!connection.isConnected) {
|
|
104
|
-
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to
|
|
104
|
+
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to Studio. Is Studio running?');
|
|
105
105
|
}
|
|
106
106
|
const bridgeAction = args.action === 'add' ? 'add-connection' : 'remove-connection';
|
|
107
107
|
const result = await connection.sendCommand(bridgeAction, {
|
|
@@ -111,7 +111,7 @@ export function registerEditorTools(mcp, connection, buffer) {
|
|
|
111
111
|
});
|
|
112
112
|
mcp.tool('fw_undo_redo', 'Undo or redo the last workflow change.', { action: z.enum(['undo', 'redo']).describe('Whether to undo or redo') }, async (args) => {
|
|
113
113
|
if (!connection.isConnected) {
|
|
114
|
-
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to
|
|
114
|
+
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to Studio. Is Studio running?');
|
|
115
115
|
}
|
|
116
116
|
const result = await connection.sendCommand(args.action, {});
|
|
117
117
|
return makeToolResult(unwrapAckResult(result));
|
|
@@ -123,7 +123,7 @@ export function registerEditorTools(mcp, connection, buffer) {
|
|
|
123
123
|
filePath: z
|
|
124
124
|
.string()
|
|
125
125
|
.optional()
|
|
126
|
-
.describe('Path to workflow file. When provided, compiles and executes directly (no
|
|
126
|
+
.describe('Path to workflow file. When provided, compiles and executes directly (no Studio needed)'),
|
|
127
127
|
workflowName: z
|
|
128
128
|
.string()
|
|
129
129
|
.optional()
|
|
@@ -134,7 +134,7 @@ export function registerEditorTools(mcp, connection, buffer) {
|
|
|
134
134
|
.optional()
|
|
135
135
|
.describe('Include execution trace events (default: true)'),
|
|
136
136
|
}, async (args, extra) => {
|
|
137
|
-
// When filePath is provided, compile and execute directly (no
|
|
137
|
+
// When filePath is provided, compile and execute directly (no Studio needed)
|
|
138
138
|
if (args.filePath) {
|
|
139
139
|
try {
|
|
140
140
|
const channel = new AgentChannel();
|
|
@@ -197,9 +197,9 @@ export function registerEditorTools(mcp, connection, buffer) {
|
|
|
197
197
|
return makeErrorResult(code, message);
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
|
-
// No filePath: delegate to
|
|
200
|
+
// No filePath: delegate to Studio via Socket.io (existing behavior)
|
|
201
201
|
if (!connection.isConnected) {
|
|
202
|
-
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to
|
|
202
|
+
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to Studio. Is Studio running?');
|
|
203
203
|
}
|
|
204
204
|
const result = await connection.sendCommand('execute-workflow', args.params ?? {});
|
|
205
205
|
return makeToolResult(unwrapAckResult(result));
|
|
@@ -247,7 +247,7 @@ export function registerEditorTools(mcp, connection, buffer) {
|
|
|
247
247
|
});
|
|
248
248
|
mcp.tool('fw_get_workflow_details', 'Get full workflow structure including nodes, connections, types, and positions.', {}, async () => {
|
|
249
249
|
if (!connection.isConnected) {
|
|
250
|
-
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to
|
|
250
|
+
return makeErrorResult('EDITOR_NOT_CONNECTED', 'Not connected to Studio. Is Studio running?');
|
|
251
251
|
}
|
|
252
252
|
const result = await connection.sendCommand('get-workflow-details', {});
|
|
253
253
|
return makeToolResult(unwrapAckResult(result));
|
package/dist/mcp/tools-query.js
CHANGED
|
@@ -21,9 +21,9 @@ export function registerQueryTools(mcp) {
|
|
|
21
21
|
mcp.tool('fw_describe', 'Describe a workflow in LLM-friendly format (nodes, connections, graph, validation).', {
|
|
22
22
|
filePath: z.string().describe('Path to the workflow .ts file'),
|
|
23
23
|
format: z
|
|
24
|
-
.enum(['json', 'text', 'mermaid', 'paths'])
|
|
24
|
+
.enum(['json', 'text', 'mermaid', 'paths', 'ascii', 'ascii-compact'])
|
|
25
25
|
.optional()
|
|
26
|
-
.describe('Output format (default: json)'),
|
|
26
|
+
.describe('Output format (default: json). ascii/ascii-compact produce terminal-readable diagrams.'),
|
|
27
27
|
node: z.string().optional().describe('Focus on a specific node ID'),
|
|
28
28
|
workflowName: z.string().optional().describe('Specific workflow if file has multiple'),
|
|
29
29
|
}, async (args) => {
|
package/dist/mcp/types.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { EventBuffer } from './event-buffer.js';
|
|
|
3
3
|
import type { EditorConnection } from './editor-connection.js';
|
|
4
4
|
/** Options for initializing the MCP server. */
|
|
5
5
|
export interface McpServerOptions {
|
|
6
|
-
/** WebSocket server URL for
|
|
6
|
+
/** WebSocket server URL for Studio connection. */
|
|
7
7
|
server?: string;
|
|
8
8
|
/** Whether to use stdio transport instead of SSE. */
|
|
9
9
|
stdio?: boolean;
|
|
@@ -27,7 +27,7 @@ export interface RegistrationDeps {
|
|
|
27
27
|
/** Resolve the path to the Flow Weaver CLI executable. */
|
|
28
28
|
resolveCliPath: () => string;
|
|
29
29
|
}
|
|
30
|
-
/** Acknowledgement response returned by
|
|
30
|
+
/** Acknowledgement response returned by Studio after processing a command. */
|
|
31
31
|
export interface AckResponse {
|
|
32
32
|
/** Unique identifier correlating the response to its originating request. */
|
|
33
33
|
requestId: string;
|
|
@@ -47,7 +47,7 @@ export interface BufferedEvent {
|
|
|
47
47
|
/** ISO 8601 timestamp of when the event was buffered. */
|
|
48
48
|
timestamp: string;
|
|
49
49
|
}
|
|
50
|
-
/** Options for configuring the
|
|
50
|
+
/** Options for configuring the Studio WebSocket connection. */
|
|
51
51
|
export interface EditorConnectionOptions {
|
|
52
52
|
/** Custom socket.io factory, primarily used for injecting mocks in tests. */
|
|
53
53
|
ioFactory?: typeof socketIO;
|
package/dist/parser.d.ts
CHANGED
|
@@ -166,6 +166,12 @@ export declare class AnnotationParser {
|
|
|
166
166
|
* Returns a map of property names to their TypeScript type strings.
|
|
167
167
|
*/
|
|
168
168
|
private extractTypeSchema;
|
|
169
|
+
/**
|
|
170
|
+
* Check if a type should be expanded into individual ports via getProperties().
|
|
171
|
+
* Returns true for object literals and interfaces, false for primitives, arrays,
|
|
172
|
+
* and built-in types whose properties are prototype methods (string, number, etc.).
|
|
173
|
+
*/
|
|
174
|
+
private isExpandableObjectType;
|
|
169
175
|
private inferPortType;
|
|
170
176
|
/**
|
|
171
177
|
* Check if a function has a valid @flowWeaver annotation (nodeType, workflow, or pattern).
|
package/dist/parser.js
CHANGED
|
@@ -1799,32 +1799,52 @@ export class AnnotationParser {
|
|
|
1799
1799
|
else {
|
|
1800
1800
|
ports.execute = { dataType: 'STEP', tsType: 'boolean', label: 'Execute' };
|
|
1801
1801
|
}
|
|
1802
|
-
// Extract data ports from
|
|
1802
|
+
// Extract data ports from parameters beyond execute
|
|
1803
1803
|
if (params.length > 1) {
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
const
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
const
|
|
1812
|
-
|
|
1813
|
-
const
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1804
|
+
// Filter out __abortSignal__ which is injected by generateInPlace and
|
|
1805
|
+
// is not a user-visible port.
|
|
1806
|
+
const dataParams = params.slice(1).filter(p => p.getName() !== '__abortSignal__');
|
|
1807
|
+
// Multiple separate params: each becomes its own port
|
|
1808
|
+
// Single param with object type: expand its properties into ports
|
|
1809
|
+
const shouldExpandProperties = dataParams.length === 1 && this.isExpandableObjectType(dataParams[0].getType());
|
|
1810
|
+
if (shouldExpandProperties) {
|
|
1811
|
+
const dataParam = dataParams[0];
|
|
1812
|
+
const dataParamType = dataParam.getType();
|
|
1813
|
+
const properties = dataParamType.getProperties();
|
|
1814
|
+
properties.forEach((prop) => {
|
|
1815
|
+
const propName = prop.getName();
|
|
1816
|
+
const propType = prop.getTypeAtLocation(dataParam);
|
|
1817
|
+
const portType = this.inferPortType(propType);
|
|
1818
|
+
const propTypeText = propType.getText();
|
|
1819
|
+
const tsSchema = portType === 'OBJECT' ? this.extractTypeSchema(propType) : undefined;
|
|
1820
|
+
const startPortConfig = config?.startPorts?.[propName];
|
|
1821
|
+
ports[propName] = {
|
|
1822
|
+
dataType: portType,
|
|
1823
|
+
label: startPortConfig?.label || this.capitalize(propName),
|
|
1824
|
+
...(startPortConfig?.metadata && { metadata: startPortConfig.metadata }),
|
|
1825
|
+
...(propTypeText && { tsType: propTypeText }),
|
|
1826
|
+
...(tsSchema && Object.keys(tsSchema).length > 0 && { tsSchema }),
|
|
1827
|
+
};
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
else {
|
|
1831
|
+
// Each parameter becomes its own port
|
|
1832
|
+
for (const param of dataParams) {
|
|
1833
|
+
const paramName = param.getName();
|
|
1834
|
+
const paramType = param.getType();
|
|
1835
|
+
const portType = this.inferPortType(paramType);
|
|
1836
|
+
const paramTypeText = paramType.getText(param);
|
|
1837
|
+
const tsSchema = portType === 'OBJECT' ? this.extractTypeSchema(paramType) : undefined;
|
|
1838
|
+
const startPortConfig = config?.startPorts?.[paramName];
|
|
1839
|
+
ports[paramName] = {
|
|
1840
|
+
dataType: portType,
|
|
1841
|
+
label: startPortConfig?.label || this.capitalize(paramName),
|
|
1842
|
+
...(startPortConfig?.metadata && { metadata: startPortConfig.metadata }),
|
|
1843
|
+
...(paramTypeText && { tsType: paramTypeText }),
|
|
1844
|
+
...(tsSchema && Object.keys(tsSchema).length > 0 && { tsSchema }),
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1828
1848
|
}
|
|
1829
1849
|
}
|
|
1830
1850
|
else {
|
|
@@ -1923,6 +1943,20 @@ export class AnnotationParser {
|
|
|
1923
1943
|
}
|
|
1924
1944
|
return Object.keys(schema).length > 0 ? schema : undefined;
|
|
1925
1945
|
}
|
|
1946
|
+
/**
|
|
1947
|
+
* Check if a type should be expanded into individual ports via getProperties().
|
|
1948
|
+
* Returns true for object literals and interfaces, false for primitives, arrays,
|
|
1949
|
+
* and built-in types whose properties are prototype methods (string, number, etc.).
|
|
1950
|
+
*/
|
|
1951
|
+
isExpandableObjectType(tsType) {
|
|
1952
|
+
const typeText = tsType.getText();
|
|
1953
|
+
const primitiveTypes = new Set(['string', 'number', 'boolean', 'any', 'unknown', 'never', 'object', 'Object']);
|
|
1954
|
+
if (primitiveTypes.has(typeText))
|
|
1955
|
+
return false;
|
|
1956
|
+
if (typeText.endsWith('[]') || typeText.startsWith('Array<'))
|
|
1957
|
+
return false;
|
|
1958
|
+
return tsType.isObject() && tsType.getProperties().length > 0;
|
|
1959
|
+
}
|
|
1926
1960
|
inferPortType(tsType) {
|
|
1927
1961
|
const typeText = tsType.getText();
|
|
1928
1962
|
// Delegate to inferDataTypeFromTS for consistent type mapping
|
package/package.json
CHANGED