linguclaw 0.4.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.
Files changed (168) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/dist/agent-system.d.ts +196 -0
  4. package/dist/agent-system.d.ts.map +1 -0
  5. package/dist/agent-system.js +738 -0
  6. package/dist/agent-system.js.map +1 -0
  7. package/dist/alphabeta.d.ts +54 -0
  8. package/dist/alphabeta.d.ts.map +1 -0
  9. package/dist/alphabeta.js +193 -0
  10. package/dist/alphabeta.js.map +1 -0
  11. package/dist/browser.d.ts +62 -0
  12. package/dist/browser.d.ts.map +1 -0
  13. package/dist/browser.js +224 -0
  14. package/dist/browser.js.map +1 -0
  15. package/dist/cli.d.ts +7 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +565 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/code-parser.d.ts +39 -0
  20. package/dist/code-parser.d.ts.map +1 -0
  21. package/dist/code-parser.js +385 -0
  22. package/dist/code-parser.js.map +1 -0
  23. package/dist/config.d.ts +66 -0
  24. package/dist/config.d.ts.map +1 -0
  25. package/dist/config.js +232 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/core/engine.d.ts +359 -0
  28. package/dist/core/engine.d.ts.map +1 -0
  29. package/dist/core/engine.js +127 -0
  30. package/dist/core/engine.js.map +1 -0
  31. package/dist/daemon.d.ts +29 -0
  32. package/dist/daemon.d.ts.map +1 -0
  33. package/dist/daemon.js +212 -0
  34. package/dist/daemon.js.map +1 -0
  35. package/dist/email-receiver.d.ts +63 -0
  36. package/dist/email-receiver.d.ts.map +1 -0
  37. package/dist/email-receiver.js +553 -0
  38. package/dist/email-receiver.js.map +1 -0
  39. package/dist/git-integration.d.ts +180 -0
  40. package/dist/git-integration.d.ts.map +1 -0
  41. package/dist/git-integration.js +850 -0
  42. package/dist/git-integration.js.map +1 -0
  43. package/dist/inbox.d.ts +84 -0
  44. package/dist/inbox.d.ts.map +1 -0
  45. package/dist/inbox.js +198 -0
  46. package/dist/inbox.js.map +1 -0
  47. package/dist/index.d.ts +6 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +41 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/languages/cpp.d.ts +51 -0
  52. package/dist/languages/cpp.d.ts.map +1 -0
  53. package/dist/languages/cpp.js +930 -0
  54. package/dist/languages/cpp.js.map +1 -0
  55. package/dist/languages/csharp.d.ts +79 -0
  56. package/dist/languages/csharp.d.ts.map +1 -0
  57. package/dist/languages/csharp.js +1776 -0
  58. package/dist/languages/csharp.js.map +1 -0
  59. package/dist/languages/go.d.ts +50 -0
  60. package/dist/languages/go.d.ts.map +1 -0
  61. package/dist/languages/go.js +882 -0
  62. package/dist/languages/go.js.map +1 -0
  63. package/dist/languages/java.d.ts +47 -0
  64. package/dist/languages/java.d.ts.map +1 -0
  65. package/dist/languages/java.js +649 -0
  66. package/dist/languages/java.js.map +1 -0
  67. package/dist/languages/python.d.ts +47 -0
  68. package/dist/languages/python.d.ts.map +1 -0
  69. package/dist/languages/python.js +655 -0
  70. package/dist/languages/python.js.map +1 -0
  71. package/dist/languages/rust.d.ts +61 -0
  72. package/dist/languages/rust.d.ts.map +1 -0
  73. package/dist/languages/rust.js +1064 -0
  74. package/dist/languages/rust.js.map +1 -0
  75. package/dist/logger.d.ts +20 -0
  76. package/dist/logger.d.ts.map +1 -0
  77. package/dist/logger.js +133 -0
  78. package/dist/logger.js.map +1 -0
  79. package/dist/longterm-memory.d.ts +47 -0
  80. package/dist/longterm-memory.d.ts.map +1 -0
  81. package/dist/longterm-memory.js +300 -0
  82. package/dist/longterm-memory.js.map +1 -0
  83. package/dist/memory.d.ts +42 -0
  84. package/dist/memory.d.ts.map +1 -0
  85. package/dist/memory.js +274 -0
  86. package/dist/memory.js.map +1 -0
  87. package/dist/messaging.d.ts +103 -0
  88. package/dist/messaging.d.ts.map +1 -0
  89. package/dist/messaging.js +645 -0
  90. package/dist/messaging.js.map +1 -0
  91. package/dist/multi-provider.d.ts +69 -0
  92. package/dist/multi-provider.d.ts.map +1 -0
  93. package/dist/multi-provider.js +484 -0
  94. package/dist/multi-provider.js.map +1 -0
  95. package/dist/orchestrator.d.ts +65 -0
  96. package/dist/orchestrator.d.ts.map +1 -0
  97. package/dist/orchestrator.js +441 -0
  98. package/dist/orchestrator.js.map +1 -0
  99. package/dist/plugins.d.ts +52 -0
  100. package/dist/plugins.d.ts.map +1 -0
  101. package/dist/plugins.js +215 -0
  102. package/dist/plugins.js.map +1 -0
  103. package/dist/prism-orchestrator.d.ts +26 -0
  104. package/dist/prism-orchestrator.d.ts.map +1 -0
  105. package/dist/prism-orchestrator.js +191 -0
  106. package/dist/prism-orchestrator.js.map +1 -0
  107. package/dist/prism.d.ts +46 -0
  108. package/dist/prism.d.ts.map +1 -0
  109. package/dist/prism.js +188 -0
  110. package/dist/prism.js.map +1 -0
  111. package/dist/privacy.d.ts +23 -0
  112. package/dist/privacy.d.ts.map +1 -0
  113. package/dist/privacy.js +220 -0
  114. package/dist/privacy.js.map +1 -0
  115. package/dist/proactive.d.ts +30 -0
  116. package/dist/proactive.d.ts.map +1 -0
  117. package/dist/proactive.js +260 -0
  118. package/dist/proactive.js.map +1 -0
  119. package/dist/refactoring-engine.d.ts +100 -0
  120. package/dist/refactoring-engine.d.ts.map +1 -0
  121. package/dist/refactoring-engine.js +717 -0
  122. package/dist/refactoring-engine.js.map +1 -0
  123. package/dist/resilience.d.ts +43 -0
  124. package/dist/resilience.d.ts.map +1 -0
  125. package/dist/resilience.js +200 -0
  126. package/dist/resilience.js.map +1 -0
  127. package/dist/safety.d.ts +40 -0
  128. package/dist/safety.d.ts.map +1 -0
  129. package/dist/safety.js +133 -0
  130. package/dist/safety.js.map +1 -0
  131. package/dist/sandbox.d.ts +33 -0
  132. package/dist/sandbox.d.ts.map +1 -0
  133. package/dist/sandbox.js +173 -0
  134. package/dist/sandbox.js.map +1 -0
  135. package/dist/scheduler.d.ts +72 -0
  136. package/dist/scheduler.d.ts.map +1 -0
  137. package/dist/scheduler.js +374 -0
  138. package/dist/scheduler.js.map +1 -0
  139. package/dist/semantic-memory.d.ts +70 -0
  140. package/dist/semantic-memory.d.ts.map +1 -0
  141. package/dist/semantic-memory.js +430 -0
  142. package/dist/semantic-memory.js.map +1 -0
  143. package/dist/skills.d.ts +97 -0
  144. package/dist/skills.d.ts.map +1 -0
  145. package/dist/skills.js +575 -0
  146. package/dist/skills.js.map +1 -0
  147. package/dist/static/dashboard.html +853 -0
  148. package/dist/static/hub.html +772 -0
  149. package/dist/static/index.html +818 -0
  150. package/dist/static/logo.svg +24 -0
  151. package/dist/static/workflow-editor.html +913 -0
  152. package/dist/tools.d.ts +67 -0
  153. package/dist/tools.d.ts.map +1 -0
  154. package/dist/tools.js +303 -0
  155. package/dist/tools.js.map +1 -0
  156. package/dist/types.d.ts +295 -0
  157. package/dist/types.d.ts.map +1 -0
  158. package/dist/types.js +90 -0
  159. package/dist/types.js.map +1 -0
  160. package/dist/web.d.ts +76 -0
  161. package/dist/web.d.ts.map +1 -0
  162. package/dist/web.js +2139 -0
  163. package/dist/web.js.map +1 -0
  164. package/dist/workflow-engine.d.ts +114 -0
  165. package/dist/workflow-engine.d.ts.map +1 -0
  166. package/dist/workflow-engine.js +855 -0
  167. package/dist/workflow-engine.js.map +1 -0
  168. package/package.json +77 -0
