@crewx/workflow 0.3.22-rc.2 → 0.3.22-rc.200

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 (46) hide show
  1. package/SKILL.md +255 -11
  2. package/dist/cli.d.ts +1 -1
  3. package/dist/cli.js +23 -945
  4. package/dist/src/card-state.d.ts +20 -0
  5. package/dist/src/card-state.js +1 -0
  6. package/dist/src/engine.d.ts +10 -1
  7. package/dist/src/engine.js +1 -300
  8. package/dist/src/failure.d.ts +41 -0
  9. package/dist/src/failure.js +1 -0
  10. package/dist/src/file-lock.d.ts +1 -1
  11. package/dist/src/file-lock.js +1 -97
  12. package/dist/src/list-view.d.ts +22 -0
  13. package/dist/src/list-view.js +3 -0
  14. package/dist/src/mermaid.d.ts +1 -1
  15. package/dist/src/mermaid.js +2 -117
  16. package/dist/src/parent-task-sync.d.ts +139 -0
  17. package/dist/src/parent-task-sync.js +1 -0
  18. package/dist/src/retry-policy.d.ts +38 -0
  19. package/dist/src/retry-policy.js +1 -0
  20. package/dist/src/run-manager.d.ts +44 -3
  21. package/dist/src/run-manager.js +4 -764
  22. package/dist/src/types.d.ts +65 -4
  23. package/dist/src/types.js +1 -3
  24. package/dist/src/utils/output-format.d.ts +13 -1
  25. package/dist/src/utils/output-format.js +23 -119
  26. package/dist/src/utils/shell-security.d.ts +18 -0
  27. package/dist/src/utils/shell-security.js +1 -0
  28. package/dist/src/workflow-persistence.d.ts +48 -0
  29. package/dist/src/workflow-persistence.js +1 -0
  30. package/dist/workflow-schema.json +66 -4
  31. package/package.json +35 -4
  32. package/workflow-schema.json +66 -4
  33. package/dist/cli.d.ts.map +0 -1
  34. package/dist/cli.js.map +0 -1
  35. package/dist/src/engine.d.ts.map +0 -1
  36. package/dist/src/engine.js.map +0 -1
  37. package/dist/src/file-lock.d.ts.map +0 -1
  38. package/dist/src/file-lock.js.map +0 -1
  39. package/dist/src/mermaid.d.ts.map +0 -1
  40. package/dist/src/mermaid.js.map +0 -1
  41. package/dist/src/run-manager.d.ts.map +0 -1
  42. package/dist/src/run-manager.js.map +0 -1
  43. package/dist/src/types.d.ts.map +0 -1
  44. package/dist/src/types.js.map +0 -1
  45. package/dist/src/utils/output-format.d.ts.map +0 -1
  46. package/dist/src/utils/output-format.js.map +0 -1
