@crewx/workflow 0.3.22-rc.136 → 0.3.22-rc.138
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +190 -2
- package/dist/cli.js.map +1 -1
- package/dist/src/card-state.d.ts +20 -0
- package/dist/src/card-state.d.ts.map +1 -0
- package/dist/src/card-state.js +121 -0
- package/dist/src/card-state.js.map +1 -0
- package/dist/src/engine.d.ts +7 -2
- package/dist/src/engine.d.ts.map +1 -1
- package/dist/src/engine.js +12 -1
- package/dist/src/engine.js.map +1 -1
- package/dist/src/parent-task-sync.d.ts +72 -1
- package/dist/src/parent-task-sync.d.ts.map +1 -1
- package/dist/src/parent-task-sync.js +188 -13
- package/dist/src/parent-task-sync.js.map +1 -1
- package/dist/src/run-manager.d.ts +9 -2
- package/dist/src/run-manager.d.ts.map +1 -1
- package/dist/src/run-manager.js +236 -20
- package/dist/src/run-manager.js.map +1 -1
- package/dist/src/workflow-persistence.d.ts +48 -0
- package/dist/src/workflow-persistence.d.ts.map +1 -0
- package/dist/src/workflow-persistence.js +224 -0
- package/dist/src/workflow-persistence.js.map +1 -0
- package/package.json +4 -4
package/dist/src/run-manager.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.RunManager = exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS = exports.WORKFLOW_RUN_INTERRUPTED_ERROR = exports.SKILL_TASK_DEFAULT_TIMEOUT = exports.FileLock = void 0;
|
|
36
|
+
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;
|
|
37
37
|
exports.parseSetArgs = parseSetArgs;
|
|
38
38
|
exports.readStdin = readStdin;
|
|
39
39
|
exports.interpolateTemplate = interpolateTemplate;
|
|
@@ -55,6 +55,8 @@ const retry_policy_1 = require("./retry-policy");
|
|
|
55
55
|
var file_lock_2 = require("./file-lock");
|
|
56
56
|
Object.defineProperty(exports, "FileLock", { enumerable: true, get: function () { return file_lock_2.FileLock; } });
|
|
57
57
|
exports.SKILL_TASK_DEFAULT_TIMEOUT = 600000;
|
|
58
|
+
exports.WORKFLOW_CANCEL_GRACE_MS = 2000;
|
|
59
|
+
const WORKFLOW_CANCEL_POLL_INTERVAL_MS = 100;
|
|
58
60
|
exports.WORKFLOW_RUN_INTERRUPTED_ERROR = 'Workflow run interrupted: runner process exited before completion';
|
|
59
61
|
exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS = 10 * 60000;
|
|
60
62
|
const RETRY_WAIT_SLICE_MS = 1000;
|
|
@@ -80,20 +82,23 @@ function resolveCrewxChildInvocation(env, cwd) {
|
|
|
80
82
|
},
|
|
81
83
|
};
|
|
82
84
|
}
|
|
83
|
-
function killProcessGroup(child) {
|
|
85
|
+
function killProcessGroup(child, signal = 'SIGKILL') {
|
|
84
86
|
try {
|
|
85
87
|
if (process.platform === 'win32') {
|
|
86
88
|
if (child.pid) {
|
|
87
|
-
|
|
89
|
+
const args = ['/pid', String(child.pid), '/T'];
|
|
90
|
+
if (signal === 'SIGKILL')
|
|
91
|
+
args.push('/F');
|
|
92
|
+
cp.spawn('taskkill', args, { stdio: 'ignore' });
|
|
88
93
|
}
|
|
89
94
|
}
|
|
90
95
|
else if (child.pid) {
|
|
91
96
|
try {
|
|
92
|
-
process.kill(-child.pid,
|
|
97
|
+
process.kill(-child.pid, signal);
|
|
93
98
|
}
|
|
94
99
|
catch {
|
|
95
100
|
try {
|
|
96
|
-
child.kill(
|
|
101
|
+
child.kill(signal);
|
|
97
102
|
}
|
|
98
103
|
catch { }
|
|
99
104
|
}
|
|
@@ -103,6 +108,26 @@ function killProcessGroup(child) {
|
|
|
103
108
|
}
|
|
104
109
|
async function runProcess(command, argv, opts) {
|
|
105
110
|
return new Promise((resolve) => {
|
|
111
|
+
const isCancelled = () => {
|
|
112
|
+
try {
|
|
113
|
+
return opts.cancellation?.isCancelled() === true;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
if (isCancelled()) {
|
|
120
|
+
resolve({
|
|
121
|
+
status: null,
|
|
122
|
+
stdout: '',
|
|
123
|
+
stderr: '',
|
|
124
|
+
timedOut: false,
|
|
125
|
+
cancelled: true,
|
|
126
|
+
signal: null,
|
|
127
|
+
errorOrigin: null,
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
106
131
|
let child;
|
|
107
132
|
try {
|
|
108
133
|
child = cp.spawn(command, argv, {
|
|
@@ -129,19 +154,65 @@ async function runProcess(command, argv, opts) {
|
|
|
129
154
|
let stdout = '';
|
|
130
155
|
let stderr = '';
|
|
131
156
|
let settled = false;
|
|
157
|
+
let finishing = false;
|
|
132
158
|
let spawned = false;
|
|
159
|
+
let cancellationRequested = false;
|
|
133
160
|
let postSpawnError;
|
|
134
161
|
let stdinError;
|
|
162
|
+
let timeoutTimer;
|
|
163
|
+
let cancellationEscalationTimer;
|
|
164
|
+
let cancellationPollTimer;
|
|
165
|
+
let activeController;
|
|
166
|
+
const outDecoder = new sdk_1.ConsoleDecoder();
|
|
167
|
+
const errDecoder = new sdk_1.ConsoleDecoder();
|
|
168
|
+
let stdoutDecodeChain = Promise.resolve();
|
|
169
|
+
let stderrDecodeChain = Promise.resolve();
|
|
170
|
+
const cleanup = () => {
|
|
171
|
+
if (timeoutTimer)
|
|
172
|
+
clearTimeout(timeoutTimer);
|
|
173
|
+
if (cancellationEscalationTimer)
|
|
174
|
+
clearTimeout(cancellationEscalationTimer);
|
|
175
|
+
if (cancellationPollTimer)
|
|
176
|
+
clearInterval(cancellationPollTimer);
|
|
177
|
+
if (opts.cancellation
|
|
178
|
+
&& activeController
|
|
179
|
+
&& activeProcessControllers.get(opts.cancellation.key) === activeController) {
|
|
180
|
+
activeProcessControllers.delete(opts.cancellation.key);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
135
183
|
const finish = (result) => {
|
|
136
184
|
if (settled)
|
|
137
185
|
return;
|
|
138
186
|
settled = true;
|
|
139
|
-
|
|
140
|
-
resolve(result);
|
|
187
|
+
cleanup();
|
|
188
|
+
resolve(cancellationRequested ? { ...result, cancelled: true, timedOut: false } : result);
|
|
141
189
|
};
|
|
142
|
-
const
|
|
190
|
+
const requestCancellation = () => {
|
|
191
|
+
if (settled || cancellationRequested)
|
|
192
|
+
return;
|
|
193
|
+
cancellationRequested = true;
|
|
194
|
+
killProcessGroup(child, 'SIGTERM');
|
|
195
|
+
cancellationEscalationTimer = setTimeout(() => {
|
|
196
|
+
if (!settled)
|
|
197
|
+
killProcessGroup(child, 'SIGKILL');
|
|
198
|
+
}, exports.WORKFLOW_CANCEL_GRACE_MS);
|
|
199
|
+
if (typeof cancellationEscalationTimer.unref === 'function') {
|
|
200
|
+
cancellationEscalationTimer.unref();
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
if (opts.cancellation) {
|
|
204
|
+
activeController = { cancel: requestCancellation };
|
|
205
|
+
activeProcessControllers.set(opts.cancellation.key, activeController);
|
|
206
|
+
cancellationPollTimer = setInterval(() => {
|
|
207
|
+
if (isCancelled())
|
|
208
|
+
requestCancellation();
|
|
209
|
+
}, WORKFLOW_CANCEL_POLL_INTERVAL_MS);
|
|
210
|
+
if (typeof cancellationPollTimer.unref === 'function')
|
|
211
|
+
cancellationPollTimer.unref();
|
|
212
|
+
}
|
|
213
|
+
timeoutTimer = setTimeout(() => {
|
|
143
214
|
killProcessGroup(child);
|
|
144
|
-
|
|
215
|
+
finishAfterDecode(() => ({
|
|
145
216
|
status: null,
|
|
146
217
|
stdout,
|
|
147
218
|
stderr,
|
|
@@ -149,12 +220,20 @@ async function runProcess(command, argv, opts) {
|
|
|
149
220
|
signal: null,
|
|
150
221
|
errorOrigin: stdinError ? 'stdin' : postSpawnError ? 'post_spawn' : null,
|
|
151
222
|
...(stdinError || postSpawnError ? { error: stdinError ?? postSpawnError } : {}),
|
|
152
|
-
});
|
|
223
|
+
}));
|
|
153
224
|
}, opts.timeout);
|
|
154
|
-
if (typeof
|
|
155
|
-
|
|
156
|
-
child.stdout?.on('data', (d) => {
|
|
157
|
-
|
|
225
|
+
if (typeof timeoutTimer.unref === 'function')
|
|
226
|
+
timeoutTimer.unref();
|
|
227
|
+
child.stdout?.on('data', (d) => {
|
|
228
|
+
stdoutDecodeChain = stdoutDecodeChain.then(async () => {
|
|
229
|
+
stdout += await outDecoder.write(d);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
child.stderr?.on('data', (d) => {
|
|
233
|
+
stderrDecodeChain = stderrDecodeChain.then(async () => {
|
|
234
|
+
stderr += await errDecoder.write(d);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
158
237
|
child.once('spawn', () => { spawned = true; });
|
|
159
238
|
child.on('error', (err) => {
|
|
160
239
|
if (!spawned) {
|
|
@@ -182,7 +261,19 @@ async function runProcess(command, argv, opts) {
|
|
|
182
261
|
stdinError = stdinError ?? err;
|
|
183
262
|
}
|
|
184
263
|
}
|
|
185
|
-
|
|
264
|
+
const finishAfterDecode = (createResult) => {
|
|
265
|
+
if (settled || finishing)
|
|
266
|
+
return;
|
|
267
|
+
finishing = true;
|
|
268
|
+
void (async () => {
|
|
269
|
+
await Promise.all([stdoutDecodeChain, stderrDecodeChain]);
|
|
270
|
+
const [tailOut, tailErr] = await Promise.all([outDecoder.end(), errDecoder.end()]);
|
|
271
|
+
stdout += tailOut;
|
|
272
|
+
stderr += tailErr;
|
|
273
|
+
finish(createResult());
|
|
274
|
+
})().catch(() => finish(createResult()));
|
|
275
|
+
};
|
|
276
|
+
child.on('close', (code, signal) => finishAfterDecode(() => ({
|
|
186
277
|
status: code,
|
|
187
278
|
stdout,
|
|
188
279
|
stderr,
|
|
@@ -190,7 +281,9 @@ async function runProcess(command, argv, opts) {
|
|
|
190
281
|
signal,
|
|
191
282
|
errorOrigin: stdinError ? 'stdin' : postSpawnError ? 'post_spawn' : null,
|
|
192
283
|
...(stdinError || postSpawnError ? { error: stdinError ?? postSpawnError } : {}),
|
|
193
|
-
}));
|
|
284
|
+
})));
|
|
285
|
+
if (isCancelled())
|
|
286
|
+
requestCancellation();
|
|
194
287
|
});
|
|
195
288
|
}
|
|
196
289
|
const AUDIT_IDENTIFIER_RE = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
@@ -663,6 +756,13 @@ function readRunEngineState(run) {
|
|
|
663
756
|
return { visitCounts, iterations };
|
|
664
757
|
}
|
|
665
758
|
const inFlightAutoRuns = new Set();
|
|
759
|
+
const activeProcessControllers = new Map();
|
|
760
|
+
function processControllerKey(resolvedDir, execId) {
|
|
761
|
+
return `${resolvedDir}::${execId}`;
|
|
762
|
+
}
|
|
763
|
+
function requestActiveProcessCancellation(resolvedDir, execId) {
|
|
764
|
+
activeProcessControllers.get(processControllerKey(resolvedDir, execId))?.cancel();
|
|
765
|
+
}
|
|
666
766
|
function isProcessAlive(pid) {
|
|
667
767
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
668
768
|
return false;
|
|
@@ -721,6 +821,7 @@ class RunManager {
|
|
|
721
821
|
constructor(runsDir = '.crewx/workflow-runs', projectRoot, deps) {
|
|
722
822
|
this.runsDir = runsDir;
|
|
723
823
|
this.sleep = deps?.sleep ?? sleep;
|
|
824
|
+
this.onRunPersisted = deps?.onRunPersisted;
|
|
724
825
|
const base = projectRoot || process.env.CREWX_WORKSPACE || process.cwd();
|
|
725
826
|
this.resolvedDir = path.resolve(base, this.runsDir);
|
|
726
827
|
this.projectRoot = path.resolve(base);
|
|
@@ -740,6 +841,20 @@ class RunManager {
|
|
|
740
841
|
throw new Error(`Invalid execution ID: "${execId}"`);
|
|
741
842
|
}
|
|
742
843
|
}
|
|
844
|
+
isRunCancelled(execId) {
|
|
845
|
+
try {
|
|
846
|
+
return this.readRunRaw(execId)?.status === 'cancelled';
|
|
847
|
+
}
|
|
848
|
+
catch {
|
|
849
|
+
return false;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
processCancellation(execId) {
|
|
853
|
+
return {
|
|
854
|
+
key: processControllerKey(this.resolvedDir, execId),
|
|
855
|
+
isCancelled: () => this.isRunCancelled(execId),
|
|
856
|
+
};
|
|
857
|
+
}
|
|
743
858
|
readRunRaw(execId) {
|
|
744
859
|
const filePath = path.join(this.resolvedDir, `${execId}.json`);
|
|
745
860
|
if (!fs.existsSync(filePath))
|
|
@@ -752,6 +867,7 @@ class RunManager {
|
|
|
752
867
|
}
|
|
753
868
|
}
|
|
754
869
|
readRun(execId) {
|
|
870
|
+
this.validateExecId(execId);
|
|
755
871
|
const run = this.readRunRaw(execId);
|
|
756
872
|
return run ? normalizeRunStatus(run) : null;
|
|
757
873
|
}
|
|
@@ -772,6 +888,24 @@ class RunManager {
|
|
|
772
888
|
const node = spec?.nodes?.[run.current_node];
|
|
773
889
|
return Boolean(node && KNOWN_AUTO_NODE_TYPES.has(node.type));
|
|
774
890
|
}
|
|
891
|
+
withRunLock(execId, callback) {
|
|
892
|
+
this.validateExecId(execId);
|
|
893
|
+
const filePath = path.join(this.resolvedDir, `${execId}.json`);
|
|
894
|
+
if (!fs.existsSync(filePath)) {
|
|
895
|
+
throw new Error(`Execution not found: ${execId}`);
|
|
896
|
+
}
|
|
897
|
+
const lock = new file_lock_1.FileLock(filePath);
|
|
898
|
+
lock.acquire();
|
|
899
|
+
try {
|
|
900
|
+
const run = this.readRunRaw(execId);
|
|
901
|
+
if (!run)
|
|
902
|
+
throw new Error(`Execution not found: ${execId}`);
|
|
903
|
+
return callback(normalizeRunStatus(run));
|
|
904
|
+
}
|
|
905
|
+
finally {
|
|
906
|
+
lock.release();
|
|
907
|
+
}
|
|
908
|
+
}
|
|
775
909
|
hasStaleRunner(run) {
|
|
776
910
|
if (run.auto_run)
|
|
777
911
|
return !isProcessAlive(run.auto_run.pid);
|
|
@@ -824,7 +958,53 @@ class RunManager {
|
|
|
824
958
|
this.ensureRunsDir();
|
|
825
959
|
run.updated_at = new Date().toISOString();
|
|
826
960
|
const filePath = path.join(this.resolvedDir, `${run.id}.json`);
|
|
827
|
-
|
|
961
|
+
let existingMode;
|
|
962
|
+
try {
|
|
963
|
+
existingMode = fs.statSync(filePath).mode & 0o7777;
|
|
964
|
+
}
|
|
965
|
+
catch (error) {
|
|
966
|
+
if (error.code !== 'ENOENT')
|
|
967
|
+
throw error;
|
|
968
|
+
}
|
|
969
|
+
const tempPath = path.join(this.resolvedDir, `.${run.id}.tmp-${process.pid}-${(0, sdk_1.generateId)('wfr')}`);
|
|
970
|
+
const serialized = JSON.stringify(run, null, 2) + '\n';
|
|
971
|
+
let tempFd;
|
|
972
|
+
let tempCreated = false;
|
|
973
|
+
try {
|
|
974
|
+
tempFd = fs.openSync(tempPath, 'wx');
|
|
975
|
+
tempCreated = true;
|
|
976
|
+
fs.writeFileSync(tempFd, serialized, 'utf-8');
|
|
977
|
+
if (existingMode !== undefined)
|
|
978
|
+
fs.fchmodSync(tempFd, existingMode);
|
|
979
|
+
fs.closeSync(tempFd);
|
|
980
|
+
tempFd = undefined;
|
|
981
|
+
fs.renameSync(tempPath, filePath);
|
|
982
|
+
tempCreated = false;
|
|
983
|
+
}
|
|
984
|
+
finally {
|
|
985
|
+
if (tempFd !== undefined) {
|
|
986
|
+
try {
|
|
987
|
+
fs.closeSync(tempFd);
|
|
988
|
+
}
|
|
989
|
+
catch {
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
if (tempCreated) {
|
|
993
|
+
try {
|
|
994
|
+
fs.unlinkSync(tempPath);
|
|
995
|
+
}
|
|
996
|
+
catch {
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
if (!this.onRunPersisted)
|
|
1001
|
+
return;
|
|
1002
|
+
try {
|
|
1003
|
+
this.onRunPersisted(run);
|
|
1004
|
+
}
|
|
1005
|
+
catch (error) {
|
|
1006
|
+
console.warn(`[RunManager] onRunPersisted failed for ${run.id}: ${getErrorMessage(error)}`);
|
|
1007
|
+
}
|
|
828
1008
|
}
|
|
829
1009
|
atomicUpdate(execId, updater) {
|
|
830
1010
|
this.validateExecId(execId);
|
|
@@ -959,6 +1139,8 @@ class RunManager {
|
|
|
959
1139
|
}
|
|
960
1140
|
moveNode(execId, nodeId) {
|
|
961
1141
|
return this.atomicUpdate(execId, (run) => {
|
|
1142
|
+
if (run.status === 'cancelled')
|
|
1143
|
+
return;
|
|
962
1144
|
if (run.current_node && !run.completed_nodes.includes(run.current_node)) {
|
|
963
1145
|
run.completed_nodes.push(run.current_node);
|
|
964
1146
|
}
|
|
@@ -970,6 +1152,9 @@ class RunManager {
|
|
|
970
1152
|
if (!snapshot) {
|
|
971
1153
|
throw new Error(`Execution not found: ${execId}`);
|
|
972
1154
|
}
|
|
1155
|
+
if (snapshot.status === 'cancelled') {
|
|
1156
|
+
throw (0, failure_1.createWorkflowFailureError)('CANCELLED', `Workflow run "${execId}" is already cancelled`);
|
|
1157
|
+
}
|
|
973
1158
|
const doc = this.loadWorkflowYaml(snapshot.workflow_file);
|
|
974
1159
|
if (!doc) {
|
|
975
1160
|
throw (0, failure_1.createWorkflowFailureError)('CONFIG_INVALID', `Failed to load workflow file: ${snapshot.workflow_file}`);
|
|
@@ -1108,8 +1293,12 @@ class RunManager {
|
|
|
1108
1293
|
timeout: nodeTimeout,
|
|
1109
1294
|
shell: invocation.shell ?? false,
|
|
1110
1295
|
windowsHide: invocation.windowsHide,
|
|
1296
|
+
cancellation: this.processCancellation(execId),
|
|
1111
1297
|
});
|
|
1112
1298
|
recordProcessResult(result);
|
|
1299
|
+
if (result.cancelled) {
|
|
1300
|
+
throw (0, failure_1.createWorkflowFailureError)('CANCELLED', `Agent execution cancelled for node "${nodeId}"`);
|
|
1301
|
+
}
|
|
1113
1302
|
if (result.timedOut) {
|
|
1114
1303
|
throw new Error(`Agent execution timed out after ${nodeTimeout}ms`);
|
|
1115
1304
|
}
|
|
@@ -1159,8 +1348,12 @@ class RunManager {
|
|
|
1159
1348
|
timeout: nodeTimeout,
|
|
1160
1349
|
shell: retryInvocation.shell ?? false,
|
|
1161
1350
|
windowsHide: retryInvocation.windowsHide,
|
|
1351
|
+
cancellation: this.processCancellation(execId),
|
|
1162
1352
|
});
|
|
1163
1353
|
recordProcessResult(retryResult);
|
|
1354
|
+
if (retryResult.cancelled) {
|
|
1355
|
+
throw (0, failure_1.createWorkflowFailureError)('CANCELLED', `Agent retry cancelled for node "${nodeId}"`);
|
|
1356
|
+
}
|
|
1164
1357
|
if (retryResult.timedOut)
|
|
1165
1358
|
throw new Error(`Agent retry timed out after ${nodeTimeout}ms`);
|
|
1166
1359
|
if (retryResult.error)
|
|
@@ -1248,8 +1441,12 @@ class RunManager {
|
|
|
1248
1441
|
shell: invocation.shell ?? false,
|
|
1249
1442
|
windowsHide: invocation.windowsHide,
|
|
1250
1443
|
stdin: skillStdin,
|
|
1444
|
+
cancellation: this.processCancellation(execId),
|
|
1251
1445
|
});
|
|
1252
1446
|
recordProcessResult(result);
|
|
1447
|
+
if (result.cancelled) {
|
|
1448
|
+
throw (0, failure_1.createWorkflowFailureError)('CANCELLED', `skill_task "${nodeId}" was cancelled`);
|
|
1449
|
+
}
|
|
1253
1450
|
if (result.timedOut)
|
|
1254
1451
|
throw new Error(`skill_task "${nodeId}" timed out after ${timeout}ms`);
|
|
1255
1452
|
if (result.error)
|
|
@@ -1309,8 +1506,12 @@ class RunManager {
|
|
|
1309
1506
|
timeout,
|
|
1310
1507
|
shell: false,
|
|
1311
1508
|
stdin: shellStdin,
|
|
1509
|
+
cancellation: this.processCancellation(execId),
|
|
1312
1510
|
});
|
|
1313
1511
|
recordProcessResult(result);
|
|
1512
|
+
if (result.cancelled) {
|
|
1513
|
+
throw (0, failure_1.createWorkflowFailureError)('CANCELLED', `shell_task "${nodeId}" was cancelled`);
|
|
1514
|
+
}
|
|
1314
1515
|
if (result.timedOut)
|
|
1315
1516
|
throw new Error(`shell_task "${nodeId}" timed out after ${timeout}ms`);
|
|
1316
1517
|
if (result.error)
|
|
@@ -1489,6 +1690,8 @@ class RunManager {
|
|
|
1489
1690
|
if (!isTerminalStatus(run.status)) {
|
|
1490
1691
|
run.status = 'running';
|
|
1491
1692
|
}
|
|
1693
|
+
if (!run.current_node)
|
|
1694
|
+
run.current_node = nodeId;
|
|
1492
1695
|
run.auto_run = {
|
|
1493
1696
|
pid: process.pid,
|
|
1494
1697
|
token,
|
|
@@ -1594,6 +1797,9 @@ class RunManager {
|
|
|
1594
1797
|
while (true) {
|
|
1595
1798
|
try {
|
|
1596
1799
|
const run = await this.executeNode(execId, nodeId, { trigger: 'auto' });
|
|
1800
|
+
if (run.status === 'cancelled') {
|
|
1801
|
+
return { run, runnerToken: token, outcome: 'cancelled', reason: run.error };
|
|
1802
|
+
}
|
|
1597
1803
|
return { run, runnerToken: token, outcome: 'success' };
|
|
1598
1804
|
}
|
|
1599
1805
|
catch (caught) {
|
|
@@ -1766,6 +1972,8 @@ class RunManager {
|
|
|
1766
1972
|
}
|
|
1767
1973
|
runnerToken = claimedToken;
|
|
1768
1974
|
run = await this.atomicUpdateAsync(execId, (r) => {
|
|
1975
|
+
if (r.status === 'cancelled')
|
|
1976
|
+
return;
|
|
1769
1977
|
const engine = readRunEngineState(r);
|
|
1770
1978
|
engine.visitCounts[nodeId] = (engine.visitCounts[nodeId] ?? 0) + 1;
|
|
1771
1979
|
engine.iterations += 1;
|
|
@@ -1783,6 +1991,9 @@ class RunManager {
|
|
|
1783
1991
|
appendFailureAudit(r, failure, 'config', 'auto');
|
|
1784
1992
|
}
|
|
1785
1993
|
});
|
|
1994
|
+
if (run.status === 'cancelled') {
|
|
1995
|
+
return { run, outcome: 'cancelled', reason: run.error, executed };
|
|
1996
|
+
}
|
|
1786
1997
|
if (run.status === 'failed') {
|
|
1787
1998
|
return { run, outcome: 'failed', reason: run.error, executed };
|
|
1788
1999
|
}
|
|
@@ -1820,14 +2031,15 @@ class RunManager {
|
|
|
1820
2031
|
}
|
|
1821
2032
|
}
|
|
1822
2033
|
cancelRun(execId, reason = 'Cancelled by user') {
|
|
1823
|
-
|
|
2034
|
+
const cancelled = this.atomicUpdate(execId, (run) => {
|
|
1824
2035
|
if (run.status === 'completed' || run.status === 'failed' || run.status === 'cancelled') {
|
|
1825
2036
|
return;
|
|
1826
2037
|
}
|
|
1827
2038
|
const now = new Date();
|
|
2039
|
+
const activeNodeId = run.auto_run?.node_id || run.current_node || '';
|
|
1828
2040
|
const failure = (0, failure_1.buildNodeFailure)({
|
|
1829
|
-
failedNode:
|
|
1830
|
-
attempt: (0, failure_1.readNodeAttempt)(run,
|
|
2041
|
+
failedNode: activeNodeId,
|
|
2042
|
+
attempt: (0, failure_1.readNodeAttempt)(run, activeNodeId),
|
|
1831
2043
|
causeCode: 'CANCELLED',
|
|
1832
2044
|
exitCode: null,
|
|
1833
2045
|
signal: null,
|
|
@@ -1839,6 +2051,10 @@ class RunManager {
|
|
|
1839
2051
|
delete run.manual_resume;
|
|
1840
2052
|
appendFailureAudit(run, failure, 'cancel', 'manual', now.toISOString());
|
|
1841
2053
|
});
|
|
2054
|
+
if (cancelled.status === 'cancelled') {
|
|
2055
|
+
requestActiveProcessCancellation(this.resolvedDir, execId);
|
|
2056
|
+
}
|
|
2057
|
+
return cancelled;
|
|
1842
2058
|
}
|
|
1843
2059
|
reset(execId) {
|
|
1844
2060
|
return this.atomicUpdate(execId, (run) => {
|