@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.
@@ -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 editor ack responses to flatten double-nested results.
8
- * Editor returns { requestId, success, result: { actualData } }
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 editor 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) => {
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 editor/workflow state from Flow Weaver.', {}, async () => {
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 the editor. Is the editor running?');
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 the Flow Weaver editor.', { nodeId: z.string().describe('The ID of the node to focus') }, async (args) => {
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 the editor. Is the editor running?');
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 the Flow Weaver editor.', {
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 the editor. Is the editor running?');
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 the Flow Weaver editor.', { filePath: z.string().describe('The path to the workflow file to open') }, async (args) => {
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 the editor. Is the editor running?');
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 the Flow Weaver editor.', {
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 the editor. Is the editor running?');
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 the editor. Is the editor running?');
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 the editor. Is the editor running?');
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 the editor. Is the editor running?');
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 the editor. Is the editor running?');
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 editor needed)'),
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 editor needed)
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 editor via Socket.io (existing behavior)
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 the editor. Is the editor running?');
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 the editor. Is the editor running?');
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));
@@ -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) => {
@@ -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 editor connection. */
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 the editor after processing a command. */
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 editor WebSocket connection. */
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 second parameter if it exists
1802
+ // Extract data ports from parameters beyond execute
1803
1803
  if (params.length > 1) {
1804
- const dataParam = params[1];
1805
- const dataParamType = dataParam.getType();
1806
- const properties = dataParamType.getProperties();
1807
- properties.forEach((prop) => {
1808
- const propName = prop.getName();
1809
- const propType = prop.getTypeAtLocation(dataParam);
1810
- const portType = this.inferPortType(propType);
1811
- const propTypeText = propType.getText();
1812
- // Extract schema for complex types (interfaces/objects)
1813
- const tsSchema = portType === 'OBJECT' ? this.extractTypeSchema(propType) : undefined;
1814
- // Check if JSDoc has explicit metadata override for this start port (from @param annotation)
1815
- // Always prefer ts-morph inferred type over JSDoc regex-based inference
1816
- // (JSDoc @param type inference uses regex which can fail for complex types)
1817
- const startPortConfig = config?.startPorts?.[propName];
1818
- ports[propName] = {
1819
- dataType: portType,
1820
- label: startPortConfig?.label || this.capitalize(propName),
1821
- ...(startPortConfig?.metadata && { metadata: startPortConfig.metadata }),
1822
- // Include original TS type for rich type display
1823
- ...(propTypeText && { tsType: propTypeText }),
1824
- // Include schema breakdown for complex types
1825
- ...(tsSchema && Object.keys(tsSchema).length > 0 && { tsSchema }),
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synergenius/flow-weaver",
3
- "version": "0.10.8",
3
+ "version": "0.10.10",
4
4
  "description": "Deterministic workflow compiler for AI agents. Compiles to standalone TypeScript, no runtime dependencies.",
5
5
  "private": false,
6
6
  "type": "module",