@@ -0,0 +1,855 @@
1
+ "use strict";
2
+ /**
3
+ * Workflow Engine - n8n-style node-based workflow execution
4
+ * Supports trigger → action → condition → transform → output chains
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.WorkflowEngine = exports.NODE_DEFINITIONS = void 0;
11
+ exports.getWorkflowEngine = getWorkflowEngine;
12
+ const events_1 = require("events");
13
+ const logger_1 = require("./logger");
14
+ const fs_1 = __importDefault(require("fs"));
15
+ const path_1 = __importDefault(require("path"));
16
+ const logger = (0, logger_1.getLogger)();
17
+ // ==================== Built-in Node Definitions ====================
18
+ function generateId() {
19
+ const chars = 'abcdef0123456789';
20
+ let id = '';
21
+ for (let i = 0; i < 8; i++)
22
+ id += chars[Math.floor(Math.random() * chars.length)];
23
+ id += '-';
24
+ for (let i = 0; i < 4; i++)
25
+ id += chars[Math.floor(Math.random() * chars.length)];
26
+ id += '-4';
27
+ for (let i = 0; i < 3; i++)
28
+ id += chars[Math.floor(Math.random() * chars.length)];
29
+ id += '-';
30
+ id += ['8', '9', 'a', 'b'][Math.floor(Math.random() * 4)];
31
+ for (let i = 0; i < 3; i++)
32
+ id += chars[Math.floor(Math.random() * chars.length)];
33
+ id += '-';
34
+ for (let i = 0; i < 12; i++)
35
+ id += chars[Math.floor(Math.random() * chars.length)];
36
+ return id;
37
+ }
38
+ exports.NODE_DEFINITIONS = [
39
+ // Triggers
40
+ {
41
+ id: 'manual-trigger',
42
+ type: 'trigger',
43
+ name: 'Manual Trigger',
44
+ icon: 'play',
45
+ category: 'Triggers',
46
+ description: 'Start workflow manually',
47
+ inputs: 0,
48
+ outputs: 1,
49
+ configFields: [],
50
+ },
51
+ {
52
+ id: 'schedule-trigger',
53
+ type: 'trigger',
54
+ name: 'Schedule',
55
+ icon: 'clock',
56
+ category: 'Triggers',
57
+ description: 'Run on a schedule (cron/interval)',
58
+ inputs: 0,
59
+ outputs: 1,
60
+ configFields: [
61
+ { key: 'mode', label: 'Mode', type: 'select', options: [{ label: 'Interval', value: 'interval' }, { label: 'Cron', value: 'cron' }], default: 'interval' },
62
+ { key: 'interval', label: 'Interval', type: 'text', placeholder: '5m, 1h, 1d' },
63
+ { key: 'cron', label: 'Cron Expression', type: 'text', placeholder: '*/5 * * * *' },
64
+ ],
65
+ },
66
+ {
67
+ id: 'webhook-trigger',
68
+ type: 'trigger',
69
+ name: 'Webhook',
70
+ icon: 'webhook',
71
+ category: 'Triggers',
72
+ description: 'Trigger via HTTP webhook',
73
+ inputs: 0,
74
+ outputs: 1,
75
+ configFields: [
76
+ { key: 'method', label: 'Method', type: 'select', options: [{ label: 'GET', value: 'GET' }, { label: 'POST', value: 'POST' }], default: 'POST' },
77
+ { key: 'path', label: 'Path', type: 'text', placeholder: '/webhook/my-flow' },
78
+ ],
79
+ },
80
+ {
81
+ id: 'email-trigger',
82
+ type: 'trigger',
83
+ name: 'Email Received',
84
+ icon: 'mail',
85
+ category: 'Triggers',
86
+ description: 'Trigger when email arrives',
87
+ inputs: 0,
88
+ outputs: 1,
89
+ configFields: [
90
+ { key: 'folder', label: 'Folder', type: 'text', placeholder: 'INBOX', default: 'INBOX' },
91
+ { key: 'filter', label: 'Subject Filter', type: 'text', placeholder: 'Optional regex' },
92
+ ],
93
+ },
94
+ // Actions
95
+ {
96
+ id: 'http-request',
97
+ type: 'action',
98
+ name: 'HTTP Request',
99
+ icon: 'globe',
100
+ category: 'Actions',
101
+ description: 'Make an HTTP request',
102
+ inputs: 1,
103
+ outputs: 1,
104
+ configFields: [
105
+ { key: 'method', label: 'Method', type: 'select', options: [{ label: 'GET', value: 'GET' }, { label: 'POST', value: 'POST' }, { label: 'PUT', value: 'PUT' }, { label: 'DELETE', value: 'DELETE' }], default: 'GET' },
106
+ { key: 'url', label: 'URL', type: 'text', placeholder: 'https://api.example.com/data', required: true },
107
+ { key: 'headers', label: 'Headers', type: 'json', placeholder: '{"Authorization": "Bearer ..."}' },
108
+ { key: 'body', label: 'Body', type: 'json', placeholder: '{"key": "value"}' },
109
+ ],
110
+ },
111
+ {
112
+ id: 'shell-command',
113
+ type: 'action',
114
+ name: 'Shell Command',
115
+ icon: 'terminal',
116
+ category: 'Actions',
117
+ description: 'Execute a shell command',
118
+ inputs: 1,
119
+ outputs: 1,
120
+ configFields: [
121
+ { key: 'command', label: 'Command', type: 'text', placeholder: 'echo "Hello World"', required: true },
122
+ { key: 'cwd', label: 'Working Directory', type: 'text', placeholder: '.' },
123
+ { key: 'timeout', label: 'Timeout (ms)', type: 'number', default: 30000 },
124
+ ],
125
+ },
126
+ {
127
+ id: 'send-email',
128
+ type: 'action',
129
+ name: 'Send Email',
130
+ icon: 'mail',
131
+ category: 'Actions',
132
+ description: 'Send an email message',
133
+ inputs: 1,
134
+ outputs: 1,
135
+ configFields: [
136
+ { key: 'to', label: 'To', type: 'text', placeholder: 'user@example.com', required: true },
137
+ { key: 'subject', label: 'Subject', type: 'text', placeholder: 'Email subject', required: true },
138
+ { key: 'body', label: 'Body', type: 'text', placeholder: 'Email body' },
139
+ ],
140
+ },
141
+ {
142
+ id: 'ai-prompt',
143
+ type: 'action',
144
+ name: 'AI Prompt',
145
+ icon: 'brain',
146
+ category: 'Actions',
147
+ description: 'Send prompt to LLM and get response',
148
+ inputs: 1,
149
+ outputs: 1,
150
+ configFields: [
151
+ { key: 'systemPrompt', label: 'System Prompt', type: 'text', placeholder: 'You are a helpful assistant' },
152
+ { key: 'userPrompt', label: 'User Prompt', type: 'text', placeholder: 'Analyze: {{input}}', required: true },
153
+ { key: 'temperature', label: 'Temperature', type: 'number', default: 0.7 },
154
+ { key: 'maxTokens', label: 'Max Tokens', type: 'number', default: 1000 },
155
+ ],
156
+ },
157
+ {
158
+ id: 'send-telegram',
159
+ type: 'action',
160
+ name: 'Send Telegram',
161
+ icon: 'send',
162
+ category: 'Actions',
163
+ description: 'Send a Telegram message',
164
+ inputs: 1,
165
+ outputs: 1,
166
+ configFields: [
167
+ { key: 'chatId', label: 'Chat ID', type: 'text', placeholder: '123456789' },
168
+ { key: 'message', label: 'Message', type: 'text', placeholder: 'Hello from LinguClaw!', required: true },
169
+ ],
170
+ },
171
+ {
172
+ id: 'read-file',
173
+ type: 'action',
174
+ name: 'Read File',
175
+ icon: 'file',
176
+ category: 'Actions',
177
+ description: 'Read contents of a file',
178
+ inputs: 1,
179
+ outputs: 1,
180
+ configFields: [
181
+ { key: 'path', label: 'File Path', type: 'text', placeholder: '/path/to/file.txt', required: true },
182
+ { key: 'encoding', label: 'Encoding', type: 'select', options: [{ label: 'UTF-8', value: 'utf8' }, { label: 'ASCII', value: 'ascii' }, { label: 'Base64', value: 'base64' }], default: 'utf8' },
183
+ ],
184
+ },
185
+ {
186
+ id: 'write-file',
187
+ type: 'action',
188
+ name: 'Write File',
189
+ icon: 'file-plus',
190
+ category: 'Actions',
191
+ description: 'Write content to a file',
192
+ inputs: 1,
193
+ outputs: 1,
194
+ configFields: [
195
+ { key: 'path', label: 'File Path', type: 'text', placeholder: '/path/to/output.txt', required: true },
196
+ { key: 'content', label: 'Content', type: 'text', placeholder: '{{input}}' },
197
+ { key: 'append', label: 'Append', type: 'boolean', default: false },
198
+ ],
199
+ },
200
+ {
201
+ id: 'memory-store',
202
+ type: 'action',
203
+ name: 'Store Memory',
204
+ icon: 'database',
205
+ category: 'Actions',
206
+ description: 'Store data in LinguClaw memory',
207
+ inputs: 1,
208
+ outputs: 1,
209
+ configFields: [
210
+ { key: 'key', label: 'Key', type: 'text', placeholder: 'my-data', required: true },
211
+ { key: 'category', label: 'Category', type: 'text', placeholder: 'workflow', default: 'workflow' },
212
+ ],
213
+ },
214
+ {
215
+ id: 'memory-retrieve',
216
+ type: 'action',
217
+ name: 'Retrieve Memory',
218
+ icon: 'database',
219
+ category: 'Actions',
220
+ description: 'Retrieve data from LinguClaw memory',
221
+ inputs: 1,
222
+ outputs: 1,
223
+ configFields: [
224
+ { key: 'key', label: 'Key', type: 'text', placeholder: 'my-data', required: true },
225
+ ],
226
+ },
227
+ {
228
+ id: 'delay',
229
+ type: 'action',
230
+ name: 'Delay',
231
+ icon: 'clock',
232
+ category: 'Actions',
233
+ description: 'Wait for specified duration',
234
+ inputs: 1,
235
+ outputs: 1,
236
+ configFields: [
237
+ { key: 'duration', label: 'Duration (ms)', type: 'number', default: 1000, required: true },
238
+ ],
239
+ },
240
+ // Conditions
241
+ {
242
+ id: 'if-condition',
243
+ type: 'condition',
244
+ name: 'IF',
245
+ icon: 'git-branch',
246
+ category: 'Logic',
247
+ description: 'Branch based on condition',
248
+ inputs: 1,
249
+ outputs: 2,
250
+ configFields: [
251
+ { key: 'field', label: 'Field', type: 'text', placeholder: 'data.status', required: true },
252
+ { key: 'operator', label: 'Operator', type: 'select', options: [
253
+ { label: 'Equals', value: 'eq' }, { label: 'Not Equals', value: 'neq' },
254
+ { label: 'Contains', value: 'contains' }, { label: 'Greater Than', value: 'gt' },
255
+ { label: 'Less Than', value: 'lt' }, { label: 'Regex Match', value: 'regex' },
256
+ { label: 'Is Empty', value: 'empty' }, { label: 'Is Not Empty', value: 'notEmpty' },
257
+ ], default: 'eq' },
258
+ { key: 'value', label: 'Value', type: 'text', placeholder: 'expected value' },
259
+ ],
260
+ },
261
+ {
262
+ id: 'switch',
263
+ type: 'condition',
264
+ name: 'Switch',
265
+ icon: 'git-branch',
266
+ category: 'Logic',
267
+ description: 'Multi-branch switch statement',
268
+ inputs: 1,
269
+ outputs: 3,
270
+ configFields: [
271
+ { key: 'field', label: 'Field', type: 'text', placeholder: 'data.type', required: true },
272
+ { key: 'case1', label: 'Case 1 (Output 1)', type: 'text', placeholder: 'value1' },
273
+ { key: 'case2', label: 'Case 2 (Output 2)', type: 'text', placeholder: 'value2' },
274
+ ],
275
+ },
276
+ // Transforms
277
+ {
278
+ id: 'code-transform',
279
+ type: 'transform',
280
+ name: 'Code',
281
+ icon: 'code',
282
+ category: 'Transform',
283
+ description: 'Transform data with JavaScript',
284
+ inputs: 1,
285
+ outputs: 1,
286
+ configFields: [
287
+ { key: 'code', label: 'JavaScript Code', type: 'code', placeholder: 'return { result: input.data };', required: true },
288
+ ],
289
+ },
290
+ {
291
+ id: 'json-transform',
292
+ type: 'transform',
293
+ name: 'JSON Transform',
294
+ icon: 'braces',
295
+ category: 'Transform',
296
+ description: 'Transform JSON data with template',
297
+ inputs: 1,
298
+ outputs: 1,
299
+ configFields: [
300
+ { key: 'template', label: 'Output Template', type: 'json', placeholder: '{"name": "{{input.name}}"}', required: true },
301
+ ],
302
+ },
303
+ {
304
+ id: 'text-template',
305
+ type: 'transform',
306
+ name: 'Text Template',
307
+ icon: 'type',
308
+ category: 'Transform',
309
+ description: 'Generate text from template',
310
+ inputs: 1,
311
+ outputs: 1,
312
+ configFields: [
313
+ { key: 'template', label: 'Template', type: 'text', placeholder: 'Hello {{input.name}}, your order #{{input.id}} is ready.', required: true },
314
+ ],
315
+ },
316
+ {
317
+ id: 'merge',
318
+ type: 'transform',
319
+ name: 'Merge',
320
+ icon: 'git-merge',
321
+ category: 'Transform',
322
+ description: 'Merge multiple inputs',
323
+ inputs: 2,
324
+ outputs: 1,
325
+ configFields: [
326
+ { key: 'mode', label: 'Mode', type: 'select', options: [{ label: 'Append', value: 'append' }, { label: 'Merge Object', value: 'merge' }], default: 'merge' },
327
+ ],
328
+ },
329
+ // Outputs
330
+ {
331
+ id: 'log-output',
332
+ type: 'output',
333
+ name: 'Log',
334
+ icon: 'file-text',
335
+ category: 'Output',
336
+ description: 'Log data to console/file',
337
+ inputs: 1,
338
+ outputs: 0,
339
+ configFields: [
340
+ { key: 'level', label: 'Level', type: 'select', options: [{ label: 'Info', value: 'info' }, { label: 'Warn', value: 'warn' }, { label: 'Error', value: 'error' }], default: 'info' },
341
+ { key: 'message', label: 'Message', type: 'text', placeholder: 'Workflow output: {{input}}' },
342
+ ],
343
+ },
344
+ {
345
+ id: 'webhook-response',
346
+ type: 'output',
347
+ name: 'Respond',
348
+ icon: 'send',
349
+ category: 'Output',
350
+ description: 'Return HTTP response for webhook',
351
+ inputs: 1,
352
+ outputs: 0,
353
+ configFields: [
354
+ { key: 'statusCode', label: 'Status Code', type: 'number', default: 200 },
355
+ { key: 'body', label: 'Response Body', type: 'json', placeholder: '{"success": true}' },
356
+ ],
357
+ },
358
+ ];
359
+ // ==================== Workflow Engine ====================
360
+ class WorkflowEngine extends events_1.EventEmitter {
361
+ workflows = new Map();
362
+ storagePath;
363
+ constructor(storagePath) {
364
+ super();
365
+ this.storagePath = storagePath || path_1.default.join(process.env.HOME || '~', '.linguclaw', 'workflows.json');
366
+ this.loadFromDisk();
367
+ }
368
+ loadFromDisk() {
369
+ try {
370
+ if (fs_1.default.existsSync(this.storagePath)) {
371
+ const data = JSON.parse(fs_1.default.readFileSync(this.storagePath, 'utf8'));
372
+ if (Array.isArray(data)) {
373
+ for (const wf of data) {
374
+ this.workflows.set(wf.id, wf);
375
+ }
376
+ }
377
+ logger.debug(`Loaded ${this.workflows.size} workflows`);
378
+ }
379
+ }
380
+ catch (e) {
381
+ logger.warn(`Failed to load workflows: ${e.message}`);
382
+ }
383
+ }
384
+ saveToDisk() {
385
+ try {
386
+ const dir = path_1.default.dirname(this.storagePath);
387
+ if (!fs_1.default.existsSync(dir))
388
+ fs_1.default.mkdirSync(dir, { recursive: true });
389
+ const data = Array.from(this.workflows.values());
390
+ fs_1.default.writeFileSync(this.storagePath, JSON.stringify(data, null, 2));
391
+ }
392
+ catch (e) {
393
+ logger.warn(`Failed to save workflows: ${e.message}`);
394
+ }
395
+ }
396
+ getNodeDefinitions() {
397
+ return exports.NODE_DEFINITIONS;
398
+ }
399
+ listWorkflows() {
400
+ return Array.from(this.workflows.values()).sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
401
+ }
402
+ getWorkflow(id) {
403
+ return this.workflows.get(id) || null;
404
+ }
405
+ createWorkflow(name, description = '') {
406
+ const workflow = {
407
+ id: generateId(),
408
+ name,
409
+ description,
410
+ nodes: [],
411
+ connections: [],
412
+ active: false,
413
+ createdAt: new Date().toISOString(),
414
+ updatedAt: new Date().toISOString(),
415
+ };
416
+ this.workflows.set(workflow.id, workflow);
417
+ this.saveToDisk();
418
+ return workflow;
419
+ }
420
+ updateWorkflow(id, updates) {
421
+ const wf = this.workflows.get(id);
422
+ if (!wf)
423
+ return null;
424
+ if (updates.name !== undefined)
425
+ wf.name = updates.name;
426
+ if (updates.description !== undefined)
427
+ wf.description = updates.description;
428
+ if (updates.nodes !== undefined)
429
+ wf.nodes = updates.nodes;
430
+ if (updates.connections !== undefined)
431
+ wf.connections = updates.connections;
432
+ if (updates.active !== undefined)
433
+ wf.active = updates.active;
434
+ wf.updatedAt = new Date().toISOString();
435
+ this.workflows.set(id, wf);
436
+ this.saveToDisk();
437
+ return wf;
438
+ }
439
+ deleteWorkflow(id) {
440
+ const deleted = this.workflows.delete(id);
441
+ if (deleted)
442
+ this.saveToDisk();
443
+ return deleted;
444
+ }
445
+ duplicateWorkflow(id) {
446
+ const original = this.workflows.get(id);
447
+ if (!original)
448
+ return null;
449
+ const copy = {
450
+ ...JSON.parse(JSON.stringify(original)),
451
+ id: generateId(),
452
+ name: original.name + ' (Copy)',
453
+ active: false,
454
+ createdAt: new Date().toISOString(),
455
+ updatedAt: new Date().toISOString(),
456
+ };
457
+ this.workflows.set(copy.id, copy);
458
+ this.saveToDisk();
459
+ return copy;
460
+ }
461
+ /**
462
+ * Execute a workflow
463
+ */
464
+ async executeWorkflow(id, triggerData, context) {
465
+ const wf = this.workflows.get(id);
466
+ if (!wf)
467
+ throw new Error('Workflow not found');
468
+ const startedAt = new Date().toISOString();
469
+ const nodeResults = [];
470
+ // Update state
471
+ wf.lastRunAt = startedAt;
472
+ wf.lastRunStatus = 'running';
473
+ this.emit('workflow:start', { workflowId: id });
474
+ try {
475
+ // Build execution graph
476
+ const executionOrder = this.topologicalSort(wf);
477
+ // Data store for node outputs
478
+ const nodeOutputs = new Map();
479
+ // Execute nodes in order
480
+ for (const nodeId of executionOrder) {
481
+ const node = wf.nodes.find(n => n.id === nodeId);
482
+ if (!node)
483
+ continue;
484
+ const startTime = Date.now();
485
+ try {
486
+ // Gather inputs from connected nodes
487
+ const inputs = this.gatherInputs(wf, nodeId, nodeOutputs);
488
+ // Execute the node
489
+ const output = await this.executeNode(node, inputs, triggerData, context);
490
+ nodeOutputs.set(nodeId, output);
491
+ const result = {
492
+ nodeId,
493
+ status: 'success',
494
+ output,
495
+ duration: Date.now() - startTime,
496
+ };
497
+ nodeResults.push(result);
498
+ this.emit('node:complete', { workflowId: id, ...result });
499
+ // Handle condition nodes - determine which output path to take
500
+ if (node.type === 'condition') {
501
+ const skippedOutputs = this.getSkippedPaths(wf, nodeId, output);
502
+ for (const skippedNodeId of skippedOutputs) {
503
+ nodeOutputs.set(skippedNodeId, { _skipped: true });
504
+ }
505
+ }
506
+ }
507
+ catch (err) {
508
+ const result = {
509
+ nodeId,
510
+ status: 'error',
511
+ output: null,
512
+ error: err.message,
513
+ duration: Date.now() - startTime,
514
+ };
515
+ nodeResults.push(result);
516
+ this.emit('node:error', { workflowId: id, ...result });
517
+ logger.error(`Node ${node.name} (${nodeId}) failed: ${err.message}`);
518
+ }
519
+ }
520
+ const completedAt = new Date().toISOString();
521
+ const hasErrors = nodeResults.some(r => r.status === 'error');
522
+ wf.lastRunStatus = hasErrors ? 'error' : 'success';
523
+ this.saveToDisk();
524
+ const execResult = {
525
+ workflowId: id,
526
+ status: hasErrors ? (nodeResults.some(r => r.status === 'success') ? 'partial' : 'error') : 'success',
527
+ nodeResults,
528
+ startedAt,
529
+ completedAt,
530
+ duration: new Date(completedAt).getTime() - new Date(startedAt).getTime(),
531
+ };
532
+ this.emit('workflow:complete', execResult);
533
+ return execResult;
534
+ }
535
+ catch (err) {
536
+ wf.lastRunStatus = 'error';
537
+ this.saveToDisk();
538
+ const result = {
539
+ workflowId: id,
540
+ status: 'error',
541
+ nodeResults,
542
+ startedAt,
543
+ completedAt: new Date().toISOString(),
544
+ duration: Date.now() - new Date(startedAt).getTime(),
545
+ };
546
+ this.emit('workflow:error', { ...result, error: err.message });
547
+ return result;
548
+ }
549
+ }
550
+ topologicalSort(wf) {
551
+ const inDegree = new Map();
552
+ const adjList = new Map();
553
+ for (const node of wf.nodes) {
554
+ inDegree.set(node.id, 0);
555
+ adjList.set(node.id, []);
556
+ }
557
+ for (const conn of wf.connections) {
558
+ const targets = adjList.get(conn.sourceNodeId) || [];
559
+ targets.push(conn.targetNodeId);
560
+ adjList.set(conn.sourceNodeId, targets);
561
+ inDegree.set(conn.targetNodeId, (inDegree.get(conn.targetNodeId) || 0) + 1);
562
+ }
563
+ const queue = [];
564
+ for (const [nodeId, degree] of inDegree) {
565
+ if (degree === 0)
566
+ queue.push(nodeId);
567
+ }
568
+ const result = [];
569
+ while (queue.length > 0) {
570
+ const nodeId = queue.shift();
571
+ result.push(nodeId);
572
+ for (const neighbor of adjList.get(nodeId) || []) {
573
+ const newDegree = (inDegree.get(neighbor) || 0) - 1;
574
+ inDegree.set(neighbor, newDegree);
575
+ if (newDegree === 0)
576
+ queue.push(neighbor);
577
+ }
578
+ }
579
+ return result;
580
+ }
581
+ gatherInputs(wf, nodeId, outputs) {
582
+ const incoming = wf.connections.filter(c => c.targetNodeId === nodeId);
583
+ if (incoming.length === 0)
584
+ return null;
585
+ if (incoming.length === 1)
586
+ return outputs.get(incoming[0].sourceNodeId);
587
+ // Multiple inputs: merge into array
588
+ return incoming.map(c => outputs.get(c.sourceNodeId)).filter(Boolean);
589
+ }
590
+ getSkippedPaths(wf, conditionNodeId, conditionResult) {
591
+ // For condition nodes, the result indicates which output(s) to skip
592
+ const skipped = [];
593
+ const outputIdx = conditionResult?._outputIndex ?? 0;
594
+ const outConnections = wf.connections.filter(c => c.sourceNodeId === conditionNodeId);
595
+ for (const conn of outConnections) {
596
+ if (conn.sourceOutput !== outputIdx) {
597
+ skipped.push(conn.targetNodeId);
598
+ }
599
+ }
600
+ return skipped;
601
+ }
602
+ async executeNode(node, input, triggerData, context) {
603
+ // Skip if marked as skipped
604
+ if (input && input._skipped)
605
+ return { _skipped: true };
606
+ const def = exports.NODE_DEFINITIONS.find(d => d.id === node.definitionId);
607
+ if (!def)
608
+ throw new Error(`Unknown node type: ${node.definitionId}`);
609
+ const config = node.config || {};
610
+ // Replace template variables in config
611
+ const resolvedConfig = this.resolveTemplates(config, input, triggerData);
612
+ switch (node.definitionId) {
613
+ case 'manual-trigger':
614
+ return triggerData || { triggered: true, timestamp: new Date().toISOString() };
615
+ case 'schedule-trigger':
616
+ return { triggered: true, schedule: config.interval || config.cron, timestamp: new Date().toISOString() };
617
+ case 'webhook-trigger':
618
+ return triggerData || { triggered: true };
619
+ case 'email-trigger':
620
+ return triggerData || { triggered: true };
621
+ case 'http-request':
622
+ return await this.executeHttpRequest(resolvedConfig);
623
+ case 'shell-command':
624
+ return await this.executeShellCommand(resolvedConfig);
625
+ case 'send-email':
626
+ return { sent: true, to: resolvedConfig.to, subject: resolvedConfig.subject };
627
+ case 'ai-prompt':
628
+ return await this.executeAIPrompt(resolvedConfig, input, context);
629
+ case 'send-telegram':
630
+ return { sent: true, chatId: resolvedConfig.chatId, message: resolvedConfig.message };
631
+ case 'read-file':
632
+ return this.executeReadFile(resolvedConfig);
633
+ case 'write-file':
634
+ return this.executeWriteFile(resolvedConfig, input);
635
+ case 'memory-store':
636
+ if (context?.memory) {
637
+ context.memory.store(resolvedConfig.key, input, resolvedConfig.category || 'workflow');
638
+ }
639
+ return { stored: true, key: resolvedConfig.key };
640
+ case 'memory-retrieve':
641
+ if (context?.memory) {
642
+ const results = context.memory.search(resolvedConfig.key, undefined, 1);
643
+ return results.length > 0 ? results[0].value : null;
644
+ }
645
+ return null;
646
+ case 'delay':
647
+ await new Promise(resolve => setTimeout(resolve, parseInt(config.duration) || 1000));
648
+ return input;
649
+ case 'if-condition':
650
+ return this.evaluateCondition(resolvedConfig, input);
651
+ case 'switch':
652
+ return this.evaluateSwitch(resolvedConfig, input);
653
+ case 'code-transform':
654
+ return this.executeCodeTransform(resolvedConfig, input);
655
+ case 'json-transform':
656
+ return this.executeJsonTransform(resolvedConfig, input);
657
+ case 'text-template':
658
+ return { text: this.applyTemplate(resolvedConfig.template || '', input) };
659
+ case 'merge':
660
+ if (Array.isArray(input)) {
661
+ return resolvedConfig.mode === 'merge' ? Object.assign({}, ...input.filter(i => i && typeof i === 'object')) : input;
662
+ }
663
+ return input;
664
+ case 'log-output':
665
+ const msg = resolvedConfig.message ? this.applyTemplate(resolvedConfig.message, input) : JSON.stringify(input);
666
+ const lvl = resolvedConfig.level || 'info';
667
+ if (lvl === 'warn')
668
+ logger.warn(`[Workflow] ${msg}`);
669
+ else if (lvl === 'error')
670
+ logger.error(`[Workflow] ${msg}`);
671
+ else
672
+ logger.info(`[Workflow] ${msg}`);
673
+ return { logged: true, message: msg };
674
+ case 'webhook-response':
675
+ return { statusCode: resolvedConfig.statusCode || 200, body: resolvedConfig.body || input };
676
+ default:
677
+ throw new Error(`Unimplemented node: ${node.definitionId}`);
678
+ }
679
+ }
680
+ resolveTemplates(config, input, triggerData) {
681
+ const resolved = {};
682
+ for (const [key, value] of Object.entries(config)) {
683
+ if (typeof value === 'string') {
684
+ resolved[key] = this.applyTemplate(value, input, triggerData);
685
+ }
686
+ else {
687
+ resolved[key] = value;
688
+ }
689
+ }
690
+ return resolved;
691
+ }
692
+ applyTemplate(template, input, triggerData) {
693
+ return template.replace(/\{\{([\w.]+)\}\}/g, (_, path) => {
694
+ const parts = path.split('.');
695
+ let value = parts[0] === 'trigger' ? triggerData : (parts[0] === 'input' ? input : input);
696
+ if (parts[0] === 'input' || parts[0] === 'trigger')
697
+ parts.shift();
698
+ for (const part of parts) {
699
+ if (value == null)
700
+ return '';
701
+ value = value[part];
702
+ }
703
+ return value != null ? String(value) : '';
704
+ });
705
+ }
706
+ async executeHttpRequest(config) {
707
+ const axios = require('axios');
708
+ let headers = {};
709
+ if (config.headers) {
710
+ try {
711
+ headers = typeof config.headers === 'string' ? JSON.parse(config.headers) : config.headers;
712
+ }
713
+ catch { /* ignore */ }
714
+ }
715
+ let data = undefined;
716
+ if (config.body) {
717
+ try {
718
+ data = typeof config.body === 'string' ? JSON.parse(config.body) : config.body;
719
+ }
720
+ catch {
721
+ data = config.body;
722
+ }
723
+ }
724
+ const response = await axios({
725
+ method: (config.method || 'GET').toLowerCase(),
726
+ url: config.url,
727
+ headers,
728
+ data,
729
+ timeout: 30000,
730
+ validateStatus: () => true,
731
+ });
732
+ return { status: response.status, headers: response.headers, data: response.data };
733
+ }
734
+ async executeShellCommand(config) {
735
+ const { exec } = require('child_process');
736
+ const { promisify } = require('util');
737
+ const execAsync = promisify(exec);
738
+ const timeout = parseInt(config.timeout) || 30000;
739
+ const result = await execAsync(config.command, {
740
+ cwd: config.cwd || undefined,
741
+ timeout,
742
+ maxBuffer: 5 * 1024 * 1024,
743
+ });
744
+ return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
745
+ }
746
+ async executeAIPrompt(config, input, context) {
747
+ if (!context?.provider)
748
+ throw new Error('No LLM provider available');
749
+ const messages = [];
750
+ if (config.systemPrompt)
751
+ messages.push({ role: 'system', content: config.systemPrompt });
752
+ const userMsg = config.userPrompt ? this.applyTemplate(config.userPrompt, input) : JSON.stringify(input);
753
+ messages.push({ role: 'user', content: userMsg });
754
+ const response = await context.provider.complete(messages, config.temperature || 0.7, config.maxTokens || 1000);
755
+ return { content: response.content, model: response.model, usage: response.usage };
756
+ }
757
+ executeReadFile(config) {
758
+ const content = fs_1.default.readFileSync(config.path, config.encoding || 'utf8');
759
+ return { content, path: config.path, size: Buffer.byteLength(content) };
760
+ }
761
+ executeWriteFile(config, input) {
762
+ const content = config.content ? this.applyTemplate(config.content, input) : JSON.stringify(input, null, 2);
763
+ if (config.append) {
764
+ fs_1.default.appendFileSync(config.path, content);
765
+ }
766
+ else {
767
+ const dir = path_1.default.dirname(config.path);
768
+ if (!fs_1.default.existsSync(dir))
769
+ fs_1.default.mkdirSync(dir, { recursive: true });
770
+ fs_1.default.writeFileSync(config.path, content);
771
+ }
772
+ return { written: true, path: config.path, size: Buffer.byteLength(content) };
773
+ }
774
+ evaluateCondition(config, input) {
775
+ const field = config.field;
776
+ const value = this.getNestedValue(input, field);
777
+ const expected = config.value;
778
+ let result = false;
779
+ switch (config.operator) {
780
+ case 'eq':
781
+ result = String(value) === String(expected);
782
+ break;
783
+ case 'neq':
784
+ result = String(value) !== String(expected);
785
+ break;
786
+ case 'contains':
787
+ result = String(value).includes(String(expected));
788
+ break;
789
+ case 'gt':
790
+ result = Number(value) > Number(expected);
791
+ break;
792
+ case 'lt':
793
+ result = Number(value) < Number(expected);
794
+ break;
795
+ case 'regex':
796
+ result = new RegExp(expected).test(String(value));
797
+ break;
798
+ case 'empty':
799
+ result = value == null || value === '' || (Array.isArray(value) && value.length === 0);
800
+ break;
801
+ case 'notEmpty':
802
+ result = value != null && value !== '' && !(Array.isArray(value) && value.length === 0);
803
+ break;
804
+ }
805
+ return { ...input, _conditionResult: result, _outputIndex: result ? 0 : 1 };
806
+ }
807
+ evaluateSwitch(config, input) {
808
+ const value = String(this.getNestedValue(input, config.field));
809
+ let outputIdx = 2; // default output
810
+ if (value === String(config.case1))
811
+ outputIdx = 0;
812
+ else if (value === String(config.case2))
813
+ outputIdx = 1;
814
+ return { ...input, _outputIndex: outputIdx };
815
+ }
816
+ executeCodeTransform(config, input) {
817
+ // Sandboxed code execution using Function constructor
818
+ const fn = new Function('input', 'JSON', `"use strict"; ${config.code}`);
819
+ return fn(input, JSON);
820
+ }
821
+ executeJsonTransform(config, input) {
822
+ const template = config.template;
823
+ if (!template)
824
+ return input;
825
+ const resolved = this.applyTemplate(typeof template === 'string' ? template : JSON.stringify(template), input);
826
+ try {
827
+ return JSON.parse(resolved);
828
+ }
829
+ catch {
830
+ return { result: resolved };
831
+ }
832
+ }
833
+ getNestedValue(obj, path) {
834
+ if (!obj || !path)
835
+ return undefined;
836
+ const parts = path.split('.');
837
+ let value = obj;
838
+ for (const part of parts) {
839
+ if (value == null)
840
+ return undefined;
841
+ value = value[part];
842
+ }
843
+ return value;
844
+ }
845
+ }
846
+ exports.WorkflowEngine = WorkflowEngine;
847
+ // Singleton
848
+ let engineInstance = null;
849
+ function getWorkflowEngine() {
850
+ if (!engineInstance) {
851
+ engineInstance = new WorkflowEngine();
852
+ }
853
+ return engineInstance;
854
+ }
855
+ //# sourceMappingURL=workflow-engine.js.map