actoviq-agent-sdk 0.4.0 → 0.4.2

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 (91) hide show
  1. package/README-zh.md +15 -2
  2. package/README.md +20 -7
  3. package/dist/src/cli/actoviq-react.js +178 -3
  4. package/dist/src/cli/actoviq-react.js.map +1 -1
  5. package/dist/src/cli/actoviq-tui.js +2 -2
  6. package/dist/src/index.d.ts +17 -0
  7. package/dist/src/index.d.ts.map +1 -1
  8. package/dist/src/index.js +17 -0
  9. package/dist/src/index.js.map +1 -1
  10. package/dist/src/router/modelRouter.d.ts +46 -0
  11. package/dist/src/router/modelRouter.d.ts.map +1 -0
  12. package/dist/src/router/modelRouter.js +227 -0
  13. package/dist/src/router/modelRouter.js.map +1 -0
  14. package/dist/src/runtime/actoviqSkills.d.ts +6 -0
  15. package/dist/src/runtime/actoviqSkills.d.ts.map +1 -1
  16. package/dist/src/runtime/actoviqSkills.js +69 -1
  17. package/dist/src/runtime/actoviqSkills.js.map +1 -1
  18. package/dist/src/runtime/agentClient.d.ts +16 -0
  19. package/dist/src/runtime/agentClient.d.ts.map +1 -1
  20. package/dist/src/runtime/agentClient.js +89 -9
  21. package/dist/src/runtime/agentClient.js.map +1 -1
  22. package/dist/src/runtime/conversationEngine.d.ts +2 -0
  23. package/dist/src/runtime/conversationEngine.d.ts.map +1 -1
  24. package/dist/src/runtime/conversationEngine.js +6 -5
  25. package/dist/src/runtime/conversationEngine.js.map +1 -1
  26. package/dist/src/storage/sessionStore.d.ts.map +1 -1
  27. package/dist/src/storage/sessionStore.js +25 -6
  28. package/dist/src/storage/sessionStore.js.map +1 -1
  29. package/dist/src/team/agentPool.d.ts +26 -0
  30. package/dist/src/team/agentPool.d.ts.map +1 -0
  31. package/dist/src/team/agentPool.js +97 -0
  32. package/dist/src/team/agentPool.js.map +1 -0
  33. package/dist/src/team/modelTeam.d.ts +56 -0
  34. package/dist/src/team/modelTeam.d.ts.map +1 -0
  35. package/dist/src/team/modelTeam.js +542 -0
  36. package/dist/src/team/modelTeam.js.map +1 -0
  37. package/dist/src/team/pricing.d.ts +15 -0
  38. package/dist/src/team/pricing.d.ts.map +1 -0
  39. package/dist/src/team/pricing.js +80 -0
  40. package/dist/src/team/pricing.js.map +1 -0
  41. package/dist/src/team/teamDefinitions.d.ts +28 -0
  42. package/dist/src/team/teamDefinitions.d.ts.map +1 -0
  43. package/dist/src/team/teamDefinitions.js +151 -0
  44. package/dist/src/team/teamDefinitions.js.map +1 -0
  45. package/dist/src/tools/actoviqCoreTools.d.ts.map +1 -1
  46. package/dist/src/tools/actoviqCoreTools.js +6 -1
  47. package/dist/src/tools/actoviqCoreTools.js.map +1 -1
  48. package/dist/src/tools/enterWorktree.d.ts +11 -0
  49. package/dist/src/tools/enterWorktree.d.ts.map +1 -0
  50. package/dist/src/tools/enterWorktree.js +79 -0
  51. package/dist/src/tools/enterWorktree.js.map +1 -0
  52. package/dist/src/tools/exitWorktree.d.ts +4 -0
  53. package/dist/src/tools/exitWorktree.d.ts.map +1 -0
  54. package/dist/src/tools/exitWorktree.js +48 -0
  55. package/dist/src/tools/exitWorktree.js.map +1 -0
  56. package/dist/src/tools/tavilySearch.d.ts +12 -0
  57. package/dist/src/tools/tavilySearch.d.ts.map +1 -0
  58. package/dist/src/tools/tavilySearch.js +139 -0
  59. package/dist/src/tools/tavilySearch.js.map +1 -0
  60. package/dist/src/tui/actoviqTui.d.ts +10 -0
  61. package/dist/src/tui/actoviqTui.d.ts.map +1 -1
  62. package/dist/src/tui/actoviqTui.js +606 -35
  63. package/dist/src/tui/actoviqTui.js.map +1 -1
  64. package/dist/src/tui/editor.d.ts +2 -0
  65. package/dist/src/tui/editor.d.ts.map +1 -1
  66. package/dist/src/tui/editor.js +5 -0
  67. package/dist/src/tui/editor.js.map +1 -1
  68. package/dist/src/types.d.ts +323 -0
  69. package/dist/src/types.d.ts.map +1 -1
  70. package/dist/src/types.js.map +1 -1
  71. package/dist/src/workflow/workflowPersistence.d.ts +34 -0
  72. package/dist/src/workflow/workflowPersistence.d.ts.map +1 -0
  73. package/dist/src/workflow/workflowPersistence.js +144 -0
  74. package/dist/src/workflow/workflowPersistence.js.map +1 -0
  75. package/dist/src/workflow/workflowScriptRuntime.d.ts +22 -0
  76. package/dist/src/workflow/workflowScriptRuntime.d.ts.map +1 -0
  77. package/dist/src/workflow/workflowScriptRuntime.js +655 -0
  78. package/dist/src/workflow/workflowScriptRuntime.js.map +1 -0
  79. package/dist/src/worktree/worktreeHooks.d.ts +38 -0
  80. package/dist/src/worktree/worktreeHooks.d.ts.map +1 -0
  81. package/dist/src/worktree/worktreeHooks.js +82 -0
  82. package/dist/src/worktree/worktreeHooks.js.map +1 -0
  83. package/dist/src/worktree/worktreeInclude.d.ts +11 -0
  84. package/dist/src/worktree/worktreeInclude.d.ts.map +1 -0
  85. package/dist/src/worktree/worktreeInclude.js +65 -0
  86. package/dist/src/worktree/worktreeInclude.js.map +1 -0
  87. package/dist/src/worktree/worktreeService.d.ts +43 -0
  88. package/dist/src/worktree/worktreeService.d.ts.map +1 -0
  89. package/dist/src/worktree/worktreeService.js +356 -0
  90. package/dist/src/worktree/worktreeService.js.map +1 -0
  91. package/package.json +2 -24
