@crewx/workflow 0.3.22-rc.137 → 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 +205 -14
- 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 +3 -3
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, {
|
|
@@ -131,20 +156,61 @@ async function runProcess(command, argv, opts) {
|
|
|
131
156
|
let settled = false;
|
|
132
157
|
let finishing = false;
|
|
133
158
|
let spawned = false;
|
|
159
|
+
let cancellationRequested = false;
|
|
134
160
|
let postSpawnError;
|
|
135
161
|
let stdinError;
|
|
162
|
+
let timeoutTimer;
|
|
163
|
+
let cancellationEscalationTimer;
|
|
164
|
+
let cancellationPollTimer;
|
|
165
|
+
let activeController;
|
|
136
166
|
const outDecoder = new sdk_1.ConsoleDecoder();
|
|
137
167
|
const errDecoder = new sdk_1.ConsoleDecoder();
|
|
138
168
|
let stdoutDecodeChain = Promise.resolve();
|
|
139
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
|
+
};
|
|
140
183
|
const finish = (result) => {
|
|
141
184
|
if (settled)
|
|
142
185
|
return;
|
|
143
186
|
settled = true;
|
|
144
|
-
|
|
145
|
-
resolve(result);
|
|
187
|
+
cleanup();
|
|
188
|
+
resolve(cancellationRequested ? { ...result, cancelled: true, timedOut: false } : result);
|
|
146
189
|
};
|
|
147
|
-
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(() => {
|
|
148
214
|
killProcessGroup(child);
|
|
149
215
|
finishAfterDecode(() => ({
|
|
150
216
|
status: null,
|
|
@@ -156,8 +222,8 @@ async function runProcess(command, argv, opts) {
|
|
|
156
222
|
...(stdinError || postSpawnError ? { error: stdinError ?? postSpawnError } : {}),
|
|
157
223
|
}));
|
|
158
224
|
}, opts.timeout);
|
|
159
|
-
if (typeof
|
|
160
|
-
|
|
225
|
+
if (typeof timeoutTimer.unref === 'function')
|
|
226
|
+
timeoutTimer.unref();
|
|
161
227
|
child.stdout?.on('data', (d) => {
|
|
162
228
|
stdoutDecodeChain = stdoutDecodeChain.then(async () => {
|
|
163
229
|
stdout += await outDecoder.write(d);
|
|
@@ -216,6 +282,8 @@ async function runProcess(command, argv, opts) {
|
|
|
216
282
|
errorOrigin: stdinError ? 'stdin' : postSpawnError ? 'post_spawn' : null,
|
|
217
283
|
...(stdinError || postSpawnError ? { error: stdinError ?? postSpawnError } : {}),
|
|
218
284
|
})));
|
|
285
|
+
if (isCancelled())
|
|
286
|
+
requestCancellation();
|
|
219
287
|
});
|
|
220
288
|
}
|
|
221
289
|
const AUDIT_IDENTIFIER_RE = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
@@ -688,6 +756,13 @@ function readRunEngineState(run) {
|
|
|
688
756
|
return { visitCounts, iterations };
|
|
689
757
|
}
|
|
690
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
|
+
}
|
|
691
766
|
function isProcessAlive(pid) {
|
|
692
767
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
693
768
|
return false;
|
|
@@ -746,6 +821,7 @@ class RunManager {
|
|
|
746
821
|
constructor(runsDir = '.crewx/workflow-runs', projectRoot, deps) {
|
|
747
822
|
this.runsDir = runsDir;
|
|
748
823
|
this.sleep = deps?.sleep ?? sleep;
|
|
824
|
+
this.onRunPersisted = deps?.onRunPersisted;
|
|
749
825
|
const base = projectRoot || process.env.CREWX_WORKSPACE || process.cwd();
|
|
750
826
|
this.resolvedDir = path.resolve(base, this.runsDir);
|
|
751
827
|
this.projectRoot = path.resolve(base);
|
|
@@ -765,6 +841,20 @@ class RunManager {
|
|
|
765
841
|
throw new Error(`Invalid execution ID: "${execId}"`);
|
|
766
842
|
}
|
|
767
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
|
+
}
|
|
768
858
|
readRunRaw(execId) {
|
|
769
859
|
const filePath = path.join(this.resolvedDir, `${execId}.json`);
|
|
770
860
|
if (!fs.existsSync(filePath))
|
|
@@ -777,6 +867,7 @@ class RunManager {
|
|
|
777
867
|
}
|
|
778
868
|
}
|
|
779
869
|
readRun(execId) {
|
|
870
|
+
this.validateExecId(execId);
|
|
780
871
|
const run = this.readRunRaw(execId);
|
|
781
872
|
return run ? normalizeRunStatus(run) : null;
|
|
782
873
|
}
|
|
@@ -797,6 +888,24 @@ class RunManager {
|
|
|
797
888
|
const node = spec?.nodes?.[run.current_node];
|
|
798
889
|
return Boolean(node && KNOWN_AUTO_NODE_TYPES.has(node.type));
|
|
799
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
|
+
}
|
|
800
909
|
hasStaleRunner(run) {
|
|
801
910
|
if (run.auto_run)
|
|
802
911
|
return !isProcessAlive(run.auto_run.pid);
|
|
@@ -849,7 +958,53 @@ class RunManager {
|
|
|
849
958
|
this.ensureRunsDir();
|
|
850
959
|
run.updated_at = new Date().toISOString();
|
|
851
960
|
const filePath = path.join(this.resolvedDir, `${run.id}.json`);
|
|
852
|
-
|
|
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
|
+
}
|
|
853
1008
|
}
|
|
854
1009
|
atomicUpdate(execId, updater) {
|
|
855
1010
|
this.validateExecId(execId);
|
|
@@ -984,6 +1139,8 @@ class RunManager {
|
|
|
984
1139
|
}
|
|
985
1140
|
moveNode(execId, nodeId) {
|
|
986
1141
|
return this.atomicUpdate(execId, (run) => {
|
|
1142
|
+
if (run.status === 'cancelled')
|
|
1143
|
+
return;
|
|
987
1144
|
if (run.current_node && !run.completed_nodes.includes(run.current_node)) {
|
|
988
1145
|
run.completed_nodes.push(run.current_node);
|
|
989
1146
|
}
|
|
@@ -995,6 +1152,9 @@ class RunManager {
|
|
|
995
1152
|
if (!snapshot) {
|
|
996
1153
|
throw new Error(`Execution not found: ${execId}`);
|
|
997
1154
|
}
|
|
1155
|
+
if (snapshot.status === 'cancelled') {
|
|
1156
|
+
throw (0, failure_1.createWorkflowFailureError)('CANCELLED', `Workflow run "${execId}" is already cancelled`);
|
|
1157
|
+
}
|
|
998
1158
|
const doc = this.loadWorkflowYaml(snapshot.workflow_file);
|
|
999
1159
|
if (!doc) {
|
|
1000
1160
|
throw (0, failure_1.createWorkflowFailureError)('CONFIG_INVALID', `Failed to load workflow file: ${snapshot.workflow_file}`);
|
|
@@ -1133,8 +1293,12 @@ class RunManager {
|
|
|
1133
1293
|
timeout: nodeTimeout,
|
|
1134
1294
|
shell: invocation.shell ?? false,
|
|
1135
1295
|
windowsHide: invocation.windowsHide,
|
|
1296
|
+
cancellation: this.processCancellation(execId),
|
|
1136
1297
|
});
|
|
1137
1298
|
recordProcessResult(result);
|
|
1299
|
+
if (result.cancelled) {
|
|
1300
|
+
throw (0, failure_1.createWorkflowFailureError)('CANCELLED', `Agent execution cancelled for node "${nodeId}"`);
|
|
1301
|
+
}
|
|
1138
1302
|
if (result.timedOut) {
|
|
1139
1303
|
throw new Error(`Agent execution timed out after ${nodeTimeout}ms`);
|
|
1140
1304
|
}
|
|
@@ -1184,8 +1348,12 @@ class RunManager {
|
|
|
1184
1348
|
timeout: nodeTimeout,
|
|
1185
1349
|
shell: retryInvocation.shell ?? false,
|
|
1186
1350
|
windowsHide: retryInvocation.windowsHide,
|
|
1351
|
+
cancellation: this.processCancellation(execId),
|
|
1187
1352
|
});
|
|
1188
1353
|
recordProcessResult(retryResult);
|
|
1354
|
+
if (retryResult.cancelled) {
|
|
1355
|
+
throw (0, failure_1.createWorkflowFailureError)('CANCELLED', `Agent retry cancelled for node "${nodeId}"`);
|
|
1356
|
+
}
|
|
1189
1357
|
if (retryResult.timedOut)
|
|
1190
1358
|
throw new Error(`Agent retry timed out after ${nodeTimeout}ms`);
|
|
1191
1359
|
if (retryResult.error)
|
|
@@ -1273,8 +1441,12 @@ class RunManager {
|
|
|
1273
1441
|
shell: invocation.shell ?? false,
|
|
1274
1442
|
windowsHide: invocation.windowsHide,
|
|
1275
1443
|
stdin: skillStdin,
|
|
1444
|
+
cancellation: this.processCancellation(execId),
|
|
1276
1445
|
});
|
|
1277
1446
|
recordProcessResult(result);
|
|
1447
|
+
if (result.cancelled) {
|
|
1448
|
+
throw (0, failure_1.createWorkflowFailureError)('CANCELLED', `skill_task "${nodeId}" was cancelled`);
|
|
1449
|
+
}
|
|
1278
1450
|
if (result.timedOut)
|
|
1279
1451
|
throw new Error(`skill_task "${nodeId}" timed out after ${timeout}ms`);
|
|
1280
1452
|
if (result.error)
|
|
@@ -1334,8 +1506,12 @@ class RunManager {
|
|
|
1334
1506
|
timeout,
|
|
1335
1507
|
shell: false,
|
|
1336
1508
|
stdin: shellStdin,
|
|
1509
|
+
cancellation: this.processCancellation(execId),
|
|
1337
1510
|
});
|
|
1338
1511
|
recordProcessResult(result);
|
|
1512
|
+
if (result.cancelled) {
|
|
1513
|
+
throw (0, failure_1.createWorkflowFailureError)('CANCELLED', `shell_task "${nodeId}" was cancelled`);
|
|
1514
|
+
}
|
|
1339
1515
|
if (result.timedOut)
|
|
1340
1516
|
throw new Error(`shell_task "${nodeId}" timed out after ${timeout}ms`);
|
|
1341
1517
|
if (result.error)
|
|
@@ -1514,6 +1690,8 @@ class RunManager {
|
|
|
1514
1690
|
if (!isTerminalStatus(run.status)) {
|
|
1515
1691
|
run.status = 'running';
|
|
1516
1692
|
}
|
|
1693
|
+
if (!run.current_node)
|
|
1694
|
+
run.current_node = nodeId;
|
|
1517
1695
|
run.auto_run = {
|
|
1518
1696
|
pid: process.pid,
|
|
1519
1697
|
token,
|
|
@@ -1619,6 +1797,9 @@ class RunManager {
|
|
|
1619
1797
|
while (true) {
|
|
1620
1798
|
try {
|
|
1621
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
|
+
}
|
|
1622
1803
|
return { run, runnerToken: token, outcome: 'success' };
|
|
1623
1804
|
}
|
|
1624
1805
|
catch (caught) {
|
|
@@ -1791,6 +1972,8 @@ class RunManager {
|
|
|
1791
1972
|
}
|
|
1792
1973
|
runnerToken = claimedToken;
|
|
1793
1974
|
run = await this.atomicUpdateAsync(execId, (r) => {
|
|
1975
|
+
if (r.status === 'cancelled')
|
|
1976
|
+
return;
|
|
1794
1977
|
const engine = readRunEngineState(r);
|
|
1795
1978
|
engine.visitCounts[nodeId] = (engine.visitCounts[nodeId] ?? 0) + 1;
|
|
1796
1979
|
engine.iterations += 1;
|
|
@@ -1808,6 +1991,9 @@ class RunManager {
|
|
|
1808
1991
|
appendFailureAudit(r, failure, 'config', 'auto');
|
|
1809
1992
|
}
|
|
1810
1993
|
});
|
|
1994
|
+
if (run.status === 'cancelled') {
|
|
1995
|
+
return { run, outcome: 'cancelled', reason: run.error, executed };
|
|
1996
|
+
}
|
|
1811
1997
|
if (run.status === 'failed') {
|
|
1812
1998
|
return { run, outcome: 'failed', reason: run.error, executed };
|
|
1813
1999
|
}
|
|
@@ -1845,14 +2031,15 @@ class RunManager {
|
|
|
1845
2031
|
}
|
|
1846
2032
|
}
|
|
1847
2033
|
cancelRun(execId, reason = 'Cancelled by user') {
|
|
1848
|
-
|
|
2034
|
+
const cancelled = this.atomicUpdate(execId, (run) => {
|
|
1849
2035
|
if (run.status === 'completed' || run.status === 'failed' || run.status === 'cancelled') {
|
|
1850
2036
|
return;
|
|
1851
2037
|
}
|
|
1852
2038
|
const now = new Date();
|
|
2039
|
+
const activeNodeId = run.auto_run?.node_id || run.current_node || '';
|
|
1853
2040
|
const failure = (0, failure_1.buildNodeFailure)({
|
|
1854
|
-
failedNode:
|
|
1855
|
-
attempt: (0, failure_1.readNodeAttempt)(run,
|
|
2041
|
+
failedNode: activeNodeId,
|
|
2042
|
+
attempt: (0, failure_1.readNodeAttempt)(run, activeNodeId),
|
|
1856
2043
|
causeCode: 'CANCELLED',
|
|
1857
2044
|
exitCode: null,
|
|
1858
2045
|
signal: null,
|
|
@@ -1864,6 +2051,10 @@ class RunManager {
|
|
|
1864
2051
|
delete run.manual_resume;
|
|
1865
2052
|
appendFailureAudit(run, failure, 'cancel', 'manual', now.toISOString());
|
|
1866
2053
|
});
|
|
2054
|
+
if (cancelled.status === 'cancelled') {
|
|
2055
|
+
requestActiveProcessCancellation(this.resolvedDir, execId);
|
|
2056
|
+
}
|
|
2057
|
+
return cancelled;
|
|
1867
2058
|
}
|
|
1868
2059
|
reset(execId) {
|
|
1869
2060
|
return this.atomicUpdate(execId, (run) => {
|