@serviceme/devtools-core 0.1.2 → 0.1.4
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/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +498 -227
- package/dist/index.mjs +495 -226
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -57,7 +57,9 @@ __export(index_exports, {
|
|
|
57
57
|
moveFiles: () => moveFiles,
|
|
58
58
|
noopLogger: () => noopLogger,
|
|
59
59
|
parseIntervalMs: () => parseIntervalMs,
|
|
60
|
-
|
|
60
|
+
resolveTaskExecutionPayload: () => resolveTaskExecutionPayload,
|
|
61
|
+
unzipFile: () => unzipFile,
|
|
62
|
+
validateTaskPayload: () => validateTaskPayload
|
|
61
63
|
});
|
|
62
64
|
module.exports = __toCommonJS(index_exports);
|
|
63
65
|
|
|
@@ -92,7 +94,8 @@ async function runCommand(command, options = {}) {
|
|
|
92
94
|
cwd: options.cwd,
|
|
93
95
|
env: options.env,
|
|
94
96
|
shell: options.shell,
|
|
95
|
-
stdio: "pipe"
|
|
97
|
+
stdio: "pipe",
|
|
98
|
+
windowsHide: true
|
|
96
99
|
});
|
|
97
100
|
let stdout = "";
|
|
98
101
|
let stderr = "";
|
|
@@ -1113,12 +1116,51 @@ var PidManager = class {
|
|
|
1113
1116
|
};
|
|
1114
1117
|
|
|
1115
1118
|
// src/scheduled-tasks/daemon/SchedulerDaemon.ts
|
|
1116
|
-
var
|
|
1119
|
+
var fs9 = __toESM(require("fs"));
|
|
1120
|
+
var os = __toESM(require("os"));
|
|
1117
1121
|
|
|
1118
1122
|
// src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
|
|
1119
1123
|
var import_node_child_process2 = require("child_process");
|
|
1120
|
-
var
|
|
1124
|
+
var fs5 = __toESM(require("fs"));
|
|
1125
|
+
|
|
1126
|
+
// src/scheduled-tasks/executors/timeout.ts
|
|
1127
|
+
function resolveConfiguredTimeoutMs(timeoutSeconds, defaultTimeoutMs) {
|
|
1128
|
+
if (timeoutSeconds === 0) {
|
|
1129
|
+
return void 0;
|
|
1130
|
+
}
|
|
1131
|
+
if (typeof timeoutSeconds !== "number" || !Number.isFinite(timeoutSeconds) || timeoutSeconds < 0) {
|
|
1132
|
+
return defaultTimeoutMs;
|
|
1133
|
+
}
|
|
1134
|
+
return timeoutSeconds * 1e3;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
|
|
1121
1138
|
var MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
|
|
1139
|
+
var DEFAULT_TIMEOUT_MS2 = 3e5;
|
|
1140
|
+
function redactArgs(args) {
|
|
1141
|
+
const redacted = [];
|
|
1142
|
+
let redactNext = false;
|
|
1143
|
+
for (const arg of args) {
|
|
1144
|
+
if (redactNext) {
|
|
1145
|
+
redacted.push("<redacted>");
|
|
1146
|
+
redactNext = false;
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
redacted.push(arg);
|
|
1150
|
+
if (arg === "--prompt") {
|
|
1151
|
+
redactNext = true;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return redacted;
|
|
1155
|
+
}
|
|
1156
|
+
function writeDiagnostic(message) {
|
|
1157
|
+
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1158
|
+
if (logPath) {
|
|
1159
|
+
fs5.appendFileSync(logPath, message);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
process.stderr.write(message);
|
|
1163
|
+
}
|
|
1122
1164
|
var GithubCopilotCliExecutor = class {
|
|
1123
1165
|
async execute(payload, abortSignal) {
|
|
1124
1166
|
const authenticated = await isCopilotAuthenticated();
|
|
@@ -1141,7 +1183,7 @@ var GithubCopilotCliExecutor = class {
|
|
|
1141
1183
|
}
|
|
1142
1184
|
executeStreaming(payload, onOutput, abortSignal) {
|
|
1143
1185
|
const p = payload;
|
|
1144
|
-
const
|
|
1186
|
+
const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS2);
|
|
1145
1187
|
let resolve;
|
|
1146
1188
|
const resultPromise = new Promise((r) => {
|
|
1147
1189
|
resolve = r;
|
|
@@ -1150,7 +1192,7 @@ var GithubCopilotCliExecutor = class {
|
|
|
1150
1192
|
const settle = (result) => {
|
|
1151
1193
|
if (settled) return;
|
|
1152
1194
|
settled = true;
|
|
1153
|
-
clearTimeout(timer);
|
|
1195
|
+
if (timer) clearTimeout(timer);
|
|
1154
1196
|
resolve?.(result);
|
|
1155
1197
|
};
|
|
1156
1198
|
if (abortSignal?.aborted) {
|
|
@@ -1176,23 +1218,32 @@ var GithubCopilotCliExecutor = class {
|
|
|
1176
1218
|
if (p.agent) {
|
|
1177
1219
|
args.push("--agent", p.agent);
|
|
1178
1220
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1221
|
+
args.push("--timeout", String(p.timeout ?? 0));
|
|
1222
|
+
writeDiagnostic(
|
|
1223
|
+
`[GithubCopilotCliExecutor] spawn: command=serviceme, args=${JSON.stringify(redactArgs(args))}, promptLen=${p.prompt.length}, cwd=${p.workspace ?? "(default)"}, platform=${process.platform}, windowsHide=true, pid=${process.pid}
|
|
1224
|
+
`
|
|
1225
|
+
);
|
|
1182
1226
|
const child = (0, import_node_child_process2.spawn)("serviceme", args, {
|
|
1183
1227
|
cwd: p.workspace,
|
|
1184
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1228
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1229
|
+
windowsHide: true
|
|
1185
1230
|
});
|
|
1186
|
-
|
|
1231
|
+
if (child.pid) {
|
|
1232
|
+
writeDiagnostic(
|
|
1233
|
+
`[GithubCopilotCliExecutor] spawned child PID=${child.pid}
|
|
1234
|
+
`
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
const timer = timeoutMs != null ? setTimeout(() => {
|
|
1187
1238
|
child.kill("SIGTERM");
|
|
1188
1239
|
setTimeout(() => {
|
|
1189
1240
|
if (!child.killed) child.kill("SIGKILL");
|
|
1190
1241
|
}, 5e3);
|
|
1191
1242
|
settle({
|
|
1192
1243
|
status: "timeout",
|
|
1193
|
-
error: `Copilot CLI execution timed out after ${
|
|
1244
|
+
error: `Copilot CLI execution timed out after ${timeoutMs / 1e3}s`
|
|
1194
1245
|
});
|
|
1195
|
-
},
|
|
1246
|
+
}, timeoutMs) : void 0;
|
|
1196
1247
|
let stdoutBuf = "";
|
|
1197
1248
|
let stderrBuf = "";
|
|
1198
1249
|
child.stdout.on("data", (chunk) => {
|
|
@@ -1236,19 +1287,19 @@ var GithubCopilotCliExecutor = class {
|
|
|
1236
1287
|
};
|
|
1237
1288
|
|
|
1238
1289
|
// src/scheduled-tasks/executors/HttpRequestExecutor.ts
|
|
1239
|
-
var
|
|
1290
|
+
var DEFAULT_TIMEOUT_MS3 = 3e4;
|
|
1240
1291
|
var HttpRequestExecutor = class {
|
|
1241
1292
|
async execute(payload, abortSignal) {
|
|
1242
1293
|
const p = payload;
|
|
1243
|
-
const
|
|
1294
|
+
const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS3);
|
|
1244
1295
|
const ac = new AbortController();
|
|
1245
|
-
let
|
|
1246
|
-
const timer = setTimeout(() => {
|
|
1247
|
-
|
|
1296
|
+
let timedOut = false;
|
|
1297
|
+
const timer = timeoutMs != null ? setTimeout(() => {
|
|
1298
|
+
timedOut = true;
|
|
1248
1299
|
ac.abort();
|
|
1249
|
-
},
|
|
1300
|
+
}, timeoutMs) : void 0;
|
|
1250
1301
|
if (abortSignal?.aborted) {
|
|
1251
|
-
clearTimeout(timer);
|
|
1302
|
+
if (timer) clearTimeout(timer);
|
|
1252
1303
|
return { status: "cancelled", error: "Execution aborted" };
|
|
1253
1304
|
}
|
|
1254
1305
|
let externalAbort = false;
|
|
@@ -1256,7 +1307,7 @@ var HttpRequestExecutor = class {
|
|
|
1256
1307
|
"abort",
|
|
1257
1308
|
() => {
|
|
1258
1309
|
externalAbort = true;
|
|
1259
|
-
clearTimeout(timer);
|
|
1310
|
+
if (timer) clearTimeout(timer);
|
|
1260
1311
|
ac.abort();
|
|
1261
1312
|
},
|
|
1262
1313
|
{ once: true }
|
|
@@ -1268,7 +1319,7 @@ var HttpRequestExecutor = class {
|
|
|
1268
1319
|
body: p.body,
|
|
1269
1320
|
signal: ac.signal
|
|
1270
1321
|
});
|
|
1271
|
-
clearTimeout(timer);
|
|
1322
|
+
if (timer) clearTimeout(timer);
|
|
1272
1323
|
const body = await response.text();
|
|
1273
1324
|
if (response.ok) {
|
|
1274
1325
|
return {
|
|
@@ -1283,14 +1334,17 @@ ${body}`.trim()
|
|
|
1283
1334
|
${body}`.trim()
|
|
1284
1335
|
};
|
|
1285
1336
|
} catch (err) {
|
|
1286
|
-
clearTimeout(timer);
|
|
1337
|
+
if (timer) clearTimeout(timer);
|
|
1287
1338
|
if (err instanceof Error && err.name === "AbortError") {
|
|
1288
1339
|
if (externalAbort) {
|
|
1289
1340
|
return { status: "cancelled", error: "Execution cancelled" };
|
|
1290
1341
|
}
|
|
1342
|
+
if (!timedOut || timeoutMs == null) {
|
|
1343
|
+
return { status: "failure", error: err.message };
|
|
1344
|
+
}
|
|
1291
1345
|
return {
|
|
1292
1346
|
status: "timeout",
|
|
1293
|
-
error: `HTTP request timed out after ${
|
|
1347
|
+
error: `HTTP request timed out after ${timeoutMs / 1e3}s`
|
|
1294
1348
|
};
|
|
1295
1349
|
}
|
|
1296
1350
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1301,8 +1355,82 @@ ${body}`.trim()
|
|
|
1301
1355
|
|
|
1302
1356
|
// src/scheduled-tasks/executors/ShellExecutor.ts
|
|
1303
1357
|
var import_node_child_process3 = require("child_process");
|
|
1304
|
-
var
|
|
1358
|
+
var fs6 = __toESM(require("fs"));
|
|
1359
|
+
var path5 = __toESM(require("path"));
|
|
1305
1360
|
var MAX_OUTPUT_BYTES2 = 1024 * 1024;
|
|
1361
|
+
var DEFAULT_TIMEOUT_MS4 = 6e4;
|
|
1362
|
+
var POSIX_SHELL_CANDIDATES = ["bash.exe", "sh.exe"];
|
|
1363
|
+
function resolveShellExecution(script, options = {}) {
|
|
1364
|
+
const platform = options.platform ?? process.platform;
|
|
1365
|
+
const env = options.env ?? process.env;
|
|
1366
|
+
const fileExists = options.fileExists ?? fs6.existsSync;
|
|
1367
|
+
if (platform === "win32") {
|
|
1368
|
+
const posixShell = usesPosixShellSyntax(script) ? findWindowsPosixShell(env, fileExists) : null;
|
|
1369
|
+
if (posixShell) {
|
|
1370
|
+
return {
|
|
1371
|
+
command: posixShell,
|
|
1372
|
+
args: ["-s"],
|
|
1373
|
+
stdinScript: script,
|
|
1374
|
+
outputEncoding: "utf8",
|
|
1375
|
+
shellKind: "posix"
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
return {
|
|
1379
|
+
command: env.ComSpec ?? env.COMSPEC ?? "cmd.exe",
|
|
1380
|
+
args: ["/d", "/s", "/c", script],
|
|
1381
|
+
outputEncoding: "utf8",
|
|
1382
|
+
shellKind: "cmd"
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
return {
|
|
1386
|
+
command: "sh",
|
|
1387
|
+
args: ["-s"],
|
|
1388
|
+
stdinScript: script,
|
|
1389
|
+
outputEncoding: "utf8",
|
|
1390
|
+
shellKind: "posix"
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
function usesPosixShellSyntax(script) {
|
|
1394
|
+
return /\$\([^)]*\)|`[^`]*`/.test(script);
|
|
1395
|
+
}
|
|
1396
|
+
function findWindowsPosixShell(env, fileExists) {
|
|
1397
|
+
const explicitShell = env.SERVICEME_POSIX_SHELL;
|
|
1398
|
+
if (explicitShell && fileExists(explicitShell)) {
|
|
1399
|
+
return explicitShell;
|
|
1400
|
+
}
|
|
1401
|
+
const knownGitBashPaths = [
|
|
1402
|
+
"C:\\Program Files\\Git\\bin\\bash.exe",
|
|
1403
|
+
"C:\\Program Files\\Git\\usr\\bin\\bash.exe",
|
|
1404
|
+
"C:\\Program Files (x86)\\Git\\bin\\bash.exe",
|
|
1405
|
+
"C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
|
|
1406
|
+
];
|
|
1407
|
+
for (const candidate of knownGitBashPaths) {
|
|
1408
|
+
if (fileExists(candidate)) return candidate;
|
|
1409
|
+
}
|
|
1410
|
+
const pathValue = env.Path ?? env.PATH ?? "";
|
|
1411
|
+
for (const dir of pathValue.split(path5.win32.delimiter)) {
|
|
1412
|
+
if (!dir) continue;
|
|
1413
|
+
for (const executable of POSIX_SHELL_CANDIDATES) {
|
|
1414
|
+
const candidate = path5.win32.join(dir, executable);
|
|
1415
|
+
if (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {
|
|
1416
|
+
return candidate;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
function isWindowsWslLauncher(candidate) {
|
|
1423
|
+
const normalized = path5.win32.normalize(candidate).toLowerCase();
|
|
1424
|
+
return normalized.endsWith("\\windows\\system32\\bash.exe") || normalized.endsWith("\\windows\\syswow64\\bash.exe");
|
|
1425
|
+
}
|
|
1426
|
+
function writeDiagnostic2(message) {
|
|
1427
|
+
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1428
|
+
if (logPath) {
|
|
1429
|
+
fs6.appendFileSync(logPath, message);
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
process.stderr.write(message);
|
|
1433
|
+
}
|
|
1306
1434
|
var ShellExecutor = class {
|
|
1307
1435
|
async execute(payload, abortSignal) {
|
|
1308
1436
|
let output = "";
|
|
@@ -1318,7 +1446,7 @@ var ShellExecutor = class {
|
|
|
1318
1446
|
}
|
|
1319
1447
|
executeStreaming(payload, onOutput, abortSignal) {
|
|
1320
1448
|
const p = payload;
|
|
1321
|
-
const
|
|
1449
|
+
const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS4);
|
|
1322
1450
|
let resolve;
|
|
1323
1451
|
const resultPromise = new Promise((r) => {
|
|
1324
1452
|
resolve = r;
|
|
@@ -1327,7 +1455,7 @@ var ShellExecutor = class {
|
|
|
1327
1455
|
const settle = (result) => {
|
|
1328
1456
|
if (settled) return;
|
|
1329
1457
|
settled = true;
|
|
1330
|
-
clearTimeout(timer);
|
|
1458
|
+
if (timer) clearTimeout(timer);
|
|
1331
1459
|
resolve?.(result);
|
|
1332
1460
|
};
|
|
1333
1461
|
if (abortSignal?.aborted) {
|
|
@@ -1340,37 +1468,65 @@ var ShellExecutor = class {
|
|
|
1340
1468
|
}
|
|
1341
1469
|
};
|
|
1342
1470
|
}
|
|
1343
|
-
const
|
|
1471
|
+
const shellExecution = resolveShellExecution(p.script);
|
|
1472
|
+
const spawnOpts = {
|
|
1344
1473
|
cwd: p.cwd,
|
|
1345
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1346
|
-
|
|
1347
|
-
|
|
1474
|
+
stdio: [shellExecution.stdinScript ? "pipe" : "ignore", "pipe", "pipe"],
|
|
1475
|
+
windowsHide: true
|
|
1476
|
+
};
|
|
1477
|
+
writeDiagnostic2(
|
|
1478
|
+
`[ShellExecutor] spawn:
|
|
1479
|
+
platform=${process.platform}
|
|
1480
|
+
shell=${shellExecution.shellKind}
|
|
1481
|
+
command=${shellExecution.command}
|
|
1482
|
+
args=${JSON.stringify(shellExecution.args.filter((a) => a !== p.script))}
|
|
1483
|
+
stdin=${shellExecution.stdinScript ? "true" : "false"}
|
|
1484
|
+
cwd=${p.cwd ?? "(default)"}
|
|
1485
|
+
timeout=${timeoutMs != null ? `${timeoutMs}ms` : "unlimited"}
|
|
1486
|
+
scriptLen=${p.script.length}
|
|
1487
|
+
windowsHide=true
|
|
1488
|
+
daemonPid=${process.pid}
|
|
1489
|
+
`
|
|
1490
|
+
);
|
|
1491
|
+
const child = (0, import_node_child_process3.spawn)(shellExecution.command, shellExecution.args, spawnOpts);
|
|
1492
|
+
if (shellExecution.stdinScript && child.stdin) {
|
|
1493
|
+
child.stdin.end(shellExecution.stdinScript);
|
|
1494
|
+
}
|
|
1495
|
+
if (child.pid) {
|
|
1496
|
+
writeDiagnostic2(`[ShellExecutor] spawned child PID=${child.pid}
|
|
1497
|
+
`);
|
|
1498
|
+
}
|
|
1499
|
+
const timer = timeoutMs != null ? setTimeout(() => {
|
|
1348
1500
|
child.kill("SIGTERM");
|
|
1349
1501
|
setTimeout(() => {
|
|
1350
1502
|
if (!child.killed) child.kill("SIGKILL");
|
|
1351
1503
|
}, 5e3);
|
|
1352
1504
|
settle({
|
|
1353
1505
|
status: "timeout",
|
|
1354
|
-
error: `Shell execution timed out after ${
|
|
1506
|
+
error: `Shell execution timed out after ${timeoutMs / 1e3}s`
|
|
1355
1507
|
});
|
|
1356
|
-
},
|
|
1508
|
+
}, timeoutMs) : void 0;
|
|
1357
1509
|
let stdoutBuf = "";
|
|
1358
1510
|
let stderrBuf = "";
|
|
1359
|
-
child.stdout
|
|
1360
|
-
const data = chunk.toString();
|
|
1511
|
+
child.stdout?.on("data", (chunk) => {
|
|
1512
|
+
const data = chunk.toString(shellExecution.outputEncoding);
|
|
1361
1513
|
stdoutBuf += data;
|
|
1362
1514
|
if (stdoutBuf.length > MAX_OUTPUT_BYTES2)
|
|
1363
1515
|
stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES2);
|
|
1364
1516
|
onOutput("stdout", data);
|
|
1365
1517
|
});
|
|
1366
|
-
child.stderr
|
|
1367
|
-
const data = chunk.toString();
|
|
1518
|
+
child.stderr?.on("data", (chunk) => {
|
|
1519
|
+
const data = chunk.toString(shellExecution.outputEncoding);
|
|
1368
1520
|
stderrBuf += data;
|
|
1369
1521
|
if (stderrBuf.length > MAX_OUTPUT_BYTES2)
|
|
1370
1522
|
stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES2);
|
|
1371
1523
|
onOutput("stderr", data);
|
|
1372
1524
|
});
|
|
1373
1525
|
child.on("close", (code) => {
|
|
1526
|
+
writeDiagnostic2(
|
|
1527
|
+
`[ShellExecutor] child PID=${child.pid} exited: code=${code}
|
|
1528
|
+
`
|
|
1529
|
+
);
|
|
1374
1530
|
if (code === 0) {
|
|
1375
1531
|
settle({ status: "success", output: stdoutBuf || void 0 });
|
|
1376
1532
|
} else {
|
|
@@ -1408,41 +1564,74 @@ var executors = {
|
|
|
1408
1564
|
github_copilot_cli: new GithubCopilotCliExecutor()
|
|
1409
1565
|
};
|
|
1410
1566
|
function getExecutor(taskType) {
|
|
1411
|
-
|
|
1567
|
+
const executor = executors[taskType];
|
|
1568
|
+
if (!executor) {
|
|
1569
|
+
throw new Error(`Unsupported bridge task type: ${taskType}`);
|
|
1570
|
+
}
|
|
1571
|
+
return executor;
|
|
1412
1572
|
}
|
|
1413
1573
|
|
|
1414
1574
|
// src/scheduled-tasks/TaskConfigManager.ts
|
|
1415
1575
|
var import_node_crypto = require("crypto");
|
|
1416
|
-
var
|
|
1417
|
-
var
|
|
1576
|
+
var fs7 = __toESM(require("fs"));
|
|
1577
|
+
var path6 = __toESM(require("path"));
|
|
1578
|
+
var import_devtools_protocol7 = require("@serviceme/devtools-protocol");
|
|
1418
1579
|
var CONFIG_DIR3 = ".serviceme";
|
|
1419
1580
|
var CONFIG_FILE = "scheduled-tasks.json";
|
|
1420
1581
|
function emptyConfig() {
|
|
1421
1582
|
return { version: 1, tasks: [] };
|
|
1422
1583
|
}
|
|
1584
|
+
function isRecord(value) {
|
|
1585
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1586
|
+
}
|
|
1587
|
+
function requireNonEmptyString(payload, field, taskType) {
|
|
1588
|
+
if (!isRecord(payload) || typeof payload[field] !== "string" || !payload[field].trim()) {
|
|
1589
|
+
throw (0, import_devtools_protocol7.createServicemeError)(
|
|
1590
|
+
"invalid_payload",
|
|
1591
|
+
`${taskType} payload ${field} is required`
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
function validateTaskPayload(taskType, payload) {
|
|
1596
|
+
switch (taskType) {
|
|
1597
|
+
case "command":
|
|
1598
|
+
requireNonEmptyString(payload, "command", taskType);
|
|
1599
|
+
return;
|
|
1600
|
+
case "shell":
|
|
1601
|
+
requireNonEmptyString(payload, "script", taskType);
|
|
1602
|
+
return;
|
|
1603
|
+
case "http_request":
|
|
1604
|
+
requireNonEmptyString(payload, "url", taskType);
|
|
1605
|
+
requireNonEmptyString(payload, "method", taskType);
|
|
1606
|
+
return;
|
|
1607
|
+
case "github_copilot_cli":
|
|
1608
|
+
requireNonEmptyString(payload, "prompt", taskType);
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1423
1612
|
var TaskConfigManager = class {
|
|
1424
1613
|
constructor(workspacePath) {
|
|
1425
|
-
this.configPath =
|
|
1614
|
+
this.configPath = path6.join(workspacePath, CONFIG_DIR3, CONFIG_FILE);
|
|
1426
1615
|
}
|
|
1427
1616
|
getConfigPath() {
|
|
1428
1617
|
return this.configPath;
|
|
1429
1618
|
}
|
|
1430
1619
|
readConfig() {
|
|
1431
|
-
if (!
|
|
1620
|
+
if (!fs7.existsSync(this.configPath)) {
|
|
1432
1621
|
return emptyConfig();
|
|
1433
1622
|
}
|
|
1434
|
-
const raw =
|
|
1623
|
+
const raw = fs7.readFileSync(this.configPath, "utf-8");
|
|
1435
1624
|
const parsed = JSON.parse(raw);
|
|
1436
1625
|
return parsed;
|
|
1437
1626
|
}
|
|
1438
1627
|
writeConfig(config) {
|
|
1439
|
-
const dir =
|
|
1440
|
-
if (!
|
|
1441
|
-
|
|
1628
|
+
const dir = path6.dirname(this.configPath);
|
|
1629
|
+
if (!fs7.existsSync(dir)) {
|
|
1630
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
1442
1631
|
}
|
|
1443
1632
|
const tmp = `${this.configPath}.tmp`;
|
|
1444
|
-
|
|
1445
|
-
|
|
1633
|
+
fs7.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
|
|
1634
|
+
fs7.renameSync(tmp, this.configPath);
|
|
1446
1635
|
}
|
|
1447
1636
|
listTasks() {
|
|
1448
1637
|
return this.readConfig().tasks;
|
|
@@ -1454,6 +1643,7 @@ var TaskConfigManager = class {
|
|
|
1454
1643
|
return this.readConfig().tasks.find((t) => t.name === name);
|
|
1455
1644
|
}
|
|
1456
1645
|
createTask(input) {
|
|
1646
|
+
validateTaskPayload(input.taskType, input.payload);
|
|
1457
1647
|
const config = this.readConfig();
|
|
1458
1648
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1459
1649
|
const task = {
|
|
@@ -1482,6 +1672,9 @@ var TaskConfigManager = class {
|
|
|
1482
1672
|
if (!existing) {
|
|
1483
1673
|
throw new Error(`Task '${id}' not found`);
|
|
1484
1674
|
}
|
|
1675
|
+
const nextTaskType = input.taskType ?? existing.taskType;
|
|
1676
|
+
const nextPayload = input.payload ?? existing.payload;
|
|
1677
|
+
validateTaskPayload(nextTaskType, nextPayload);
|
|
1485
1678
|
const updated = {
|
|
1486
1679
|
...existing,
|
|
1487
1680
|
...input.name !== void 0 && { name: input.name },
|
|
@@ -1520,10 +1713,197 @@ var TaskConfigManager = class {
|
|
|
1520
1713
|
}
|
|
1521
1714
|
};
|
|
1522
1715
|
|
|
1716
|
+
// src/scheduled-tasks/TaskExecutionEngine.ts
|
|
1717
|
+
function hasNonEmptyString(value) {
|
|
1718
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
1719
|
+
}
|
|
1720
|
+
function resolveTaskExecutionPayload(taskType, payload, workspacePath) {
|
|
1721
|
+
if (!hasNonEmptyString(workspacePath)) {
|
|
1722
|
+
return payload;
|
|
1723
|
+
}
|
|
1724
|
+
if (taskType === "shell") {
|
|
1725
|
+
const shellPayload = payload;
|
|
1726
|
+
if (hasNonEmptyString(shellPayload.cwd)) {
|
|
1727
|
+
return shellPayload;
|
|
1728
|
+
}
|
|
1729
|
+
return { ...shellPayload, cwd: workspacePath };
|
|
1730
|
+
}
|
|
1731
|
+
if (taskType === "github_copilot_cli") {
|
|
1732
|
+
const copilotPayload = payload;
|
|
1733
|
+
if (hasNonEmptyString(copilotPayload.workspace)) {
|
|
1734
|
+
return copilotPayload;
|
|
1735
|
+
}
|
|
1736
|
+
return { ...copilotPayload, workspace: workspacePath };
|
|
1737
|
+
}
|
|
1738
|
+
return payload;
|
|
1739
|
+
}
|
|
1740
|
+
var TaskExecutionEngine = class {
|
|
1741
|
+
constructor(getExecutor2) {
|
|
1742
|
+
this.getExecutor = getExecutor2;
|
|
1743
|
+
this.running = /* @__PURE__ */ new Map();
|
|
1744
|
+
this.taskExecutions = /* @__PURE__ */ new Map();
|
|
1745
|
+
this.listener = null;
|
|
1746
|
+
}
|
|
1747
|
+
setListener(listener) {
|
|
1748
|
+
this.listener = listener;
|
|
1749
|
+
}
|
|
1750
|
+
async execute(snapshot) {
|
|
1751
|
+
const policy = snapshot.concurrencyPolicy ?? "reject";
|
|
1752
|
+
if (policy === "reject") {
|
|
1753
|
+
const existingIds = this.taskExecutions.get(snapshot.taskId);
|
|
1754
|
+
if (existingIds && existingIds.size > 0) {
|
|
1755
|
+
throw new Error(
|
|
1756
|
+
`TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
|
|
1757
|
+
);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1761
|
+
if (!this.taskExecutions.has(snapshot.taskId)) {
|
|
1762
|
+
this.taskExecutions.set(snapshot.taskId, /* @__PURE__ */ new Set());
|
|
1763
|
+
}
|
|
1764
|
+
this.taskExecutions.get(snapshot.taskId)?.add(snapshot.executionId);
|
|
1765
|
+
const ac = new AbortController();
|
|
1766
|
+
const runningExec = {
|
|
1767
|
+
executionId: snapshot.executionId,
|
|
1768
|
+
taskId: snapshot.taskId,
|
|
1769
|
+
startedAt,
|
|
1770
|
+
cancel: () => ac.abort()
|
|
1771
|
+
};
|
|
1772
|
+
this.running.set(snapshot.executionId, runningExec);
|
|
1773
|
+
this.listener?.onStarted({
|
|
1774
|
+
executionId: snapshot.executionId,
|
|
1775
|
+
taskId: snapshot.taskId,
|
|
1776
|
+
startedAt
|
|
1777
|
+
});
|
|
1778
|
+
try {
|
|
1779
|
+
const executionPayload = resolveTaskExecutionPayload(
|
|
1780
|
+
snapshot.type,
|
|
1781
|
+
snapshot.payload,
|
|
1782
|
+
snapshot.workspacePath
|
|
1783
|
+
);
|
|
1784
|
+
validateTaskPayload(snapshot.type, executionPayload);
|
|
1785
|
+
const executor = this.getExecutor(snapshot.type);
|
|
1786
|
+
let result;
|
|
1787
|
+
if (isStreamingTaskExecutor(executor)) {
|
|
1788
|
+
const handle = executor.executeStreaming(
|
|
1789
|
+
executionPayload,
|
|
1790
|
+
(stream, data) => {
|
|
1791
|
+
this.listener?.onOutput({
|
|
1792
|
+
executionId: snapshot.executionId,
|
|
1793
|
+
stream,
|
|
1794
|
+
data
|
|
1795
|
+
});
|
|
1796
|
+
},
|
|
1797
|
+
ac.signal
|
|
1798
|
+
);
|
|
1799
|
+
runningExec.cancel = () => {
|
|
1800
|
+
handle.cancel();
|
|
1801
|
+
ac.abort();
|
|
1802
|
+
};
|
|
1803
|
+
result = await handle.result;
|
|
1804
|
+
} else {
|
|
1805
|
+
result = await executor.execute(executionPayload, ac.signal);
|
|
1806
|
+
}
|
|
1807
|
+
const log = {
|
|
1808
|
+
id: snapshot.executionId,
|
|
1809
|
+
taskId: snapshot.taskId,
|
|
1810
|
+
taskName: snapshot.name,
|
|
1811
|
+
startedAt,
|
|
1812
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1813
|
+
status: result.status === "running" ? "failure" : result.status,
|
|
1814
|
+
output: result.output,
|
|
1815
|
+
error: result.error
|
|
1816
|
+
};
|
|
1817
|
+
switch (log.status) {
|
|
1818
|
+
case "success":
|
|
1819
|
+
this.listener?.onCompleted({
|
|
1820
|
+
executionId: snapshot.executionId,
|
|
1821
|
+
log
|
|
1822
|
+
});
|
|
1823
|
+
break;
|
|
1824
|
+
case "cancelled":
|
|
1825
|
+
this.listener?.onCancelled({
|
|
1826
|
+
executionId: snapshot.executionId,
|
|
1827
|
+
log
|
|
1828
|
+
});
|
|
1829
|
+
break;
|
|
1830
|
+
case "timeout":
|
|
1831
|
+
this.listener?.onFailed({
|
|
1832
|
+
executionId: snapshot.executionId,
|
|
1833
|
+
status: "timeout",
|
|
1834
|
+
reason: "timeout",
|
|
1835
|
+
log
|
|
1836
|
+
});
|
|
1837
|
+
break;
|
|
1838
|
+
default:
|
|
1839
|
+
this.listener?.onFailed({
|
|
1840
|
+
executionId: snapshot.executionId,
|
|
1841
|
+
status: "failure",
|
|
1842
|
+
reason: "error",
|
|
1843
|
+
log
|
|
1844
|
+
});
|
|
1845
|
+
break;
|
|
1846
|
+
}
|
|
1847
|
+
} catch (err) {
|
|
1848
|
+
const log = {
|
|
1849
|
+
id: snapshot.executionId,
|
|
1850
|
+
taskId: snapshot.taskId,
|
|
1851
|
+
taskName: snapshot.name,
|
|
1852
|
+
startedAt,
|
|
1853
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1854
|
+
status: ac.signal.aborted ? "cancelled" : "failure",
|
|
1855
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1856
|
+
};
|
|
1857
|
+
if (ac.signal.aborted) {
|
|
1858
|
+
this.listener?.onCancelled({
|
|
1859
|
+
executionId: snapshot.executionId,
|
|
1860
|
+
log
|
|
1861
|
+
});
|
|
1862
|
+
} else {
|
|
1863
|
+
this.listener?.onFailed({
|
|
1864
|
+
executionId: snapshot.executionId,
|
|
1865
|
+
status: "failure",
|
|
1866
|
+
reason: "error",
|
|
1867
|
+
log
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
} finally {
|
|
1871
|
+
this.running.delete(snapshot.executionId);
|
|
1872
|
+
const taskExecIds = this.taskExecutions.get(snapshot.taskId);
|
|
1873
|
+
if (taskExecIds) {
|
|
1874
|
+
taskExecIds.delete(snapshot.executionId);
|
|
1875
|
+
if (taskExecIds.size === 0) {
|
|
1876
|
+
this.taskExecutions.delete(snapshot.taskId);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
cancel(executionId) {
|
|
1882
|
+
const exec = this.running.get(executionId);
|
|
1883
|
+
if (!exec) return false;
|
|
1884
|
+
exec.cancel();
|
|
1885
|
+
return true;
|
|
1886
|
+
}
|
|
1887
|
+
listRunning() {
|
|
1888
|
+
return Array.from(this.running.values()).map((e) => ({
|
|
1889
|
+
executionId: e.executionId,
|
|
1890
|
+
taskId: e.taskId,
|
|
1891
|
+
startedAt: e.startedAt
|
|
1892
|
+
}));
|
|
1893
|
+
}
|
|
1894
|
+
dispose() {
|
|
1895
|
+
for (const exec of this.running.values()) {
|
|
1896
|
+
exec.cancel();
|
|
1897
|
+
}
|
|
1898
|
+
this.running.clear();
|
|
1899
|
+
this.taskExecutions.clear();
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1902
|
+
|
|
1523
1903
|
// src/scheduled-tasks/TaskLogManager.ts
|
|
1524
1904
|
var import_node_crypto2 = require("crypto");
|
|
1525
|
-
var
|
|
1526
|
-
var
|
|
1905
|
+
var fs8 = __toESM(require("fs"));
|
|
1906
|
+
var path7 = __toESM(require("path"));
|
|
1527
1907
|
var CONFIG_DIR4 = ".serviceme";
|
|
1528
1908
|
var LOG_FILE2 = "scheduled-tasks-log.json";
|
|
1529
1909
|
var MAX_LOGS = 200;
|
|
@@ -1548,17 +1928,17 @@ function validateAndRepairLogFile(raw) {
|
|
|
1548
1928
|
}
|
|
1549
1929
|
var TaskLogManager = class {
|
|
1550
1930
|
constructor(workspacePath) {
|
|
1551
|
-
this.logPath =
|
|
1931
|
+
this.logPath = path7.join(workspacePath, CONFIG_DIR4, LOG_FILE2);
|
|
1552
1932
|
}
|
|
1553
1933
|
getLogPath() {
|
|
1554
1934
|
return this.logPath;
|
|
1555
1935
|
}
|
|
1556
1936
|
readLogFile() {
|
|
1557
|
-
if (!
|
|
1937
|
+
if (!fs8.existsSync(this.logPath)) {
|
|
1558
1938
|
return emptyLogFile();
|
|
1559
1939
|
}
|
|
1560
1940
|
try {
|
|
1561
|
-
const raw =
|
|
1941
|
+
const raw = fs8.readFileSync(this.logPath, "utf-8");
|
|
1562
1942
|
const parsed = JSON.parse(raw);
|
|
1563
1943
|
const file = validateAndRepairLogFile(parsed);
|
|
1564
1944
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.logs) || parsed.logs.length !== file.logs.length) {
|
|
@@ -1574,21 +1954,21 @@ var TaskLogManager = class {
|
|
|
1574
1954
|
}
|
|
1575
1955
|
backupCorruptedFile() {
|
|
1576
1956
|
try {
|
|
1577
|
-
if (
|
|
1957
|
+
if (fs8.existsSync(this.logPath)) {
|
|
1578
1958
|
const backupPath = `${this.logPath}.corrupted.${Date.now()}`;
|
|
1579
|
-
|
|
1959
|
+
fs8.copyFileSync(this.logPath, backupPath);
|
|
1580
1960
|
}
|
|
1581
1961
|
} catch {
|
|
1582
1962
|
}
|
|
1583
1963
|
}
|
|
1584
1964
|
writeLogFile(file) {
|
|
1585
|
-
const dir =
|
|
1586
|
-
if (!
|
|
1587
|
-
|
|
1965
|
+
const dir = path7.dirname(this.logPath);
|
|
1966
|
+
if (!fs8.existsSync(dir)) {
|
|
1967
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1588
1968
|
}
|
|
1589
1969
|
const tmp = `${this.logPath}.tmp`;
|
|
1590
|
-
|
|
1591
|
-
|
|
1970
|
+
fs8.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
|
|
1971
|
+
fs8.renameSync(tmp, this.logPath);
|
|
1592
1972
|
}
|
|
1593
1973
|
appendLog(input) {
|
|
1594
1974
|
const file = this.readLogFile();
|
|
@@ -1658,7 +2038,23 @@ var SchedulerDaemon = class {
|
|
|
1658
2038
|
this.running = true;
|
|
1659
2039
|
this.startTime = Date.now();
|
|
1660
2040
|
this.pidManager.writePid(process.pid);
|
|
1661
|
-
this.
|
|
2041
|
+
const configPath = this.configManager.getConfigPath();
|
|
2042
|
+
const logPath = this.logger.getLogPath();
|
|
2043
|
+
this.logger.log("info", "=".repeat(60));
|
|
2044
|
+
this.logger.log(
|
|
2045
|
+
"info",
|
|
2046
|
+
`Scheduler daemon started
|
|
2047
|
+
PID: ${process.pid}
|
|
2048
|
+
Node: ${process.version}
|
|
2049
|
+
OS: ${os.type()} ${os.release()} (${process.arch})
|
|
2050
|
+
Platform: ${process.platform}
|
|
2051
|
+
Hostname: ${os.hostname()}
|
|
2052
|
+
User: ${os.userInfo().username}
|
|
2053
|
+
Workspace: ${this.workspacePath}
|
|
2054
|
+
Config: ${configPath}
|
|
2055
|
+
LogPath: ${logPath}
|
|
2056
|
+
ExecPath: ${process.execPath}`
|
|
2057
|
+
);
|
|
1662
2058
|
this.tickTimer = setInterval(() => this.tick(), TICK_INTERVAL);
|
|
1663
2059
|
this.setupConfigWatch();
|
|
1664
2060
|
process.on("SIGTERM", () => this.stop());
|
|
@@ -1683,8 +2079,8 @@ var SchedulerDaemon = class {
|
|
|
1683
2079
|
const configPath = this.configManager.getConfigPath();
|
|
1684
2080
|
const dir = configPath.substring(0, configPath.lastIndexOf("/"));
|
|
1685
2081
|
try {
|
|
1686
|
-
if (
|
|
1687
|
-
this.watcher =
|
|
2082
|
+
if (fs9.existsSync(dir)) {
|
|
2083
|
+
this.watcher = fs9.watch(dir, (_eventType, filename) => {
|
|
1688
2084
|
if (filename === "scheduled-tasks.json") {
|
|
1689
2085
|
this.logger.log("info", "Config file changed, reconciling...");
|
|
1690
2086
|
}
|
|
@@ -1700,7 +2096,11 @@ var SchedulerDaemon = class {
|
|
|
1700
2096
|
for (const task of config.tasks) {
|
|
1701
2097
|
if (!task.enabled) continue;
|
|
1702
2098
|
if (this.taskRunning.has(task.id)) continue;
|
|
1703
|
-
|
|
2099
|
+
let lastExec = this.lastRun.get(task.id);
|
|
2100
|
+
if (lastExec === void 0) {
|
|
2101
|
+
this.lastRun.set(task.id, now);
|
|
2102
|
+
lastExec = now;
|
|
2103
|
+
}
|
|
1704
2104
|
if (this.shouldRun(task, lastExec, now)) {
|
|
1705
2105
|
this.executeTask(task, now);
|
|
1706
2106
|
}
|
|
@@ -1722,11 +2122,25 @@ var SchedulerDaemon = class {
|
|
|
1722
2122
|
this.taskRunning.add(task.id);
|
|
1723
2123
|
this.lastRun.set(task.id, now);
|
|
1724
2124
|
const startedAt = new Date(now).toISOString();
|
|
1725
|
-
|
|
2125
|
+
const startMs = Date.now();
|
|
2126
|
+
this.logger.log(
|
|
2127
|
+
"info",
|
|
2128
|
+
`Executing task: ${task.name} (${task.id})
|
|
2129
|
+
type: ${task.taskType}
|
|
2130
|
+
schedule: ${task.schedule} (${task.scheduleType})
|
|
2131
|
+
enabled: ${task.enabled}`
|
|
2132
|
+
);
|
|
1726
2133
|
try {
|
|
2134
|
+
const executionPayload = resolveTaskExecutionPayload(
|
|
2135
|
+
task.taskType,
|
|
2136
|
+
task.payload,
|
|
2137
|
+
this.workspacePath
|
|
2138
|
+
);
|
|
2139
|
+
validateTaskPayload(task.taskType, executionPayload);
|
|
1727
2140
|
const executor = getExecutor(task.taskType);
|
|
1728
|
-
const result = await executor.execute(
|
|
2141
|
+
const result = await executor.execute(executionPayload);
|
|
1729
2142
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2143
|
+
const durationMs = Date.now() - startMs;
|
|
1730
2144
|
this.logManager.appendLog({
|
|
1731
2145
|
taskId: task.id,
|
|
1732
2146
|
taskName: task.name,
|
|
@@ -1736,9 +2150,17 @@ var SchedulerDaemon = class {
|
|
|
1736
2150
|
output: result.output,
|
|
1737
2151
|
error: result.error
|
|
1738
2152
|
});
|
|
1739
|
-
|
|
2153
|
+
const outputBytes = result.output ? Buffer.byteLength(result.output) : 0;
|
|
2154
|
+
this.logger.log(
|
|
2155
|
+
"info",
|
|
2156
|
+
`Task completed: ${task.name} (${task.id})
|
|
2157
|
+
status: ${result.status}
|
|
2158
|
+
duration: ${durationMs}ms
|
|
2159
|
+
outputBytes: ${outputBytes}`
|
|
2160
|
+
);
|
|
1740
2161
|
} catch (err) {
|
|
1741
2162
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2163
|
+
const durationMs = Date.now() - startMs;
|
|
1742
2164
|
const message = err instanceof Error ? err.message : String(err);
|
|
1743
2165
|
this.logManager.appendLog({
|
|
1744
2166
|
taskId: task.id,
|
|
@@ -1748,7 +2170,12 @@ var SchedulerDaemon = class {
|
|
|
1748
2170
|
status: "failure",
|
|
1749
2171
|
error: message
|
|
1750
2172
|
});
|
|
1751
|
-
this.logger.log(
|
|
2173
|
+
this.logger.log(
|
|
2174
|
+
"error",
|
|
2175
|
+
`Task failed: ${task.name} (${task.id})
|
|
2176
|
+
duration: ${durationMs}ms
|
|
2177
|
+
error: ${message}`
|
|
2178
|
+
);
|
|
1752
2179
|
} finally {
|
|
1753
2180
|
this.taskRunning.delete(task.id);
|
|
1754
2181
|
}
|
|
@@ -1805,164 +2232,6 @@ function matchCronField(field, value) {
|
|
|
1805
2232
|
const values = field.split(",");
|
|
1806
2233
|
return values.some((v) => Number.parseInt(v, 10) === value);
|
|
1807
2234
|
}
|
|
1808
|
-
|
|
1809
|
-
// src/scheduled-tasks/TaskExecutionEngine.ts
|
|
1810
|
-
var TaskExecutionEngine = class {
|
|
1811
|
-
constructor(getExecutor2) {
|
|
1812
|
-
this.getExecutor = getExecutor2;
|
|
1813
|
-
this.running = /* @__PURE__ */ new Map();
|
|
1814
|
-
this.taskExecutions = /* @__PURE__ */ new Map();
|
|
1815
|
-
this.listener = null;
|
|
1816
|
-
}
|
|
1817
|
-
setListener(listener) {
|
|
1818
|
-
this.listener = listener;
|
|
1819
|
-
}
|
|
1820
|
-
async execute(snapshot) {
|
|
1821
|
-
const policy = snapshot.concurrencyPolicy ?? "reject";
|
|
1822
|
-
if (policy === "reject") {
|
|
1823
|
-
const existingIds = this.taskExecutions.get(snapshot.taskId);
|
|
1824
|
-
if (existingIds && existingIds.size > 0) {
|
|
1825
|
-
throw new Error(
|
|
1826
|
-
`TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
|
|
1827
|
-
);
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
const executor = this.getExecutor(snapshot.type);
|
|
1831
|
-
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1832
|
-
if (!this.taskExecutions.has(snapshot.taskId)) {
|
|
1833
|
-
this.taskExecutions.set(snapshot.taskId, /* @__PURE__ */ new Set());
|
|
1834
|
-
}
|
|
1835
|
-
this.taskExecutions.get(snapshot.taskId)?.add(snapshot.executionId);
|
|
1836
|
-
const ac = new AbortController();
|
|
1837
|
-
const runningExec = {
|
|
1838
|
-
executionId: snapshot.executionId,
|
|
1839
|
-
taskId: snapshot.taskId,
|
|
1840
|
-
startedAt,
|
|
1841
|
-
cancel: () => ac.abort()
|
|
1842
|
-
};
|
|
1843
|
-
this.running.set(snapshot.executionId, runningExec);
|
|
1844
|
-
this.listener?.onStarted({
|
|
1845
|
-
executionId: snapshot.executionId,
|
|
1846
|
-
taskId: snapshot.taskId,
|
|
1847
|
-
startedAt
|
|
1848
|
-
});
|
|
1849
|
-
try {
|
|
1850
|
-
let result;
|
|
1851
|
-
if (isStreamingTaskExecutor(executor)) {
|
|
1852
|
-
const handle = executor.executeStreaming(
|
|
1853
|
-
snapshot.payload,
|
|
1854
|
-
(stream, data) => {
|
|
1855
|
-
this.listener?.onOutput({
|
|
1856
|
-
executionId: snapshot.executionId,
|
|
1857
|
-
stream,
|
|
1858
|
-
data
|
|
1859
|
-
});
|
|
1860
|
-
},
|
|
1861
|
-
ac.signal
|
|
1862
|
-
);
|
|
1863
|
-
runningExec.cancel = () => {
|
|
1864
|
-
handle.cancel();
|
|
1865
|
-
ac.abort();
|
|
1866
|
-
};
|
|
1867
|
-
result = await handle.result;
|
|
1868
|
-
} else {
|
|
1869
|
-
result = await executor.execute(snapshot.payload, ac.signal);
|
|
1870
|
-
}
|
|
1871
|
-
const log = {
|
|
1872
|
-
id: snapshot.executionId,
|
|
1873
|
-
taskId: snapshot.taskId,
|
|
1874
|
-
taskName: snapshot.name,
|
|
1875
|
-
startedAt,
|
|
1876
|
-
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1877
|
-
status: result.status === "running" ? "failure" : result.status,
|
|
1878
|
-
output: result.output,
|
|
1879
|
-
error: result.error
|
|
1880
|
-
};
|
|
1881
|
-
switch (log.status) {
|
|
1882
|
-
case "success":
|
|
1883
|
-
this.listener?.onCompleted({
|
|
1884
|
-
executionId: snapshot.executionId,
|
|
1885
|
-
log
|
|
1886
|
-
});
|
|
1887
|
-
break;
|
|
1888
|
-
case "cancelled":
|
|
1889
|
-
this.listener?.onCancelled({
|
|
1890
|
-
executionId: snapshot.executionId,
|
|
1891
|
-
log
|
|
1892
|
-
});
|
|
1893
|
-
break;
|
|
1894
|
-
case "timeout":
|
|
1895
|
-
this.listener?.onFailed({
|
|
1896
|
-
executionId: snapshot.executionId,
|
|
1897
|
-
status: "timeout",
|
|
1898
|
-
reason: "timeout",
|
|
1899
|
-
log
|
|
1900
|
-
});
|
|
1901
|
-
break;
|
|
1902
|
-
default:
|
|
1903
|
-
this.listener?.onFailed({
|
|
1904
|
-
executionId: snapshot.executionId,
|
|
1905
|
-
status: "failure",
|
|
1906
|
-
reason: "error",
|
|
1907
|
-
log
|
|
1908
|
-
});
|
|
1909
|
-
break;
|
|
1910
|
-
}
|
|
1911
|
-
} catch (err) {
|
|
1912
|
-
const log = {
|
|
1913
|
-
id: snapshot.executionId,
|
|
1914
|
-
taskId: snapshot.taskId,
|
|
1915
|
-
taskName: snapshot.name,
|
|
1916
|
-
startedAt,
|
|
1917
|
-
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1918
|
-
status: ac.signal.aborted ? "cancelled" : "failure",
|
|
1919
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1920
|
-
};
|
|
1921
|
-
if (ac.signal.aborted) {
|
|
1922
|
-
this.listener?.onCancelled({
|
|
1923
|
-
executionId: snapshot.executionId,
|
|
1924
|
-
log
|
|
1925
|
-
});
|
|
1926
|
-
} else {
|
|
1927
|
-
this.listener?.onFailed({
|
|
1928
|
-
executionId: snapshot.executionId,
|
|
1929
|
-
status: "failure",
|
|
1930
|
-
reason: "error",
|
|
1931
|
-
log
|
|
1932
|
-
});
|
|
1933
|
-
}
|
|
1934
|
-
} finally {
|
|
1935
|
-
this.running.delete(snapshot.executionId);
|
|
1936
|
-
const taskExecIds = this.taskExecutions.get(snapshot.taskId);
|
|
1937
|
-
if (taskExecIds) {
|
|
1938
|
-
taskExecIds.delete(snapshot.executionId);
|
|
1939
|
-
if (taskExecIds.size === 0) {
|
|
1940
|
-
this.taskExecutions.delete(snapshot.taskId);
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
cancel(executionId) {
|
|
1946
|
-
const exec = this.running.get(executionId);
|
|
1947
|
-
if (!exec) return false;
|
|
1948
|
-
exec.cancel();
|
|
1949
|
-
return true;
|
|
1950
|
-
}
|
|
1951
|
-
listRunning() {
|
|
1952
|
-
return Array.from(this.running.values()).map((e) => ({
|
|
1953
|
-
executionId: e.executionId,
|
|
1954
|
-
taskId: e.taskId,
|
|
1955
|
-
startedAt: e.startedAt
|
|
1956
|
-
}));
|
|
1957
|
-
}
|
|
1958
|
-
dispose() {
|
|
1959
|
-
for (const exec of this.running.values()) {
|
|
1960
|
-
exec.cancel();
|
|
1961
|
-
}
|
|
1962
|
-
this.running.clear();
|
|
1963
|
-
this.taskExecutions.clear();
|
|
1964
|
-
}
|
|
1965
|
-
};
|
|
1966
2235
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1967
2236
|
0 && (module.exports = {
|
|
1968
2237
|
DaemonLogger,
|
|
@@ -1992,5 +2261,7 @@ var TaskExecutionEngine = class {
|
|
|
1992
2261
|
moveFiles,
|
|
1993
2262
|
noopLogger,
|
|
1994
2263
|
parseIntervalMs,
|
|
1995
|
-
|
|
2264
|
+
resolveTaskExecutionPayload,
|
|
2265
|
+
unzipFile,
|
|
2266
|
+
validateTaskPayload
|
|
1996
2267
|
});
|