@@ -0,0 +1,655 @@
1
+ /**
2
+ * WorkflowScriptRuntime — executes dynamic workflow JavaScript scripts
3
+ * in a sandboxed vm.Module with Host Bridge for agent()/parallel()/pipeline().
4
+ *
5
+ * The script VM has ZERO fs/net/process access. All external calls go
6
+ * through the Host Bridge via message-passing.
7
+ */
8
+ import vm from 'node:vm';
9
+ import { randomUUID } from 'node:crypto';
10
+ // ═══════════════════════════════════════════════════════════════════
11
+ // StructuredOutput tool for schema enforcement (append mode)
12
+ // ═══════════════════════════════════════════════════════════════════
13
+ const STRUCTURED_OUTPUT_TOOL_NAME = 'StructuredOutput';
14
+ function createStructuredOutputTool(schema) {
15
+ return {
16
+ kind: 'local',
17
+ name: STRUCTURED_OUTPUT_TOOL_NAME,
18
+ description: 'Return your final structured result. You MUST call this tool with your answer ' +
19
+ 'when you have completed the task. All fields in the schema are required unless marked optional.',
20
+ inputSchema: {
21
+ parse: (input) => input,
22
+ _type: undefined,
23
+ },
24
+ inputJsonSchema: schema,
25
+ async execute(input) {
26
+ return JSON.stringify(input);
27
+ },
28
+ interruptBehavior: 'block',
29
+ isConcurrencySafe: () => true,
30
+ };
31
+ }
32
+ function validateAgainstSchema(data, schema) {
33
+ if (!schema.properties)
34
+ return { valid: true };
35
+ if (typeof data !== 'object' || data === null) {
36
+ return { valid: false, errors: ['Output must be a JSON object.'] };
37
+ }
38
+ const errors = [];
39
+ const props = schema.properties;
40
+ const required = schema.required ?? [];
41
+ const dataObj = data;
42
+ // Check required fields
43
+ for (const field of required) {
44
+ if (!(field in dataObj) || dataObj[field] === undefined) {
45
+ errors.push(`Missing required field: ${field}`);
46
+ }
47
+ }
48
+ // Type check known fields
49
+ for (const [key, propSchema] of Object.entries(props)) {
50
+ if (key in dataObj && dataObj[key] !== undefined) {
51
+ const type = propSchema.type;
52
+ const value = dataObj[key];
53
+ if (type === 'string' && typeof value !== 'string') {
54
+ errors.push(`Field "${key}" must be a string, got ${typeof value}`);
55
+ }
56
+ else if (type === 'number' && typeof value !== 'number') {
57
+ errors.push(`Field "${key}" must be a number, got ${typeof value}`);
58
+ }
59
+ else if (type === 'boolean' && typeof value !== 'boolean') {
60
+ errors.push(`Field "${key}" must be a boolean, got ${typeof value}`);
61
+ }
62
+ else if (type === 'array' && !Array.isArray(value)) {
63
+ errors.push(`Field "${key}" must be an array, got ${typeof value}`);
64
+ }
65
+ }
66
+ }
67
+ return { valid: errors.length === 0, errors: errors.length > 0 ? errors : undefined };
68
+ }
69
+ function tryParseStructuredOutput(text) {
70
+ // Try to find JSON in the response (model may wrap it in text)
71
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
72
+ if (!jsonMatch)
73
+ return null;
74
+ try {
75
+ return { parsed: JSON.parse(jsonMatch[0]), json: jsonMatch[0] };
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ }
81
+ // ═══════════════════════════════════════════════════════════════════
82
+ // Cache key normalization
83
+ // ═══════════════════════════════════════════════════════════════════
84
+ function sortedJson(obj) {
85
+ if (obj === null || obj === undefined)
86
+ return String(obj);
87
+ if (typeof obj !== 'object')
88
+ return JSON.stringify(obj);
89
+ if (Array.isArray(obj))
90
+ return JSON.stringify(obj.map(sortedJson));
91
+ const sorted = {};
92
+ for (const key of Object.keys(obj).sort()) {
93
+ sorted[key] = obj[key];
94
+ }
95
+ return JSON.stringify(sorted);
96
+ }
97
+ function canonicalAgentKey(prompt, opts, phase) {
98
+ return `${prompt}|${sortedJson(opts)}|${phase ?? ''}`;
99
+ }
100
+ // ═══════════════════════════════════════════════════════════════════
101
+ // Script syntax check
102
+ // ═══════════════════════════════════════════════════════════════════
103
+ const BANNED_GLOBALS = ['Date', 'Math.random', 'process', 'require', 'import', 'fs', 'net', 'child_process'];
104
+ function validateScript(script) {
105
+ // Ban Date.now(), Math.random(), new Date() — break determinism for resume
106
+ if (/Date\.now\s*\(/.test(script)) {
107
+ throw new Error('Date.now() is not allowed in workflow scripts (breaks resume determinism).');
108
+ }
109
+ if (/Math\.random\s*\(/.test(script)) {
110
+ throw new Error('Math.random() is not allowed in workflow scripts (breaks resume determinism).');
111
+ }
112
+ if (/new\s+Date\s*\(/.test(script)) {
113
+ throw new Error('new Date() is not allowed in workflow scripts (breaks resume determinism).');
114
+ }
115
+ // Must have export const meta
116
+ if (!/export\s+const\s+meta\s*=/.test(script)) {
117
+ throw new Error('Workflow script must export const meta = { name, description, phases }.');
118
+ }
119
+ }
120
+ // ═══════════════════════════════════════════════════════════════════
121
+ // Meta extraction
122
+ // ═══════════════════════════════════════════════════════════════════
123
+ function extractMeta(script) {
124
+ const metaMatch = script.match(/export\s+const\s+meta\s*=\s*(\{[\s\S]*?\});/);
125
+ if (!metaMatch) {
126
+ throw new Error('Could not extract meta from workflow script.');
127
+ }
128
+ // Validate pure literal (no variables, function calls, template strings)
129
+ const metaBlock = metaMatch[1];
130
+ if (/[a-zA-Z_$]\s*\(/.test(metaBlock.replace(/"([^"\\]|\\.)*"|'([^'\\]|\\.)*'/g, '').replace(/`[\s\S]*?`/g, ''))) {
131
+ throw new Error('Workflow meta must be a pure literal — no function calls or computed values.');
132
+ }
133
+ // Evaluate the meta object in a sandbox
134
+ try {
135
+ const sandbox = {};
136
+ vm.createContext(sandbox);
137
+ const metaObj = vm.runInContext(`(${metaBlock})`, sandbox);
138
+ if (!metaObj.name || !metaObj.description) {
139
+ throw new Error('Workflow meta must include name and description.');
140
+ }
141
+ return metaObj;
142
+ }
143
+ catch (err) {
144
+ if (err.message?.includes('name') || err.message?.includes('description'))
145
+ throw err;
146
+ throw new Error(`Failed to parse workflow meta: ${err.message}`);
147
+ }
148
+ }
149
+ // ═══════════════════════════════════════════════════════════════════
150
+ // Host Bridge
151
+ // ═══════════════════════════════════════════════════════════════════
152
+ class HostBridge {
153
+ agentQueue = [];
154
+ activeAgents = 0;
155
+ maxConcurrent;
156
+ totalAgents = 0;
157
+ maxTotal = 1000;
158
+ signal;
159
+ sdk;
160
+ cache;
161
+ resumeCompleted;
162
+ agentRecords;
163
+ onEvent;
164
+ runId;
165
+ // Budget tracking
166
+ budgetTotal;
167
+ totalSpentTokens = 0;
168
+ // Phase tracking
169
+ currentPhase = null;
170
+ phases = new Map();
171
+ phaseOrder = [];
172
+ _logMessages = [];
173
+ _errors = [];
174
+ constructor(options) {
175
+ this.sdk = options.sdk;
176
+ this.maxConcurrent = options.maxConcurrent ?? 16;
177
+ this.budgetTotal = options.budgetTotal ?? null;
178
+ this.signal = options.signal;
179
+ this.onEvent = options.onEvent;
180
+ this.runId = randomUUID();
181
+ this.agentRecords = [];
182
+ // Resume state
183
+ this.cache = options.resumeState?.cache ?? new Map();
184
+ this.resumeCompleted = options.resumeState?.completedAgentIds ?? new Set();
185
+ // Restore phases from resume
186
+ if (options.resumeState?.phases) {
187
+ for (const phase of options.resumeState.phases) {
188
+ this.phases.set(phase.title, phase);
189
+ this.phaseOrder.push(phase.title);
190
+ }
191
+ }
192
+ }
193
+ // ── Budget ─────────────────────────────────────────────────────
194
+ createBudget() {
195
+ return {
196
+ total: this.budgetTotal,
197
+ spent: () => this.totalSpentTokens,
198
+ remaining: () => {
199
+ if (this.budgetTotal === null)
200
+ return Infinity;
201
+ return Math.max(0, this.budgetTotal - this.totalSpentTokens);
202
+ },
203
+ };
204
+ }
205
+ // ── Phase ──────────────────────────────────────────────────────
206
+ setPhase(title) {
207
+ this.currentPhase = title;
208
+ if (!this.phases.has(title)) {
209
+ const pp = {
210
+ title,
211
+ agentCount: 0,
212
+ completedCount: 0,
213
+ failedCount: 0,
214
+ totalTokens: 0,
215
+ startedAt: new Date().toISOString(),
216
+ };
217
+ this.phases.set(title, pp);
218
+ this.phaseOrder.push(title);
219
+ }
220
+ this.onEvent?.({
221
+ type: 'workflow.phase.start',
222
+ runId: this.runId,
223
+ phase: title,
224
+ timestamp: new Date().toISOString(),
225
+ });
226
+ }
227
+ // ── Log ────────────────────────────────────────────────────────
228
+ log(message) {
229
+ this._logMessages.push(message);
230
+ this.onEvent?.({
231
+ type: 'workflow.log',
232
+ runId: this.runId,
233
+ message,
234
+ timestamp: new Date().toISOString(),
235
+ });
236
+ }
237
+ // ── Agent ──────────────────────────────────────────────────────
238
+ async agent(prompt, opts = {}) {
239
+ const phase = opts.phase ?? this.currentPhase ?? undefined;
240
+ const key = canonicalAgentKey(prompt, opts, phase);
241
+ // Cache hit?
242
+ const cached = this.cache.get(key);
243
+ if (cached) {
244
+ const record = {
245
+ id: randomUUID(),
246
+ prompt,
247
+ opts,
248
+ phase,
249
+ result: cached.result,
250
+ tokens: cached.tokens,
251
+ durationMs: cached.durationMs,
252
+ startedAt: cached.cachedAt,
253
+ completedAt: cached.cachedAt,
254
+ cached: true,
255
+ };
256
+ this.agentRecords.push(record);
257
+ if (phase) {
258
+ const pp = this.phases.get(phase);
259
+ if (pp) {
260
+ pp.agentCount++;
261
+ pp.completedCount++;
262
+ if (cached.tokens)
263
+ pp.totalTokens += cached.tokens.input + cached.tokens.output;
264
+ }
265
+ }
266
+ this.onEvent?.({
267
+ type: 'workflow.agent.start',
268
+ runId: this.runId,
269
+ agentId: record.id,
270
+ label: opts.label,
271
+ phase,
272
+ cached: true,
273
+ timestamp: new Date().toISOString(),
274
+ });
275
+ this.onEvent?.({
276
+ type: 'workflow.agent.done',
277
+ runId: this.runId,
278
+ agentId: record.id,
279
+ phase,
280
+ cached: true,
281
+ durationMs: cached.durationMs,
282
+ tokens: cached.tokens,
283
+ timestamp: new Date().toISOString(),
284
+ });
285
+ return cached.result;
286
+ }
287
+ // Check budget
288
+ if (this.budgetTotal !== null && this.totalSpentTokens >= this.budgetTotal) {
289
+ throw new Error('Budget exhausted — cannot spawn more agents.');
290
+ }
291
+ // Check total cap
292
+ if (this.totalAgents >= this.maxTotal) {
293
+ throw new Error('Agent cap reached (1000 total per run).');
294
+ }
295
+ // Wait for concurrency slot
296
+ while (this.activeAgents >= this.maxConcurrent) {
297
+ if (this.signal?.aborted)
298
+ throw new Error('Aborted.');
299
+ await delay(50);
300
+ }
301
+ this.activeAgents++;
302
+ this.totalAgents++;
303
+ const record = {
304
+ id: randomUUID(),
305
+ prompt,
306
+ opts,
307
+ phase,
308
+ startedAt: new Date().toISOString(),
309
+ cached: false,
310
+ };
311
+ this.agentRecords.push(record);
312
+ if (phase) {
313
+ const pp = this.phases.get(phase);
314
+ if (pp)
315
+ pp.agentCount++;
316
+ }
317
+ this.onEvent?.({
318
+ type: 'workflow.agent.start',
319
+ runId: this.runId,
320
+ agentId: record.id,
321
+ label: opts.label,
322
+ phase,
323
+ cached: false,
324
+ timestamp: record.startedAt,
325
+ });
326
+ try {
327
+ // Execute via SDK
328
+ const session = await this.sdk.createSession({
329
+ title: opts.label ?? `workflow-agent-${record.id.slice(0, 8)}`,
330
+ });
331
+ // Build tool list: user tools + optional StructuredOutput for schema enforcement
332
+ const userTools = opts.tools?.map((t) => this.sdk.getTool(t)).filter((t) => t !== undefined) ?? [];
333
+ const allTools = opts.schema
334
+ ? [...userTools, createStructuredOutputTool(opts.schema)]
335
+ : userTools;
336
+ const maxRetries = 3;
337
+ let lastResult;
338
+ let finalTokens;
339
+ let execResult = await session.send(prompt, {
340
+ systemPrompt: undefined,
341
+ model: opts.model,
342
+ tools: allTools.length > 0 ? allTools : undefined,
343
+ signal: this.signal,
344
+ permissionMode: 'acceptEdits',
345
+ });
346
+ finalTokens = (execResult.usage?.input_tokens != null && execResult.usage?.output_tokens != null)
347
+ ? { input: execResult.usage.input_tokens, output: execResult.usage.output_tokens }
348
+ : undefined;
349
+ if (!opts.schema) {
350
+ lastResult = execResult.text;
351
+ }
352
+ else {
353
+ // Schema enforcement with retry
354
+ let validated = false;
355
+ for (let attempt = 0; attempt < maxRetries && !validated; attempt++) {
356
+ if (attempt > 0) {
357
+ // Re-prompt on same session to fix output
358
+ execResult = await session.send('Your previous output did not match the required schema. ' +
359
+ 'Please call the StructuredOutput tool with the correct format.', {
360
+ systemPrompt: undefined,
361
+ model: opts.model,
362
+ tools: allTools.length > 0 ? allTools : undefined,
363
+ signal: this.signal,
364
+ permissionMode: 'acceptEdits',
365
+ });
366
+ const retryTokens = (execResult.usage?.input_tokens != null && execResult.usage?.output_tokens != null)
367
+ ? { input: execResult.usage.input_tokens, output: execResult.usage.output_tokens }
368
+ : undefined;
369
+ if (retryTokens && finalTokens) {
370
+ finalTokens.input += retryTokens.input;
371
+ finalTokens.output += retryTokens.output;
372
+ }
373
+ }
374
+ const parsed = tryParseStructuredOutput(execResult.text);
375
+ if (parsed) {
376
+ const validation = validateAgainstSchema(parsed.parsed, opts.schema);
377
+ if (validation.valid) {
378
+ lastResult = parsed.parsed;
379
+ validated = true;
380
+ break;
381
+ }
382
+ if (attempt < maxRetries - 1) {
383
+ this.log(`Schema validation failed (attempt ${attempt + 1}): ${validation.errors?.join(', ')}`);
384
+ }
385
+ }
386
+ else if (attempt < maxRetries - 1) {
387
+ this.log(`No structured output found (attempt ${attempt + 1}), retrying...`);
388
+ }
389
+ }
390
+ if (!validated) {
391
+ lastResult = execResult.text; // Last attempt: return raw text
392
+ }
393
+ }
394
+ record.result = lastResult;
395
+ record.tokens = finalTokens;
396
+ record.completedAt = new Date().toISOString();
397
+ record.durationMs = new Date(record.completedAt).getTime() - new Date(record.startedAt).getTime();
398
+ // Cache
399
+ if (record.tokens) {
400
+ this.totalSpentTokens += record.tokens.input + record.tokens.output;
401
+ }
402
+ this.cache.set(key, {
403
+ key,
404
+ result: record.result,
405
+ tokens: record.tokens,
406
+ durationMs: record.durationMs ?? 0,
407
+ cachedAt: new Date().toISOString(),
408
+ });
409
+ if (phase && record.tokens) {
410
+ const pp = this.phases.get(phase);
411
+ if (pp) {
412
+ pp.completedCount++;
413
+ pp.totalTokens += record.tokens.input + record.tokens.output;
414
+ }
415
+ }
416
+ this.onEvent?.({
417
+ type: 'workflow.agent.done',
418
+ runId: this.runId,
419
+ agentId: record.id,
420
+ phase,
421
+ cached: false,
422
+ durationMs: record.durationMs,
423
+ tokens: record.tokens,
424
+ timestamp: record.completedAt,
425
+ });
426
+ return record.result;
427
+ }
428
+ catch (err) {
429
+ record.error = err.message;
430
+ record.completedAt = new Date().toISOString();
431
+ record.durationMs = new Date(record.completedAt).getTime() - new Date(record.startedAt).getTime();
432
+ if (phase) {
433
+ const pp = this.phases.get(phase);
434
+ if (pp)
435
+ pp.failedCount++;
436
+ }
437
+ this._errors.push({ agentId: record.id, phase, error: err.message });
438
+ this.log(`Agent "${opts.label ?? record.id}" failed: ${err.message}`);
439
+ this.onEvent?.({
440
+ type: 'workflow.agent.done',
441
+ runId: this.runId,
442
+ agentId: record.id,
443
+ phase,
444
+ cached: false,
445
+ durationMs: record.durationMs,
446
+ error: err.message,
447
+ timestamp: record.completedAt,
448
+ });
449
+ throw err;
450
+ }
451
+ finally {
452
+ this.activeAgents = Math.max(0, this.activeAgents - 1);
453
+ }
454
+ }
455
+ // ── Parallel ───────────────────────────────────────────────────
456
+ async parallel(thunks) {
457
+ if (thunks.length > 4096) {
458
+ throw new Error('parallel() supports at most 4096 items.');
459
+ }
460
+ return Promise.all(thunks.map(async (thunk) => {
461
+ try {
462
+ return await thunk();
463
+ }
464
+ catch {
465
+ return null;
466
+ }
467
+ }));
468
+ }
469
+ // ── Pipeline ───────────────────────────────────────────────────
470
+ async pipeline(items, ...stages) {
471
+ if (items.length > 4096) {
472
+ throw new Error('pipeline() supports at most 4096 items.');
473
+ }
474
+ const results = new Array(items.length).fill(null);
475
+ const errors = [];
476
+ // Process each item through all stages independently (no barrier)
477
+ const itemPromises = items.map(async (item, index) => {
478
+ let current = null;
479
+ for (let si = 0; si < stages.length; si++) {
480
+ const stage = stages[si];
481
+ try {
482
+ const result = await stage(current, item, index);
483
+ if (result === null) {
484
+ // Item skipped
485
+ return null;
486
+ }
487
+ current = result;
488
+ }
489
+ catch (err) {
490
+ errors.push({ itemIndex: index, stageIndex: si, error: err.message });
491
+ this._errors.push({
492
+ agentId: `pipeline-${index}-${si}`,
493
+ error: err.message,
494
+ itemIndex: index,
495
+ stageIndex: si,
496
+ });
497
+ return null; // Item dropped, other items continue
498
+ }
499
+ }
500
+ results[index] = current;
501
+ return current;
502
+ });
503
+ await Promise.all(itemPromises);
504
+ // Auto-report failure stats
505
+ if (errors.length > 0) {
506
+ this.log(`${errors.length}/${items.length} items failed across pipeline stages.`);
507
+ }
508
+ return results;
509
+ }
510
+ // ── State snapshot for resume ──────────────────────────────────
511
+ getResumeState() {
512
+ return {
513
+ runId: this.runId,
514
+ cache: this.cache,
515
+ agentCallIds: this.agentRecords.map((r) => r.id),
516
+ completedAgentIds: new Set(this.agentRecords.filter((r) => r.completedAt).map((r) => r.id)),
517
+ phases: [...this.phases.values()],
518
+ errors: [...this._errors],
519
+ };
520
+ }
521
+ getRunState() {
522
+ return {
523
+ runId: this.runId,
524
+ meta: { name: '', description: '' },
525
+ status: 'running',
526
+ phases: [...this.phases.values()],
527
+ agentCalls: this.agentRecords,
528
+ errors: [...this._errors],
529
+ startedAt: '',
530
+ totalTokens: this.totalSpentTokens,
531
+ estimatedCost: null,
532
+ };
533
+ }
534
+ getLogMessages() {
535
+ return [...this._logMessages];
536
+ }
537
+ }
538
+ // ═══════════════════════════════════════════════════════════════════
539
+ // Script Runtime
540
+ // ═══════════════════════════════════════════════════════════════════
541
+ export class WorkflowScriptRuntime {
542
+ options;
543
+ constructor(options) {
544
+ this.options = options;
545
+ }
546
+ async execute(script) {
547
+ // Validate
548
+ validateScript(script);
549
+ // Extract meta
550
+ const meta = extractMeta(script);
551
+ // Create Host Bridge
552
+ const bridge = new HostBridge(this.options);
553
+ // Build sandbox context
554
+ const sandbox = {
555
+ agent: (prompt, opts) => bridge.agent(prompt, opts),
556
+ parallel: (thunks) => bridge.parallel(thunks),
557
+ pipeline: (items, ...stages) => bridge.pipeline(items, ...stages),
558
+ phase: (title) => bridge.setPhase(title),
559
+ log: (message) => bridge.log(message),
560
+ budget: bridge.createBudget(),
561
+ args: this.options.args,
562
+ meta,
563
+ };
564
+ // Strip the `export const meta = ...` block and execute the script body
565
+ const body = script.replace(/export\s+const\s+meta\s*=\s*\{[\s\S]*?\};/, '').trim();
566
+ // Wrap in async IIFE (vm.runInContext doesn't support top-level await).
567
+ // Store the result in __sandbox__.__result so we can read it after execution.
568
+ const wrappedScript = [
569
+ `(async () => {`,
570
+ `const { agent, parallel, pipeline, phase, log, budget, args, meta } = __sandbox__;`,
571
+ body,
572
+ `}).call(null);`,
573
+ ].join('\n');
574
+ this.options.onEvent?.({
575
+ type: 'workflow.script.start',
576
+ runId: bridge.getRunState().runId,
577
+ workflowName: meta.name,
578
+ phases: meta.phases,
579
+ timestamp: new Date().toISOString(),
580
+ });
581
+ const startedAt = Date.now();
582
+ try {
583
+ // Execute in vm.Script (not vm.Module since we handle modules differently)
584
+ const vmContext = vm.createContext({
585
+ __sandbox__: sandbox,
586
+ console: {
587
+ log: (...args) => bridge.log(args.map(String).join(' ')),
588
+ error: (...args) => bridge.log(`[ERROR] ${args.map(String).join(' ')}`),
589
+ },
590
+ setTimeout: () => { throw new Error('setTimeout is not available in workflow scripts.'); },
591
+ setInterval: () => { throw new Error('setInterval is not available in workflow scripts.'); },
592
+ clearTimeout: () => { },
593
+ clearInterval: () => { },
594
+ process: undefined,
595
+ require: undefined,
596
+ import: undefined,
597
+ fs: undefined,
598
+ net: undefined,
599
+ child_process: undefined,
600
+ });
601
+ const result = await vm.runInContext(wrappedScript, vmContext, {
602
+ filename: `workflow-${meta.name}.js`,
603
+ timeout: 600_000, // 10 min max
604
+ });
605
+ const durationMs = Date.now() - startedAt;
606
+ const state = bridge.getRunState();
607
+ state.meta = meta;
608
+ state.startedAt = new Date(startedAt).toISOString();
609
+ state.completedAt = new Date().toISOString();
610
+ state.status = 'completed';
611
+ this.options.onEvent?.({
612
+ type: 'workflow.script.done',
613
+ runId: state.runId,
614
+ workflowName: meta.name,
615
+ status: 'completed',
616
+ durationMs,
617
+ agentCount: state.agentCalls.length,
618
+ totalTokens: state.totalTokens,
619
+ errors: state.errors.length > 0 ? state.errors : undefined,
620
+ timestamp: new Date().toISOString(),
621
+ });
622
+ return {
623
+ result,
624
+ state,
625
+ resumeState: bridge.getResumeState(),
626
+ logs: bridge.getLogMessages(),
627
+ };
628
+ }
629
+ catch (err) {
630
+ const durationMs = Date.now() - startedAt;
631
+ const state = bridge.getRunState();
632
+ state.meta = meta;
633
+ state.startedAt = new Date(startedAt).toISOString();
634
+ state.completedAt = new Date().toISOString();
635
+ state.status = 'failed';
636
+ state.errors.push({ agentId: 'script', error: err.message });
637
+ this.options.onEvent?.({
638
+ type: 'workflow.script.done',
639
+ runId: state.runId,
640
+ workflowName: meta.name,
641
+ status: 'failed',
642
+ durationMs,
643
+ agentCount: state.agentCalls.length,
644
+ totalTokens: state.totalTokens,
645
+ errors: state.errors,
646
+ timestamp: new Date().toISOString(),
647
+ });
648
+ throw err;
649
+ }
650
+ }
651
+ }
652
+ function delay(ms) {
653
+ return new Promise((resolve) => setTimeout(resolve, ms));
654
+ }
655
+ //# sourceMappingURL=workflowScriptRuntime.js.map