@synergenius/flow-weaver 0.6.0 → 0.7.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/dist/annotation-generator.js +4 -2
- package/dist/api/generate-in-place.js +3 -0
- package/dist/ast/types.d.ts +2 -0
- package/dist/built-in-nodes/invoke-workflow.js +3 -3
- package/dist/built-in-nodes/mock-types.d.ts +20 -0
- package/dist/built-in-nodes/mock-types.js +30 -0
- package/dist/built-in-nodes/wait-for-agent.js +5 -4
- package/dist/built-in-nodes/wait-for-event.js +3 -3
- package/dist/cli/commands/compile.js +6 -0
- package/dist/cli/commands/init.js +2 -0
- package/dist/cli/commands/run.d.ts +2 -0
- package/dist/cli/commands/run.js +38 -0
- package/dist/cli/commands/validate.js +12 -0
- package/dist/cli/flow-weaver.mjs +103 -16
- package/dist/mcp/tools-editor.js +20 -1
- package/dist/mcp/workflow-executor.js +7 -2
- package/dist/validator.js +19 -0
- package/docs/reference/concepts.md +44 -4
- package/docs/reference/debugging.md +33 -0
- package/package.json +1 -1
|
@@ -40,9 +40,11 @@ export class AnnotationGenerator {
|
|
|
40
40
|
}
|
|
41
41
|
// Generate JSDoc comment block (only when no functionText)
|
|
42
42
|
lines.push("/**");
|
|
43
|
-
// Add description if present
|
|
43
|
+
// Add description if present (handle multi-line descriptions)
|
|
44
44
|
if (includeComments && nodeType.description) {
|
|
45
|
-
|
|
45
|
+
for (const descLine of nodeType.description.split('\n')) {
|
|
46
|
+
lines.push(` * ${descLine}`);
|
|
47
|
+
}
|
|
46
48
|
lines.push(` *`);
|
|
47
49
|
}
|
|
48
50
|
// @flowWeaver nodeType marker
|
|
@@ -408,6 +408,9 @@ function replaceWorkflowFunctionBody(source, functionName, newBody) {
|
|
|
408
408
|
}
|
|
409
409
|
// Find the closing brace
|
|
410
410
|
const closeBraceIdx = functionNode.body.end - 1;
|
|
411
|
+
if (closeBraceIdx <= openBraceIdx) {
|
|
412
|
+
return { code: source, changed: false };
|
|
413
|
+
}
|
|
411
414
|
const before = source.slice(0, openBraceIdx + 1);
|
|
412
415
|
const after = source.slice(closeBraceIdx);
|
|
413
416
|
const newBodyWithMarkers = [
|
package/dist/ast/types.d.ts
CHANGED
|
@@ -637,6 +637,8 @@ export type TValidationError = {
|
|
|
637
637
|
node?: string;
|
|
638
638
|
connection?: TConnectionAST;
|
|
639
639
|
location?: TSourceLocation;
|
|
640
|
+
/** Reference to documentation explaining this error and how to fix it. */
|
|
641
|
+
docUrl?: string;
|
|
640
642
|
};
|
|
641
643
|
export type TAnalysisResult = {
|
|
642
644
|
controlFlowGraph: TControlFlowGraph;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getMockConfig } from './mock-types.js';
|
|
1
|
+
import { getMockConfig, lookupMock } from './mock-types.js';
|
|
2
2
|
/**
|
|
3
3
|
* @flowWeaver nodeType
|
|
4
4
|
* @input functionId - Inngest function ID (e.g. "my-service/sub-workflow")
|
|
@@ -11,8 +11,8 @@ export async function invokeWorkflow(execute, functionId, payload, timeout) {
|
|
|
11
11
|
return { onSuccess: false, onFailure: false, result: {} };
|
|
12
12
|
const mocks = getMockConfig();
|
|
13
13
|
if (mocks) {
|
|
14
|
-
// Mock mode
|
|
15
|
-
const mockResult = mocks.invocations
|
|
14
|
+
// Mock mode — look up result by functionId (supports instance-qualified keys)
|
|
15
|
+
const mockResult = lookupMock(mocks.invocations, functionId);
|
|
16
16
|
if (mockResult !== undefined) {
|
|
17
17
|
return { onSuccess: true, onFailure: false, result: mockResult };
|
|
18
18
|
}
|
|
@@ -17,4 +17,24 @@ export interface FwMockConfig {
|
|
|
17
17
|
* Read the mock config from globalThis, returning undefined if not set.
|
|
18
18
|
*/
|
|
19
19
|
export declare function getMockConfig(): FwMockConfig | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Look up a mock value from a section, supporting instance-qualified keys.
|
|
22
|
+
*
|
|
23
|
+
* Checks "instanceId:key" first (for per-node targeting), then falls back
|
|
24
|
+
* to plain "key". The instance ID comes from __fw_current_node_id__ which
|
|
25
|
+
* the generated code sets before each node invocation.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```json
|
|
29
|
+
* {
|
|
30
|
+
* "invocations": {
|
|
31
|
+
* "retryCall:api/process": { "status": "ok" },
|
|
32
|
+
* "api/process": { "status": "default" }
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
* When the node "retryCall" invokes "api/process", it gets `{ status: "ok" }`.
|
|
37
|
+
* Any other node invoking "api/process" gets `{ status: "default" }`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function lookupMock<T>(section: Record<string, T> | undefined, key: string): T | undefined;
|
|
20
40
|
//# sourceMappingURL=mock-types.d.ts.map
|
|
@@ -9,4 +9,34 @@
|
|
|
9
9
|
export function getMockConfig() {
|
|
10
10
|
return globalThis.__fw_mocks__;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Look up a mock value from a section, supporting instance-qualified keys.
|
|
14
|
+
*
|
|
15
|
+
* Checks "instanceId:key" first (for per-node targeting), then falls back
|
|
16
|
+
* to plain "key". The instance ID comes from __fw_current_node_id__ which
|
|
17
|
+
* the generated code sets before each node invocation.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```json
|
|
21
|
+
* {
|
|
22
|
+
* "invocations": {
|
|
23
|
+
* "retryCall:api/process": { "status": "ok" },
|
|
24
|
+
* "api/process": { "status": "default" }
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
* When the node "retryCall" invokes "api/process", it gets `{ status: "ok" }`.
|
|
29
|
+
* Any other node invoking "api/process" gets `{ status: "default" }`.
|
|
30
|
+
*/
|
|
31
|
+
export function lookupMock(section, key) {
|
|
32
|
+
if (!section)
|
|
33
|
+
return undefined;
|
|
34
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
35
|
+
if (nodeId) {
|
|
36
|
+
const qualified = section[`${nodeId}:${key}`];
|
|
37
|
+
if (qualified !== undefined)
|
|
38
|
+
return qualified;
|
|
39
|
+
}
|
|
40
|
+
return section[key];
|
|
41
|
+
}
|
|
12
42
|
//# sourceMappingURL=mock-types.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getMockConfig } from './mock-types.js';
|
|
1
|
+
import { getMockConfig, lookupMock } from './mock-types.js';
|
|
2
2
|
/**
|
|
3
3
|
* @flowWeaver nodeType
|
|
4
4
|
* @input agentId - Agent/task identifier
|
|
@@ -9,10 +9,11 @@ import { getMockConfig } from './mock-types.js';
|
|
|
9
9
|
export async function waitForAgent(execute, agentId, context, prompt) {
|
|
10
10
|
if (!execute)
|
|
11
11
|
return { onSuccess: false, onFailure: false, agentResult: {} };
|
|
12
|
-
// 1. Check mocks first
|
|
12
|
+
// 1. Check mocks first (supports instance-qualified keys)
|
|
13
13
|
const mocks = getMockConfig();
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const mockResult = lookupMock(mocks?.agents, agentId);
|
|
15
|
+
if (mockResult !== undefined) {
|
|
16
|
+
return { onSuccess: true, onFailure: false, agentResult: mockResult };
|
|
16
17
|
}
|
|
17
18
|
// 2. Check agent channel (set by executor for pause/resume)
|
|
18
19
|
const channel = globalThis.__fw_agent_channel__;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getMockConfig } from './mock-types.js';
|
|
1
|
+
import { getMockConfig, lookupMock } from './mock-types.js';
|
|
2
2
|
/**
|
|
3
3
|
* @flowWeaver nodeType
|
|
4
4
|
* @input eventName - Event name to wait for (e.g. "app/approval.received")
|
|
@@ -11,8 +11,8 @@ export async function waitForEvent(execute, eventName, match, timeout) {
|
|
|
11
11
|
return { onSuccess: false, onFailure: false, eventData: {} };
|
|
12
12
|
const mocks = getMockConfig();
|
|
13
13
|
if (mocks) {
|
|
14
|
-
// Mock mode
|
|
15
|
-
const mockData = mocks.events
|
|
14
|
+
// Mock mode — look up event data by name (supports instance-qualified keys)
|
|
15
|
+
const mockData = lookupMock(mocks.events, eventName);
|
|
16
16
|
if (mockData !== undefined) {
|
|
17
17
|
return { onSuccess: true, onFailure: false, eventData: mockData };
|
|
18
18
|
}
|
|
@@ -116,6 +116,9 @@ export async function compileCommand(input, options = {}) {
|
|
|
116
116
|
const loc = err.location ? `[line ${err.location.line}] ` : '';
|
|
117
117
|
logger.error(` ${loc}${friendly.title}: ${friendly.explanation}`);
|
|
118
118
|
logger.info(` How to fix: ${friendly.fix}`);
|
|
119
|
+
if (err.docUrl) {
|
|
120
|
+
logger.info(` See: ${err.docUrl}`);
|
|
121
|
+
}
|
|
119
122
|
}
|
|
120
123
|
else {
|
|
121
124
|
let msg = ` - ${err.message}`;
|
|
@@ -123,6 +126,9 @@ export async function compileCommand(input, options = {}) {
|
|
|
123
126
|
msg += ` (node: ${err.node})`;
|
|
124
127
|
}
|
|
125
128
|
logger.error(msg);
|
|
129
|
+
if (err.docUrl) {
|
|
130
|
+
logger.info(` See: ${err.docUrl}`);
|
|
131
|
+
}
|
|
126
132
|
}
|
|
127
133
|
});
|
|
128
134
|
errorCount++;
|
|
@@ -273,12 +273,14 @@ export function generateProjectFiles(projectName, template, format = 'esm') {
|
|
|
273
273
|
].join('\n');
|
|
274
274
|
}
|
|
275
275
|
const gitignore = `node_modules/\ndist/\n.tsbuildinfo\n`;
|
|
276
|
+
const configYaml = `defaultFileType: ts\n`;
|
|
276
277
|
return {
|
|
277
278
|
'package.json': packageJson,
|
|
278
279
|
'tsconfig.json': tsconfigJson,
|
|
279
280
|
[`src/${workflowFile}`]: workflowCode,
|
|
280
281
|
'src/main.ts': mainTs,
|
|
281
282
|
'.gitignore': gitignore,
|
|
283
|
+
'.flowweaver/config.yaml': configYaml,
|
|
282
284
|
};
|
|
283
285
|
}
|
|
284
286
|
// ── Filesystem writer ────────────────────────────────────────────────────────
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Run command - execute a workflow file directly from the CLI
|
|
3
3
|
*/
|
|
4
|
+
import type { FwMockConfig } from '../../built-in-nodes/mock-types.js';
|
|
4
5
|
export interface RunOptions {
|
|
5
6
|
/** Specific workflow name to run (if file contains multiple workflows) */
|
|
6
7
|
workflow?: string;
|
|
@@ -48,4 +49,5 @@ export interface RunOptions {
|
|
|
48
49
|
* ```
|
|
49
50
|
*/
|
|
50
51
|
export declare function runCommand(input: string, options: RunOptions): Promise<void>;
|
|
52
|
+
export declare function validateMockConfig(mocks: FwMockConfig, filePath: string, workflowName?: string): Promise<void>;
|
|
51
53
|
//# sourceMappingURL=run.d.ts.map
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -9,6 +9,7 @@ import { AgentChannel } from '../../mcp/agent-channel.js';
|
|
|
9
9
|
import { logger } from '../utils/logger.js';
|
|
10
10
|
import { getFriendlyError } from '../../friendly-errors.js';
|
|
11
11
|
import { getErrorMessage } from '../../utils/error-utils.js';
|
|
12
|
+
import { parseWorkflow } from '../../api/index.js';
|
|
12
13
|
/**
|
|
13
14
|
* Execute a workflow file and output the result.
|
|
14
15
|
*
|
|
@@ -85,6 +86,10 @@ export async function runCommand(input, options) {
|
|
|
85
86
|
throw new Error(`Failed to parse mocks file: ${options.mocksFile}`);
|
|
86
87
|
}
|
|
87
88
|
}
|
|
89
|
+
// Validate mock config against workflow when mocks are provided
|
|
90
|
+
if (mocks && !options.json) {
|
|
91
|
+
await validateMockConfig(mocks, filePath, options.workflow);
|
|
92
|
+
}
|
|
88
93
|
// Set up timeout if specified
|
|
89
94
|
let timeoutId;
|
|
90
95
|
let timedOut = false;
|
|
@@ -263,6 +268,39 @@ export async function runCommand(input, options) {
|
|
|
263
268
|
}
|
|
264
269
|
}
|
|
265
270
|
}
|
|
271
|
+
const VALID_MOCK_KEYS = new Set(['events', 'invocations', 'agents', 'fast']);
|
|
272
|
+
const BUILT_IN_NODE_TYPES = new Set(['delay', 'waitForEvent', 'invokeWorkflow', 'waitForAgent']);
|
|
273
|
+
const MOCK_SECTION_TO_NODE = {
|
|
274
|
+
events: 'waitForEvent',
|
|
275
|
+
invocations: 'invokeWorkflow',
|
|
276
|
+
agents: 'waitForAgent',
|
|
277
|
+
};
|
|
278
|
+
export async function validateMockConfig(mocks, filePath, workflowName) {
|
|
279
|
+
// Check for unknown top-level keys (catches typos like "invocation" instead of "invocations")
|
|
280
|
+
for (const key of Object.keys(mocks)) {
|
|
281
|
+
if (!VALID_MOCK_KEYS.has(key)) {
|
|
282
|
+
logger.warn(`Mock config has unknown key "${key}". Valid keys: ${[...VALID_MOCK_KEYS].join(', ')}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Quick-parse the workflow to check which built-in node types are used
|
|
286
|
+
try {
|
|
287
|
+
const result = await parseWorkflow(filePath, { workflowName });
|
|
288
|
+
if (result.errors.length > 0 || !result.ast?.instances)
|
|
289
|
+
return;
|
|
290
|
+
const usedNodeTypes = new Set(result.ast.instances.map((i) => i.nodeType));
|
|
291
|
+
for (const [section, nodeType] of Object.entries(MOCK_SECTION_TO_NODE)) {
|
|
292
|
+
const mockSection = mocks[section];
|
|
293
|
+
if (mockSection && typeof mockSection === 'object' && Object.keys(mockSection).length > 0) {
|
|
294
|
+
if (!usedNodeTypes.has(nodeType)) {
|
|
295
|
+
logger.warn(`Mock config has "${section}" entries but workflow has no ${nodeType} nodes`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
// Parsing failed — skip validation, the execution will report the real error
|
|
302
|
+
}
|
|
303
|
+
}
|
|
266
304
|
function promptForInput(question) {
|
|
267
305
|
return new Promise((resolve) => {
|
|
268
306
|
const rl = readline.createInterface({
|
|
@@ -140,6 +140,9 @@ export async function validateCommand(input, options = {}) {
|
|
|
140
140
|
const loc = err.location ? `[line ${err.location.line}] ` : '';
|
|
141
141
|
logger.error(` ${loc}${friendly.title}: ${friendly.explanation}`);
|
|
142
142
|
logger.info(` How to fix: ${friendly.fix}`);
|
|
143
|
+
if (err.docUrl) {
|
|
144
|
+
logger.info(` See: ${err.docUrl}`);
|
|
145
|
+
}
|
|
143
146
|
}
|
|
144
147
|
else {
|
|
145
148
|
let msg = ` - ${err.message}`;
|
|
@@ -153,6 +156,9 @@ export async function validateCommand(input, options = {}) {
|
|
|
153
156
|
msg += ` (connection: ${err.connection.from.node}:${err.connection.from.port} -> ${err.connection.to.node}:${err.connection.to.port})`;
|
|
154
157
|
}
|
|
155
158
|
logger.error(msg);
|
|
159
|
+
if (err.docUrl) {
|
|
160
|
+
logger.info(` See: ${err.docUrl}`);
|
|
161
|
+
}
|
|
156
162
|
}
|
|
157
163
|
});
|
|
158
164
|
}
|
|
@@ -167,6 +173,9 @@ export async function validateCommand(input, options = {}) {
|
|
|
167
173
|
const loc = warn.location ? `[line ${warn.location.line}] ` : '';
|
|
168
174
|
logger.warn(` ${loc}${friendly.title}: ${friendly.explanation}`);
|
|
169
175
|
logger.info(` How to fix: ${friendly.fix}`);
|
|
176
|
+
if (warn.docUrl) {
|
|
177
|
+
logger.info(` See: ${warn.docUrl}`);
|
|
178
|
+
}
|
|
170
179
|
}
|
|
171
180
|
else {
|
|
172
181
|
let msg = ` - ${warn.message}`;
|
|
@@ -177,6 +186,9 @@ export async function validateCommand(input, options = {}) {
|
|
|
177
186
|
msg += ` (node: ${warn.node})`;
|
|
178
187
|
}
|
|
179
188
|
logger.warn(msg);
|
|
189
|
+
if (warn.docUrl) {
|
|
190
|
+
logger.info(` See: ${warn.docUrl}`);
|
|
191
|
+
}
|
|
180
192
|
}
|
|
181
193
|
});
|
|
182
194
|
}
|
package/dist/cli/flow-weaver.mjs
CHANGED
|
@@ -11870,7 +11870,7 @@ var init_type_checker = __esm({
|
|
|
11870
11870
|
});
|
|
11871
11871
|
|
|
11872
11872
|
// src/validator.ts
|
|
11873
|
-
var WorkflowValidator, validator;
|
|
11873
|
+
var DOCS_BASE, ERROR_DOC_URLS, WorkflowValidator, validator;
|
|
11874
11874
|
var init_validator = __esm({
|
|
11875
11875
|
"src/validator.ts"() {
|
|
11876
11876
|
"use strict";
|
|
@@ -11878,6 +11878,18 @@ var init_validator = __esm({
|
|
|
11878
11878
|
init_string_distance();
|
|
11879
11879
|
init_signature_parser();
|
|
11880
11880
|
init_type_checker();
|
|
11881
|
+
DOCS_BASE = "https://docs.flowweaver.dev/reference";
|
|
11882
|
+
ERROR_DOC_URLS = {
|
|
11883
|
+
UNKNOWN_NODE_TYPE: `${DOCS_BASE}/concepts#node-registration`,
|
|
11884
|
+
UNKNOWN_SOURCE_PORT: `${DOCS_BASE}/concepts#port-architecture`,
|
|
11885
|
+
UNKNOWN_TARGET_PORT: `${DOCS_BASE}/concepts#port-architecture`,
|
|
11886
|
+
TYPE_MISMATCH: `${DOCS_BASE}/compilation#type-compatibility`,
|
|
11887
|
+
UNREACHABLE_NODE: `${DOCS_BASE}/concepts#graph-structure`,
|
|
11888
|
+
MISSING_START_CONNECTION: `${DOCS_BASE}/concepts#start-and-exit`,
|
|
11889
|
+
MISSING_EXIT_CONNECTION: `${DOCS_BASE}/concepts#start-and-exit`,
|
|
11890
|
+
INFERRED_NODE_TYPE: `${DOCS_BASE}/node-conversion`,
|
|
11891
|
+
DUPLICATE_CONNECTION: `${DOCS_BASE}/concepts#connections`
|
|
11892
|
+
};
|
|
11881
11893
|
WorkflowValidator = class {
|
|
11882
11894
|
errors = [];
|
|
11883
11895
|
warnings = [];
|
|
@@ -12005,6 +12017,11 @@ var init_validator = __esm({
|
|
|
12005
12017
|
return true;
|
|
12006
12018
|
});
|
|
12007
12019
|
}
|
|
12020
|
+
for (const diag of [...this.errors, ...this.warnings]) {
|
|
12021
|
+
if (!diag.docUrl && ERROR_DOC_URLS[diag.code]) {
|
|
12022
|
+
diag.docUrl = ERROR_DOC_URLS[diag.code];
|
|
12023
|
+
}
|
|
12024
|
+
}
|
|
12008
12025
|
return {
|
|
12009
12026
|
valid: this.errors.length === 0,
|
|
12010
12027
|
errors: this.errors,
|
|
@@ -30777,7 +30794,9 @@ var AnnotationGenerator = class {
|
|
|
30777
30794
|
}
|
|
30778
30795
|
lines.push("/**");
|
|
30779
30796
|
if (includeComments && nodeType.description) {
|
|
30780
|
-
|
|
30797
|
+
for (const descLine of nodeType.description.split("\n")) {
|
|
30798
|
+
lines.push(` * ${descLine}`);
|
|
30799
|
+
}
|
|
30781
30800
|
lines.push(` *`);
|
|
30782
30801
|
}
|
|
30783
30802
|
lines.push(" * @flowWeaver nodeType");
|
|
@@ -31510,6 +31529,9 @@ function replaceWorkflowFunctionBody(source, functionName, newBody) {
|
|
|
31510
31529
|
return { code: source, changed: false };
|
|
31511
31530
|
}
|
|
31512
31531
|
const closeBraceIdx = functionNode.body.end - 1;
|
|
31532
|
+
if (closeBraceIdx <= openBraceIdx) {
|
|
31533
|
+
return { code: source, changed: false };
|
|
31534
|
+
}
|
|
31513
31535
|
const before = source.slice(0, openBraceIdx + 1);
|
|
31514
31536
|
const after = source.slice(closeBraceIdx);
|
|
31515
31537
|
const newBodyWithMarkers = [
|
|
@@ -57939,12 +57961,18 @@ async function compileCommand(input, options = {}) {
|
|
|
57939
57961
|
const loc = err.location ? `[line ${err.location.line}] ` : "";
|
|
57940
57962
|
logger.error(` ${loc}${friendly.title}: ${friendly.explanation}`);
|
|
57941
57963
|
logger.info(` How to fix: ${friendly.fix}`);
|
|
57964
|
+
if (err.docUrl) {
|
|
57965
|
+
logger.info(` See: ${err.docUrl}`);
|
|
57966
|
+
}
|
|
57942
57967
|
} else {
|
|
57943
57968
|
let msg = ` - ${err.message}`;
|
|
57944
57969
|
if (err.node) {
|
|
57945
57970
|
msg += ` (node: ${err.node})`;
|
|
57946
57971
|
}
|
|
57947
57972
|
logger.error(msg);
|
|
57973
|
+
if (err.docUrl) {
|
|
57974
|
+
logger.info(` See: ${err.docUrl}`);
|
|
57975
|
+
}
|
|
57948
57976
|
}
|
|
57949
57977
|
});
|
|
57950
57978
|
errorCount++;
|
|
@@ -62030,6 +62058,9 @@ async function validateCommand(input, options = {}) {
|
|
|
62030
62058
|
const loc = err.location ? `[line ${err.location.line}] ` : "";
|
|
62031
62059
|
logger.error(` ${loc}${friendly.title}: ${friendly.explanation}`);
|
|
62032
62060
|
logger.info(` How to fix: ${friendly.fix}`);
|
|
62061
|
+
if (err.docUrl) {
|
|
62062
|
+
logger.info(` See: ${err.docUrl}`);
|
|
62063
|
+
}
|
|
62033
62064
|
} else {
|
|
62034
62065
|
let msg = ` - ${err.message}`;
|
|
62035
62066
|
if (err.location) {
|
|
@@ -62042,6 +62073,9 @@ async function validateCommand(input, options = {}) {
|
|
|
62042
62073
|
msg += ` (connection: ${err.connection.from.node}:${err.connection.from.port} -> ${err.connection.to.node}:${err.connection.to.port})`;
|
|
62043
62074
|
}
|
|
62044
62075
|
logger.error(msg);
|
|
62076
|
+
if (err.docUrl) {
|
|
62077
|
+
logger.info(` See: ${err.docUrl}`);
|
|
62078
|
+
}
|
|
62045
62079
|
}
|
|
62046
62080
|
});
|
|
62047
62081
|
}
|
|
@@ -62056,6 +62090,9 @@ async function validateCommand(input, options = {}) {
|
|
|
62056
62090
|
const loc = warn.location ? `[line ${warn.location.line}] ` : "";
|
|
62057
62091
|
logger.warn(` ${loc}${friendly.title}: ${friendly.explanation}`);
|
|
62058
62092
|
logger.info(` How to fix: ${friendly.fix}`);
|
|
62093
|
+
if (warn.docUrl) {
|
|
62094
|
+
logger.info(` See: ${warn.docUrl}`);
|
|
62095
|
+
}
|
|
62059
62096
|
} else {
|
|
62060
62097
|
let msg = ` - ${warn.message}`;
|
|
62061
62098
|
if (warn.location) {
|
|
@@ -62065,6 +62102,9 @@ async function validateCommand(input, options = {}) {
|
|
|
62065
62102
|
msg += ` (node: ${warn.node})`;
|
|
62066
62103
|
}
|
|
62067
62104
|
logger.warn(msg);
|
|
62105
|
+
if (warn.docUrl) {
|
|
62106
|
+
logger.info(` See: ${warn.docUrl}`);
|
|
62107
|
+
}
|
|
62068
62108
|
}
|
|
62069
62109
|
});
|
|
62070
62110
|
}
|
|
@@ -65370,13 +65410,16 @@ function generateProjectFiles(projectName, template, format = "esm") {
|
|
|
65370
65410
|
const gitignore = `node_modules/
|
|
65371
65411
|
dist/
|
|
65372
65412
|
.tsbuildinfo
|
|
65413
|
+
`;
|
|
65414
|
+
const configYaml = `defaultFileType: ts
|
|
65373
65415
|
`;
|
|
65374
65416
|
return {
|
|
65375
65417
|
"package.json": packageJson,
|
|
65376
65418
|
"tsconfig.json": tsconfigJson,
|
|
65377
65419
|
[`src/${workflowFile}`]: workflowCode,
|
|
65378
65420
|
"src/main.ts": mainTs,
|
|
65379
|
-
".gitignore": gitignore
|
|
65421
|
+
".gitignore": gitignore,
|
|
65422
|
+
".flowweaver/config.yaml": configYaml
|
|
65380
65423
|
};
|
|
65381
65424
|
}
|
|
65382
65425
|
function scaffoldProject(targetDir, files, options) {
|
|
@@ -65576,22 +65619,19 @@ async function watchCommand(input, options = {}) {
|
|
|
65576
65619
|
init_esm5();
|
|
65577
65620
|
import * as path19 from "path";
|
|
65578
65621
|
import * as fs18 from "fs";
|
|
65579
|
-
import * as
|
|
65622
|
+
import * as os from "os";
|
|
65580
65623
|
import { spawn } from "child_process";
|
|
65581
65624
|
|
|
65582
65625
|
// src/mcp/workflow-executor.ts
|
|
65583
65626
|
import * as path18 from "path";
|
|
65584
65627
|
import * as fs17 from "fs";
|
|
65585
|
-
import * as os from "os";
|
|
65586
65628
|
import { pathToFileURL } from "url";
|
|
65587
65629
|
import ts4 from "typescript";
|
|
65588
65630
|
async function executeWorkflowFromFile(filePath, params, options) {
|
|
65589
65631
|
const resolvedPath = path18.resolve(filePath);
|
|
65590
65632
|
const includeTrace = options?.includeTrace !== false;
|
|
65591
|
-
const
|
|
65592
|
-
|
|
65593
|
-
`fw-exec-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
65594
|
-
);
|
|
65633
|
+
const tmpId = `fw-exec-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
65634
|
+
const tmpBase = path18.join(path18.dirname(resolvedPath), tmpId);
|
|
65595
65635
|
const tmpTsFile = `${tmpBase}.ts`;
|
|
65596
65636
|
const tmpFile = `${tmpBase}.mjs`;
|
|
65597
65637
|
try {
|
|
@@ -65887,7 +65927,7 @@ async function runInngestDevMode(filePath, options) {
|
|
|
65887
65927
|
if (missingDeps.length > 0) {
|
|
65888
65928
|
throw new Error(`Missing dependencies: ${missingDeps.join(", ")}. Install them with: npm install ${missingDeps.join(" ")}`);
|
|
65889
65929
|
}
|
|
65890
|
-
const tmpDir = fs18.mkdtempSync(path19.join(
|
|
65930
|
+
const tmpDir = fs18.mkdtempSync(path19.join(os.tmpdir(), "fw-inngest-dev-"));
|
|
65891
65931
|
const inngestOutputPath = path19.join(tmpDir, path19.basename(filePath).replace(/\.ts$/, ".inngest.ts"));
|
|
65892
65932
|
let serverProcess = null;
|
|
65893
65933
|
const compileInngest = async () => {
|
|
@@ -84262,15 +84302,30 @@ function registerEditorTools(mcp, connection, buffer) {
|
|
|
84262
84302
|
params: external_exports.record(external_exports.unknown()).optional().describe("Optional execution parameters"),
|
|
84263
84303
|
includeTrace: external_exports.boolean().optional().describe("Include execution trace events (default: true)")
|
|
84264
84304
|
},
|
|
84265
|
-
async (args) => {
|
|
84305
|
+
async (args, extra) => {
|
|
84266
84306
|
if (args.filePath) {
|
|
84267
84307
|
try {
|
|
84268
84308
|
const channel = new AgentChannel();
|
|
84269
84309
|
const runId = `run-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
84310
|
+
const progressToken = extra._meta?.progressToken;
|
|
84311
|
+
let eventCount = 0;
|
|
84312
|
+
const onEvent = progressToken ? (event) => {
|
|
84313
|
+
eventCount++;
|
|
84314
|
+
extra.sendNotification({
|
|
84315
|
+
method: "notifications/progress",
|
|
84316
|
+
params: {
|
|
84317
|
+
progressToken,
|
|
84318
|
+
progress: eventCount,
|
|
84319
|
+
message: event.type === "STATUS_CHANGED" ? `${event.data?.id ?? ""}: ${event.data?.status ?? ""}` : event.type
|
|
84320
|
+
}
|
|
84321
|
+
}).catch(() => {
|
|
84322
|
+
});
|
|
84323
|
+
} : void 0;
|
|
84270
84324
|
const execPromise = executeWorkflowFromFile(args.filePath, args.params, {
|
|
84271
84325
|
workflowName: args.workflowName,
|
|
84272
84326
|
includeTrace: args.includeTrace,
|
|
84273
|
-
agentChannel: channel
|
|
84327
|
+
agentChannel: channel,
|
|
84328
|
+
onEvent
|
|
84274
84329
|
});
|
|
84275
84330
|
const raceResult = await Promise.race([
|
|
84276
84331
|
execPromise.then((r) => ({ type: "completed", result: r })),
|
|
@@ -92226,6 +92281,9 @@ async function runCommand(input, options) {
|
|
|
92226
92281
|
throw new Error(`Failed to parse mocks file: ${options.mocksFile}`);
|
|
92227
92282
|
}
|
|
92228
92283
|
}
|
|
92284
|
+
if (mocks && !options.json) {
|
|
92285
|
+
await validateMockConfig(mocks, filePath, options.workflow);
|
|
92286
|
+
}
|
|
92229
92287
|
let timeoutId;
|
|
92230
92288
|
let timedOut = false;
|
|
92231
92289
|
if (options.timeout) {
|
|
@@ -92380,6 +92438,35 @@ async function runCommand(input, options) {
|
|
|
92380
92438
|
}
|
|
92381
92439
|
}
|
|
92382
92440
|
}
|
|
92441
|
+
var VALID_MOCK_KEYS = /* @__PURE__ */ new Set(["events", "invocations", "agents", "fast"]);
|
|
92442
|
+
var MOCK_SECTION_TO_NODE = {
|
|
92443
|
+
events: "waitForEvent",
|
|
92444
|
+
invocations: "invokeWorkflow",
|
|
92445
|
+
agents: "waitForAgent"
|
|
92446
|
+
};
|
|
92447
|
+
async function validateMockConfig(mocks, filePath, workflowName) {
|
|
92448
|
+
for (const key of Object.keys(mocks)) {
|
|
92449
|
+
if (!VALID_MOCK_KEYS.has(key)) {
|
|
92450
|
+
logger.warn(`Mock config has unknown key "${key}". Valid keys: ${[...VALID_MOCK_KEYS].join(", ")}`);
|
|
92451
|
+
}
|
|
92452
|
+
}
|
|
92453
|
+
try {
|
|
92454
|
+
const result = await parseWorkflow(filePath, { workflowName });
|
|
92455
|
+
if (result.errors.length > 0 || !result.ast?.instances) return;
|
|
92456
|
+
const usedNodeTypes = new Set(result.ast.instances.map((i) => i.nodeType));
|
|
92457
|
+
for (const [section, nodeType] of Object.entries(MOCK_SECTION_TO_NODE)) {
|
|
92458
|
+
const mockSection = mocks[section];
|
|
92459
|
+
if (mockSection && typeof mockSection === "object" && Object.keys(mockSection).length > 0) {
|
|
92460
|
+
if (!usedNodeTypes.has(nodeType)) {
|
|
92461
|
+
logger.warn(
|
|
92462
|
+
`Mock config has "${section}" entries but workflow has no ${nodeType} nodes`
|
|
92463
|
+
);
|
|
92464
|
+
}
|
|
92465
|
+
}
|
|
92466
|
+
}
|
|
92467
|
+
} catch {
|
|
92468
|
+
}
|
|
92469
|
+
}
|
|
92383
92470
|
function promptForInput(question) {
|
|
92384
92471
|
return new Promise((resolve27) => {
|
|
92385
92472
|
const rl = readline9.createInterface({
|
|
@@ -92876,7 +92963,7 @@ async function serveCommand(dir, options) {
|
|
|
92876
92963
|
// src/export/index.ts
|
|
92877
92964
|
import * as path33 from "path";
|
|
92878
92965
|
import * as fs32 from "fs";
|
|
92879
|
-
import * as
|
|
92966
|
+
import * as os2 from "os";
|
|
92880
92967
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
92881
92968
|
|
|
92882
92969
|
// src/export/templates.ts
|
|
@@ -93073,7 +93160,7 @@ async function exportMultiWorkflow(options) {
|
|
|
93073
93160
|
throw new Error(`None of the requested workflows found. Available: ${available}`);
|
|
93074
93161
|
}
|
|
93075
93162
|
}
|
|
93076
|
-
const workDir = isDryRun ? path33.join(
|
|
93163
|
+
const workDir = isDryRun ? path33.join(os2.tmpdir(), `fw-export-multi-dryrun-${Date.now()}`) : outputDir;
|
|
93077
93164
|
fs32.mkdirSync(workDir, { recursive: true });
|
|
93078
93165
|
fs32.mkdirSync(path33.join(workDir, "workflows"), { recursive: true });
|
|
93079
93166
|
fs32.mkdirSync(path33.join(workDir, "runtime"), { recursive: true });
|
|
@@ -93797,7 +93884,7 @@ async function exportWorkflow(options) {
|
|
|
93797
93884
|
const available = parseResult.workflows.map((w) => w.name).join(", ");
|
|
93798
93885
|
throw new Error(`Workflow "${options.workflow}" not found. Available: ${available}`);
|
|
93799
93886
|
}
|
|
93800
|
-
const workDir = isDryRun ? path33.join(
|
|
93887
|
+
const workDir = isDryRun ? path33.join(os2.tmpdir(), `fw-export-dryrun-${Date.now()}`) : outputDir;
|
|
93801
93888
|
fs32.mkdirSync(workDir, { recursive: true });
|
|
93802
93889
|
let compiledContent;
|
|
93803
93890
|
let compiledPath;
|
|
@@ -95041,7 +95128,7 @@ function displayInstalledPackage(pkg) {
|
|
|
95041
95128
|
}
|
|
95042
95129
|
|
|
95043
95130
|
// src/cli/index.ts
|
|
95044
|
-
var version2 = true ? "0.
|
|
95131
|
+
var version2 = true ? "0.7.0" : "0.0.0-dev";
|
|
95045
95132
|
var program2 = new Command();
|
|
95046
95133
|
program2.name("flow-weaver").description("Flow Weaver Annotations - Compile and validate workflow files").version(version2, "-v, --version", "Output the current version");
|
|
95047
95134
|
program2.configureOutput({
|
package/dist/mcp/tools-editor.js
CHANGED
|
@@ -133,16 +133,35 @@ export function registerEditorTools(mcp, connection, buffer) {
|
|
|
133
133
|
.boolean()
|
|
134
134
|
.optional()
|
|
135
135
|
.describe('Include execution trace events (default: true)'),
|
|
136
|
-
}, async (args) => {
|
|
136
|
+
}, async (args, extra) => {
|
|
137
137
|
// When filePath is provided, compile and execute directly (no editor needed)
|
|
138
138
|
if (args.filePath) {
|
|
139
139
|
try {
|
|
140
140
|
const channel = new AgentChannel();
|
|
141
141
|
const runId = `run-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
142
|
+
// Send progress notifications for trace events when client supports it
|
|
143
|
+
const progressToken = extra._meta?.progressToken;
|
|
144
|
+
let eventCount = 0;
|
|
145
|
+
const onEvent = progressToken
|
|
146
|
+
? (event) => {
|
|
147
|
+
eventCount++;
|
|
148
|
+
extra.sendNotification({
|
|
149
|
+
method: 'notifications/progress',
|
|
150
|
+
params: {
|
|
151
|
+
progressToken,
|
|
152
|
+
progress: eventCount,
|
|
153
|
+
message: event.type === 'STATUS_CHANGED'
|
|
154
|
+
? `${event.data?.id ?? ''}: ${event.data?.status ?? ''}`
|
|
155
|
+
: event.type,
|
|
156
|
+
},
|
|
157
|
+
}).catch(() => { });
|
|
158
|
+
}
|
|
159
|
+
: undefined;
|
|
142
160
|
const execPromise = executeWorkflowFromFile(args.filePath, args.params, {
|
|
143
161
|
workflowName: args.workflowName,
|
|
144
162
|
includeTrace: args.includeTrace,
|
|
145
163
|
agentChannel: channel,
|
|
164
|
+
onEvent,
|
|
146
165
|
});
|
|
147
166
|
// Race between workflow completing and workflow pausing
|
|
148
167
|
const raceResult = await Promise.race([
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import * as fs from 'fs';
|
|
7
|
-
import * as os from 'os';
|
|
8
7
|
import { pathToFileURL } from 'url';
|
|
9
8
|
import ts from 'typescript';
|
|
10
9
|
import { compileWorkflow } from '../api/index.js';
|
|
@@ -31,7 +30,13 @@ export async function executeWorkflowFromFile(filePath, params, options) {
|
|
|
31
30
|
// In-place compilation preserves all functions in the module (node types,
|
|
32
31
|
// sibling workflows), which is required for workflow composition where one
|
|
33
32
|
// workflow calls another as a node type.
|
|
34
|
-
|
|
33
|
+
//
|
|
34
|
+
// Temp files are written in the source file's directory (not os.tmpdir())
|
|
35
|
+
// so that ESM module resolution can walk up to the project's node_modules.
|
|
36
|
+
// On Windows, os.tmpdir() is disconnected from the project tree, causing
|
|
37
|
+
// bare import specifiers (e.g. 'zod', 'openai') to fail with MODULE_NOT_FOUND.
|
|
38
|
+
const tmpId = `fw-exec-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
39
|
+
const tmpBase = path.join(path.dirname(resolvedPath), tmpId);
|
|
35
40
|
const tmpTsFile = `${tmpBase}.ts`;
|
|
36
41
|
const tmpFile = `${tmpBase}.mjs`;
|
|
37
42
|
try {
|
package/dist/validator.js
CHANGED
|
@@ -2,6 +2,19 @@ import { RESERVED_NODE_NAMES, isStartNode, isExitNode, isExecutePort, isReserved
|
|
|
2
2
|
import { findClosestMatches } from './utils/string-distance.js';
|
|
3
3
|
import { parseFunctionSignature } from './jsdoc-port-sync/signature-parser.js';
|
|
4
4
|
import { checkTypeCompatibilityFromStrings } from './type-checker.js';
|
|
5
|
+
const DOCS_BASE = 'https://docs.flowweaver.dev/reference';
|
|
6
|
+
/** Map error codes to the documentation page that explains how to fix them. */
|
|
7
|
+
const ERROR_DOC_URLS = {
|
|
8
|
+
UNKNOWN_NODE_TYPE: `${DOCS_BASE}/concepts#node-registration`,
|
|
9
|
+
UNKNOWN_SOURCE_PORT: `${DOCS_BASE}/concepts#port-architecture`,
|
|
10
|
+
UNKNOWN_TARGET_PORT: `${DOCS_BASE}/concepts#port-architecture`,
|
|
11
|
+
TYPE_MISMATCH: `${DOCS_BASE}/compilation#type-compatibility`,
|
|
12
|
+
UNREACHABLE_NODE: `${DOCS_BASE}/concepts#graph-structure`,
|
|
13
|
+
MISSING_START_CONNECTION: `${DOCS_BASE}/concepts#start-and-exit`,
|
|
14
|
+
MISSING_EXIT_CONNECTION: `${DOCS_BASE}/concepts#start-and-exit`,
|
|
15
|
+
INFERRED_NODE_TYPE: `${DOCS_BASE}/node-conversion`,
|
|
16
|
+
DUPLICATE_CONNECTION: `${DOCS_BASE}/concepts#connections`,
|
|
17
|
+
};
|
|
5
18
|
export class WorkflowValidator {
|
|
6
19
|
errors = [];
|
|
7
20
|
warnings = [];
|
|
@@ -149,6 +162,12 @@ export class WorkflowValidator {
|
|
|
149
162
|
return true;
|
|
150
163
|
});
|
|
151
164
|
}
|
|
165
|
+
// Attach doc URLs to diagnostics that have mapped error codes
|
|
166
|
+
for (const diag of [...this.errors, ...this.warnings]) {
|
|
167
|
+
if (!diag.docUrl && ERROR_DOC_URLS[diag.code]) {
|
|
168
|
+
diag.docUrl = ERROR_DOC_URLS[diag.code];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
152
171
|
return {
|
|
153
172
|
valid: this.errors.length === 0,
|
|
154
173
|
errors: this.errors,
|
|
@@ -215,11 +215,24 @@ Use `@fwImport` to turn npm package functions or local module exports into node
|
|
|
215
215
|
*/
|
|
216
216
|
```
|
|
217
217
|
|
|
218
|
-
|
|
219
|
-
- Second identifier: exported function name
|
|
220
|
-
- String: package name or relative path
|
|
218
|
+
**Syntax**: `@fwImport <nodeTypeName> <functionName> from "<package-or-path>"`
|
|
221
219
|
|
|
222
|
-
|
|
220
|
+
- **Node type name** (first identifier): used in `@node` declarations. Convention: `npm/pkg/fn` for packages, `local/path/fn` for local modules.
|
|
221
|
+
- **Function name** (second identifier): the actual exported function name to import.
|
|
222
|
+
- **Source** (quoted string): npm package name or relative path to a local module.
|
|
223
|
+
|
|
224
|
+
**Prefix semantics**:
|
|
225
|
+
- `npm/` — resolves to a bare package specifier. The package must be installed in `node_modules`. At compile time, the compiler generates an `import { fn } from "package"` statement in the output.
|
|
226
|
+
- `local/` — resolves to a relative import from the workflow file's directory. Generates `import { fn } from "./path"`.
|
|
227
|
+
|
|
228
|
+
**Type inference**: port types are inferred from the function's TypeScript signature (from `.d.ts` files for npm packages, or from the source for local modules). If type information isn't available, ports default to `ANY`.
|
|
229
|
+
|
|
230
|
+
**What happens at compile time**: the compiler parses the `@fwImport` annotation, resolves the function signature, creates a virtual node type with inferred ports, and emits the corresponding import statement in the generated code. The imported function is called as an expression node — no `execute` parameter, no STEP ports.
|
|
231
|
+
|
|
232
|
+
**Common errors**:
|
|
233
|
+
- Package not installed: `npm install <package>` before compiling.
|
|
234
|
+
- Wrong export name: check the package's exports with your IDE or `npm info <package>`.
|
|
235
|
+
- No type information: install `@types/<package>` for community type definitions.
|
|
223
236
|
|
|
224
237
|
## Mandatory Signatures
|
|
225
238
|
|
|
@@ -245,6 +258,33 @@ export function myWorkflow(execute: boolean, params: { inputA: Type }): {...}
|
|
|
245
258
|
|
|
246
259
|
> **Key difference:** Nodes use direct params, workflows use `params` object.
|
|
247
260
|
|
|
261
|
+
## Node Registration
|
|
262
|
+
|
|
263
|
+
Every node used in a workflow must be explicitly declared with `@node`. The compiler builds a static directed graph from annotations at compile time, so it needs to know about every node before code generation begins. This is different from normal function calls where you just invoke a function directly.
|
|
264
|
+
|
|
265
|
+
Built-in nodes (`delay`, `waitForEvent`, `invokeWorkflow`, `waitForAgent`) are exported from the library but still need explicit declaration in your workflow file. The compiler validates all `@node` references against the set of available node types — functions annotated with `@flowWeaver nodeType` in the same file or imported via `@fwImport`.
|
|
266
|
+
|
|
267
|
+
To use a built-in node, define or import the function and annotate it:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import { waitForEvent } from '@synergenius/flow-weaver/built-in-nodes';
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* @flowWeaver nodeType
|
|
274
|
+
* @input eventName - Event to wait for
|
|
275
|
+
* @output eventData - Received event payload
|
|
276
|
+
*/
|
|
277
|
+
// (function body provided by the library)
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* @flowWeaver workflow
|
|
281
|
+
* @node wait waitForEvent
|
|
282
|
+
* @connect Start.eventName -> wait.eventName
|
|
283
|
+
* @connect wait.eventData -> Exit.data
|
|
284
|
+
*/
|
|
285
|
+
export function myWorkflow(execute: boolean, params: { eventName: string }) { ... }
|
|
286
|
+
```
|
|
287
|
+
|
|
248
288
|
## Port Types
|
|
249
289
|
|
|
250
290
|
STRING, NUMBER, BOOLEAN, OBJECT, ARRAY, FUNCTION, ANY, STEP
|
|
@@ -169,6 +169,39 @@ flow-weaver describe src/workflows/my-workflow.ts --format mermaid # diagram
|
|
|
169
169
|
flow-weaver describe src/workflows/my-workflow.ts --node fetcher1 # focus on a node
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
+
### flow-weaver run --stream vs --trace
|
|
173
|
+
|
|
174
|
+
Both flags give you execution trace data, but in different ways:
|
|
175
|
+
|
|
176
|
+
**`--stream`** writes events to stderr in real-time as nodes execute. Each STATUS_CHANGED event prints the node ID, new status, and duration. Use this for live debugging during development — you can watch the workflow progress node by node.
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
flow-weaver run workflow.ts --stream
|
|
180
|
+
# Output (to stderr):
|
|
181
|
+
# [STATUS_CHANGED] fetcher: → RUNNING
|
|
182
|
+
# [STATUS_CHANGED] fetcher: → SUCCEEDED (142ms)
|
|
183
|
+
# [STATUS_CHANGED] processor: → RUNNING
|
|
184
|
+
# [VARIABLE_SET] processor.result
|
|
185
|
+
# [STATUS_CHANGED] processor: → SUCCEEDED (38ms)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**`--trace`** collects all ExecutionTraceEvent objects during execution and includes them in the output after completion. Use this for post-mortem analysis or programmatic consumption (e.g., in CI or with `--json`).
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
flow-weaver run workflow.ts --trace
|
|
192
|
+
# Shows: "12 events captured" + first 5 events as summary
|
|
193
|
+
|
|
194
|
+
flow-weaver run workflow.ts --trace --json | jq '.traceCount'
|
|
195
|
+
# Outputs: 12
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Combining both**: `--stream --trace` gives you real-time output during execution AND the collected trace array in the result. Useful when you want to watch progress live but also capture the full event log.
|
|
199
|
+
|
|
200
|
+
**When to use which**:
|
|
201
|
+
- Debugging interactively → `--stream`
|
|
202
|
+
- CI pipeline or scripted analysis → `--trace --json`
|
|
203
|
+
- Both → `--stream --trace`
|
|
204
|
+
|
|
172
205
|
### Diagnostic Strategy
|
|
173
206
|
|
|
174
207
|
1. **flow-weaver validate** -- Get all errors and warnings. Fix errors first.
|
package/package.json
CHANGED