@synergenius/flow-weaver 0.22.5 → 0.22.7
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/agent/agent-loop.d.ts +10 -0
- package/dist/agent/agent-loop.js +115 -0
- package/dist/agent/cli-session.d.ts +77 -0
- package/dist/agent/cli-session.js +309 -0
- package/dist/agent/env-allowlist.d.ts +29 -0
- package/dist/agent/env-allowlist.js +60 -0
- package/dist/agent/index.d.ts +16 -0
- package/dist/agent/index.js +20 -0
- package/dist/agent/mcp-bridge.d.ts +22 -0
- package/dist/agent/mcp-bridge.js +132 -0
- package/dist/agent/mcp-tool-server.d.ts +30 -0
- package/dist/agent/mcp-tool-server.js +210 -0
- package/dist/agent/providers/anthropic.d.ts +23 -0
- package/dist/agent/providers/anthropic.js +185 -0
- package/dist/agent/providers/claude-cli.d.ts +21 -0
- package/dist/agent/providers/claude-cli.js +155 -0
- package/dist/agent/streaming.d.ts +36 -0
- package/dist/agent/streaming.js +183 -0
- package/dist/agent/types.d.ts +152 -0
- package/dist/agent/types.js +7 -0
- package/dist/cli/flow-weaver.mjs +30 -11
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/dist/generator/code-utils.js +29 -9
- package/dist/generator/unified.js +3 -1
- package/package.json +5 -1
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude CLI provider — spawns the Claude Code CLI with stream-json output
|
|
3
|
+
* and MCP bridge for tool execution.
|
|
4
|
+
*
|
|
5
|
+
* Adapted from platform's streamClaudeCliChat. Platform-specific dependencies
|
|
6
|
+
* (spawnSandboxed, getBinPath, config) are replaced with injectable options.
|
|
7
|
+
*/
|
|
8
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
9
|
+
import { StreamJsonParser } from '../streaming.js';
|
|
10
|
+
import { createMcpBridge } from '../mcp-bridge.js';
|
|
11
|
+
export class ClaudeCliProvider {
|
|
12
|
+
binPath;
|
|
13
|
+
cwd;
|
|
14
|
+
env;
|
|
15
|
+
model;
|
|
16
|
+
mcpConfigPath;
|
|
17
|
+
spawnFn;
|
|
18
|
+
timeout;
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.binPath = options.binPath ?? 'claude';
|
|
21
|
+
this.cwd = options.cwd ?? process.cwd();
|
|
22
|
+
this.env = options.env ?? process.env;
|
|
23
|
+
this.model = options.model;
|
|
24
|
+
this.mcpConfigPath = options.mcpConfigPath;
|
|
25
|
+
this.spawnFn = options.spawnFn ?? ((cmd, args, opts) => nodeSpawn(cmd, args, { ...opts, stdio: opts.stdio }));
|
|
26
|
+
this.timeout = options.timeout ?? 120_000;
|
|
27
|
+
}
|
|
28
|
+
async *stream(messages, tools, options) {
|
|
29
|
+
const model = options?.model ?? this.model;
|
|
30
|
+
// Format messages into a single prompt for -p mode
|
|
31
|
+
const prompt = formatPrompt(messages);
|
|
32
|
+
const systemPrompt = options?.systemPrompt;
|
|
33
|
+
// Set up MCP bridge for tool access if tools are provided and no config given
|
|
34
|
+
let bridge = null;
|
|
35
|
+
let mcpConfigPath = this.mcpConfigPath;
|
|
36
|
+
if (tools.length > 0 && !mcpConfigPath) {
|
|
37
|
+
// Create a temporary bridge — tool execution is handled by the agent loop,
|
|
38
|
+
// but we still need to advertise tool definitions to the CLI via MCP.
|
|
39
|
+
// The bridge executor won't be called because the CLI handles its own tool
|
|
40
|
+
// loop internally when using MCP tools.
|
|
41
|
+
bridge = await createMcpBridge(tools, async () => ({ result: 'Not implemented', isError: true }));
|
|
42
|
+
mcpConfigPath = bridge.configPath;
|
|
43
|
+
}
|
|
44
|
+
const args = [
|
|
45
|
+
'-p',
|
|
46
|
+
'--verbose',
|
|
47
|
+
'--output-format',
|
|
48
|
+
'stream-json',
|
|
49
|
+
'--include-partial-messages',
|
|
50
|
+
'--setting-sources',
|
|
51
|
+
'user,local',
|
|
52
|
+
'--dangerously-skip-permissions',
|
|
53
|
+
'--permission-mode',
|
|
54
|
+
'bypassPermissions',
|
|
55
|
+
...(systemPrompt ? ['--system-prompt', systemPrompt] : []),
|
|
56
|
+
...(mcpConfigPath ? ['--mcp-config', mcpConfigPath, '--strict-mcp-config'] : []),
|
|
57
|
+
...(model ? ['--model', model] : []),
|
|
58
|
+
];
|
|
59
|
+
// Spawn the CLI process
|
|
60
|
+
const spawnResult = this.spawnFn(this.binPath, args, { cwd: this.cwd, stdio: ['pipe', 'pipe', 'pipe'], env: this.env });
|
|
61
|
+
const child = 'child' in spawnResult ? spawnResult.child : spawnResult;
|
|
62
|
+
const spawnCleanup = 'cleanup' in spawnResult ? spawnResult.cleanup : undefined;
|
|
63
|
+
const signal = options?.signal;
|
|
64
|
+
if (signal) {
|
|
65
|
+
signal.addEventListener('abort', () => {
|
|
66
|
+
child.kill('SIGTERM');
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
if (!child.killed)
|
|
69
|
+
child.kill('SIGKILL');
|
|
70
|
+
}, 2000);
|
|
71
|
+
}, { once: true });
|
|
72
|
+
}
|
|
73
|
+
// Timeout
|
|
74
|
+
const timer = setTimeout(() => {
|
|
75
|
+
child.kill('SIGTERM');
|
|
76
|
+
}, this.timeout);
|
|
77
|
+
child.stdin.write(prompt);
|
|
78
|
+
child.stdin.end();
|
|
79
|
+
// Collect events from the parser
|
|
80
|
+
const events = [];
|
|
81
|
+
let done = false;
|
|
82
|
+
const parser = new StreamJsonParser((event) => {
|
|
83
|
+
events.push(event);
|
|
84
|
+
});
|
|
85
|
+
let stderrBuf = '';
|
|
86
|
+
child.stderr.on('data', (chunk) => {
|
|
87
|
+
stderrBuf += chunk.toString();
|
|
88
|
+
});
|
|
89
|
+
let stdoutBuffer = '';
|
|
90
|
+
child.stdout.on('data', (chunk) => {
|
|
91
|
+
stdoutBuffer += chunk.toString();
|
|
92
|
+
const lines = stdoutBuffer.split('\n');
|
|
93
|
+
stdoutBuffer = lines.pop() || '';
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
parser.feed(line);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
child.on('close', (code) => {
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
// Process remaining buffer
|
|
101
|
+
if (stdoutBuffer.trim()) {
|
|
102
|
+
parser.feed(stdoutBuffer);
|
|
103
|
+
}
|
|
104
|
+
if (code !== 0 && !parser.hasText && stderrBuf.trim()) {
|
|
105
|
+
events.push({ type: 'text_delta', text: `Claude CLI error: ${stderrBuf.trim().slice(0, 500)}` });
|
|
106
|
+
}
|
|
107
|
+
if (!events.some((e) => e.type === 'message_stop')) {
|
|
108
|
+
events.push({ type: 'message_stop', finishReason: code === 0 ? 'stop' : 'error' });
|
|
109
|
+
}
|
|
110
|
+
done = true;
|
|
111
|
+
});
|
|
112
|
+
child.on('error', () => {
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
events.push({ type: 'message_stop', finishReason: 'error' });
|
|
115
|
+
done = true;
|
|
116
|
+
});
|
|
117
|
+
// Yield events as they become available
|
|
118
|
+
try {
|
|
119
|
+
while (!done || events.length > 0) {
|
|
120
|
+
if (events.length > 0) {
|
|
121
|
+
yield events.shift();
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
bridge?.cleanup();
|
|
130
|
+
spawnCleanup?.();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Format agent messages into a single prompt string for the CLI's -p mode.
|
|
136
|
+
*/
|
|
137
|
+
function formatPrompt(messages) {
|
|
138
|
+
const parts = [];
|
|
139
|
+
for (const msg of messages) {
|
|
140
|
+
if (msg.role === 'user') {
|
|
141
|
+
parts.push(`User: ${typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)}\n`);
|
|
142
|
+
}
|
|
143
|
+
else if (msg.role === 'assistant') {
|
|
144
|
+
parts.push(`Assistant: ${typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)}\n`);
|
|
145
|
+
}
|
|
146
|
+
else if (msg.role === 'tool') {
|
|
147
|
+
parts.push(`Tool result (${msg.toolCallId}): ${typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)}\n`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return parts.join('\n');
|
|
151
|
+
}
|
|
152
|
+
export function createClaudeCliProvider(options) {
|
|
153
|
+
return new ClaudeCliProvider(options);
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=claude-cli.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StreamJsonParser — parses Claude CLI stream-json NDJSON output into
|
|
3
|
+
* typed StreamEvent values.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from the duplicated parsing logic in platform's claude.ts
|
|
6
|
+
* and cli-session.ts. Handles:
|
|
7
|
+
* - stream_event wrapper unwrapping
|
|
8
|
+
* - content_block lifecycle (text, tool_use, thinking)
|
|
9
|
+
* - Tool argument accumulation (input_json_delta → tool_use_end)
|
|
10
|
+
* - Tool result blocks from CLI's internal tool loop (user events)
|
|
11
|
+
* - Usage tracking from message_start, message_delta, and result
|
|
12
|
+
* - result event as text fallback and turn boundary
|
|
13
|
+
*
|
|
14
|
+
* The parser does NOT decide turn boundaries — consumers (one-shot vs
|
|
15
|
+
* persistent session) handle that differently.
|
|
16
|
+
*/
|
|
17
|
+
import type { StreamEvent } from './types.js';
|
|
18
|
+
export type EventCallback = (event: StreamEvent) => void;
|
|
19
|
+
export declare class StreamJsonParser {
|
|
20
|
+
private pushEvent;
|
|
21
|
+
private hasAssistantText;
|
|
22
|
+
private insideToolUse;
|
|
23
|
+
private activeToolJsonChunks;
|
|
24
|
+
constructor(pushEvent: EventCallback);
|
|
25
|
+
/** Reset per-turn state. Call before starting a new turn in persistent sessions. */
|
|
26
|
+
reset(): void;
|
|
27
|
+
/** Whether any text_delta events have been emitted this turn. */
|
|
28
|
+
get hasText(): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Feed a single NDJSON line (without trailing newline).
|
|
31
|
+
* Parses and emits StreamEvent(s) via the callback.
|
|
32
|
+
*/
|
|
33
|
+
feed(line: string): void;
|
|
34
|
+
private processEvent;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=streaming.d.ts.map
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StreamJsonParser — parses Claude CLI stream-json NDJSON output into
|
|
3
|
+
* typed StreamEvent values.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from the duplicated parsing logic in platform's claude.ts
|
|
6
|
+
* and cli-session.ts. Handles:
|
|
7
|
+
* - stream_event wrapper unwrapping
|
|
8
|
+
* - content_block lifecycle (text, tool_use, thinking)
|
|
9
|
+
* - Tool argument accumulation (input_json_delta → tool_use_end)
|
|
10
|
+
* - Tool result blocks from CLI's internal tool loop (user events)
|
|
11
|
+
* - Usage tracking from message_start, message_delta, and result
|
|
12
|
+
* - result event as text fallback and turn boundary
|
|
13
|
+
*
|
|
14
|
+
* The parser does NOT decide turn boundaries — consumers (one-shot vs
|
|
15
|
+
* persistent session) handle that differently.
|
|
16
|
+
*/
|
|
17
|
+
export class StreamJsonParser {
|
|
18
|
+
pushEvent;
|
|
19
|
+
hasAssistantText = false;
|
|
20
|
+
insideToolUse = false;
|
|
21
|
+
activeToolJsonChunks = new Map();
|
|
22
|
+
constructor(pushEvent) {
|
|
23
|
+
this.pushEvent = pushEvent;
|
|
24
|
+
}
|
|
25
|
+
/** Reset per-turn state. Call before starting a new turn in persistent sessions. */
|
|
26
|
+
reset() {
|
|
27
|
+
this.hasAssistantText = false;
|
|
28
|
+
this.insideToolUse = false;
|
|
29
|
+
this.activeToolJsonChunks.clear();
|
|
30
|
+
}
|
|
31
|
+
/** Whether any text_delta events have been emitted this turn. */
|
|
32
|
+
get hasText() {
|
|
33
|
+
return this.hasAssistantText;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Feed a single NDJSON line (without trailing newline).
|
|
37
|
+
* Parses and emits StreamEvent(s) via the callback.
|
|
38
|
+
*/
|
|
39
|
+
feed(line) {
|
|
40
|
+
if (!line.trim())
|
|
41
|
+
return;
|
|
42
|
+
let event;
|
|
43
|
+
try {
|
|
44
|
+
event = JSON.parse(line);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Non-JSON line — only use as text if no other source is available
|
|
48
|
+
if (line.trim() && !this.hasAssistantText) {
|
|
49
|
+
this.pushEvent({ type: 'text_delta', text: line });
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Unwrap stream_event wrapper (--include-partial-messages wraps API events)
|
|
54
|
+
if (event.type === 'stream_event' && event.event) {
|
|
55
|
+
event = event.event;
|
|
56
|
+
}
|
|
57
|
+
this.processEvent(event);
|
|
58
|
+
}
|
|
59
|
+
processEvent(event) {
|
|
60
|
+
const block = event.content_block;
|
|
61
|
+
const delta = event.delta;
|
|
62
|
+
// --- content_block_start ---
|
|
63
|
+
if (event.type === 'content_block_start' && block?.type === 'tool_use') {
|
|
64
|
+
this.insideToolUse = true;
|
|
65
|
+
const id = block.id || `cli-tool-${Date.now()}`;
|
|
66
|
+
const name = block.name || 'unknown';
|
|
67
|
+
this.pushEvent({ type: 'tool_use_start', id, name });
|
|
68
|
+
this.activeToolJsonChunks.set(id, { name, chunks: [] });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (event.type === 'content_block_start' && block?.type === 'text') {
|
|
72
|
+
this.insideToolUse = false;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// --- content_block_delta ---
|
|
76
|
+
if (event.type === 'content_block_delta' && delta?.type === 'input_json_delta') {
|
|
77
|
+
const lastTool = [...this.activeToolJsonChunks.entries()].pop();
|
|
78
|
+
if (lastTool)
|
|
79
|
+
lastTool[1].chunks.push(delta.partial_json || '');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (event.type === 'content_block_delta' && delta?.type === 'thinking_delta' && delta?.thinking) {
|
|
83
|
+
this.pushEvent({ type: 'thinking_delta', text: delta.thinking });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (event.type === 'content_block_delta' && delta?.text && !this.insideToolUse) {
|
|
87
|
+
this.hasAssistantText = true;
|
|
88
|
+
this.pushEvent({ type: 'text_delta', text: delta.text });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// --- content_block_stop ---
|
|
92
|
+
if (event.type === 'content_block_stop' && this.insideToolUse) {
|
|
93
|
+
const lastTool = [...this.activeToolJsonChunks.entries()].pop();
|
|
94
|
+
if (lastTool) {
|
|
95
|
+
const [id, { chunks }] = lastTool;
|
|
96
|
+
let args = {};
|
|
97
|
+
try {
|
|
98
|
+
args = JSON.parse(chunks.join(''));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
/* malformed */
|
|
102
|
+
}
|
|
103
|
+
this.pushEvent({ type: 'tool_use_end', id, arguments: args });
|
|
104
|
+
this.activeToolJsonChunks.delete(id);
|
|
105
|
+
}
|
|
106
|
+
this.insideToolUse = false;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// --- user event with tool_result blocks (CLI's internal tool loop) ---
|
|
110
|
+
if (event.type === 'user' && event.message?.content) {
|
|
111
|
+
const content = event.message.content;
|
|
112
|
+
for (const contentBlock of content) {
|
|
113
|
+
if (contentBlock.type === 'tool_result' && contentBlock.tool_use_id) {
|
|
114
|
+
const text = Array.isArray(contentBlock.content)
|
|
115
|
+
? contentBlock.content
|
|
116
|
+
.map((c) => c.text || '')
|
|
117
|
+
.join('')
|
|
118
|
+
: String(contentBlock.content || '');
|
|
119
|
+
this.pushEvent({
|
|
120
|
+
type: 'tool_result',
|
|
121
|
+
id: contentBlock.tool_use_id,
|
|
122
|
+
result: text,
|
|
123
|
+
isError: !!contentBlock.is_error,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// --- message_start (usage) ---
|
|
130
|
+
if (event.type === 'message_start' && event.message?.usage) {
|
|
131
|
+
const usage = event.message.usage;
|
|
132
|
+
this.pushEvent({
|
|
133
|
+
type: 'usage',
|
|
134
|
+
promptTokens: usage.input_tokens ?? 0,
|
|
135
|
+
completionTokens: usage.output_tokens ?? 0,
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// --- message_delta (usage) ---
|
|
140
|
+
if (event.type === 'message_delta' && event.usage) {
|
|
141
|
+
const usage = event.usage;
|
|
142
|
+
this.pushEvent({
|
|
143
|
+
type: 'usage',
|
|
144
|
+
promptTokens: 0,
|
|
145
|
+
completionTokens: usage.output_tokens ?? 0,
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// --- message_stop ---
|
|
150
|
+
if (event.type === 'message_stop') {
|
|
151
|
+
this.pushEvent({ type: 'message_stop', finishReason: 'stop' });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
// --- result event (CLI turn boundary) ---
|
|
155
|
+
if (event.type === 'result') {
|
|
156
|
+
if (event.is_error) {
|
|
157
|
+
this.pushEvent({ type: 'message_stop', finishReason: 'error' });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
// result text is a fallback — only use if content_block_delta never fired
|
|
161
|
+
if (event.result && !this.hasAssistantText) {
|
|
162
|
+
this.pushEvent({ type: 'text_delta', text: event.result });
|
|
163
|
+
}
|
|
164
|
+
if (event.usage) {
|
|
165
|
+
const usage = event.usage;
|
|
166
|
+
this.pushEvent({
|
|
167
|
+
type: 'usage',
|
|
168
|
+
promptTokens: usage.input_tokens ?? 0,
|
|
169
|
+
completionTokens: usage.output_tokens ?? 0,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
this.pushEvent({ type: 'message_stop', finishReason: 'stop' });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// --- assistant event (auth failure detection) ---
|
|
176
|
+
if (event.type === 'assistant') {
|
|
177
|
+
if (event.error === 'authentication_failed') {
|
|
178
|
+
this.pushEvent({ type: 'message_stop', finishReason: 'error' });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=streaming.js.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the agent loop, providers, and MCP bridge.
|
|
3
|
+
*
|
|
4
|
+
* All types are pure — no runtime imports, no side effects.
|
|
5
|
+
*/
|
|
6
|
+
import type { ChildProcess } from 'node:child_process';
|
|
7
|
+
export type StreamEvent = {
|
|
8
|
+
type: 'text_delta';
|
|
9
|
+
text: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: 'thinking_delta';
|
|
12
|
+
text: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: 'tool_use_start';
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'tool_use_delta';
|
|
19
|
+
id: string;
|
|
20
|
+
partialJson: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'tool_use_end';
|
|
23
|
+
id: string;
|
|
24
|
+
arguments: Record<string, unknown>;
|
|
25
|
+
} | {
|
|
26
|
+
type: 'tool_result';
|
|
27
|
+
id: string;
|
|
28
|
+
result: string;
|
|
29
|
+
isError: boolean;
|
|
30
|
+
} | {
|
|
31
|
+
type: 'message_stop';
|
|
32
|
+
finishReason: 'stop' | 'tool_calls' | 'length' | 'error';
|
|
33
|
+
} | {
|
|
34
|
+
type: 'usage';
|
|
35
|
+
promptTokens: number;
|
|
36
|
+
completionTokens: number;
|
|
37
|
+
};
|
|
38
|
+
export interface AgentMessage {
|
|
39
|
+
role: 'user' | 'assistant' | 'tool';
|
|
40
|
+
content: string | Array<Record<string, unknown>>;
|
|
41
|
+
toolCalls?: Array<{
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
arguments: Record<string, unknown>;
|
|
45
|
+
}>;
|
|
46
|
+
toolCallId?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface ToolDefinition {
|
|
49
|
+
name: string;
|
|
50
|
+
description: string;
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: string;
|
|
53
|
+
properties: Record<string, unknown>;
|
|
54
|
+
required?: string[];
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export type ToolExecutor = (name: string, args: Record<string, unknown>) => Promise<{
|
|
58
|
+
result: string;
|
|
59
|
+
isError: boolean;
|
|
60
|
+
}>;
|
|
61
|
+
export interface ToolEvent {
|
|
62
|
+
type: 'tool_call_start' | 'tool_call_result';
|
|
63
|
+
name: string;
|
|
64
|
+
args?: Record<string, unknown>;
|
|
65
|
+
result?: string;
|
|
66
|
+
isError?: boolean;
|
|
67
|
+
}
|
|
68
|
+
export interface StreamOptions {
|
|
69
|
+
systemPrompt?: string;
|
|
70
|
+
model?: string;
|
|
71
|
+
maxTokens?: number;
|
|
72
|
+
signal?: AbortSignal;
|
|
73
|
+
}
|
|
74
|
+
export interface AgentProvider {
|
|
75
|
+
stream(messages: AgentMessage[], tools: ToolDefinition[], options?: StreamOptions): AsyncGenerator<StreamEvent>;
|
|
76
|
+
}
|
|
77
|
+
export interface McpBridge {
|
|
78
|
+
/** Path to the MCP config JSON file — pass to --mcp-config */
|
|
79
|
+
configPath: string;
|
|
80
|
+
/** Update the executor and event callback for a new request */
|
|
81
|
+
setHandlers: (executor: ToolExecutor, onToolEvent?: (event: ToolEvent) => void) => void;
|
|
82
|
+
/** Tear down the socket server and remove temp files */
|
|
83
|
+
cleanup: () => void;
|
|
84
|
+
}
|
|
85
|
+
export interface AgentLoopOptions {
|
|
86
|
+
systemPrompt?: string;
|
|
87
|
+
maxIterations?: number;
|
|
88
|
+
maxTokens?: number;
|
|
89
|
+
model?: string;
|
|
90
|
+
signal?: AbortSignal;
|
|
91
|
+
onToolEvent?: (event: ToolEvent) => void;
|
|
92
|
+
onStreamEvent?: (event: StreamEvent) => void;
|
|
93
|
+
logger?: Logger;
|
|
94
|
+
}
|
|
95
|
+
export interface AgentLoopResult {
|
|
96
|
+
success: boolean;
|
|
97
|
+
summary: string;
|
|
98
|
+
messages: AgentMessage[];
|
|
99
|
+
toolCallCount: number;
|
|
100
|
+
usage: {
|
|
101
|
+
promptTokens: number;
|
|
102
|
+
completionTokens: number;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export type SpawnFn = (command: string, args: string[], options: {
|
|
106
|
+
cwd: string;
|
|
107
|
+
stdio: string[];
|
|
108
|
+
env: NodeJS.ProcessEnv;
|
|
109
|
+
}) => ChildProcess | {
|
|
110
|
+
child: ChildProcess;
|
|
111
|
+
cleanup?: () => void;
|
|
112
|
+
};
|
|
113
|
+
export interface ClaudeCliProviderOptions {
|
|
114
|
+
/** Absolute path to the claude binary. Defaults to 'claude' (found via PATH). */
|
|
115
|
+
binPath?: string;
|
|
116
|
+
/** Working directory for the CLI process. */
|
|
117
|
+
cwd?: string;
|
|
118
|
+
/** Environment variables for the CLI process. */
|
|
119
|
+
env?: NodeJS.ProcessEnv;
|
|
120
|
+
/** Model override. */
|
|
121
|
+
model?: string;
|
|
122
|
+
/** Pre-configured MCP config path (skips auto-bridge creation). */
|
|
123
|
+
mcpConfigPath?: string;
|
|
124
|
+
/** Custom spawn function. Defaults to child_process.spawn. */
|
|
125
|
+
spawnFn?: SpawnFn;
|
|
126
|
+
/** CLI timeout in milliseconds. Defaults to 120000. */
|
|
127
|
+
timeout?: number;
|
|
128
|
+
}
|
|
129
|
+
export interface CliSessionOptions {
|
|
130
|
+
/** Absolute path to the claude binary. */
|
|
131
|
+
binPath: string;
|
|
132
|
+
/** Working directory for the CLI process. */
|
|
133
|
+
cwd: string;
|
|
134
|
+
/** Environment variables for the CLI process. */
|
|
135
|
+
env?: NodeJS.ProcessEnv;
|
|
136
|
+
/** Model to use. */
|
|
137
|
+
model: string;
|
|
138
|
+
/** Pre-configured MCP config path. */
|
|
139
|
+
mcpConfigPath?: string;
|
|
140
|
+
/** Custom spawn function. Defaults to child_process.spawn. */
|
|
141
|
+
spawnFn?: SpawnFn;
|
|
142
|
+
/** Idle timeout in milliseconds. Defaults to 600000 (10 minutes). */
|
|
143
|
+
idleTimeout?: number;
|
|
144
|
+
/** Logger instance. */
|
|
145
|
+
logger?: Logger;
|
|
146
|
+
}
|
|
147
|
+
export interface Logger {
|
|
148
|
+
info(msg: string, data?: unknown): void;
|
|
149
|
+
warn(msg: string, data?: unknown): void;
|
|
150
|
+
error(msg: string, data?: unknown): void;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/cli/flow-weaver.mjs
CHANGED
|
@@ -9886,7 +9886,7 @@ var VERSION;
|
|
|
9886
9886
|
var init_generated_version = __esm({
|
|
9887
9887
|
"src/generated-version.ts"() {
|
|
9888
9888
|
"use strict";
|
|
9889
|
-
VERSION = "0.22.
|
|
9889
|
+
VERSION = "0.22.7";
|
|
9890
9890
|
}
|
|
9891
9891
|
});
|
|
9892
9892
|
|
|
@@ -10791,16 +10791,35 @@ function buildNodeArgumentsWithContext(opts) {
|
|
|
10791
10791
|
} else if (skipPorts?.has("execute")) {
|
|
10792
10792
|
args.push(`${safeId}_execute`);
|
|
10793
10793
|
} else if (executeConnections.length > 0) {
|
|
10794
|
-
const conn = executeConnections[0];
|
|
10795
|
-
const sourceNode = conn.from.node;
|
|
10796
|
-
const sourcePort = conn.from.port;
|
|
10797
10794
|
const varName = `${safeId}_execute`;
|
|
10798
|
-
|
|
10799
|
-
|
|
10800
|
-
|
|
10801
|
-
|
|
10802
|
-
|
|
10803
|
-
|
|
10795
|
+
if (executeConnections.length === 1) {
|
|
10796
|
+
const conn = executeConnections[0];
|
|
10797
|
+
const sourceNode = conn.from.node;
|
|
10798
|
+
const sourcePort = conn.from.port;
|
|
10799
|
+
const sourceIdx = isStartNode(sourceNode) ? "startIdx" : `${toValidIdentifier(sourceNode)}Idx`;
|
|
10800
|
+
const isConstSource = isStartNode(sourceNode) || sourceNode === instanceParent;
|
|
10801
|
+
if (isConstSource) {
|
|
10802
|
+
lines.push(
|
|
10803
|
+
`${indent}const ${varName} = ${getCall}({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx} }) as boolean;`
|
|
10804
|
+
);
|
|
10805
|
+
} else {
|
|
10806
|
+
lines.push(
|
|
10807
|
+
`${indent}const ${varName} = ${sourceIdx} !== undefined ? ${getCall}({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx} }) as boolean : false;`
|
|
10808
|
+
);
|
|
10809
|
+
}
|
|
10810
|
+
} else {
|
|
10811
|
+
const parts = executeConnections.map((conn) => {
|
|
10812
|
+
const sourceNode = conn.from.node;
|
|
10813
|
+
const sourcePort = conn.from.port;
|
|
10814
|
+
const sourceIdx = isStartNode(sourceNode) ? "startIdx" : `${toValidIdentifier(sourceNode)}Idx`;
|
|
10815
|
+
const isConstSource = isStartNode(sourceNode) || sourceNode === instanceParent;
|
|
10816
|
+
if (isConstSource) {
|
|
10817
|
+
return `(${getCall}({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx} }) as boolean)`;
|
|
10818
|
+
}
|
|
10819
|
+
return `(${sourceIdx} !== undefined ? ${getCall}({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx} }) as boolean : false)`;
|
|
10820
|
+
});
|
|
10821
|
+
lines.push(`${indent}const ${varName} = ${parts.join(" || ")};`);
|
|
10822
|
+
}
|
|
10804
10823
|
if (emitInputEvents) {
|
|
10805
10824
|
lines.push(
|
|
10806
10825
|
`${indent}${setCall}({ id: '${id}', portName: 'execute', executionIndex: ${safeId}Idx, nodeTypeName: '${effectiveNodeTypeName}' }, ${varName});`
|
|
@@ -94920,7 +94939,7 @@ var {
|
|
|
94920
94939
|
// src/cli/index.ts
|
|
94921
94940
|
init_logger();
|
|
94922
94941
|
init_error_utils();
|
|
94923
|
-
var version2 = true ? "0.22.
|
|
94942
|
+
var version2 = true ? "0.22.7" : "0.0.0-dev";
|
|
94924
94943
|
var program2 = new Command();
|
|
94925
94944
|
program2.name("fw").description("Flow Weaver Annotations - Compile and validate workflow files").option("-v, --version", "Output the current version").option("--no-color", "Disable colors").option("--color", "Force colors").on("option:version", () => {
|
|
94926
94945
|
logger.banner(version2);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.22.
|
|
1
|
+
export declare const VERSION = "0.22.7";
|
|
2
2
|
//# sourceMappingURL=generated-version.d.ts.map
|
|
@@ -172,16 +172,36 @@ export function buildNodeArgumentsWithContext(opts) {
|
|
|
172
172
|
args.push(`${safeId}_execute`);
|
|
173
173
|
}
|
|
174
174
|
else if (executeConnections.length > 0) {
|
|
175
|
-
// Execute port has
|
|
176
|
-
const conn = executeConnections[0];
|
|
177
|
-
const sourceNode = conn.from.node;
|
|
178
|
-
const sourcePort = conn.from.port;
|
|
175
|
+
// Execute port has connections - use them
|
|
179
176
|
const varName = `${safeId}_execute`;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
177
|
+
if (executeConnections.length === 1) {
|
|
178
|
+
const conn = executeConnections[0];
|
|
179
|
+
const sourceNode = conn.from.node;
|
|
180
|
+
const sourcePort = conn.from.port;
|
|
181
|
+
const sourceIdx = isStartNode(sourceNode) ? 'startIdx' : `${toValidIdentifier(sourceNode)}Idx`;
|
|
182
|
+
const isConstSource = isStartNode(sourceNode) || sourceNode === instanceParent;
|
|
183
|
+
if (isConstSource) {
|
|
184
|
+
lines.push(`${indent}const ${varName} = ${getCall}({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx} }) as boolean;`);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
// Non-const source may be undefined (CANCELLED branch) — guard with false default
|
|
188
|
+
lines.push(`${indent}const ${varName} = ${sourceIdx} !== undefined ? ${getCall}({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx} }) as boolean : false;`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// Multiple execute connections — coalesce with ||, each guarded
|
|
193
|
+
const parts = executeConnections.map((conn) => {
|
|
194
|
+
const sourceNode = conn.from.node;
|
|
195
|
+
const sourcePort = conn.from.port;
|
|
196
|
+
const sourceIdx = isStartNode(sourceNode) ? 'startIdx' : `${toValidIdentifier(sourceNode)}Idx`;
|
|
197
|
+
const isConstSource = isStartNode(sourceNode) || sourceNode === instanceParent;
|
|
198
|
+
if (isConstSource) {
|
|
199
|
+
return `(${getCall}({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx} }) as boolean)`;
|
|
200
|
+
}
|
|
201
|
+
return `(${sourceIdx} !== undefined ? ${getCall}({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx} }) as boolean : false)`;
|
|
202
|
+
});
|
|
203
|
+
lines.push(`${indent}const ${varName} = ${parts.join(' || ')};`);
|
|
204
|
+
}
|
|
185
205
|
// Emit VARIABLE_SET for execute input port
|
|
186
206
|
if (emitInputEvents) {
|
|
187
207
|
lines.push(`${indent}${setCall}({ id: '${id}', portName: 'execute', executionIndex: ${safeId}Idx, nodeTypeName: '${effectiveNodeTypeName}' }, ${varName});`);
|
|
@@ -814,7 +814,9 @@ isAsync = false) {
|
|
|
814
814
|
if (!instance)
|
|
815
815
|
return;
|
|
816
816
|
const safeId = toValidIdentifier(instanceId);
|
|
817
|
-
//
|
|
817
|
+
// Use const (block-scoped) intentionally — the outer `let ${safeId}Idx`
|
|
818
|
+
// stays undefined, which signals to downstream guards that this node was
|
|
819
|
+
// CANCELLED and its data ports should not be read.
|
|
818
820
|
lines.push(`${indent}const ${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
|
|
819
821
|
// Set STEP port variables so downstream nodes reading onSuccess/onFailure
|
|
820
822
|
// from this cancelled node don't crash with "Variable not found".
|