@@ -1,764 +1,4 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.RunManager = void 0;
37
- exports.parseSetArgs = parseSetArgs;
38
- exports.readStdin = readStdin;
39
- exports.interpolateTemplate = interpolateTemplate;
40
- exports.safeEvaluate = safeEvaluate;
41
- const fs = __importStar(require("fs"));
42
- const path = __importStar(require("path"));
43
- const yaml = __importStar(require("js-yaml"));
44
- const cp = __importStar(require("child_process"));
45
- const sdk_1 = require("@crewx/sdk");
46
- const file_lock_1 = require("./file-lock");
47
- const output_format_1 = require("./utils/output-format");
48
- function parseValue(raw) {
49
- try {
50
- return JSON.parse(raw);
51
- }
52
- catch {
53
- return raw;
54
- }
55
- }
56
- function parseSetArgs(args) {
57
- const sets = {};
58
- for (let i = 0; i < args.length; i++) {
59
- const arg = args[i];
60
- if (arg === '--set' && i + 1 < args.length) {
61
- const pair = args[i + 1];
62
- const eqIdx = pair.indexOf('=');
63
- if (eqIdx !== -1) {
64
- const key = pair.substring(0, eqIdx);
65
- const value = pair.substring(eqIdx + 1);
66
- sets[key] = parseValue(value);
67
- }
68
- i++;
69
- }
70
- else if (arg.startsWith('--set=')) {
71
- const rest = arg.substring('--set='.length);
72
- const eqIdx = rest.indexOf('=');
73
- if (eqIdx !== -1) {
74
- const key = rest.substring(0, eqIdx);
75
- const value = rest.substring(eqIdx + 1);
76
- sets[key] = parseValue(value);
77
- }
78
- }
79
- }
80
- return sets;
81
- }
82
- async function readStdin() {
83
- const chunks = [];
84
- for await (const chunk of process.stdin) {
85
- chunks.push(chunk);
86
- }
87
- return Buffer.concat(chunks).toString('utf-8').trimEnd();
88
- }
89
- function interpolateTemplate(template, state) {
90
- return template.replace(/\{\{([\w.]+)\}\}/g, (_, expr) => {
91
- const parts = expr.split('.');
92
- let value = state[parts[0]];
93
- for (let i = 1; i < parts.length && value != null; i++) {
94
- value = value[parts[i]];
95
- }
96
- if (value === null || value === undefined)
97
- return '';
98
- if (typeof value === 'object')
99
- return JSON.stringify(value, null, 2);
100
- return String(value);
101
- });
102
- }
103
- const FORBIDDEN_IDENTS = new Set([
104
- 'require', 'import', 'process', 'global', 'globalThis',
105
- 'Function', 'eval', '__proto__', 'constructor', 'prototype',
106
- ]);
107
- function tokenize(expr) {
108
- const tokens = [];
109
- let i = 0;
110
- while (i < expr.length) {
111
- const ch = expr[i];
112
- if (/\s/.test(ch)) {
113
- i++;
114
- continue;
115
- }
116
- if (/\d/.test(ch)) {
117
- let num = '';
118
- while (i < expr.length && /[\d.]/.test(expr[i])) {
119
- num += expr[i];
120
- i++;
121
- }
122
- tokens.push({ type: 'NUMBER', value: Number(num) });
123
- continue;
124
- }
125
- if (ch === "'" || ch === '"') {
126
- const quote = ch;
127
- i++;
128
- let str = '';
129
- while (i < expr.length && expr[i] !== quote) {
130
- if (expr[i] === '\\') {
131
- i++;
132
- str += expr[i] ?? '';
133
- }
134
- else {
135
- str += expr[i];
136
- }
137
- i++;
138
- }
139
- if (i >= expr.length)
140
- throw new Error('Unterminated string literal');
141
- i++;
142
- tokens.push({ type: 'STRING', value: str });
143
- continue;
144
- }
145
- if (/[a-zA-Z_]/.test(ch)) {
146
- let ident = '';
147
- while (i < expr.length && /\w/.test(expr[i])) {
148
- ident += expr[i];
149
- i++;
150
- }
151
- if (ident === 'true' || ident === 'false') {
152
- tokens.push({ type: 'BOOLEAN', value: ident === 'true' });
153
- }
154
- else {
155
- if (FORBIDDEN_IDENTS.has(ident)) {
156
- throw new Error(`Forbidden identifier in expression: "${ident}"`);
157
- }
158
- tokens.push({ type: 'IDENT', value: ident });
159
- }
160
- continue;
161
- }
162
- if (i + 1 < expr.length) {
163
- const two = expr.substring(i, i + 2);
164
- const twoCharMap = {
165
- '>=': 'GTE', '<=': 'LTE', '==': 'EQ', '!=': 'NEQ', '&&': 'AND', '||': 'OR',
166
- };
167
- if (twoCharMap[two]) {
168
- tokens.push({ type: twoCharMap[two], value: two });
169
- i += 2;
170
- continue;
171
- }
172
- }
173
- const singleCharMap = {
174
- '+': 'PLUS', '-': 'MINUS', '*': 'STAR', '/': 'SLASH', '%': 'PERCENT',
175
- '>': 'GT', '<': 'LT', '!': 'NOT',
176
- '?': 'QUESTION', ':': 'COLON',
177
- '(': 'LPAREN', ')': 'RPAREN',
178
- };
179
- if (singleCharMap[ch]) {
180
- tokens.push({ type: singleCharMap[ch], value: ch });
181
- i++;
182
- continue;
183
- }
184
- if (ch === '.' || ch === '[' || ch === ']') {
185
- throw new Error(`Forbidden character in expression: "${ch}" (property access not allowed)`);
186
- }
187
- throw new Error(`Unexpected character in expression: "${ch}"`);
188
- }
189
- tokens.push({ type: 'EOF', value: '' });
190
- return tokens;
191
- }
192
- class ExprParser {
193
- constructor(tokens, state) {
194
- this.tokens = tokens;
195
- this.state = state;
196
- this.pos = 0;
197
- }
198
- peek() { return this.tokens[this.pos]; }
199
- advance() { return this.tokens[this.pos++]; }
200
- expect(type) {
201
- const t = this.advance();
202
- if (t.type !== type)
203
- throw new Error(`Expected ${type}, got ${t.type}`);
204
- return t;
205
- }
206
- parse() {
207
- const result = this.parseTernary();
208
- if (this.peek().type !== 'EOF') {
209
- throw new Error(`Unexpected token after expression: ${this.peek().type}`);
210
- }
211
- return result;
212
- }
213
- parseTernary() {
214
- const cond = this.parseOr();
215
- if (this.peek().type === 'QUESTION') {
216
- this.advance();
217
- const thenVal = this.parseTernary();
218
- this.expect('COLON');
219
- const elseVal = this.parseTernary();
220
- return cond ? thenVal : elseVal;
221
- }
222
- return cond;
223
- }
224
- parseOr() {
225
- let left = this.parseAnd();
226
- while (this.peek().type === 'OR') {
227
- this.advance();
228
- const right = this.parseAnd();
229
- left = left || right;
230
- }
231
- return left;
232
- }
233
- parseAnd() {
234
- let left = this.parseEquality();
235
- while (this.peek().type === 'AND') {
236
- this.advance();
237
- const right = this.parseEquality();
238
- left = left && right;
239
- }
240
- return left;
241
- }
242
- parseEquality() {
243
- let left = this.parseComparison();
244
- while (this.peek().type === 'EQ' || this.peek().type === 'NEQ') {
245
- const op = this.advance().type;
246
- const right = this.parseComparison();
247
- left = op === 'EQ' ? left == right : left != right;
248
- }
249
- return left;
250
- }
251
- parseComparison() {
252
- let left = this.parseAdditive();
253
- while (['GT', 'LT', 'GTE', 'LTE'].includes(this.peek().type)) {
254
- const op = this.advance().type;
255
- const right = this.parseAdditive();
256
- switch (op) {
257
- case 'GT':
258
- left = left > right;
259
- break;
260
- case 'LT':
261
- left = left < right;
262
- break;
263
- case 'GTE':
264
- left = left >= right;
265
- break;
266
- case 'LTE':
267
- left = left <= right;
268
- break;
269
- }
270
- }
271
- return left;
272
- }
273
- parseAdditive() {
274
- let left = this.parseMultiplicative();
275
- while (this.peek().type === 'PLUS' || this.peek().type === 'MINUS') {
276
- const op = this.advance().type;
277
- const right = this.parseMultiplicative();
278
- if (op === 'PLUS') {
279
- if (typeof left === 'string' || typeof right === 'string') {
280
- left = String(left) + String(right);
281
- }
282
- else {
283
- left = left + right;
284
- }
285
- }
286
- else {
287
- left = left - right;
288
- }
289
- }
290
- return left;
291
- }
292
- parseMultiplicative() {
293
- let left = this.parseUnary();
294
- while (['STAR', 'SLASH', 'PERCENT'].includes(this.peek().type)) {
295
- const op = this.advance().type;
296
- const right = this.parseUnary();
297
- switch (op) {
298
- case 'STAR':
299
- left = left * right;
300
- break;
301
- case 'SLASH':
302
- left = left / right;
303
- break;
304
- case 'PERCENT':
305
- left = left % right;
306
- break;
307
- }
308
- }
309
- return left;
310
- }
311
- parseUnary() {
312
- if (this.peek().type === 'NOT') {
313
- this.advance();
314
- return !this.parseUnary();
315
- }
316
- if (this.peek().type === 'MINUS') {
317
- this.advance();
318
- return -this.parseUnary();
319
- }
320
- return this.parsePrimary();
321
- }
322
- parsePrimary() {
323
- const token = this.peek();
324
- switch (token.type) {
325
- case 'NUMBER':
326
- this.advance();
327
- return token.value;
328
- case 'STRING':
329
- this.advance();
330
- return token.value;
331
- case 'BOOLEAN':
332
- this.advance();
333
- return token.value;
334
- case 'IDENT': {
335
- this.advance();
336
- const key = token.value;
337
- const val = this.state[key];
338
- if (val === undefined || val === null)
339
- return 0;
340
- const strVal = String(val);
341
- if (strVal === '')
342
- return '';
343
- const num = Number(strVal);
344
- return isNaN(num) ? strVal : num;
345
- }
346
- case 'LPAREN': {
347
- this.advance();
348
- const result = this.parseTernary();
349
- this.expect('RPAREN');
350
- return result;
351
- }
352
- default:
353
- throw new Error(`Unexpected token: ${token.type}`);
354
- }
355
- }
356
- }
357
- function safeEvaluate(expr, state) {
358
- const tokens = tokenize(expr);
359
- const parser = new ExprParser(tokens, state);
360
- return parser.parse();
361
- }
362
- class RunManager {
363
- constructor(runsDir = '.crewx/workflow-runs') {
364
- this.runsDir = runsDir;
365
- const base = process.env.CREWX_WORKSPACE || process.cwd();
366
- this.resolvedDir = path.resolve(base, this.runsDir);
367
- }
368
- ensureRunsDir() {
369
- if (!fs.existsSync(this.resolvedDir)) {
370
- fs.mkdirSync(this.resolvedDir, { recursive: true });
371
- }
372
- }
373
- validateExecId(execId) {
374
- if (/[/\\]|\.\./.test(execId)) {
375
- throw new Error(`Invalid execution ID: "${execId}"`);
376
- }
377
- }
378
- loadRun(execId) {
379
- this.validateExecId(execId);
380
- const filePath = path.join(this.resolvedDir, `${execId}.json`);
381
- if (!fs.existsSync(filePath))
382
- return null;
383
- try {
384
- return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
385
- }
386
- catch (e) {
387
- throw new Error(`Failed to parse execution file ${filePath}: ${e.message}`);
388
- }
389
- }
390
- saveRun(run) {
391
- this.ensureRunsDir();
392
- run.updated_at = new Date().toISOString();
393
- const filePath = path.join(this.resolvedDir, `${run.id}.json`);
394
- fs.writeFileSync(filePath, JSON.stringify(run, null, 2) + '\n', 'utf-8');
395
- }
396
- atomicUpdate(execId, updater) {
397
- this.validateExecId(execId);
398
- this.ensureRunsDir();
399
- const filePath = path.join(this.resolvedDir, `${execId}.json`);
400
- const lock = new file_lock_1.FileLock(filePath);
401
- lock.acquire();
402
- try {
403
- const run = this.loadRun(execId);
404
- if (!run)
405
- throw new Error(`Execution not found: ${execId}`);
406
- updater(run);
407
- this.saveRun(run);
408
- return run;
409
- }
410
- finally {
411
- lock.release();
412
- }
413
- }
414
- async atomicUpdateAsync(execId, updater) {
415
- this.validateExecId(execId);
416
- this.ensureRunsDir();
417
- const filePath = path.join(this.resolvedDir, `${execId}.json`);
418
- const lock = new file_lock_1.FileLock(filePath);
419
- await lock.acquireAsync();
420
- try {
421
- const run = this.loadRun(execId);
422
- if (!run)
423
- throw new Error(`Execution not found: ${execId}`);
424
- updater(run);
425
- this.saveRun(run);
426
- return run;
427
- }
428
- finally {
429
- lock.release();
430
- }
431
- }
432
- loadWorkflowYaml(yamlPath) {
433
- if (!fs.existsSync(yamlPath))
434
- return null;
435
- const content = fs.readFileSync(yamlPath, 'utf-8');
436
- const doc = yaml.load(content);
437
- if (!doc || !doc.workflows || typeof doc.workflows !== 'object')
438
- return null;
439
- return doc;
440
- }
441
- start(yamlPath, workflowId, overrides) {
442
- const doc = this.loadWorkflowYaml(yamlPath);
443
- if (!doc) {
444
- throw new Error(`Failed to load workflow: ${yamlPath}`);
445
- }
446
- const workflowEntries = Object.entries(doc.workflows);
447
- if (workflowEntries.length === 0) {
448
- throw new Error(`No workflows found in ${yamlPath}`);
449
- }
450
- let resolvedId;
451
- let spec;
452
- if (workflowId) {
453
- if (!doc.workflows[workflowId]) {
454
- throw new Error(`Workflow "${workflowId}" not found in ${yamlPath}`);
455
- }
456
- resolvedId = workflowId;
457
- spec = doc.workflows[workflowId];
458
- }
459
- else {
460
- [resolvedId, spec] = workflowEntries[0];
461
- }
462
- const execId = (0, sdk_1.generateId)('wfr');
463
- const initialState = spec.state
464
- ? JSON.parse(JSON.stringify(spec.state))
465
- : {};
466
- const sets = overrides ?? {};
467
- const state = { ...initialState, ...sets };
468
- const run = {
469
- id: execId,
470
- workflow_id: resolvedId,
471
- workflow_file: yamlPath,
472
- created_at: new Date().toISOString(),
473
- updated_at: new Date().toISOString(),
474
- current_node: '',
475
- completed_nodes: [],
476
- state,
477
- initial_state: { ...initialState, ...sets },
478
- };
479
- this.saveRun(run);
480
- return run;
481
- }
482
- getState(execId) {
483
- const run = this.loadRun(execId);
484
- if (!run) {
485
- throw new Error(`Execution not found: ${execId}`);
486
- }
487
- return run.state;
488
- }
489
- setState(execId, updates) {
490
- this.atomicUpdate(execId, (run) => {
491
- Object.assign(run.state, updates);
492
- });
493
- }
494
- moveNode(execId, nodeId) {
495
- return this.atomicUpdate(execId, (run) => {
496
- if (run.current_node && !run.completed_nodes.includes(run.current_node)) {
497
- run.completed_nodes.push(run.current_node);
498
- }
499
- run.current_node = nodeId;
500
- });
501
- }
502
- async executeNode(execId, nodeId, opts) {
503
- const snapshot = this.loadRun(execId);
504
- if (!snapshot) {
505
- throw new Error(`Execution not found: ${execId}`);
506
- }
507
- const doc = this.loadWorkflowYaml(snapshot.workflow_file);
508
- if (!doc) {
509
- throw new Error(`Failed to load workflow file: ${snapshot.workflow_file}`);
510
- }
511
- const spec = doc.workflows[snapshot.workflow_id];
512
- if (!spec) {
513
- throw new Error(`Workflow "${snapshot.workflow_id}" not found in ${snapshot.workflow_file}`);
514
- }
515
- const nodeSpec = spec.nodes?.[nodeId];
516
- if (!nodeSpec) {
517
- throw new Error(`Node "${nodeId}" not found in workflow "${snapshot.workflow_id}"`);
518
- }
519
- const prevNode = snapshot.current_node;
520
- const stateUpdates = {};
521
- const nodesToComplete = [];
522
- let currentNode = nodeId;
523
- let status;
524
- let nodeTaskId;
525
- try {
526
- switch (nodeSpec.type) {
527
- case 'agent_task': {
528
- if (!nodeSpec.agent) {
529
- throw new Error(`Node "${nodeId}" is agent_task but has no agent defined`);
530
- }
531
- const input = nodeSpec.input
532
- ? interpolateTemplate(nodeSpec.input, { ...snapshot.state, state: snapshot.state })
533
- : '';
534
- const finalInput = nodeSpec.output_format === 'json'
535
- ? input + (0, output_format_1.buildJsonOutputSuffix)(nodeSpec.output_schema, nodeSpec.output_strict)
536
- : input;
537
- const mode = nodeSpec.mode === 'query' ? 'q' : 'x';
538
- const prompt = `@${nodeSpec.agent} ${finalInput}`;
539
- if (opts?.dryRun) {
540
- console.log(`[dry-run] Would execute: crewx ${mode} "@${nodeSpec.agent} ..."`);
541
- break;
542
- }
543
- console.log(`Executing: @${nodeSpec.agent} (${mode} mode)`);
544
- const crewxCli = process.env.CREWX_CLI || 'npx crewx';
545
- const cliParts = crewxCli.split(/\s+/);
546
- const bin = cliParts[0];
547
- const baseArgs = cliParts.slice(1);
548
- const nodeTimeout = Number(process.env[mode === 'q' ? 'CREWX_TIMEOUT_QUERY' : 'CREWX_TIMEOUT_EXECUTE']) || 8 * 3600000;
549
- const wfArgs = [...baseArgs, mode, prompt];
550
- if (process.env.CREWX_WORKFLOW_THREAD === 'on') {
551
- wfArgs.push(`--thread=workflow:${snapshot.id}`);
552
- }
553
- const wfMetadata = {
554
- workflow: {
555
- exec_id: snapshot.id,
556
- node_id: nodeId,
557
- workflow_id: snapshot.workflow_id,
558
- },
559
- };
560
- wfArgs.push('--metadata', JSON.stringify(wfMetadata));
561
- const program = (0, sdk_1.resolveWindowsSpawnProgram)({
562
- command: bin,
563
- allowShellFallback: true,
564
- packageName: 'crewx',
565
- });
566
- const invocation = (0, sdk_1.materializeWindowsSpawnProgram)(program, wfArgs);
567
- const result = cp.spawnSync(invocation.command, invocation.argv, {
568
- encoding: 'utf-8',
569
- timeout: nodeTimeout,
570
- cwd: process.cwd(),
571
- stdio: ['inherit', 'pipe', 'pipe'],
572
- shell: invocation.shell ?? false,
573
- windowsHide: invocation.windowsHide,
574
- env: {
575
- ...process.env,
576
- CREWX_WORKFLOW_EXEC_ID: snapshot.id,
577
- CREWX_WORKFLOW_NODE_ID: nodeId,
578
- CREWX_WORKFLOW_ID: snapshot.workflow_id,
579
- },
580
- });
581
- if (result.error) {
582
- throw result.error;
583
- }
584
- const taskIdMatch = result.stderr?.match(/crewx kill (tsk_\w+)/);
585
- nodeTaskId = taskIdMatch?.[1];
586
- if (result.status !== 0) {
587
- const stderr = result.stderr?.trim() ?? '';
588
- throw new Error(`Agent execution failed (exit ${result.status}): ${stderr}`);
589
- }
590
- const output = result.stdout?.trim() ?? '';
591
- if (nodeSpec.output) {
592
- if (nodeSpec.output_format === 'json') {
593
- const retryMax = nodeSpec.output_retry ?? 1;
594
- let parsed = null;
595
- let lastError = null;
596
- let retryInput = finalInput;
597
- for (let attempt = 0; attempt <= retryMax; attempt++) {
598
- let attemptOutput = output;
599
- if (attempt > 0) {
600
- retryInput = finalInput +
601
- `\n\n## ⚠️ PREVIOUS OUTPUT FAILED JSON.parse: ${lastError.message}\n` +
602
- `Return STRICT JSON ONLY. First char "{", last char "}". No prose, no fences.`;
603
- const retryPrompt = `@${nodeSpec.agent} ${retryInput}`;
604
- const retryArgs = [...baseArgs, mode, retryPrompt];
605
- if (process.env.CREWX_WORKFLOW_THREAD === 'on') {
606
- retryArgs.push(`--thread=workflow:${snapshot.id}`);
607
- }
608
- retryArgs.push('--metadata', JSON.stringify(wfMetadata));
609
- const retryInvocation = (0, sdk_1.materializeWindowsSpawnProgram)(program, retryArgs);
610
- const retryResult = cp.spawnSync(retryInvocation.command, retryInvocation.argv, {
611
- encoding: 'utf-8',
612
- timeout: nodeTimeout,
613
- cwd: process.cwd(),
614
- stdio: ['inherit', 'pipe', 'pipe'],
615
- shell: retryInvocation.shell ?? false,
616
- windowsHide: retryInvocation.windowsHide,
617
- env: {
618
- ...process.env,
619
- CREWX_WORKFLOW_EXEC_ID: snapshot.id,
620
- CREWX_WORKFLOW_NODE_ID: nodeId,
621
- CREWX_WORKFLOW_ID: snapshot.workflow_id,
622
- },
623
- });
624
- if (retryResult.error)
625
- throw retryResult.error;
626
- if (retryResult.status !== 0) {
627
- throw new Error(`Agent retry failed (exit ${retryResult.status}): ${retryResult.stderr?.trim()}`);
628
- }
629
- attemptOutput = retryResult.stdout?.trim() ?? '';
630
- }
631
- try {
632
- parsed = JSON.parse((0, output_format_1.extractJson)(attemptOutput));
633
- if (nodeSpec.output_schema) {
634
- await (0, output_format_1.validateJsonSchema)(parsed, nodeSpec.output_schema);
635
- }
636
- break;
637
- }
638
- catch (e) {
639
- lastError = e;
640
- }
641
- }
642
- if (parsed === null) {
643
- throw new Error(`Node "${nodeId}" output_format=json parse failed after ${retryMax + 1} attempt(s): ${lastError.message}`);
644
- }
645
- stateUpdates[nodeSpec.output] = parsed;
646
- console.log(`Result stored in state.${nodeSpec.output} (JSON object)`);
647
- }
648
- else {
649
- stateUpdates[nodeSpec.output] = output;
650
- console.log(`Result stored in state.${nodeSpec.output} (${output.length} chars)`);
651
- }
652
- }
653
- break;
654
- }
655
- case 'branch': {
656
- if (!nodeSpec.condition) {
657
- throw new Error(`Branch node "${nodeId}" has no condition`);
658
- }
659
- const evalFn = new Function('state', `return (${nodeSpec.condition});`);
660
- const condResult = String(evalFn(snapshot.state));
661
- const branches = nodeSpec.branches;
662
- const targetNode = branches?.[condResult] ?? nodeSpec.default;
663
- if (!targetNode) {
664
- throw new Error(`Branch "${nodeId}": no matching branch for "${condResult}" and no default`);
665
- }
666
- console.log(`Branch "${nodeId}": condition="${nodeSpec.condition}" → ${condResult} → ${targetNode}`);
667
- nodesToComplete.push(nodeId);
668
- currentNode = targetNode;
669
- break;
670
- }
671
- case 'parallel': {
672
- const branches = nodeSpec.branches;
673
- if (branches && Array.isArray(branches)) {
674
- console.log(`Parallel "${nodeId}": branches = [${branches.join(', ')}]`);
675
- console.log(` Enter each branch sequentially with: workflow run node <exec-id> <branch-node>`);
676
- }
677
- break;
678
- }
679
- case 'join': {
680
- console.log(`Join "${nodeId}": completed_nodes = [${snapshot.completed_nodes.join(', ')}]`);
681
- break;
682
- }
683
- case 'end': {
684
- status = 'completed';
685
- console.log(`End "${nodeId}": workflow execution completed`);
686
- break;
687
- }
688
- case 'expression': {
689
- if (!nodeSpec.set || typeof nodeSpec.set !== 'object') {
690
- throw new Error(`Expression node "${nodeId}" requires "set" field`);
691
- }
692
- for (const [key, expr] of Object.entries(nodeSpec.set)) {
693
- const result = safeEvaluate(String(expr), snapshot.state);
694
- stateUpdates[key] = String(result);
695
- console.log(` ${key} = ${result}`);
696
- }
697
- console.log(`Expression "${nodeId}": set ${Object.keys(nodeSpec.set).join(', ')}`);
698
- if (nodeSpec.next) {
699
- nodesToComplete.push(nodeId);
700
- currentNode = nodeSpec.next;
701
- }
702
- break;
703
- }
704
- case 'approval': {
705
- console.log(`Approval "${nodeId}": manual intervention required (not auto-executed)`);
706
- break;
707
- }
708
- default:
709
- console.log(`Unknown node type "${nodeSpec.type}" for node "${nodeId}"`);
710
- }
711
- }
712
- catch (e) {
713
- await this.atomicUpdateAsync(execId, (run) => {
714
- run.current_node = nodeId;
715
- run.status = 'failed';
716
- });
717
- throw e;
718
- }
719
- return this.atomicUpdateAsync(execId, (run) => {
720
- Object.assign(run.state, stateUpdates);
721
- for (const node of nodesToComplete) {
722
- if (!run.completed_nodes.includes(node)) {
723
- run.completed_nodes.push(node);
724
- }
725
- }
726
- if (prevNode && prevNode !== nodeId && !run.completed_nodes.includes(prevNode)) {
727
- run.completed_nodes.push(prevNode);
728
- }
729
- run.current_node = currentNode;
730
- if (status)
731
- run.status = status;
732
- if (nodeTaskId) {
733
- run.tasks = run.tasks ?? {};
734
- run.tasks[nodeId] = nodeTaskId;
735
- }
736
- });
737
- }
738
- reset(execId) {
739
- return this.atomicUpdate(execId, (run) => {
740
- run.state = JSON.parse(JSON.stringify(run.initial_state));
741
- run.current_node = '';
742
- run.completed_nodes = [];
743
- });
744
- }
745
- listRuns(opts) {
746
- this.ensureRunsDir();
747
- const files = fs.readdirSync(this.resolvedDir).filter(f => f.endsWith('.json'));
748
- const runs = [];
749
- for (const file of files) {
750
- try {
751
- const run = JSON.parse(fs.readFileSync(path.join(this.resolvedDir, file), 'utf-8'));
752
- if (opts?.workflowId && run.workflow_id !== opts.workflowId)
753
- continue;
754
- runs.push(run);
755
- }
756
- catch {
757
- }
758
- }
759
- runs.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
760
- return runs;
761
- }
762
- }
763
- exports.RunManager = RunManager;
764
- //# sourceMappingURL=run-manager.js.map
1
+ "use strict";var xe=exports&&exports.__createBinding||(Object.create?(function(a,e,t,o){if(o===void 0)o=t;var r=Object.getOwnPropertyDescriptor(e,t);if(!r||("get"in r?!e.__esModule:r.writable||r.configurable)){r={enumerable:true,get:function(){return e[t]}}}Object.defineProperty(a,o,r)}):(function(a,e,t,o){if(o===void 0)o=t;a[o]=e[t]}));var Ie=exports&&exports.__setModuleDefault||(Object.create?(function(a,e){Object.defineProperty(a,"default",{enumerable:true,value:e})}):function(a,e){a["default"]=e});var ie=exports&&exports.__importStar||(function(){var a=function(e){a=Object.getOwnPropertyNames||function(t){var o=[];for(var r in t)if(Object.prototype.hasOwnProperty.call(t,r))o[o.length]=r;return o};return a(e)};return function(e){if(e&&e.__esModule)return e;var t={};if(e!=null){for(var o=a(e),r=0;r<o.length;r++)if(o[r]!=="default")xe(t,e,o[r])}Ie(t,e);return t}})();Object.defineProperty(exports,"__esModule",{value:true});exports.RunManager=exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS=exports.WORKFLOW_RUN_INTERRUPTED_ERROR=exports.WORKFLOW_CANCEL_GRACE_MS=exports.SKILL_TASK_DEFAULT_TIMEOUT=exports.FileLock=void 0;exports.parseSetArgs=He;exports.readStdin=Ze;exports.interpolateTemplate=b;exports.safeEvaluate=Ee;exports.safeEvaluateBranchCondition=de;exports.validateSafeBranchCondition=st;exports.normalizeRunStatus=ee;exports.isRetryableFailure=Ue;const R=ie(require("fs"));const W=ie(require("path"));const Me=ie(require("js-yaml"));const Ge=ie(require("child_process"));const N=require("@crewx/sdk");const d=require("./failure");const le=require("./file-lock");const K=require("./utils/output-format");const U=require("./utils/shell-security");const ce=require("./retry-policy");var Ke=require("./file-lock");Object.defineProperty(exports,"FileLock",{enumerable:true,get:function(){return Ke.FileLock}});exports.SKILL_TASK_DEFAULT_TIMEOUT=6e5;exports.WORKFLOW_CANCEL_GRACE_MS=2e3;const Ve=100;exports.WORKFLOW_RUN_INTERRUPTED_ERROR="Workflow run interrupted: runner process exited before completion";exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS=10*6e4;const qe=1e3;function ze(a){return new Promise(e=>{setTimeout(e,a)})}const Ae=new Set(["agent_task","skill_task","shell_task","branch","expression","join","end","error"]);const Xe=new Set(["branch","expression","end","error"]);function Se(a,e){const t=(0,N.resolveCrewxExecutable)({env:a,cwd:e});if(!t.ok){throw(0,d.createWorkflowFailureError)("CLI_UNRESOLVABLE",(0,N.formatCrewxExecutableFailure)(t))}return(0,N.createCrewxChildInvocation)(t,{baseEnv:a})}async function ye(a,e="SIGKILL"){if(!a.pid)return;try{const t=await(0,N.terminateProcessTree)(a.pid,{signal:e});if(t.status==="failed"||t.status==="not_found"){try{a.kill(e)}catch{}}}catch{try{a.kill(e)}catch{}}}async function ue(a,e,t){return new Promise(o=>{const r=()=>{try{return t.cancellation?.isCancelled()===true}catch{return false}};if(r()){o({status:null,stdout:"",stderr:"",timedOut:false,cancelled:true,signal:null,errorOrigin:null});return}let s;try{s=Ge.spawn(a,e,{cwd:t.cwd,env:t.env,stdio:[t.stdin!==void 0?"pipe":"ignore","pipe","pipe"],shell:t.shell??false,detached:process.platform!=="win32",windowsHide:t.windowsHide??true})}catch(g){o({status:null,stdout:"",stderr:"",timedOut:false,signal:null,errorOrigin:"spawn",error:g});return}let i="";let n="";let c=false;let p=false;let l=false;let f=false;let m;let _;let S;let C;let w;let k;const F=new N.ConsoleDecoder;const H=new N.ConsoleDecoder;let X=Promise.resolve();let q=Promise.resolve();const Z=()=>{if(S)clearTimeout(S);if(C)clearTimeout(C);if(w)clearInterval(w);if(t.cancellation&&k&&fe.get(t.cancellation.key)===k){fe.delete(t.cancellation.key)}};const I=g=>{if(c)return;c=true;Z();o(f?{...g,cancelled:true,timedOut:false}:g)};const B=()=>{if(c||f)return;f=true;void ye(s,"SIGTERM");C=setTimeout(()=>{if(!c)void ye(s,"SIGKILL")},exports.WORKFLOW_CANCEL_GRACE_MS);if(typeof C.unref==="function"){C.unref()}};if(t.cancellation){k={cancel:B};fe.set(t.cancellation.key,k);w=setInterval(()=>{if(r())B()},Ve);if(typeof w.unref==="function")w.unref()}S=setTimeout(()=>{void ye(s);Q(()=>({status:null,stdout:i,stderr:n,timedOut:true,signal:null,errorOrigin:_?"stdin":m?"post_spawn":null,..._||m?{error:_??m}:{}}))},t.timeout);if(typeof S.unref==="function")S.unref();s.stdout?.on("data",g=>{X=X.then(async()=>{i+=await F.write(g)})});s.stderr?.on("data",g=>{q=q.then(async()=>{n+=await H.write(g)})});s.once("spawn",()=>{l=true});s.on("error",g=>{if(!l){I({status:null,stdout:i,stderr:n,timedOut:false,signal:null,errorOrigin:"spawn",error:g});return}m=m??g});if(t.stdin!==void 0){s.stdin?.on("error",g=>{_=_??g});try{s.stdin?.end(t.stdin)}catch(g){_=_??g}}const Q=g=>{if(c||p)return;p=true;void(async()=>{await Promise.all([X,q]);const[M,pe]=await Promise.all([F.end(),H.end()]);i+=M;n+=pe;I(g())})().catch(()=>I(g()))};s.on("close",(g,M)=>Q(()=>({status:g,stdout:i,stderr:n,timedOut:false,signal:M,errorOrigin:_?"stdin":m?"post_spawn":null,..._||m?{error:_??m}:{}})));if(r())B()})}const Be=/^[A-Za-z0-9_.-]{1,128}$/;function be(a){if(a===void 0)return void 0;const e=Be.exec(a);return e?.[0]===a?a:void 0}function Je(a){switch(a.type){case"agent_task":{const e=be(a.agent);return e?`@${e} (${a.mode==="query"?"q":"x"})`:`agent_task (${a.mode==="query"?"q":"x"})`}case"skill_task":{const e=be(a.skill);return e?`skill ${e}`:"skill_task"}case"shell_task":return"shell_task";case"branch":return"branch";case"expression":return"expression";default:return"workflow node"}}function J(a){if(a instanceof Error)return a.message;return typeof a==="string"?a:"Node execution failed"}function Ye(a){return{status:a.status,timedOut:a.timedOut,signal:a.signal,errorOrigin:a.errorOrigin}}function Y(a,e,t,o,r=new Date().toISOString()){a.audit=a.audit??[];a.audit.push({exec_id:a.id,node_id:e.failedNode,type:t,summary:e.causeMessage,started_at:r,ended_at:r,duration_ms:0,exit_code:e.exitCode,error:e.causeMessage,trigger:o})}function V(a,e,t){a.status="failed";a.error=(0,d.sanitizeLocalRunError)(t,e.causeMessage);a.last_failure=e}async function Le(a,e,t,o){if(!a.output)return;const r=t.trim();if(a.output_format==="json"){let s;try{s=JSON.parse((0,K.extractJson)(r));if(a.output_schema){await(0,K.validateJsonSchema)(s,a.output_schema)}}catch(i){throw(0,d.createWorkflowFailureError)("OUTPUT_INVALID",`Node "${e}" output_format=json parse or validation failed: ${J(i)}`)}o[a.output]=s}else{o[a.output]=r}}function We(a){try{return JSON.parse(a)}catch{return a}}function He(a){const e={};for(let t=0;t<a.length;t++){const o=a[t];if(o==="--set"&&t+1<a.length){const r=a[t+1];const s=r.indexOf("=");if(s!==-1){const i=r.substring(0,s);const n=r.substring(s+1);e[i]=We(n)}t++}else if(o.startsWith("--set=")){const r=o.substring("--set=".length);const s=r.indexOf("=");if(s!==-1){const i=r.substring(0,s);const n=r.substring(s+1);e[i]=We(n)}}}return e}async function Ze(){const a=[];for await(const e of process.stdin){a.push(e)}return Buffer.concat(a).toString("utf-8").trimEnd()}function b(a,e){return a.replace(/\{\{([\w.]+)\}\}/g,(t,o)=>{const r=o.split(".");let s=e[r[0]];for(let i=1;i<r.length&&s!=null;i++){s=s[r[i]]}if(s===null||s===void 0)return"";if(typeof s==="object")return JSON.stringify(s,null,2);return String(s)})}const Qe=new Set(["require","import","process","global","globalThis","Function","eval","__proto__","constructor","prototype"]);const et=new Set(["__proto__","constructor","prototype"]);function tt(a){const e=[];let t=0;while(t<a.length){const o=a[t];if(/\s/.test(o)){t++;continue}if(/\d/.test(o)){let s="";while(t<a.length&&/[\d.]/.test(a[t])){s+=a[t];t++}e.push({type:"NUMBER",value:Number(s)});continue}if(o==="'"||o==='"'){const s=o;t++;let i="";while(t<a.length&&a[t]!==s){if(a[t]==="\\"){t++;i+=a[t]??""}else{i+=a[t]}t++}if(t>=a.length)throw new Error("Unterminated string literal");t++;e.push({type:"STRING",value:i});continue}if(/[a-zA-Z_]/.test(o)){let s="";while(t<a.length&&/\w/.test(a[t])){s+=a[t];t++}if(s==="true"||s==="false"){e.push({type:"BOOLEAN",value:s==="true"})}else{if(Qe.has(s)){throw new Error(`Forbidden identifier in expression: "${s}"`)}e.push({type:"IDENT",value:s})}continue}if(t+1<a.length){const s=a.substring(t,t+2);const i={">=":"GTE","<=":"LTE","==":"EQ","!=":"NEQ","&&":"AND","||":"OR"};if(i[s]){e.push({type:i[s],value:s});t+=2;continue}}const r={"+":"PLUS","-":"MINUS","*":"STAR","/":"SLASH","%":"PERCENT",">":"GT","<":"LT","!":"NOT","?":"QUESTION",":":"COLON","(":"LPAREN",")":"RPAREN"};if(r[o]){e.push({type:r[o],value:o});t++;continue}if(o==="."||o==="["||o==="]"){throw new Error(`Forbidden character in expression: "${o}" (property access not allowed)`)}throw new Error(`Unexpected character in expression: "${o}"`)}e.push({type:"EOF",value:""});return e}class rt{constructor(e,t){this.tokens=e;this.state=t;this.pos=0}peek(){return this.tokens[this.pos]}advance(){return this.tokens[this.pos++]}expect(e){const t=this.advance();if(t.type!==e)throw new Error(`Expected ${e}, got ${t.type}`);return t}parse(){const e=this.parseTernary();if(this.peek().type!=="EOF"){throw new Error(`Unexpected token after expression: ${this.peek().type}`)}return e}parseTernary(){const e=this.parseOr();if(this.peek().type==="QUESTION"){this.advance();const t=this.parseTernary();this.expect("COLON");const o=this.parseTernary();return e?t:o}return e}parseOr(){let e=this.parseAnd();while(this.peek().type==="OR"){this.advance();const t=this.parseAnd();e=e||t}return e}parseAnd(){let e=this.parseEquality();while(this.peek().type==="AND"){this.advance();const t=this.parseEquality();e=e&&t}return e}parseEquality(){let e=this.parseComparison();while(this.peek().type==="EQ"||this.peek().type==="NEQ"){const t=this.advance().type;const o=this.parseComparison();e=t==="EQ"?e==o:e!=o}return e}parseComparison(){let e=this.parseAdditive();while(["GT","LT","GTE","LTE"].includes(this.peek().type)){const t=this.advance().type;const o=this.parseAdditive();switch(t){case"GT":e=e>o;break;case"LT":e=e<o;break;case"GTE":e=e>=o;break;case"LTE":e=e<=o;break}}return e}parseAdditive(){let e=this.parseMultiplicative();while(this.peek().type==="PLUS"||this.peek().type==="MINUS"){const t=this.advance().type;const o=this.parseMultiplicative();if(t==="PLUS"){if(typeof e==="string"||typeof o==="string"){e=String(e)+String(o)}else{e=e+o}}else{e=e-o}}return e}parseMultiplicative(){let e=this.parseUnary();while(["STAR","SLASH","PERCENT"].includes(this.peek().type)){const t=this.advance().type;const o=this.parseUnary();switch(t){case"STAR":e=e*o;break;case"SLASH":e=e/o;break;case"PERCENT":e=e%o;break}}return e}parseUnary(){if(this.peek().type==="NOT"){this.advance();return!this.parseUnary()}if(this.peek().type==="MINUS"){this.advance();return-this.parseUnary()}return this.parsePrimary()}parsePrimary(){const e=this.peek();switch(e.type){case"NUMBER":this.advance();return e.value;case"STRING":this.advance();return e.value;case"BOOLEAN":this.advance();return e.value;case"IDENT":{this.advance();const t=e.value;const o=this.state[t];if(o===void 0||o===null)return 0;if(typeof o==="boolean"||typeof o==="number")return o;const r=String(o);if(r==="")return"";const s=Number(r);return isNaN(s)?r:s}case"LPAREN":{this.advance();const t=this.parseTernary();this.expect("RPAREN");return t}default:throw new Error(`Unexpected token: ${e.type}`)}}}function Ee(a,e){const t=tt(a);const o=new rt(t,e);return o.parse()}function Fe(a,e){const t=e.split(".");let o=a;for(const r of t){if(et.has(r)){throw new Error(`Forbidden state path segment in expression: "${r}"`)}if(!r||o===null||o===void 0||typeof o!=="object"){return void 0}o=o[r]}return o}function ot(a){return a.replace(/\\'/g,"'").replace(/\\"/g,'"').replace(/\\\\/g,"\\")}function nt(a,e){const t={};let o=0;const r=n=>{const c=`__state_${o++}`;t[c]=n;return c};let s=a.replace(/!==/g,"!=").replace(/===/g,"==");const i=n=>{s=s.replace(n,(c,p,l)=>{const f=Fe(e,p);return r(String(f??"").includes(ot(l)))})};i(/\bstate\.([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\.includes\(\s*'((?:\\.|[^'\\])*)'\s*\)/g);i(/\bstate\.([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\.includes\(\s*"((?:\\.|[^"\\])*)"\s*\)/g);s=s.replace(/\bstate\.([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/g,(n,c)=>r(Fe(e,c)));s=s.replace(/\bNumber\(\s*([A-Za-z_]\w*)\s*\)/g,"$1");s=s.replace(/\bparseInt\(\s*([A-Za-z_]\w*(?:\s*\|\|\s*(?:'[^']*'|"[^"]*"))?)\s*\)/g,"($1)");return{expr:s,scope:t}}function de(a,e){const t=nt(a,e);return Ee(t.expr,{...e,...t.scope})}function st(a){de(a,{})}function at(a){const e=a.state["__engine"];if(!e||typeof e!=="object")return{visitCounts:{},iterations:0};const t=e;const o=t["visitCounts"];const r={};if(o&&typeof o==="object"){for(const[i,n]of Object.entries(o)){if(typeof n==="number"&&Number.isFinite(n))r[i]=n}}const s=typeof t["iterations"]==="number"&&Number.isFinite(t["iterations"])?t["iterations"]:0;return{visitCounts:r,iterations:s}}const ke=new Set;const fe=new Map;function Te(a,e){return`${a}::${e}`}function it(a,e){fe.get(Te(a,e))?.cancel()}function De(a){if(!Number.isInteger(a)||a<=0)return false;if(a===process.pid)return true;try{process.kill(a,0);return true}catch(e){return e.code==="EPERM"}}function Re(a){return a==="completed"||a==="failed"||a==="cancelled"}function lt(a,e){if(a.status!==void 0||a.auto_run)return false;const t=a.audit?.at(-1);if(t?.type!=="retry")return false;const o=Date.parse(a.updated_at);return Number.isFinite(o)&&e-o<exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS}function ee(a,e=Date.now()){if(a.status!==void 0)return a;let t;if(a.current_node===""){t="pending"}else if(a.auto_run){t="running"}else if(lt(a,e)){t="retrying"}else{t="pending"}return{...a,status:t}}function Ue(a,e){if(a.status!=="failed")return false;if(!a.current_node)return false;const t=e.nodes?.[a.current_node];if(!t)return false;return t.type!=="error"}class ct{constructor(e=".crewx/workflow-runs",t,o){this.runsDir=e;this.sleep=o?.sleep??ze;this.onRunPersisted=o?.onRunPersisted;const r=t||process.env.CREWX_WORKSPACE||process.cwd();this.resolvedDir=W.resolve(r,this.runsDir);this.projectRoot=W.resolve(r);const s=R.existsSync(W.join(this.projectRoot,"crewx.yaml"))||R.existsSync(W.join(this.projectRoot,"crewx.yml"));if(!s){console.warn(`[RunManager] projectRoot "${this.projectRoot}" has no crewx.yaml/crewx.yml \u2014 agent_task nodes may fail to resolve agents.`)}}ensureRunsDir(){if(!R.existsSync(this.resolvedDir)){R.mkdirSync(this.resolvedDir,{recursive:true})}}validateExecId(e){if(/[/\\]|\.\./.test(e)){throw new Error(`Invalid execution ID: "${e}"`)}}isRunCancelled(e){try{return this.readRunRaw(e)?.status==="cancelled"}catch{return false}}processCancellation(e){return{key:Te(this.resolvedDir,e),isCancelled:()=>this.isRunCancelled(e)}}readRunRaw(e){const t=W.join(this.resolvedDir,`${e}.json`);if(!R.existsSync(t))return null;try{return JSON.parse(R.readFileSync(t,"utf-8"))}catch(o){throw new Error(`Failed to parse execution file ${t}: ${o.message}`)}}readRun(e){this.validateExecId(e);const t=this.readRunRaw(e);return t?ee(t):null}isLegacyStatuslessStale(e){if(e.status!==void 0||e.auto_run||e.manual_resume||!e.current_node||!e.audit?.length){return false}const t=Date.parse(e.updated_at);if(!Number.isFinite(t)||Date.now()-t<exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS){return false}const o=this.loadWorkflowYaml(e.workflow_file);const r=o?.workflows[e.workflow_id];const s=r?.nodes?.[e.current_node];return Boolean(s&&Ae.has(s.type))}withRunLock(e,t){this.validateExecId(e);const o=W.join(this.resolvedDir,`${e}.json`);if(!R.existsSync(o)){throw new Error(`Execution not found: ${e}`)}const r=new le.FileLock(o);r.acquire();try{const s=this.readRunRaw(e);if(!s)throw new Error(`Execution not found: ${e}`);return t(ee(s))}finally{r.release()}}hasStaleRunner(e){if(e.auto_run)return!De(e.auto_run.pid);return this.isLegacyStatuslessStale(e)}recoverStaleRunner(e){const t=W.join(this.resolvedDir,`${e}.json`);const o=new le.FileLock(t);o.acquire();try{const r=this.readRunRaw(e);if(!r||!this.hasStaleRunner(r))return r?ee(r):null;if(Re(r.status)){delete r.auto_run;delete r.manual_resume;this.saveRun(r);return r}const s=r.auto_run;const i=s?.node_id||r.current_node;const n=new Date().toISOString();const c=(0,d.buildNodeFailure)({failedNode:i,attempt:(0,d.readNodeAttempt)(r,i),causeCode:"INTERRUPTED",exitCode:null,signal:null,errorOrigin:null});V(r,c,exports.WORKFLOW_RUN_INTERRUPTED_ERROR);delete r.auto_run;delete r.manual_resume;Y(r,c,"interrupted","auto",n);this.saveRun(r);return r}finally{o.release()}}loadRun(e){this.validateExecId(e);const t=this.readRunRaw(e);if(!t||!this.hasStaleRunner(t))return t?ee(t):null;return this.recoverStaleRunner(e)}saveRun(e){this.ensureRunsDir();e.updated_at=new Date().toISOString();const t=W.join(this.resolvedDir,`${e.id}.json`);let o;try{o=R.statSync(t).mode&4095}catch(c){if(c.code!=="ENOENT")throw c}const r=W.join(this.resolvedDir,`.${e.id}.tmp-${process.pid}-${(0,N.generateId)("wfr")}`);const s=JSON.stringify(e,null,2)+"\n";let i;let n=false;try{i=R.openSync(r,"wx");n=true;R.writeFileSync(i,s,"utf-8");if(o!==void 0)R.fchmodSync(i,o);R.closeSync(i);i=void 0;R.renameSync(r,t);n=false}finally{if(i!==void 0){try{R.closeSync(i)}catch{}}if(n){try{R.unlinkSync(r)}catch{}}}if(!this.onRunPersisted)return;try{this.onRunPersisted(e)}catch(c){console.warn(`[RunManager] onRunPersisted failed for ${e.id}: ${J(c)}`)}}atomicUpdate(e,t){this.validateExecId(e);this.ensureRunsDir();const o=W.join(this.resolvedDir,`${e}.json`);const r=new le.FileLock(o);r.acquire();try{const s=this.readRun(e);if(!s)throw new Error(`Execution not found: ${e}`);t(s);this.saveRun(s);return s}finally{r.release()}}async atomicUpdateAsync(e,t){this.validateExecId(e);this.ensureRunsDir();const o=W.join(this.resolvedDir,`${e}.json`);const r=new le.FileLock(o);await r.acquireAsync();try{const s=this.readRun(e);if(!s)throw new Error(`Execution not found: ${e}`);t(s);this.saveRun(s);return s}finally{r.release()}}async reserveNodeAttempt(e,t){let o=1;await this.atomicUpdateAsync(e,r=>{if(r.status==="cancelled"){o=(0,d.readNodeAttempt)(r,t);return}const s=r.node_attempts?.[t];const i=typeof s==="number"&&Number.isInteger(s)&&s>0?s:0;o=i+1;r.node_attempts=r.node_attempts??{};r.node_attempts[t]=o});return o}clearNodeAttempt(e,t){if(!e.node_attempts)return;delete e.node_attempts[t];if(Object.keys(e.node_attempts).length===0)delete e.node_attempts}loadWorkflowYaml(e){if(!R.existsSync(e))return null;const t=R.readFileSync(e,"utf-8");const o=Me.load(t);if(!o||!o.workflows||typeof o.workflows!=="object")return null;return o}getNodeType(e,t,o){const r=this.loadWorkflowYaml(e);const s=r?.workflows[t];const i=s?.nodes?.[o];return typeof i?.type==="string"?i.type:null}start(e,t,o){const r=this.loadWorkflowYaml(e);if(!r){throw new Error(`Failed to load workflow: ${e}`)}const s=Object.entries(r.workflows);if(s.length===0){throw new Error(`No workflows found in ${e}`)}let i;let n;if(t){if(!r.workflows[t]){throw new Error(`Workflow "${t}" not found in ${e}`)}i=t;n=r.workflows[t]}else{[i,n]=s[0]}const c=(0,N.generateId)("wfr");const p=n.state?JSON.parse(JSON.stringify(n.state)):{};const l=o??{};const f={...p,...l};const m={id:c,workflow_id:i,workflow_file:e,created_at:new Date().toISOString(),updated_at:new Date().toISOString(),current_node:"",completed_nodes:[],state:f,initial_state:{...p,...l},status:"pending"};this.saveRun(m);return m}getState(e){const t=this.loadRun(e);if(!t){throw new Error(`Execution not found: ${e}`)}return t.state}setState(e,t){this.atomicUpdate(e,o=>{Object.assign(o.state,t)})}moveNode(e,t){return this.atomicUpdate(e,o=>{if(o.status==="cancelled")return;if(o.current_node&&!o.completed_nodes.includes(o.current_node)){o.completed_nodes.push(o.current_node)}o.current_node=t})}async executeNode(e,t,o){const r=this.loadRun(e);if(!r){throw new Error(`Execution not found: ${e}`)}if(r.status==="cancelled"){throw(0,d.createWorkflowFailureError)("CANCELLED",`Workflow run "${e}" is already cancelled`)}const s=this.loadWorkflowYaml(r.workflow_file);if(!s){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Failed to load workflow file: ${r.workflow_file}`)}const i=s.workflows[r.workflow_id];if(!i){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Workflow "${r.workflow_id}" not found in ${r.workflow_file}`)}const n=i.nodes?.[t];if(!n){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}" not found in workflow "${r.workflow_id}"`)}const c=r.current_node;const p={};const l=[];let f=t;let m;let _;let S;let C;const w=o?.trigger??"manual";const k=new Date;let F=null;let H=null;let X=null;let q;let Z;const I=u=>{F=u.status;H=u.signal;X=u.errorOrigin;q=Ye(u)};const B=async()=>{const u=(0,N.generateId)("tsk");await this.atomicUpdateAsync(e,h=>{h.tasks=h.tasks??{};h.tasks[t]=u});C=u;return u};const Q=u=>{const h=new Date;return{exec_id:r.id,node_id:t,type:n.type,summary:Je(n),started_at:k.toISOString(),ended_at:h.toISOString(),duration_ms:h.getTime()-k.getTime(),exit_code:F,...u?{error:u}:{},trigger:w}};const g=await this.reserveNodeAttempt(e,t);try{switch(n.type){case"agent_task":{if(!n.agent){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}" is agent_task but has no agent defined`)}const u={...r.state,state:r.state};const h=b(n.agent,u).trim();if(!h){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": assignee not resolved (agent field "${n.agent}" interpolated to empty)`)}const E=(0,U.normalizeCwd)(n.cwd?b(n.cwd,u):void 0,this.projectRoot);if(!E.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${E.error}`)}const $=E.resolved;const v=(0,U.normalizeCwd)(void 0,this.projectRoot);if(!v.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${v.error}`)}const y=v.resolved;const G=n.input?b(n.input,u):"";const j=process.env.CREWX_WORKFLOW_PREAMBLE==="off"?"":(0,K.buildWorkflowPreamble)({workflowId:r.workflow_id,nodeId:t,execId:r.id});const P=n.output_format==="json"?j+G+(0,K.buildJsonOutputSuffix)(n.output_schema,n.output_strict):j+G;const T=n.mode==="query"?"q":"x";const L=`@${h} ${P}`;if(o?.dryRun){console.log(`[dry-run] Would execute: crewx ${T} "@${h} ..."`);break}console.log(`Executing: @${h} (${T} mode)`);const A=Number(process.env[T==="q"?"CREWX_TIMEOUT_QUERY":"CREWX_TIMEOUT_EXECUTE"])||8*36e5;const O=Se({...process.env,CREWX_WORKSPACE:y,CREWX_WORKFLOW_EXEC_ID:r.id,CREWX_WORKFLOW_NODE_ID:t,CREWX_WORKFLOW_ID:r.workflow_id},y);const z=[...O.args,T,L];if(process.env.CREWX_WORKFLOW_THREAD==="on"){z.push(`--thread=workflow:${r.id}`)}const te={workflow:{exec_id:r.id,node_id:t,workflow_id:r.workflow_id}};z.push("--metadata",JSON.stringify(te));const Ne=(0,N.resolveWindowsSpawnProgram)({command:O.command,env:O.env,cwd:$,allowShellFallback:true,packageName:"crewx"});const re=(0,N.materializeWindowsSpawnProgram)(Ne,z);const je=await B();const D=await ue(re.command,re.argv,{cwd:$,env:(0,N.withoutCrewxRootContext)({...O.env,CREWX_TRACE_ID:je}),timeout:A,shell:re.shell??false,windowsHide:re.windowsHide,cancellation:this.processCancellation(e)});I(D);if(D.cancelled){throw(0,d.createWorkflowFailureError)("CANCELLED",`Agent execution cancelled for node "${t}"`)}if(D.timedOut){throw new Error(`Agent execution timed out after ${A}ms`)}if(D.error){throw D.error}const Oe=D.stderr.match(/crewx kill (tsk_\w+)/);if(Oe?.[1])C=Oe[1];if(D.status!==0){const ne=D.stderr.trim();throw new Error(`Agent execution failed (exit ${D.status}): ${ne}`)}const oe=D.stdout.trim();const ve=(0,K.detectWorkflowSignal)(oe);if(ve){throw new Error(`Node "${t}" agent signaled failure: ${ve.reason??"(no reason given)"}`)}if(n.output){if(n.output_format==="json"){const ne=n.output_retry??1;let se=null;let we=null;let Ce=P;for(let he=0;he<=ne;he++){let _e=oe;if(he>0){Ce=P+`
2
+
3
+ ## \u26A0\uFE0F PREVIOUS OUTPUT FAILED JSON.parse: ${we.message}
4
+ Return STRICT JSON ONLY. First char "{", last char "}". No prose, no fences.`;const me=`@${h} ${Ce}`;const ge=[...O.args,T,me];if(process.env.CREWX_WORKFLOW_THREAD==="on"){ge.push(`--thread=workflow:${r.id}`)}ge.push("--metadata",JSON.stringify(te));const ae=(0,N.materializeWindowsSpawnProgram)(Ne,ge);const Pe=await B();const x=await ue(ae.command,ae.argv,{cwd:$,env:(0,N.withoutCrewxRootContext)({...O.env,CREWX_TRACE_ID:Pe}),timeout:A,shell:ae.shell??false,windowsHide:ae.windowsHide,cancellation:this.processCancellation(e)});I(x);if(x.cancelled){throw(0,d.createWorkflowFailureError)("CANCELLED",`Agent retry cancelled for node "${t}"`)}if(x.timedOut)throw new Error(`Agent retry timed out after ${A}ms`);if(x.error)throw x.error;if(x.status!==0){throw new Error(`Agent retry failed (exit ${x.status}): ${x.stderr.trim()}`)}_e=x.stdout.trim();const $e=(0,K.detectWorkflowSignal)(_e);if($e){throw new Error(`Node "${t}" agent signaled failure: ${$e.reason??"(no reason given)"}`)}}try{se=JSON.parse((0,K.extractJson)(_e));if(n.output_schema){await(0,K.validateJsonSchema)(se,n.output_schema)}break}catch(me){we=me}}if(se===null){throw(0,d.createWorkflowFailureError)("OUTPUT_INVALID",`Node "${t}" output_format=json parse failed after ${ne+1} attempt(s): ${we.message}`)}p[n.output]=se;console.log(`Result stored in state.${n.output} (JSON object)`)}else{p[n.output]=oe;console.log(`Result stored in state.${n.output} (${oe.length} chars)`)}}break}case"skill_task":{if(!n.skill){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}" is skill_task but has no skill defined`)}if(!U.SKILL_NAME_RE.test(n.skill)){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}" has invalid skill name "${n.skill}" (must match ^[a-z0-9][a-z0-9-]*$)`)}const u={...r.state,state:r.state};const h=(n.args??[]).map(O=>b(String(O),u));const E=n.stdin!==void 0?b(String(n.stdin),u):void 0;const $=(0,U.normalizeCwd)(n.cwd?b(n.cwd,u):void 0,this.projectRoot);if(!$.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${$.error}`)}const v=(0,U.normalizeCwd)(void 0,this.projectRoot);if(!v.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${v.error}`)}const y=v.resolved;const G=n.timeout??exports.SKILL_TASK_DEFAULT_TIMEOUT;if(o?.dryRun){console.log(`[dry-run] Would execute: crewx skill ${n.skill} ${h.join(" ")}`.trim());break}console.log(`Executing skill: ${n.skill} ${h.join(" ")}`.trim());const j=Se({...process.env,CREWX_WORKSPACE:y,CREWX_WORKFLOW_EXEC_ID:r.id,CREWX_WORKFLOW_NODE_ID:t,CREWX_WORKFLOW_ID:r.workflow_id},y);const P=[...j.args,"skill",n.skill,...h];const T=(0,N.resolveWindowsSpawnProgram)({command:j.command,env:j.env,cwd:$.resolved,allowShellFallback:true,packageName:"crewx"});const L=(0,N.materializeWindowsSpawnProgram)(T,P);const A=await ue(L.command,L.argv,{cwd:$.resolved,env:j.env,timeout:G,shell:L.shell??false,windowsHide:L.windowsHide,stdin:E,cancellation:this.processCancellation(e)});I(A);if(A.cancelled){throw(0,d.createWorkflowFailureError)("CANCELLED",`skill_task "${t}" was cancelled`)}if(A.timedOut)throw new Error(`skill_task "${t}" timed out after ${G}ms`);if(A.error)throw A.error;if(A.status!==0){throw new Error(`skill_task "${t}" failed (exit ${A.status}): ${A.stderr.trim()}`)}await Le(n,t,A.stdout,p);if(n.output){console.log(`Result stored in state.${n.output}`)}break}case"shell_task":{const u=process.env.CREWX_WORKFLOW_SHELL==="1";const h=i.metadata?.shell_task_allowed===true;if(!u||!h){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID","shell_task is disabled. Set CREWX_WORKFLOW_SHELL=1 and metadata.shell_task_allowed=true to enable.")}const E=(0,U.validateShellCommand)(n.command);if(!E.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${E.error}`)}const $=(0,U.validateEnvKeys)(n.env);if(!$.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${$.error}`)}const v={...r.state,state:r.state};const y=n.command.map(z=>b(z,v));const G=(0,U.validateShellCommand)(y);if(!G.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${G.error}`)}const j=n.env?Object.fromEntries(Object.entries(n.env).map(([z,te])=>[z,b(String(te),v)])):void 0;const P=(0,U.normalizeCwd)(n.cwd?b(n.cwd,v):void 0,this.projectRoot);if(!P.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${P.error}`)}const T=n.timeout??exports.SKILL_TASK_DEFAULT_TIMEOUT;if(o?.dryRun){console.log(`[dry-run] Would execute: ${y.join(" ")}`);break}console.log(`Executing shell_task: ${y[0]} (${y.length-1} args)`);const L=(0,U.buildShellEnv)(j,process.env);L.CREWX_WORKFLOW_EXEC_ID=r.id;L.CREWX_WORKFLOW_NODE_ID=t;L.CREWX_WORKFLOW_ID=r.workflow_id;const A=n.stdin!==void 0?b(String(n.stdin),v):void 0;const O=await ue(y[0],y.slice(1),{cwd:P.resolved,env:L,timeout:T,shell:false,stdin:A,cancellation:this.processCancellation(e)});I(O);if(O.cancelled){throw(0,d.createWorkflowFailureError)("CANCELLED",`shell_task "${t}" was cancelled`)}if(O.timedOut)throw new Error(`shell_task "${t}" timed out after ${T}ms`);if(O.error)throw O.error;if(O.status!==0){throw new Error(`shell_task "${t}" failed (exit ${O.status}): ${O.stderr.trim()}`)}await Le(n,t,O.stdout,p);if(n.output){console.log(`Result stored in state.${n.output}`)}break}case"branch":{if(!n.condition){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Branch node "${t}" has no condition`)}const u=String(de(String(n.condition),r.state));const h=n.branches;const E=h?.[u]??n.default;if(!E){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Branch "${t}": no matching branch for "${u}" and no default`)}console.log(`Branch "${t}": condition="${n.condition}" \u2192 ${u} \u2192 ${E}`);l.push(t);f=E;break}case"parallel":{const u=n.branches;if(u&&Array.isArray(u)){console.log(`Parallel "${t}": branches = [${u.join(", ")}]`);console.log(` Enter each branch sequentially with: workflow run node <exec-id> <branch-node>`)}break}case"join":{console.log(`Join "${t}": completed_nodes = [${r.completed_nodes.join(", ")}]`);break}case"end":{m="completed";console.log(`End "${t}": workflow execution completed`);break}case"error":{m="failed";S="UNKNOWN";const u={...r.state,state:r.state};_=n.message?b(n.message,u):`Workflow failed at error node "${t}"`;console.log(`Error "${t}": workflow execution failed \u2014 ${_}`);break}case"expression":{if(!n.set||typeof n.set!=="object"){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Expression node "${t}" requires "set" field`)}for(const[u,h]of Object.entries(n.set)){const E=Ee(String(h),r.state);p[u]=String(E);console.log(` ${u} = ${E}`)}console.log(`Expression "${t}": set ${Object.keys(n.set).join(", ")}`);if(n.next){l.push(t);f=n.next}break}case"approval":{console.log(`Approval "${t}": manual intervention required (not auto-executed)`);break}default:console.log(`Unknown node type "${n.type}" for node "${t}"`)}if(n.fail_when&&!o?.dryRun&&(n.type==="agent_task"||n.type==="skill_task"||n.type==="shell_task")){const u=de(String(n.fail_when),{...r.state,...p});if(u===true||u==="true"){const h=n.output?p[n.output]:void 0;if(h&&typeof h==="object"&&!Array.isArray(h)){const E=h.reason;if(typeof E==="string"){const $=(0,N.redactTechnicalMessage)(E);const v=$?.split(/\r\n|\r|\n/u).find(y=>y.trim());if(v)Z=v.trim().slice(0,180)}}throw new Error(`Node "${t}" judged failed by fail_when: ${n.fail_when}`)}}}catch(u){const h=J(u);const E=(0,d.classifyWorkflowFailure)({errorCode:(0,d.getWorkflowFailureCode)(u),process:q});const $=(0,d.buildNodeFailure)({failedNode:t,attempt:g,causeCode:E,exitCode:E==="NODE_EXIT_NONZERO"&&typeof F==="number"?F:null,signal:E==="INTERRUPTED"?H:null,errorOrigin:X,timedOut:q?.timedOut??false});const v=Q(Z??$.causeMessage);await this.atomicUpdateAsync(e,y=>{if(y.status==="cancelled")return;Object.assign(y.state,p);delete y.manual_resume;y.current_node=t;V(y,$,Z??h);y.audit=y.audit??[];y.audit.push(v)});throw u}const M=S?(0,d.buildNodeFailure)({failedNode:t,attempt:g,causeCode:S,exitCode:null,signal:null,errorOrigin:null}):null;const pe=Q(M?.causeMessage);return this.atomicUpdateAsync(e,u=>{if(u.status==="cancelled")return;Object.assign(u.state,p);delete u.manual_resume;for(const h of l){if(!u.completed_nodes.includes(h)){u.completed_nodes.push(h)}}if(c&&c!==t&&!u.completed_nodes.includes(c)){u.completed_nodes.push(c)}u.current_node=f;if(M){V(u,M,_??M.causeMessage)}else{delete u.error;delete u.last_failure;this.clearNodeAttempt(u,t);if(m){u.status=m}else if(u.status!=="completed"){u.status="running"}}if(C){u.tasks=u.tasks??{};u.tasks[t]=C}u.audit=u.audit??[];u.audit.push(pe)})}async claimAutoRunLease(e,t,o){let r=false;const s=await this.atomicUpdateAsync(e,i=>{if(Re(i.status))return;const n=i.auto_run;const c=n?.pid===process.pid&&typeof n.token==="string";if(n&&!c&&De(n.pid)){throw new Error(`Workflow run "${e}" is already owned by another runner process`)}const p=o??(c?n.token:(0,N.generateId)("wfr"));if(!Re(i.status)){i.status="running"}if(!i.current_node)i.current_node=t;i.auto_run={pid:process.pid,token:p,node_id:t,started_at:c&&n.token===p?n.started_at:new Date().toISOString()};delete i.manual_resume;r=true});return r?s.auto_run?.token??null:null}async releaseAutoRunLease(e,t){await this.atomicUpdateAsync(e,o=>{if(o.auto_run?.token===t)delete o.auto_run})}async failAutoConfiguration(e,t,o){return this.atomicUpdateAsync(e,r=>{if(r.status==="cancelled")return;const s=(0,d.buildNodeFailure)({failedNode:t,attempt:(0,d.readNodeAttempt)(r,t),causeCode:"CONFIG_INVALID",exitCode:null,signal:null,errorOrigin:null});V(r,s,o);Y(r,s,"config","auto")})}async reserveAutoRetry(e,t,o,r,s){let i=false;const n=await this.atomicUpdateAsync(e,c=>{if(c.status==="cancelled")return;if(c.status!=="failed")return;if(c.auto_run?.token!==o)return;const p=(0,ce.decideRetry)({policy:r,run:c,nodeId:t});if(!p.retry||p.attempt===void 0)return;const l=new Date().toISOString();c.status="retrying";delete c.error;delete c.manual_resume;c.audit=c.audit??[];c.audit.push({exec_id:c.id,node_id:t,type:"retry",summary:`auto retry ${p.attempt+1}/${r.max+1} in ${s}ms`,started_at:l,ended_at:l,duration_ms:0,exit_code:null,trigger:"auto"});i=true});return{run:n,reserved:i}}async waitForRetry(e,t,o){const r=()=>{const c=this.loadRun(e);if(!c)throw new Error(`Execution not found: ${e}`);if(c.status==="cancelled"){return{run:c,outcome:"cancelled",reason:"run cancelled during retry delay"}}if(c.auto_run?.token!==t){return{run:c,outcome:"paused",reason:"auto-run lease lost during retry delay"}}if(c.status!=="retrying"){return{run:c,outcome:"paused",reason:"run state changed during retry delay"}}return null};let s=Math.max(0,o);while(s>0){const c=Math.min(s,qe);await this.sleep(c);s-=c;const p=r();if(p)return p}const i=r();if(i)return i;const n=this.loadRun(e);if(!n)throw new Error(`Execution not found: ${e}`);return{run:n,outcome:"ready"}}async runNodeWithRetry(e,t,o,r){let s=o;while(true){try{const i=await this.executeNode(e,t,{trigger:"auto"});if(i.status==="cancelled"){return{run:i,runnerToken:s,outcome:"cancelled",reason:i.error}}return{run:i,runnerToken:s,outcome:"success"}}catch(i){const n=this.loadRun(e);if(!n)throw new Error(`Execution not found: ${e}`);if(n.status==="cancelled"){return{run:n,runnerToken:s,outcome:"cancelled",reason:n.error}}if(n.status!=="failed"||!r){return{run:n,runnerToken:s,outcome:n.status==="failed"?"failed":"paused",reason:J(i)}}const c=(0,ce.decideRetry)({policy:r,run:n,nodeId:t});if(!c.retry||c.attempt===void 0){return{run:n,runnerToken:s,outcome:"failed",reason:J(i)}}const p=(0,ce.computeRetryDelayMs)(r,c.attempt);const l=await this.reserveAutoRetry(e,t,s,r,p);if(!l.reserved){const _=l.run;if(_.status==="cancelled"){return{run:_,runnerToken:s,outcome:"cancelled",reason:_.error}}return{run:_,runnerToken:s,outcome:_.status==="failed"?"failed":"paused",reason:J(i)}}const f=await this.waitForRetry(e,s,p);if(f.outcome!=="ready"){return{run:f.run,runnerToken:s,outcome:f.outcome,reason:f.reason}}const m=await this.claimAutoRunLease(e,t,s);if(!m){const _=this.loadRun(e);if(!_)throw new Error(`Execution not found: ${e}`);if(_.status==="cancelled"){return{run:_,runnerToken:s,outcome:"cancelled",reason:_.error}}return{run:_,runnerToken:s,outcome:"paused",reason:"run stopped before retry lease could be reclaimed"}}s=m}}}async runAuto(e){const t=`${this.resolvedDir}::${e}`;if(ke.has(t)){const r=this.loadRun(e);if(!r)throw new Error(`Execution not found: ${e}`);console.log(`runAuto "${e}": already in-flight \u2014 skipping duplicate auto-run (single-flight guard)`);return{run:r,outcome:"paused",reason:"execution already in-flight (single-flight guard)",executed:[]}}ke.add(t);let o;try{const r=this.loadRun(e);if(!r)throw new Error(`Execution not found: ${e}`);if(r.auto_run?.pid===process.pid){o=r.auto_run.token}const s=this.loadWorkflowYaml(r.workflow_file);if(!s){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Failed to load workflow file: ${r.workflow_file}`)}const i=s.workflows[r.workflow_id];if(!i){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Workflow "${r.workflow_id}" not found in ${r.workflow_file}`)}const n=[];if(r.status==="completed"||r.status==="failed"||r.status==="cancelled"){return{run:r,outcome:r.status==="completed"?"completed":r.status==="cancelled"?"cancelled":"failed",reason:`run already ${r.status}`,executed:n}}const c=Object.keys(i.nodes??{})[0];const p=typeof i.max_iterations==="number"&&i.max_iterations>0?i.max_iterations:30;let l=r;while(true){l=this.loadRun(e);if(l.status==="completed")return{run:l,outcome:"completed",executed:n};if(l.status==="failed")return{run:l,outcome:"failed",reason:l.error,executed:n};if(l.status==="cancelled")return{run:l,outcome:"cancelled",reason:l.error,executed:n};const f=l.current_node||c;if(!f){l=await this.atomicUpdateAsync(e,w=>{const k=(0,d.buildNodeFailure)({failedNode:w.current_node||"",attempt:(0,d.readNodeAttempt)(w,w.current_node||""),causeCode:"CONFIG_INVALID",exitCode:null,signal:null,errorOrigin:null});V(w,k,"workflow has no nodes");Y(w,k,"config","auto")});return{run:l,outcome:"failed",reason:l.error,executed:n}}const m=i.nodes[f];if(!m){l=await this.atomicUpdateAsync(e,w=>{w.current_node=f;const k=(0,d.buildNodeFailure)({failedNode:f,attempt:(0,d.readNodeAttempt)(w,f),causeCode:"CONFIG_INVALID",exitCode:null,signal:null,errorOrigin:null});V(w,k,`node "${f}" not found in workflow`);Y(w,k,"config","auto")});return{run:l,outcome:"failed",reason:l.error,executed:n}}const _=(0,ce.parseRetryPolicy)(m);if(!_.ok){l=await this.failAutoConfiguration(e,f,_.reason);if(l.status==="cancelled"){return{run:l,outcome:"cancelled",reason:l.error,executed:n}}return{run:l,outcome:"failed",reason:l.error,executed:n}}if(m.type==="approval"){if(l.current_node!==f){l=await this.atomicUpdateAsync(e,w=>{w.current_node=f})}return{run:l,outcome:"paused",reason:`approval node "${f}" requires manual decision`,executed:n}}if(m.type==="parallel"){if(l.current_node!==f){l=await this.atomicUpdateAsync(e,w=>{w.current_node=f})}return{run:l,outcome:"paused",reason:`parallel auto-run not implemented (node "${f}")`,executed:n}}if(!Ae.has(m.type)){if(l.current_node!==f){l=await this.atomicUpdateAsync(e,w=>{w.current_node=f})}return{run:l,outcome:"paused",reason:`unknown node type "${m.type}" (node "${f}")`,executed:n}}const S=await this.claimAutoRunLease(e,f,o);if(!S){l=this.loadRun(e);return{run:l,outcome:l.status==="cancelled"?"cancelled":"paused",reason:l.status==="cancelled"?l.error:"run stopped before auto-run lease could be claimed",executed:n}}o=S;l=await this.atomicUpdateAsync(e,w=>{if(w.status==="cancelled")return;const k=at(w);k.visitCounts[f]=(k.visitCounts[f]??0)+1;k.iterations+=1;w.state["__engine"]=k;if(k.visitCounts[f]>p||k.iterations>p){const F=(0,d.buildNodeFailure)({failedNode:f,attempt:(0,d.readNodeAttempt)(w,f),causeCode:"CONFIG_INVALID",exitCode:null,signal:null,errorOrigin:null});V(w,F,`auto runner exceeded max_iterations (${p}); possible loop at "${f}"`);Y(w,F,"config","auto")}});if(l.status==="cancelled"){return{run:l,outcome:"cancelled",reason:l.error,executed:n}}if(l.status==="failed"){return{run:l,outcome:"failed",reason:l.error,executed:n}}const C=await this.runNodeWithRetry(e,f,o,_.policy);l=C.run;o=C.runnerToken;if(C.outcome!=="success"){return{run:l,outcome:C.outcome,reason:C.reason,executed:n}}n.push(f);if(l.status==="completed")return{run:l,outcome:"completed",executed:n};if(l.status==="failed")return{run:l,outcome:"failed",reason:l.error,executed:n};if(l.status==="cancelled")return{run:l,outcome:"cancelled",reason:l.error,executed:n};if(!Xe.has(m.type)){const w=m.next;if(!w){return{run:l,outcome:"paused",reason:`node "${f}" has no "next"; stopping`,executed:n}}l=this.moveNode(e,w)}}}finally{if(o){try{await this.releaseAutoRunLease(e,o)}catch{}}ke.delete(t)}}cancelRun(e,t="Cancelled by user"){const o=this.atomicUpdate(e,r=>{if(r.status==="completed"||r.status==="failed"||r.status==="cancelled"){return}const s=new Date;const i=r.auto_run?.node_id||r.current_node||"";const n=(0,d.buildNodeFailure)({failedNode:i,attempt:(0,d.readNodeAttempt)(r,i),causeCode:"CANCELLED",exitCode:null,signal:null,errorOrigin:null});V(r,n,t);r.status="cancelled";delete r.auto_run;delete r.manual_resume;Y(r,n,"cancel","manual",s.toISOString())});if(o.status==="cancelled"){it(this.resolvedDir,e)}return o}reset(e){return this.atomicUpdate(e,t=>{t.state=JSON.parse(JSON.stringify(t.initial_state));t.current_node="";t.completed_nodes=[];t.status="pending";delete t.error;delete t.last_failure;delete t.node_attempts;delete t.auto_run;delete t.manual_resume})}retry(e,t={}){const o=this.loadRun(e);if(!o)throw new Error(`Execution not found: ${e}`);const r=this.loadWorkflowYaml(o.workflow_file);if(!r)throw new Error(`Failed to load workflow file: ${o.workflow_file}`);const s=r.workflows[o.workflow_id];if(!s)throw new Error(`Workflow "${o.workflow_id}" not found`);if(o.status!=="failed"){throw new Error(`Run "${e}" is not failed (status=${o.status??"running"}); only failed runs can be retried`)}if(!Ue(o,s)){throw new Error(`Run "${e}" failed at a non-retryable node "${o.current_node}" (error node); use reset to restart`)}return this.atomicUpdate(e,i=>{if(i.status!=="failed"){throw new Error(`Run "${e}" is not failed (status=${i.status??"pending"}); only failed runs can be retried`)}const n=i.current_node;if(n&&i.last_failure){const p=i.last_failure.attempt;const l=i.node_attempts?.[n];if(typeof p==="number"&&Number.isInteger(p)&&p>0&&(typeof l!=="number"||l<p)){i.node_attempts=i.node_attempts??{};i.node_attempts[n]=p}}i.status="retrying";delete i.error;delete i.manual_resume;i.retry_count=(i.retry_count??0)+1;i.audit=i.audit??[];const c=new Date().toISOString();if(t.autoRun===false){delete i.auto_run;i.manual_resume={node_id:n,requested_at:c}}else{i.auto_run={pid:process.pid,token:(0,N.generateId)("wfr"),node_id:n,started_at:c}}i.audit.push({exec_id:i.id,node_id:n,type:"retry",summary:`manual retry #${i.retry_count}`,started_at:c,ended_at:c,duration_ms:0,exit_code:null,trigger:"manual"})})}listRunIds(){try{return R.readdirSync(this.resolvedDir,{withFileTypes:true}).filter(e=>e.isFile()&&!e.name.startsWith(".")&&e.name.endsWith(".json")).map(e=>e.name.slice(0,-".json".length))}catch(e){if(e.code==="ENOENT")return[];throw e}}listRuns(e){this.ensureRunsDir();const t=R.readdirSync(this.resolvedDir).filter(r=>r.endsWith(".json"));const o=[];for(const r of t){try{const s=r.slice(0,-".json".length);const i=this.loadRun(s);if(!i)continue;if(e?.workflowId&&i.workflow_id!==e.workflowId)continue;o.push(i)}catch{}}o.sort((r,s)=>new Date(s.created_at).getTime()-new Date(r.created_at).getTime());return o}}exports.RunManager=ct;