@synergenius/flowweaver-pack-weaver 0.1.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/bot/agent-provider.d.ts +24 -0
- package/dist/bot/agent-provider.d.ts.map +1 -0
- package/dist/bot/agent-provider.js +95 -0
- package/dist/bot/agent-provider.js.map +1 -0
- package/dist/bot/bot-agent-channel.d.ts +30 -0
- package/dist/bot/bot-agent-channel.d.ts.map +1 -0
- package/dist/bot/bot-agent-channel.js +44 -0
- package/dist/bot/bot-agent-channel.js.map +1 -0
- package/dist/bot/cli-provider.d.ts +12 -0
- package/dist/bot/cli-provider.d.ts.map +1 -0
- package/dist/bot/cli-provider.js +50 -0
- package/dist/bot/cli-provider.js.map +1 -0
- package/dist/bot/index.d.ts +11 -0
- package/dist/bot/index.d.ts.map +1 -0
- package/dist/bot/index.js +7 -0
- package/dist/bot/index.js.map +1 -0
- package/dist/bot/notifications.d.ts +18 -0
- package/dist/bot/notifications.d.ts.map +1 -0
- package/dist/bot/notifications.js +144 -0
- package/dist/bot/notifications.js.map +1 -0
- package/dist/bot/runner.d.ts +8 -0
- package/dist/bot/runner.d.ts.map +1 -0
- package/dist/bot/runner.js +123 -0
- package/dist/bot/runner.js.map +1 -0
- package/dist/bot/system-prompt.d.ts +6 -0
- package/dist/bot/system-prompt.d.ts.map +1 -0
- package/dist/bot/system-prompt.js +161 -0
- package/dist/bot/system-prompt.js.map +1 -0
- package/dist/bot/types.d.ts +44 -0
- package/dist/bot/types.d.ts.map +1 -0
- package/dist/bot/types.js +2 -0
- package/dist/bot/types.js.map +1 -0
- package/dist/docs/docs/weaver-config.md +141 -0
- package/dist/docs/weaver-config.md +141 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/templates/index.d.ts +5 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +4 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/weaver-template.d.ts +11 -0
- package/dist/templates/weaver-template.d.ts.map +1 -0
- package/dist/templates/weaver-template.js +672 -0
- package/dist/templates/weaver-template.js.map +1 -0
- package/flowweaver.manifest.json +42 -0
- package/package.json +45 -0
- package/templates.js +1 -0
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
export const weaverTemplate = {
|
|
2
|
+
id: 'weaver',
|
|
3
|
+
name: 'Weaver: Autonomous workflow runner',
|
|
4
|
+
description: 'AI-powered runner that executes any Flow Weaver workflow autonomously, with auto-detected providers and notification support',
|
|
5
|
+
category: 'automation',
|
|
6
|
+
generate: (_opts) => {
|
|
7
|
+
return `import { execSync } from 'node:child_process';
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import * as os from 'node:os';
|
|
11
|
+
|
|
12
|
+
// ============================================================
|
|
13
|
+
// Types
|
|
14
|
+
// ============================================================
|
|
15
|
+
|
|
16
|
+
interface WeaverConfig {
|
|
17
|
+
provider?: 'auto' | 'anthropic' | 'claude-cli' | 'copilot-cli' | { name: string; model?: string; maxTokens?: number };
|
|
18
|
+
target?: string;
|
|
19
|
+
approval?: 'auto' | 'timeout-auto' | { mode: string; timeoutSeconds?: number };
|
|
20
|
+
notify?: Array<{ channel: 'discord' | 'slack' | 'webhook'; url: string; events?: string[]; headers?: Record<string, string> }>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ProviderInfo {
|
|
24
|
+
type: 'anthropic' | 'claude-cli' | 'copilot-cli';
|
|
25
|
+
model?: string;
|
|
26
|
+
maxTokens?: number;
|
|
27
|
+
apiKey?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ============================================================
|
|
31
|
+
// Helpers
|
|
32
|
+
// ============================================================
|
|
33
|
+
|
|
34
|
+
function run(cmd: string, cwd: string): string {
|
|
35
|
+
return execSync(cmd, { cwd, encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function runSafe(cmd: string, cwd: string): string {
|
|
39
|
+
try { return run(cmd, cwd); } catch { return ''; }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function info(msg: string): void { console.log(\`\\x1b[36m→ \${msg}\\x1b[0m\`); }
|
|
43
|
+
function success(msg: string): void { console.log(\`\\x1b[32m✓ \${msg}\\x1b[0m\`); }
|
|
44
|
+
function warn(msg: string): void { console.log(\`\\x1b[33m! \${msg}\\x1b[0m\`); }
|
|
45
|
+
|
|
46
|
+
function callCli(provider: string, prompt: string): string {
|
|
47
|
+
if (provider === 'claude-cli') {
|
|
48
|
+
return execSync('claude -p --output-format text', {
|
|
49
|
+
input: prompt,
|
|
50
|
+
encoding: 'utf-8',
|
|
51
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
52
|
+
timeout: 120_000,
|
|
53
|
+
}).trim();
|
|
54
|
+
}
|
|
55
|
+
if (provider === 'copilot-cli') {
|
|
56
|
+
return execSync('copilot -p --silent --allow-all-tools', {
|
|
57
|
+
input: prompt,
|
|
58
|
+
encoding: 'utf-8',
|
|
59
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
60
|
+
timeout: 120_000,
|
|
61
|
+
}).trim();
|
|
62
|
+
}
|
|
63
|
+
throw new Error(\`Unknown CLI provider: \${provider}\`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function callApiAsync(
|
|
67
|
+
apiKey: string,
|
|
68
|
+
model: string,
|
|
69
|
+
maxTokens: number,
|
|
70
|
+
systemPrompt: string,
|
|
71
|
+
userPrompt: string,
|
|
72
|
+
): Promise<string> {
|
|
73
|
+
const body = JSON.stringify({
|
|
74
|
+
model,
|
|
75
|
+
max_tokens: maxTokens,
|
|
76
|
+
system: systemPrompt,
|
|
77
|
+
messages: [{ role: 'user', content: userPrompt }],
|
|
78
|
+
});
|
|
79
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: {
|
|
82
|
+
'x-api-key': apiKey,
|
|
83
|
+
'anthropic-version': '2023-06-01',
|
|
84
|
+
'content-type': 'application/json',
|
|
85
|
+
},
|
|
86
|
+
body,
|
|
87
|
+
});
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
const text = await response.text();
|
|
90
|
+
throw new Error(\`Anthropic API error \${response.status}: \${text.slice(0, 200)}\`);
|
|
91
|
+
}
|
|
92
|
+
const json = await response.json() as { content: Array<{ type: string; text: string }> };
|
|
93
|
+
return json.content[0]?.text ?? '';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function parseJsonResponse(text: string): Record<string, unknown> {
|
|
97
|
+
let cleaned = text.trim();
|
|
98
|
+
if (cleaned.startsWith('\\\`\\\`\\\`')) {
|
|
99
|
+
cleaned = cleaned.replace(/^\\\`\\\`\\\`(?:json)?\\s*\\n?/, '').replace(/\\n?\\\`\\\`\\\`\\s*$/, '');
|
|
100
|
+
}
|
|
101
|
+
try { return JSON.parse(cleaned); } catch {}
|
|
102
|
+
const match = cleaned.match(/\\{[\\s\\S]*\\}/);
|
|
103
|
+
if (match) return JSON.parse(match[0]);
|
|
104
|
+
throw new Error(\`Failed to parse AI response as JSON: \${text.slice(0, 200)}\`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function sendWebhook(
|
|
108
|
+
config: { channel: string; url: string; headers?: Record<string, string> },
|
|
109
|
+
event: Record<string, unknown>,
|
|
110
|
+
): void {
|
|
111
|
+
const headers: Record<string, string> = {
|
|
112
|
+
'content-type': 'application/json',
|
|
113
|
+
...config.headers,
|
|
114
|
+
};
|
|
115
|
+
let body: string;
|
|
116
|
+
if (config.channel === 'discord') {
|
|
117
|
+
const color = event.success ? 0x22c55e : 0xef4444;
|
|
118
|
+
body = JSON.stringify({
|
|
119
|
+
embeds: [{
|
|
120
|
+
title: \`Weaver: \${event.outcome ?? 'update'}\`,
|
|
121
|
+
description: String(event.summary ?? '').slice(0, 2000),
|
|
122
|
+
color,
|
|
123
|
+
fields: [
|
|
124
|
+
{ name: 'Workflow', value: String(event.targetPath ?? 'unknown'), inline: true },
|
|
125
|
+
{ name: 'Provider', value: String(event.providerType ?? 'unknown'), inline: true },
|
|
126
|
+
],
|
|
127
|
+
timestamp: new Date().toISOString(),
|
|
128
|
+
}],
|
|
129
|
+
});
|
|
130
|
+
} else if (config.channel === 'slack') {
|
|
131
|
+
const emoji = event.success ? ':white_check_mark:' : ':x:';
|
|
132
|
+
body = JSON.stringify({
|
|
133
|
+
blocks: [
|
|
134
|
+
{ type: 'header', text: { type: 'plain_text', text: \`\${emoji} Weaver: \${event.outcome ?? 'update'}\` } },
|
|
135
|
+
{ type: 'section', text: { type: 'mrkdwn', text: String(event.summary ?? '').slice(0, 2000) } },
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
body = JSON.stringify(event);
|
|
140
|
+
}
|
|
141
|
+
const headerFlags = Object.entries(headers).map(([k, v]) => \`-H "\${k}: \${v}"\`).join(' ');
|
|
142
|
+
try {
|
|
143
|
+
execSync(\`curl -sS -X POST \${headerFlags} -d @- "\${config.url}"\`, {
|
|
144
|
+
input: body,
|
|
145
|
+
encoding: 'utf-8',
|
|
146
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
147
|
+
timeout: 10_000,
|
|
148
|
+
});
|
|
149
|
+
} catch { /* notification failure is non-fatal */ }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
153
|
+
async function buildWeaverPrompt(): Promise<string> {
|
|
154
|
+
const FALLBACK = 'You are Weaver, an expert AI companion for Flow Weaver workflows. Respond ONLY with valid JSON. No markdown, no code fences, no explanation outside the JSON structure.';
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
|
+
const docMeta: any = await import('@synergenius/flow-weaver/doc-metadata');
|
|
159
|
+
|
|
160
|
+
const annotations: Array<{ name: string; syntax: string; description: string; category: string }> = docMeta.ALL_ANNOTATIONS ?? [];
|
|
161
|
+
const portMods: Array<{ syntax: string; description: string }> = docMeta.PORT_MODIFIERS ?? [];
|
|
162
|
+
const nodeMods: Array<{ syntax: string; description: string }> = docMeta.NODE_MODIFIERS ?? [];
|
|
163
|
+
const codes: Array<{ code: string; severity: string; title: string; description: string }> = docMeta.VALIDATION_CODES ?? [];
|
|
164
|
+
const commands: Array<{ name: string; description: string; group?: string }> = docMeta.CLI_COMMANDS ?? [];
|
|
165
|
+
|
|
166
|
+
// Format annotations grouped by category
|
|
167
|
+
const groups = new Map<string, typeof annotations>();
|
|
168
|
+
for (const a of annotations) {
|
|
169
|
+
const list = groups.get(a.category) ?? [];
|
|
170
|
+
list.push(a);
|
|
171
|
+
groups.set(a.category, list);
|
|
172
|
+
}
|
|
173
|
+
const annotationLines: string[] = [];
|
|
174
|
+
for (const [category, items] of groups) {
|
|
175
|
+
annotationLines.push('[' + category + ']');
|
|
176
|
+
for (const item of items) annotationLines.push(' ' + item.syntax + ' -- ' + item.description);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Format modifiers
|
|
180
|
+
const modLines: string[] = [];
|
|
181
|
+
if (portMods.length > 0) {
|
|
182
|
+
modLines.push('Port modifiers (after port name):');
|
|
183
|
+
for (const m of portMods) modLines.push(' ' + m.syntax + ' -- ' + m.description);
|
|
184
|
+
}
|
|
185
|
+
if (nodeMods.length > 0) {
|
|
186
|
+
modLines.push('Node instance modifiers (in @node declaration):');
|
|
187
|
+
for (const m of nodeMods) modLines.push(' ' + m.syntax + ' -- ' + m.description);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Format errors (top 15)
|
|
191
|
+
const errors = codes.filter(c => c.severity === 'error').slice(0, 15);
|
|
192
|
+
const errorLines = errors.map(c => ' ' + c.code + ': ' + c.title + ' -- ' + c.description);
|
|
193
|
+
|
|
194
|
+
// Format top-level CLI commands
|
|
195
|
+
const topCmds = commands.filter(c => !c.group);
|
|
196
|
+
const cmdLines = topCmds.map(c => ' flow-weaver ' + c.name + ' -- ' + c.description);
|
|
197
|
+
|
|
198
|
+
return \`You are Weaver, an expert AI companion for Flow Weaver workflows. You have deep knowledge of the entire Flow Weaver ecosystem: annotation grammar, compilation, CLI tools, node patterns, error diagnosis, and the Genesis self-evolution protocol.
|
|
199
|
+
|
|
200
|
+
## Core Mental Model
|
|
201
|
+
|
|
202
|
+
The code IS the workflow. Flow Weaver workflows are plain TypeScript files with JSDoc annotations above functions. The compiler reads annotations and generates deterministic execution code between @flow-weaver-body-start/end markers. Compiled code is standalone with no runtime dependency on flow-weaver.
|
|
203
|
+
|
|
204
|
+
Three block types:
|
|
205
|
+
- @flowWeaver nodeType: A reusable function (node) with typed inputs/outputs
|
|
206
|
+
- @flowWeaver workflow: A DAG orchestration that wires node instances together
|
|
207
|
+
- @flowWeaver pattern: A reusable fragment with boundary ports (IN/OUT instead of Start/Exit)
|
|
208
|
+
|
|
209
|
+
## Annotation Grammar
|
|
210
|
+
|
|
211
|
+
\${annotationLines.join('\\n')}
|
|
212
|
+
|
|
213
|
+
\${modLines.join('\\n')}
|
|
214
|
+
|
|
215
|
+
## Node Execution Model
|
|
216
|
+
|
|
217
|
+
Expression nodes (@expression):
|
|
218
|
+
- No execute/onSuccess/onFailure params. Just inputs and return value.
|
|
219
|
+
- throw = failure path, return = success path
|
|
220
|
+
- Synchronous. Use execSync for shell commands.
|
|
221
|
+
- Preferred for deterministic operations.
|
|
222
|
+
|
|
223
|
+
Standard nodes:
|
|
224
|
+
- execute: boolean param gates execution
|
|
225
|
+
- Return { onSuccess: boolean, onFailure: boolean, ...outputs }
|
|
226
|
+
- Can be async for I/O operations
|
|
227
|
+
|
|
228
|
+
Async agent nodes:
|
|
229
|
+
- Use (globalThis as any).__fw_agent_channel__ to pause workflow
|
|
230
|
+
- Call channel.request({ agentId, context, prompt }) which returns a Promise
|
|
231
|
+
- Workflow suspends until agent responds
|
|
232
|
+
- NOT @expression (must be async)
|
|
233
|
+
|
|
234
|
+
Pass-through pattern:
|
|
235
|
+
- FW auto-connects ports by matching names on adjacent nodes
|
|
236
|
+
- To forward data through intermediate nodes, declare it as both @input and @output with the same name
|
|
237
|
+
- For non-adjacent wiring, use @connect sourceNode.port -> targetNode.port
|
|
238
|
+
|
|
239
|
+
Data flow:
|
|
240
|
+
- @path A -> B -> C: Linear execution path (sugar for multiple @connect)
|
|
241
|
+
- @autoConnect: Auto-wire all nodes in declaration order
|
|
242
|
+
- @connect: Explicit port-to-port wiring
|
|
243
|
+
- Merge strategies for fan-in: FIRST, LAST, COLLECT, MERGE, CONCAT
|
|
244
|
+
|
|
245
|
+
## CLI Commands
|
|
246
|
+
|
|
247
|
+
\${cmdLines.join('\\n')}
|
|
248
|
+
|
|
249
|
+
Key workflows:
|
|
250
|
+
flow-weaver compile <file> -- Generate executable code (in-place)
|
|
251
|
+
flow-weaver validate <file> -- Check for errors without compiling
|
|
252
|
+
flow-weaver modify <op> --file <f> -- Structural changes (addNode, removeNode, addConnection, removeConnection)
|
|
253
|
+
flow-weaver implement <file> -- Replace declare stubs with implementations
|
|
254
|
+
flow-weaver diff <a> <b> -- Semantic diff between two workflow versions
|
|
255
|
+
flow-weaver diagram <file> -f ascii-compact -- Generate ASCII diagram
|
|
256
|
+
|
|
257
|
+
## Validation Errors
|
|
258
|
+
|
|
259
|
+
\${errorLines.join('\\n')}
|
|
260
|
+
|
|
261
|
+
When you encounter validation errors, suggest the specific fix. Common resolutions:
|
|
262
|
+
- UNKNOWN_NODE_TYPE: Ensure the referenced function has @flowWeaver nodeType annotation
|
|
263
|
+
- MISSING_REQUIRED_INPUT: Add a @connect from a source port or make the input optional with [brackets]
|
|
264
|
+
- UNKNOWN_SOURCE_PORT / UNKNOWN_TARGET_PORT: Check port name spelling in @connect
|
|
265
|
+
- CYCLE_DETECTED: Break the cycle; use scoped iteration (@scope, @map) instead of circular dependencies
|
|
266
|
+
|
|
267
|
+
## Genesis Protocol
|
|
268
|
+
|
|
269
|
+
Genesis is a 17-step self-evolving workflow engine:
|
|
270
|
+
1. Load config (.genesis/config.json with intent, focus, constraints, approval thresholds)
|
|
271
|
+
2. Observe project (fingerprint: files, package.json, git, CI, tests, existing FW workflows)
|
|
272
|
+
3. Load task workflow (genesis-task.ts)
|
|
273
|
+
4. Diff fingerprint against last cycle
|
|
274
|
+
5. Check stabilize mode (3+ rollbacks/rejections = read-only, or explicit flag)
|
|
275
|
+
6. Wait for agent (YOU decide what evolutions to propose)
|
|
276
|
+
7. Propose evolution (map your decisions to FwModifyOperation[])
|
|
277
|
+
8. Validate proposal (budget: max 3 cost units per cycle. addNode=1, removeNode=1, addConnection=1, removeConnection=1, implementNode=2)
|
|
278
|
+
9. Snapshot current task workflow for rollback
|
|
279
|
+
10. Apply changes via flow-weaver CLI
|
|
280
|
+
11. Compile and validate (auto-rollback on failure)
|
|
281
|
+
12. Diff workflow (semantic diff)
|
|
282
|
+
13. Check approval threshold (CRITICAL > BREAKING > MINOR > COSMETIC)
|
|
283
|
+
14. Wait for approval (if impact >= threshold)
|
|
284
|
+
15. Commit or rollback based on approval
|
|
285
|
+
16. Update history (.genesis/history.json)
|
|
286
|
+
17. Report summary
|
|
287
|
+
|
|
288
|
+
When stabilize mode is active, only fix-up operations are allowed: removeNode, removeConnection, implementNode. No new nodes or connections.
|
|
289
|
+
|
|
290
|
+
## Response Format
|
|
291
|
+
|
|
292
|
+
Return ONLY valid JSON. No markdown, no code fences, no explanation outside the JSON structure. Your response must parse with JSON.parse() directly.\`;
|
|
293
|
+
} catch {
|
|
294
|
+
return FALLBACK;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ============================================================
|
|
299
|
+
// Node Types
|
|
300
|
+
// ============================================================
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* @flowWeaver nodeType
|
|
304
|
+
* @expression
|
|
305
|
+
* @label Load Config
|
|
306
|
+
* @input [projectDir] [order:0] - Project root directory (defaults to cwd)
|
|
307
|
+
* @output projectDir [order:0] - Project root directory (pass-through)
|
|
308
|
+
* @output config [order:1] - Weaver configuration (JSON)
|
|
309
|
+
*/
|
|
310
|
+
function loadConfig(projectDir?: string): { projectDir: string; config: string } {
|
|
311
|
+
const dir = projectDir || process.cwd();
|
|
312
|
+
const configPath = path.join(dir, '.weaver.json');
|
|
313
|
+
let config: WeaverConfig = { provider: 'auto' };
|
|
314
|
+
if (fs.existsSync(configPath)) {
|
|
315
|
+
config = { ...config, ...JSON.parse(fs.readFileSync(configPath, 'utf-8')) };
|
|
316
|
+
info(\`Loaded config from \${configPath}\`);
|
|
317
|
+
} else {
|
|
318
|
+
info('No .weaver.json found, using defaults (provider: auto)');
|
|
319
|
+
}
|
|
320
|
+
return { projectDir: dir, config: JSON.stringify(config) };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* @flowWeaver nodeType
|
|
325
|
+
* @expression
|
|
326
|
+
* @label Detect Provider
|
|
327
|
+
* @input projectDir [order:0] - Project root directory
|
|
328
|
+
* @input config [order:1] - Weaver configuration (JSON)
|
|
329
|
+
* @output projectDir [order:0] - Project root directory (pass-through)
|
|
330
|
+
* @output config [order:1] - Config (pass-through)
|
|
331
|
+
* @output providerType [order:2] - Resolved provider type
|
|
332
|
+
* @output providerInfo [order:3] - Provider details (JSON)
|
|
333
|
+
*/
|
|
334
|
+
function detectProvider(projectDir: string, config: string): {
|
|
335
|
+
projectDir: string; config: string;
|
|
336
|
+
providerType: string; providerInfo: string;
|
|
337
|
+
} {
|
|
338
|
+
const cfg: WeaverConfig = JSON.parse(config);
|
|
339
|
+
let providerSetting = cfg.provider ?? 'auto';
|
|
340
|
+
|
|
341
|
+
let type: string;
|
|
342
|
+
let model: string | undefined;
|
|
343
|
+
let maxTokens: number | undefined;
|
|
344
|
+
|
|
345
|
+
if (typeof providerSetting === 'object') {
|
|
346
|
+
type = providerSetting.name;
|
|
347
|
+
model = providerSetting.model;
|
|
348
|
+
maxTokens = providerSetting.maxTokens;
|
|
349
|
+
} else if (providerSetting !== 'auto') {
|
|
350
|
+
type = providerSetting;
|
|
351
|
+
} else {
|
|
352
|
+
// Auto-detection
|
|
353
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
354
|
+
type = 'anthropic';
|
|
355
|
+
} else if (runSafe('which claude', projectDir)) {
|
|
356
|
+
type = 'claude-cli';
|
|
357
|
+
} else if (runSafe('which copilot', projectDir)) {
|
|
358
|
+
type = 'copilot-cli';
|
|
359
|
+
} else {
|
|
360
|
+
throw new Error(
|
|
361
|
+
'No AI provider found. Options:\\n' +
|
|
362
|
+
' 1. Set ANTHROPIC_API_KEY environment variable\\n' +
|
|
363
|
+
' 2. Install Claude CLI: https://docs.anthropic.com/claude-code\\n' +
|
|
364
|
+
' 3. Install GitHub Copilot CLI: https://github.com/features/copilot'
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const providerInfo: ProviderInfo = {
|
|
370
|
+
type: type as ProviderInfo['type'],
|
|
371
|
+
model: model ?? (type === 'anthropic' ? 'claude-sonnet-4-6' : undefined),
|
|
372
|
+
maxTokens: maxTokens ?? (type === 'anthropic' ? 4096 : undefined),
|
|
373
|
+
apiKey: type === 'anthropic' ? process.env.ANTHROPIC_API_KEY : undefined,
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
if (type === 'anthropic' && !providerInfo.apiKey) {
|
|
377
|
+
throw new Error('Provider is "anthropic" but ANTHROPIC_API_KEY is not set');
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const label = providerInfo.model ? \`\${type} (\${providerInfo.model})\` : type;
|
|
381
|
+
info(\`Provider: \${label}\`);
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
projectDir, config,
|
|
385
|
+
providerType: type,
|
|
386
|
+
providerInfo: JSON.stringify(providerInfo),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* @flowWeaver nodeType
|
|
392
|
+
* @expression
|
|
393
|
+
* @label Resolve Target
|
|
394
|
+
* @input projectDir [order:0] - Project root directory
|
|
395
|
+
* @input config [order:1] - Config (JSON)
|
|
396
|
+
* @input providerType [order:2] - Provider type (pass-through)
|
|
397
|
+
* @input providerInfo [order:3] - Provider info (pass-through)
|
|
398
|
+
* @output projectDir [order:0] - Project root directory (pass-through)
|
|
399
|
+
* @output config [order:1] - Config (pass-through)
|
|
400
|
+
* @output providerType [order:2] - Provider type (pass-through)
|
|
401
|
+
* @output providerInfo [order:3] - Provider info (pass-through)
|
|
402
|
+
* @output targetPath [order:4] - Absolute path to target workflow
|
|
403
|
+
*/
|
|
404
|
+
function resolveTarget(
|
|
405
|
+
projectDir: string, config: string, providerType: string, providerInfo: string,
|
|
406
|
+
): {
|
|
407
|
+
projectDir: string; config: string; providerType: string; providerInfo: string;
|
|
408
|
+
targetPath: string;
|
|
409
|
+
} {
|
|
410
|
+
const cfg: WeaverConfig = JSON.parse(config);
|
|
411
|
+
|
|
412
|
+
if (cfg.target) {
|
|
413
|
+
const abs = path.resolve(projectDir, cfg.target);
|
|
414
|
+
if (!fs.existsSync(abs)) throw new Error(\`Target workflow not found: \${abs}\`);
|
|
415
|
+
info(\`Target: \${abs}\`);
|
|
416
|
+
return { projectDir, config, providerType, providerInfo, targetPath: abs };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Scan for workflow files (depth 2)
|
|
420
|
+
const found: string[] = [];
|
|
421
|
+
const scan = (dir: string, depth: number): void => {
|
|
422
|
+
if (depth > 2) return;
|
|
423
|
+
let entries: fs.Dirent[];
|
|
424
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
425
|
+
for (const entry of entries) {
|
|
426
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
427
|
+
const full = path.join(dir, entry.name);
|
|
428
|
+
if (entry.isDirectory()) { scan(full, depth + 1); }
|
|
429
|
+
else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.d.ts')) {
|
|
430
|
+
try {
|
|
431
|
+
const content = fs.readFileSync(full, 'utf-8').slice(0, 2000);
|
|
432
|
+
if (content.includes('@flowWeaver workflow')) found.push(full);
|
|
433
|
+
} catch { /* skip */ }
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
scan(projectDir, 0);
|
|
438
|
+
|
|
439
|
+
if (found.length === 0) {
|
|
440
|
+
throw new Error(\`No workflow files found in \${projectDir}. Set "target" in .weaver.json or pass a file path.\`);
|
|
441
|
+
}
|
|
442
|
+
if (found.length > 1) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
\`Multiple workflows found. Set "target" in .weaver.json to pick one:\\n\` +
|
|
445
|
+
found.map(f => \` - \${path.relative(projectDir, f)}\`).join('\\n')
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
info(\`Target: \${found[0]}\`);
|
|
450
|
+
return { projectDir, config, providerType, providerInfo, targetPath: found[0]! };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* @flowWeaver nodeType
|
|
455
|
+
* @label Execute Target
|
|
456
|
+
* @input projectDir [order:0] - Project root directory
|
|
457
|
+
* @input config [order:1] - Config (JSON)
|
|
458
|
+
* @input providerType [order:2] - Provider type
|
|
459
|
+
* @input providerInfo [order:3] - Provider info (JSON)
|
|
460
|
+
* @input targetPath [order:4] - Absolute path to target workflow
|
|
461
|
+
* @output projectDir [order:0] - Project root directory (pass-through)
|
|
462
|
+
* @output config [order:1] - Config (pass-through)
|
|
463
|
+
* @output targetPath [order:2] - Target path (pass-through)
|
|
464
|
+
* @output resultJson [order:3] - Workflow execution result (JSON)
|
|
465
|
+
* @output onSuccess [order:-2] - On Success
|
|
466
|
+
* @output onFailure [order:-1] - On Failure
|
|
467
|
+
*/
|
|
468
|
+
async function executeTarget(
|
|
469
|
+
execute: boolean,
|
|
470
|
+
projectDir: string,
|
|
471
|
+
config: string,
|
|
472
|
+
providerType: string,
|
|
473
|
+
providerInfo: string,
|
|
474
|
+
targetPath: string,
|
|
475
|
+
): Promise<{
|
|
476
|
+
onSuccess: boolean; onFailure: boolean;
|
|
477
|
+
projectDir: string; config: string; targetPath: string; resultJson: string;
|
|
478
|
+
}> {
|
|
479
|
+
if (!execute) {
|
|
480
|
+
return {
|
|
481
|
+
onSuccess: true, onFailure: false,
|
|
482
|
+
projectDir, config, targetPath,
|
|
483
|
+
resultJson: JSON.stringify({ success: true, summary: 'Dry run', outcome: 'skipped' }),
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const pInfo: ProviderInfo = JSON.parse(providerInfo);
|
|
488
|
+
const cfg: WeaverConfig = JSON.parse(config);
|
|
489
|
+
|
|
490
|
+
// Build expert system prompt from flow-weaver's doc-metadata
|
|
491
|
+
const systemPrompt = await buildWeaverPrompt();
|
|
492
|
+
|
|
493
|
+
// Resolve approval settings
|
|
494
|
+
const approvalSetting = cfg.approval ?? 'auto';
|
|
495
|
+
const approvalMode = typeof approvalSetting === 'string' ? approvalSetting : approvalSetting.mode;
|
|
496
|
+
|
|
497
|
+
// Create agent channel
|
|
498
|
+
const agentChannel = {
|
|
499
|
+
request: async (req: { agentId: string; context: Record<string, unknown>; prompt: string }) => {
|
|
500
|
+
// Handle approval requests
|
|
501
|
+
if (req.agentId.includes('approval')) {
|
|
502
|
+
if (approvalMode === 'auto') {
|
|
503
|
+
info('Auto-approving');
|
|
504
|
+
return { approved: true, reason: 'auto-approved' };
|
|
505
|
+
}
|
|
506
|
+
if (approvalMode === 'timeout-auto') {
|
|
507
|
+
const timeout = typeof approvalSetting === 'object' ? (approvalSetting.timeoutSeconds ?? 300) : 300;
|
|
508
|
+
info(\`Waiting \${timeout}s before auto-approving...\`);
|
|
509
|
+
await new Promise(resolve => setTimeout(resolve, timeout * 1000));
|
|
510
|
+
return { approved: true, reason: 'timeout-auto-approved' };
|
|
511
|
+
}
|
|
512
|
+
return { approved: true, reason: 'default-approved' };
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Regular agent decisions
|
|
516
|
+
const contextStr = typeof req.context === 'string'
|
|
517
|
+
? req.context
|
|
518
|
+
: JSON.stringify(req.context, null, 2);
|
|
519
|
+
const userPrompt = \`Context:\\n\${contextStr}\\n\\nInstructions:\\n\${req.prompt}\`;
|
|
520
|
+
|
|
521
|
+
let text: string;
|
|
522
|
+
if (pInfo.type === 'anthropic') {
|
|
523
|
+
text = await callApiAsync(
|
|
524
|
+
pInfo.apiKey!,
|
|
525
|
+
pInfo.model ?? 'claude-sonnet-4-6',
|
|
526
|
+
pInfo.maxTokens ?? 4096,
|
|
527
|
+
systemPrompt,
|
|
528
|
+
userPrompt,
|
|
529
|
+
);
|
|
530
|
+
} else {
|
|
531
|
+
text = callCli(pInfo.type, systemPrompt + '\\n\\n' + userPrompt);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return parseJsonResponse(text);
|
|
535
|
+
},
|
|
536
|
+
onPause: () => new Promise<never>(() => {}),
|
|
537
|
+
resume: () => {},
|
|
538
|
+
fail: () => {},
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
info(\`Executing: \${targetPath}\`);
|
|
543
|
+
const startTime = Date.now();
|
|
544
|
+
|
|
545
|
+
const mod = '@synergenius/flow-weaver/dist/mcp/workflow-executor.js';
|
|
546
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
547
|
+
const { executeWorkflowFromFile } = await (import(mod) as Promise<any>);
|
|
548
|
+
const execResult = await executeWorkflowFromFile(targetPath, {}, {
|
|
549
|
+
agentChannel,
|
|
550
|
+
includeTrace: false,
|
|
551
|
+
production: true,
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
555
|
+
const result = execResult.result as { onSuccess?: boolean; summary?: string } | null;
|
|
556
|
+
const ok = result?.onSuccess ?? false;
|
|
557
|
+
const summary = result?.summary ?? 'No summary';
|
|
558
|
+
|
|
559
|
+
if (ok) success(\`Completed in \${elapsed}s: \${summary}\`);
|
|
560
|
+
else warn(\`Failed after \${elapsed}s: \${summary}\`);
|
|
561
|
+
|
|
562
|
+
const resultObj = {
|
|
563
|
+
success: ok,
|
|
564
|
+
summary,
|
|
565
|
+
outcome: ok ? 'completed' : 'failed',
|
|
566
|
+
functionName: execResult.functionName,
|
|
567
|
+
executionTime: Number(elapsed),
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
return {
|
|
571
|
+
onSuccess: ok, onFailure: !ok,
|
|
572
|
+
projectDir, config, targetPath,
|
|
573
|
+
resultJson: JSON.stringify(resultObj),
|
|
574
|
+
};
|
|
575
|
+
} catch (err: unknown) {
|
|
576
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
577
|
+
warn(\`Error: \${msg}\`);
|
|
578
|
+
return {
|
|
579
|
+
onSuccess: false, onFailure: true,
|
|
580
|
+
projectDir, config, targetPath,
|
|
581
|
+
resultJson: JSON.stringify({ success: false, summary: msg, outcome: 'error' }),
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* @flowWeaver nodeType
|
|
588
|
+
* @expression
|
|
589
|
+
* @label Notify Result
|
|
590
|
+
* @input projectDir [order:0] - Project root directory
|
|
591
|
+
* @input config [order:1] - Config (JSON)
|
|
592
|
+
* @input targetPath [order:2] - Target path
|
|
593
|
+
* @input resultJson [order:3] - Result (JSON)
|
|
594
|
+
* @output projectDir [order:0] - Project root directory (pass-through)
|
|
595
|
+
* @output targetPath [order:1] - Target path (pass-through)
|
|
596
|
+
* @output resultJson [order:2] - Result (pass-through)
|
|
597
|
+
*/
|
|
598
|
+
function sendNotify(
|
|
599
|
+
projectDir: string, config: string, targetPath: string, resultJson: string,
|
|
600
|
+
): { projectDir: string; targetPath: string; resultJson: string } {
|
|
601
|
+
const cfg: WeaverConfig = JSON.parse(config);
|
|
602
|
+
const result = JSON.parse(resultJson);
|
|
603
|
+
const channels = cfg.notify ?? [];
|
|
604
|
+
|
|
605
|
+
for (const ch of channels) {
|
|
606
|
+
const events = ch.events ?? ['workflow-complete', 'error'];
|
|
607
|
+
const eventType = result.success ? 'workflow-complete' : 'error';
|
|
608
|
+
if (!events.includes(eventType)) continue;
|
|
609
|
+
sendWebhook(ch, { ...result, targetPath, providerType: cfg.provider, projectDir });
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (channels.length > 0) info(\`Sent \${channels.length} notification(s)\`);
|
|
613
|
+
return { projectDir, targetPath, resultJson };
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* @flowWeaver nodeType
|
|
618
|
+
* @expression
|
|
619
|
+
* @label Report
|
|
620
|
+
* @input projectDir [order:0] - Project root directory
|
|
621
|
+
* @input targetPath [order:1] - Target workflow path
|
|
622
|
+
* @input resultJson [order:2] - Result (JSON)
|
|
623
|
+
* @output summary [order:0] - Summary string
|
|
624
|
+
*/
|
|
625
|
+
function report(projectDir: string, targetPath: string, resultJson: string): { summary: string } {
|
|
626
|
+
const result = JSON.parse(resultJson);
|
|
627
|
+
const relPath = path.relative(projectDir, targetPath);
|
|
628
|
+
const lines = [
|
|
629
|
+
\`Weaver: \${result.outcome} (\${relPath})\`,
|
|
630
|
+
result.summary,
|
|
631
|
+
];
|
|
632
|
+
if (result.executionTime) lines.push(\`Time: \${result.executionTime}s\`);
|
|
633
|
+
success(lines[0]!);
|
|
634
|
+
return { summary: lines.join('\\n') };
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// ============================================================
|
|
638
|
+
// Workflow
|
|
639
|
+
// ============================================================
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* @flowWeaver workflow
|
|
643
|
+
* @node cfg loadConfig [color: "teal"] [icon: "settings"] [position: 100 0]
|
|
644
|
+
* @node detect detectProvider [color: "cyan"] [icon: "search"] [position: 250 0]
|
|
645
|
+
* @node target resolveTarget [color: "blue"] [icon: "code"] [position: 400 0]
|
|
646
|
+
* @node exec executeTarget [color: "purple"] [icon: "psychology"] [position: 550 0]
|
|
647
|
+
* @node notify sendNotify [color: "yellow"] [icon: "notifications"] [position: 700 0]
|
|
648
|
+
* @node rep report [color: "green"] [icon: "description"] [position: 850 0]
|
|
649
|
+
* @path Start -> cfg -> detect -> target -> exec -> notify -> rep -> Exit
|
|
650
|
+
* @position Start 0 0
|
|
651
|
+
* @position Exit 1000 0
|
|
652
|
+
* @connect rep.summary -> Exit.summary
|
|
653
|
+
* @param execute [order:-1] - Execute
|
|
654
|
+
* @param params [order:0] - Params
|
|
655
|
+
* @returns onSuccess [order:-2] - On Success
|
|
656
|
+
* @returns onFailure [order:-1] - On Failure
|
|
657
|
+
* @returns summary [order:0] - Summary text
|
|
658
|
+
*/
|
|
659
|
+
export async function weaver(
|
|
660
|
+
execute: boolean,
|
|
661
|
+
params: Record<string, never> = {},
|
|
662
|
+
__abortSignal__?: AbortSignal,
|
|
663
|
+
): Promise<{ onSuccess: boolean; onFailure: boolean; summary: string | null }> {
|
|
664
|
+
// @flow-weaver-body-start
|
|
665
|
+
// (auto-generated by compiler)
|
|
666
|
+
// @flow-weaver-body-end
|
|
667
|
+
return { onSuccess: false, onFailure: true, summary: null };
|
|
668
|
+
}
|
|
669
|
+
`;
|
|
670
|
+
},
|
|
671
|
+
};
|
|
672
|
+
//# sourceMappingURL=weaver-template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"weaver-template.js","sourceRoot":"","sources":["../../src/templates/weaver-template.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,MAAM,cAAc,GAAqB;IAC9C,EAAE,EAAE,QAAQ;IACZ,IAAI,EAAE,oCAAoC;IAC1C,WAAW,EAAE,8HAA8H;IAC3I,QAAQ,EAAE,YAAY;IACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;QAClB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAspBV,CAAC;IACA,CAAC;CACF,CAAC"}
|