@serviceme/devtools-core 0.1.3 → 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 +493 -226
- package/dist/index.mjs +490 -225
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -39,7 +39,8 @@ async function runCommand(command, options = {}) {
|
|
|
39
39
|
cwd: options.cwd,
|
|
40
40
|
env: options.env,
|
|
41
41
|
shell: options.shell,
|
|
42
|
-
stdio: "pipe"
|
|
42
|
+
stdio: "pipe",
|
|
43
|
+
windowsHide: true
|
|
43
44
|
});
|
|
44
45
|
let stdout = "";
|
|
45
46
|
let stderr = "";
|
|
@@ -1081,12 +1082,51 @@ var PidManager = class {
|
|
|
1081
1082
|
};
|
|
1082
1083
|
|
|
1083
1084
|
// src/scheduled-tasks/daemon/SchedulerDaemon.ts
|
|
1084
|
-
import * as
|
|
1085
|
+
import * as fs9 from "fs";
|
|
1086
|
+
import * as os from "os";
|
|
1085
1087
|
|
|
1086
1088
|
// src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
|
|
1087
1089
|
import { spawn as spawn2 } from "child_process";
|
|
1088
|
-
|
|
1090
|
+
import * as fs5 from "fs";
|
|
1091
|
+
|
|
1092
|
+
// src/scheduled-tasks/executors/timeout.ts
|
|
1093
|
+
function resolveConfiguredTimeoutMs(timeoutSeconds, defaultTimeoutMs) {
|
|
1094
|
+
if (timeoutSeconds === 0) {
|
|
1095
|
+
return void 0;
|
|
1096
|
+
}
|
|
1097
|
+
if (typeof timeoutSeconds !== "number" || !Number.isFinite(timeoutSeconds) || timeoutSeconds < 0) {
|
|
1098
|
+
return defaultTimeoutMs;
|
|
1099
|
+
}
|
|
1100
|
+
return timeoutSeconds * 1e3;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
|
|
1089
1104
|
var MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
|
|
1105
|
+
var DEFAULT_TIMEOUT_MS2 = 3e5;
|
|
1106
|
+
function redactArgs(args) {
|
|
1107
|
+
const redacted = [];
|
|
1108
|
+
let redactNext = false;
|
|
1109
|
+
for (const arg of args) {
|
|
1110
|
+
if (redactNext) {
|
|
1111
|
+
redacted.push("<redacted>");
|
|
1112
|
+
redactNext = false;
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
redacted.push(arg);
|
|
1116
|
+
if (arg === "--prompt") {
|
|
1117
|
+
redactNext = true;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
return redacted;
|
|
1121
|
+
}
|
|
1122
|
+
function writeDiagnostic(message) {
|
|
1123
|
+
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1124
|
+
if (logPath) {
|
|
1125
|
+
fs5.appendFileSync(logPath, message);
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
process.stderr.write(message);
|
|
1129
|
+
}
|
|
1090
1130
|
var GithubCopilotCliExecutor = class {
|
|
1091
1131
|
async execute(payload, abortSignal) {
|
|
1092
1132
|
const authenticated = await isCopilotAuthenticated();
|
|
@@ -1109,7 +1149,7 @@ var GithubCopilotCliExecutor = class {
|
|
|
1109
1149
|
}
|
|
1110
1150
|
executeStreaming(payload, onOutput, abortSignal) {
|
|
1111
1151
|
const p = payload;
|
|
1112
|
-
const
|
|
1152
|
+
const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS2);
|
|
1113
1153
|
let resolve;
|
|
1114
1154
|
const resultPromise = new Promise((r) => {
|
|
1115
1155
|
resolve = r;
|
|
@@ -1118,7 +1158,7 @@ var GithubCopilotCliExecutor = class {
|
|
|
1118
1158
|
const settle = (result) => {
|
|
1119
1159
|
if (settled) return;
|
|
1120
1160
|
settled = true;
|
|
1121
|
-
clearTimeout(timer);
|
|
1161
|
+
if (timer) clearTimeout(timer);
|
|
1122
1162
|
resolve?.(result);
|
|
1123
1163
|
};
|
|
1124
1164
|
if (abortSignal?.aborted) {
|
|
@@ -1144,23 +1184,32 @@ var GithubCopilotCliExecutor = class {
|
|
|
1144
1184
|
if (p.agent) {
|
|
1145
1185
|
args.push("--agent", p.agent);
|
|
1146
1186
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1187
|
+
args.push("--timeout", String(p.timeout ?? 0));
|
|
1188
|
+
writeDiagnostic(
|
|
1189
|
+
`[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}
|
|
1190
|
+
`
|
|
1191
|
+
);
|
|
1150
1192
|
const child = spawn2("serviceme", args, {
|
|
1151
1193
|
cwd: p.workspace,
|
|
1152
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1194
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1195
|
+
windowsHide: true
|
|
1153
1196
|
});
|
|
1154
|
-
|
|
1197
|
+
if (child.pid) {
|
|
1198
|
+
writeDiagnostic(
|
|
1199
|
+
`[GithubCopilotCliExecutor] spawned child PID=${child.pid}
|
|
1200
|
+
`
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
const timer = timeoutMs != null ? setTimeout(() => {
|
|
1155
1204
|
child.kill("SIGTERM");
|
|
1156
1205
|
setTimeout(() => {
|
|
1157
1206
|
if (!child.killed) child.kill("SIGKILL");
|
|
1158
1207
|
}, 5e3);
|
|
1159
1208
|
settle({
|
|
1160
1209
|
status: "timeout",
|
|
1161
|
-
error: `Copilot CLI execution timed out after ${
|
|
1210
|
+
error: `Copilot CLI execution timed out after ${timeoutMs / 1e3}s`
|
|
1162
1211
|
});
|
|
1163
|
-
},
|
|
1212
|
+
}, timeoutMs) : void 0;
|
|
1164
1213
|
let stdoutBuf = "";
|
|
1165
1214
|
let stderrBuf = "";
|
|
1166
1215
|
child.stdout.on("data", (chunk) => {
|
|
@@ -1204,19 +1253,19 @@ var GithubCopilotCliExecutor = class {
|
|
|
1204
1253
|
};
|
|
1205
1254
|
|
|
1206
1255
|
// src/scheduled-tasks/executors/HttpRequestExecutor.ts
|
|
1207
|
-
var
|
|
1256
|
+
var DEFAULT_TIMEOUT_MS3 = 3e4;
|
|
1208
1257
|
var HttpRequestExecutor = class {
|
|
1209
1258
|
async execute(payload, abortSignal) {
|
|
1210
1259
|
const p = payload;
|
|
1211
|
-
const
|
|
1260
|
+
const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS3);
|
|
1212
1261
|
const ac = new AbortController();
|
|
1213
|
-
let
|
|
1214
|
-
const timer = setTimeout(() => {
|
|
1215
|
-
|
|
1262
|
+
let timedOut = false;
|
|
1263
|
+
const timer = timeoutMs != null ? setTimeout(() => {
|
|
1264
|
+
timedOut = true;
|
|
1216
1265
|
ac.abort();
|
|
1217
|
-
},
|
|
1266
|
+
}, timeoutMs) : void 0;
|
|
1218
1267
|
if (abortSignal?.aborted) {
|
|
1219
|
-
clearTimeout(timer);
|
|
1268
|
+
if (timer) clearTimeout(timer);
|
|
1220
1269
|
return { status: "cancelled", error: "Execution aborted" };
|
|
1221
1270
|
}
|
|
1222
1271
|
let externalAbort = false;
|
|
@@ -1224,7 +1273,7 @@ var HttpRequestExecutor = class {
|
|
|
1224
1273
|
"abort",
|
|
1225
1274
|
() => {
|
|
1226
1275
|
externalAbort = true;
|
|
1227
|
-
clearTimeout(timer);
|
|
1276
|
+
if (timer) clearTimeout(timer);
|
|
1228
1277
|
ac.abort();
|
|
1229
1278
|
},
|
|
1230
1279
|
{ once: true }
|
|
@@ -1236,7 +1285,7 @@ var HttpRequestExecutor = class {
|
|
|
1236
1285
|
body: p.body,
|
|
1237
1286
|
signal: ac.signal
|
|
1238
1287
|
});
|
|
1239
|
-
clearTimeout(timer);
|
|
1288
|
+
if (timer) clearTimeout(timer);
|
|
1240
1289
|
const body = await response.text();
|
|
1241
1290
|
if (response.ok) {
|
|
1242
1291
|
return {
|
|
@@ -1251,14 +1300,17 @@ ${body}`.trim()
|
|
|
1251
1300
|
${body}`.trim()
|
|
1252
1301
|
};
|
|
1253
1302
|
} catch (err) {
|
|
1254
|
-
clearTimeout(timer);
|
|
1303
|
+
if (timer) clearTimeout(timer);
|
|
1255
1304
|
if (err instanceof Error && err.name === "AbortError") {
|
|
1256
1305
|
if (externalAbort) {
|
|
1257
1306
|
return { status: "cancelled", error: "Execution cancelled" };
|
|
1258
1307
|
}
|
|
1308
|
+
if (!timedOut || timeoutMs == null) {
|
|
1309
|
+
return { status: "failure", error: err.message };
|
|
1310
|
+
}
|
|
1259
1311
|
return {
|
|
1260
1312
|
status: "timeout",
|
|
1261
|
-
error: `HTTP request timed out after ${
|
|
1313
|
+
error: `HTTP request timed out after ${timeoutMs / 1e3}s`
|
|
1262
1314
|
};
|
|
1263
1315
|
}
|
|
1264
1316
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1269,8 +1321,82 @@ ${body}`.trim()
|
|
|
1269
1321
|
|
|
1270
1322
|
// src/scheduled-tasks/executors/ShellExecutor.ts
|
|
1271
1323
|
import { spawn as spawn3 } from "child_process";
|
|
1272
|
-
|
|
1324
|
+
import * as fs6 from "fs";
|
|
1325
|
+
import * as path5 from "path";
|
|
1273
1326
|
var MAX_OUTPUT_BYTES2 = 1024 * 1024;
|
|
1327
|
+
var DEFAULT_TIMEOUT_MS4 = 6e4;
|
|
1328
|
+
var POSIX_SHELL_CANDIDATES = ["bash.exe", "sh.exe"];
|
|
1329
|
+
function resolveShellExecution(script, options = {}) {
|
|
1330
|
+
const platform = options.platform ?? process.platform;
|
|
1331
|
+
const env = options.env ?? process.env;
|
|
1332
|
+
const fileExists = options.fileExists ?? fs6.existsSync;
|
|
1333
|
+
if (platform === "win32") {
|
|
1334
|
+
const posixShell = usesPosixShellSyntax(script) ? findWindowsPosixShell(env, fileExists) : null;
|
|
1335
|
+
if (posixShell) {
|
|
1336
|
+
return {
|
|
1337
|
+
command: posixShell,
|
|
1338
|
+
args: ["-s"],
|
|
1339
|
+
stdinScript: script,
|
|
1340
|
+
outputEncoding: "utf8",
|
|
1341
|
+
shellKind: "posix"
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
return {
|
|
1345
|
+
command: env.ComSpec ?? env.COMSPEC ?? "cmd.exe",
|
|
1346
|
+
args: ["/d", "/s", "/c", script],
|
|
1347
|
+
outputEncoding: "utf8",
|
|
1348
|
+
shellKind: "cmd"
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
return {
|
|
1352
|
+
command: "sh",
|
|
1353
|
+
args: ["-s"],
|
|
1354
|
+
stdinScript: script,
|
|
1355
|
+
outputEncoding: "utf8",
|
|
1356
|
+
shellKind: "posix"
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
function usesPosixShellSyntax(script) {
|
|
1360
|
+
return /\$\([^)]*\)|`[^`]*`/.test(script);
|
|
1361
|
+
}
|
|
1362
|
+
function findWindowsPosixShell(env, fileExists) {
|
|
1363
|
+
const explicitShell = env.SERVICEME_POSIX_SHELL;
|
|
1364
|
+
if (explicitShell && fileExists(explicitShell)) {
|
|
1365
|
+
return explicitShell;
|
|
1366
|
+
}
|
|
1367
|
+
const knownGitBashPaths = [
|
|
1368
|
+
"C:\\Program Files\\Git\\bin\\bash.exe",
|
|
1369
|
+
"C:\\Program Files\\Git\\usr\\bin\\bash.exe",
|
|
1370
|
+
"C:\\Program Files (x86)\\Git\\bin\\bash.exe",
|
|
1371
|
+
"C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
|
|
1372
|
+
];
|
|
1373
|
+
for (const candidate of knownGitBashPaths) {
|
|
1374
|
+
if (fileExists(candidate)) return candidate;
|
|
1375
|
+
}
|
|
1376
|
+
const pathValue = env.Path ?? env.PATH ?? "";
|
|
1377
|
+
for (const dir of pathValue.split(path5.win32.delimiter)) {
|
|
1378
|
+
if (!dir) continue;
|
|
1379
|
+
for (const executable of POSIX_SHELL_CANDIDATES) {
|
|
1380
|
+
const candidate = path5.win32.join(dir, executable);
|
|
1381
|
+
if (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {
|
|
1382
|
+
return candidate;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
return null;
|
|
1387
|
+
}
|
|
1388
|
+
function isWindowsWslLauncher(candidate) {
|
|
1389
|
+
const normalized = path5.win32.normalize(candidate).toLowerCase();
|
|
1390
|
+
return normalized.endsWith("\\windows\\system32\\bash.exe") || normalized.endsWith("\\windows\\syswow64\\bash.exe");
|
|
1391
|
+
}
|
|
1392
|
+
function writeDiagnostic2(message) {
|
|
1393
|
+
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1394
|
+
if (logPath) {
|
|
1395
|
+
fs6.appendFileSync(logPath, message);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
process.stderr.write(message);
|
|
1399
|
+
}
|
|
1274
1400
|
var ShellExecutor = class {
|
|
1275
1401
|
async execute(payload, abortSignal) {
|
|
1276
1402
|
let output = "";
|
|
@@ -1286,7 +1412,7 @@ var ShellExecutor = class {
|
|
|
1286
1412
|
}
|
|
1287
1413
|
executeStreaming(payload, onOutput, abortSignal) {
|
|
1288
1414
|
const p = payload;
|
|
1289
|
-
const
|
|
1415
|
+
const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS4);
|
|
1290
1416
|
let resolve;
|
|
1291
1417
|
const resultPromise = new Promise((r) => {
|
|
1292
1418
|
resolve = r;
|
|
@@ -1295,7 +1421,7 @@ var ShellExecutor = class {
|
|
|
1295
1421
|
const settle = (result) => {
|
|
1296
1422
|
if (settled) return;
|
|
1297
1423
|
settled = true;
|
|
1298
|
-
clearTimeout(timer);
|
|
1424
|
+
if (timer) clearTimeout(timer);
|
|
1299
1425
|
resolve?.(result);
|
|
1300
1426
|
};
|
|
1301
1427
|
if (abortSignal?.aborted) {
|
|
@@ -1308,37 +1434,65 @@ var ShellExecutor = class {
|
|
|
1308
1434
|
}
|
|
1309
1435
|
};
|
|
1310
1436
|
}
|
|
1311
|
-
const
|
|
1437
|
+
const shellExecution = resolveShellExecution(p.script);
|
|
1438
|
+
const spawnOpts = {
|
|
1312
1439
|
cwd: p.cwd,
|
|
1313
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1314
|
-
|
|
1315
|
-
|
|
1440
|
+
stdio: [shellExecution.stdinScript ? "pipe" : "ignore", "pipe", "pipe"],
|
|
1441
|
+
windowsHide: true
|
|
1442
|
+
};
|
|
1443
|
+
writeDiagnostic2(
|
|
1444
|
+
`[ShellExecutor] spawn:
|
|
1445
|
+
platform=${process.platform}
|
|
1446
|
+
shell=${shellExecution.shellKind}
|
|
1447
|
+
command=${shellExecution.command}
|
|
1448
|
+
args=${JSON.stringify(shellExecution.args.filter((a) => a !== p.script))}
|
|
1449
|
+
stdin=${shellExecution.stdinScript ? "true" : "false"}
|
|
1450
|
+
cwd=${p.cwd ?? "(default)"}
|
|
1451
|
+
timeout=${timeoutMs != null ? `${timeoutMs}ms` : "unlimited"}
|
|
1452
|
+
scriptLen=${p.script.length}
|
|
1453
|
+
windowsHide=true
|
|
1454
|
+
daemonPid=${process.pid}
|
|
1455
|
+
`
|
|
1456
|
+
);
|
|
1457
|
+
const child = spawn3(shellExecution.command, shellExecution.args, spawnOpts);
|
|
1458
|
+
if (shellExecution.stdinScript && child.stdin) {
|
|
1459
|
+
child.stdin.end(shellExecution.stdinScript);
|
|
1460
|
+
}
|
|
1461
|
+
if (child.pid) {
|
|
1462
|
+
writeDiagnostic2(`[ShellExecutor] spawned child PID=${child.pid}
|
|
1463
|
+
`);
|
|
1464
|
+
}
|
|
1465
|
+
const timer = timeoutMs != null ? setTimeout(() => {
|
|
1316
1466
|
child.kill("SIGTERM");
|
|
1317
1467
|
setTimeout(() => {
|
|
1318
1468
|
if (!child.killed) child.kill("SIGKILL");
|
|
1319
1469
|
}, 5e3);
|
|
1320
1470
|
settle({
|
|
1321
1471
|
status: "timeout",
|
|
1322
|
-
error: `Shell execution timed out after ${
|
|
1472
|
+
error: `Shell execution timed out after ${timeoutMs / 1e3}s`
|
|
1323
1473
|
});
|
|
1324
|
-
},
|
|
1474
|
+
}, timeoutMs) : void 0;
|
|
1325
1475
|
let stdoutBuf = "";
|
|
1326
1476
|
let stderrBuf = "";
|
|
1327
|
-
child.stdout
|
|
1328
|
-
const data = chunk.toString();
|
|
1477
|
+
child.stdout?.on("data", (chunk) => {
|
|
1478
|
+
const data = chunk.toString(shellExecution.outputEncoding);
|
|
1329
1479
|
stdoutBuf += data;
|
|
1330
1480
|
if (stdoutBuf.length > MAX_OUTPUT_BYTES2)
|
|
1331
1481
|
stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES2);
|
|
1332
1482
|
onOutput("stdout", data);
|
|
1333
1483
|
});
|
|
1334
|
-
child.stderr
|
|
1335
|
-
const data = chunk.toString();
|
|
1484
|
+
child.stderr?.on("data", (chunk) => {
|
|
1485
|
+
const data = chunk.toString(shellExecution.outputEncoding);
|
|
1336
1486
|
stderrBuf += data;
|
|
1337
1487
|
if (stderrBuf.length > MAX_OUTPUT_BYTES2)
|
|
1338
1488
|
stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES2);
|
|
1339
1489
|
onOutput("stderr", data);
|
|
1340
1490
|
});
|
|
1341
1491
|
child.on("close", (code) => {
|
|
1492
|
+
writeDiagnostic2(
|
|
1493
|
+
`[ShellExecutor] child PID=${child.pid} exited: code=${code}
|
|
1494
|
+
`
|
|
1495
|
+
);
|
|
1342
1496
|
if (code === 0) {
|
|
1343
1497
|
settle({ status: "success", output: stdoutBuf || void 0 });
|
|
1344
1498
|
} else {
|
|
@@ -1385,36 +1539,65 @@ function getExecutor(taskType) {
|
|
|
1385
1539
|
|
|
1386
1540
|
// src/scheduled-tasks/TaskConfigManager.ts
|
|
1387
1541
|
import { randomUUID } from "crypto";
|
|
1388
|
-
import * as
|
|
1389
|
-
import * as
|
|
1542
|
+
import * as fs7 from "fs";
|
|
1543
|
+
import * as path6 from "path";
|
|
1544
|
+
import { createServicemeError as createServicemeError7 } from "@serviceme/devtools-protocol";
|
|
1390
1545
|
var CONFIG_DIR3 = ".serviceme";
|
|
1391
1546
|
var CONFIG_FILE = "scheduled-tasks.json";
|
|
1392
1547
|
function emptyConfig() {
|
|
1393
1548
|
return { version: 1, tasks: [] };
|
|
1394
1549
|
}
|
|
1550
|
+
function isRecord(value) {
|
|
1551
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1552
|
+
}
|
|
1553
|
+
function requireNonEmptyString(payload, field, taskType) {
|
|
1554
|
+
if (!isRecord(payload) || typeof payload[field] !== "string" || !payload[field].trim()) {
|
|
1555
|
+
throw createServicemeError7(
|
|
1556
|
+
"invalid_payload",
|
|
1557
|
+
`${taskType} payload ${field} is required`
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
function validateTaskPayload(taskType, payload) {
|
|
1562
|
+
switch (taskType) {
|
|
1563
|
+
case "command":
|
|
1564
|
+
requireNonEmptyString(payload, "command", taskType);
|
|
1565
|
+
return;
|
|
1566
|
+
case "shell":
|
|
1567
|
+
requireNonEmptyString(payload, "script", taskType);
|
|
1568
|
+
return;
|
|
1569
|
+
case "http_request":
|
|
1570
|
+
requireNonEmptyString(payload, "url", taskType);
|
|
1571
|
+
requireNonEmptyString(payload, "method", taskType);
|
|
1572
|
+
return;
|
|
1573
|
+
case "github_copilot_cli":
|
|
1574
|
+
requireNonEmptyString(payload, "prompt", taskType);
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1395
1578
|
var TaskConfigManager = class {
|
|
1396
1579
|
constructor(workspacePath) {
|
|
1397
|
-
this.configPath =
|
|
1580
|
+
this.configPath = path6.join(workspacePath, CONFIG_DIR3, CONFIG_FILE);
|
|
1398
1581
|
}
|
|
1399
1582
|
getConfigPath() {
|
|
1400
1583
|
return this.configPath;
|
|
1401
1584
|
}
|
|
1402
1585
|
readConfig() {
|
|
1403
|
-
if (!
|
|
1586
|
+
if (!fs7.existsSync(this.configPath)) {
|
|
1404
1587
|
return emptyConfig();
|
|
1405
1588
|
}
|
|
1406
|
-
const raw =
|
|
1589
|
+
const raw = fs7.readFileSync(this.configPath, "utf-8");
|
|
1407
1590
|
const parsed = JSON.parse(raw);
|
|
1408
1591
|
return parsed;
|
|
1409
1592
|
}
|
|
1410
1593
|
writeConfig(config) {
|
|
1411
|
-
const dir =
|
|
1412
|
-
if (!
|
|
1413
|
-
|
|
1594
|
+
const dir = path6.dirname(this.configPath);
|
|
1595
|
+
if (!fs7.existsSync(dir)) {
|
|
1596
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
1414
1597
|
}
|
|
1415
1598
|
const tmp = `${this.configPath}.tmp`;
|
|
1416
|
-
|
|
1417
|
-
|
|
1599
|
+
fs7.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
|
|
1600
|
+
fs7.renameSync(tmp, this.configPath);
|
|
1418
1601
|
}
|
|
1419
1602
|
listTasks() {
|
|
1420
1603
|
return this.readConfig().tasks;
|
|
@@ -1426,6 +1609,7 @@ var TaskConfigManager = class {
|
|
|
1426
1609
|
return this.readConfig().tasks.find((t) => t.name === name);
|
|
1427
1610
|
}
|
|
1428
1611
|
createTask(input) {
|
|
1612
|
+
validateTaskPayload(input.taskType, input.payload);
|
|
1429
1613
|
const config = this.readConfig();
|
|
1430
1614
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1431
1615
|
const task = {
|
|
@@ -1454,6 +1638,9 @@ var TaskConfigManager = class {
|
|
|
1454
1638
|
if (!existing) {
|
|
1455
1639
|
throw new Error(`Task '${id}' not found`);
|
|
1456
1640
|
}
|
|
1641
|
+
const nextTaskType = input.taskType ?? existing.taskType;
|
|
1642
|
+
const nextPayload = input.payload ?? existing.payload;
|
|
1643
|
+
validateTaskPayload(nextTaskType, nextPayload);
|
|
1457
1644
|
const updated = {
|
|
1458
1645
|
...existing,
|
|
1459
1646
|
...input.name !== void 0 && { name: input.name },
|
|
@@ -1492,10 +1679,197 @@ var TaskConfigManager = class {
|
|
|
1492
1679
|
}
|
|
1493
1680
|
};
|
|
1494
1681
|
|
|
1682
|
+
// src/scheduled-tasks/TaskExecutionEngine.ts
|
|
1683
|
+
function hasNonEmptyString(value) {
|
|
1684
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
1685
|
+
}
|
|
1686
|
+
function resolveTaskExecutionPayload(taskType, payload, workspacePath) {
|
|
1687
|
+
if (!hasNonEmptyString(workspacePath)) {
|
|
1688
|
+
return payload;
|
|
1689
|
+
}
|
|
1690
|
+
if (taskType === "shell") {
|
|
1691
|
+
const shellPayload = payload;
|
|
1692
|
+
if (hasNonEmptyString(shellPayload.cwd)) {
|
|
1693
|
+
return shellPayload;
|
|
1694
|
+
}
|
|
1695
|
+
return { ...shellPayload, cwd: workspacePath };
|
|
1696
|
+
}
|
|
1697
|
+
if (taskType === "github_copilot_cli") {
|
|
1698
|
+
const copilotPayload = payload;
|
|
1699
|
+
if (hasNonEmptyString(copilotPayload.workspace)) {
|
|
1700
|
+
return copilotPayload;
|
|
1701
|
+
}
|
|
1702
|
+
return { ...copilotPayload, workspace: workspacePath };
|
|
1703
|
+
}
|
|
1704
|
+
return payload;
|
|
1705
|
+
}
|
|
1706
|
+
var TaskExecutionEngine = class {
|
|
1707
|
+
constructor(getExecutor2) {
|
|
1708
|
+
this.getExecutor = getExecutor2;
|
|
1709
|
+
this.running = /* @__PURE__ */ new Map();
|
|
1710
|
+
this.taskExecutions = /* @__PURE__ */ new Map();
|
|
1711
|
+
this.listener = null;
|
|
1712
|
+
}
|
|
1713
|
+
setListener(listener) {
|
|
1714
|
+
this.listener = listener;
|
|
1715
|
+
}
|
|
1716
|
+
async execute(snapshot) {
|
|
1717
|
+
const policy = snapshot.concurrencyPolicy ?? "reject";
|
|
1718
|
+
if (policy === "reject") {
|
|
1719
|
+
const existingIds = this.taskExecutions.get(snapshot.taskId);
|
|
1720
|
+
if (existingIds && existingIds.size > 0) {
|
|
1721
|
+
throw new Error(
|
|
1722
|
+
`TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1727
|
+
if (!this.taskExecutions.has(snapshot.taskId)) {
|
|
1728
|
+
this.taskExecutions.set(snapshot.taskId, /* @__PURE__ */ new Set());
|
|
1729
|
+
}
|
|
1730
|
+
this.taskExecutions.get(snapshot.taskId)?.add(snapshot.executionId);
|
|
1731
|
+
const ac = new AbortController();
|
|
1732
|
+
const runningExec = {
|
|
1733
|
+
executionId: snapshot.executionId,
|
|
1734
|
+
taskId: snapshot.taskId,
|
|
1735
|
+
startedAt,
|
|
1736
|
+
cancel: () => ac.abort()
|
|
1737
|
+
};
|
|
1738
|
+
this.running.set(snapshot.executionId, runningExec);
|
|
1739
|
+
this.listener?.onStarted({
|
|
1740
|
+
executionId: snapshot.executionId,
|
|
1741
|
+
taskId: snapshot.taskId,
|
|
1742
|
+
startedAt
|
|
1743
|
+
});
|
|
1744
|
+
try {
|
|
1745
|
+
const executionPayload = resolveTaskExecutionPayload(
|
|
1746
|
+
snapshot.type,
|
|
1747
|
+
snapshot.payload,
|
|
1748
|
+
snapshot.workspacePath
|
|
1749
|
+
);
|
|
1750
|
+
validateTaskPayload(snapshot.type, executionPayload);
|
|
1751
|
+
const executor = this.getExecutor(snapshot.type);
|
|
1752
|
+
let result;
|
|
1753
|
+
if (isStreamingTaskExecutor(executor)) {
|
|
1754
|
+
const handle = executor.executeStreaming(
|
|
1755
|
+
executionPayload,
|
|
1756
|
+
(stream, data) => {
|
|
1757
|
+
this.listener?.onOutput({
|
|
1758
|
+
executionId: snapshot.executionId,
|
|
1759
|
+
stream,
|
|
1760
|
+
data
|
|
1761
|
+
});
|
|
1762
|
+
},
|
|
1763
|
+
ac.signal
|
|
1764
|
+
);
|
|
1765
|
+
runningExec.cancel = () => {
|
|
1766
|
+
handle.cancel();
|
|
1767
|
+
ac.abort();
|
|
1768
|
+
};
|
|
1769
|
+
result = await handle.result;
|
|
1770
|
+
} else {
|
|
1771
|
+
result = await executor.execute(executionPayload, ac.signal);
|
|
1772
|
+
}
|
|
1773
|
+
const log = {
|
|
1774
|
+
id: snapshot.executionId,
|
|
1775
|
+
taskId: snapshot.taskId,
|
|
1776
|
+
taskName: snapshot.name,
|
|
1777
|
+
startedAt,
|
|
1778
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1779
|
+
status: result.status === "running" ? "failure" : result.status,
|
|
1780
|
+
output: result.output,
|
|
1781
|
+
error: result.error
|
|
1782
|
+
};
|
|
1783
|
+
switch (log.status) {
|
|
1784
|
+
case "success":
|
|
1785
|
+
this.listener?.onCompleted({
|
|
1786
|
+
executionId: snapshot.executionId,
|
|
1787
|
+
log
|
|
1788
|
+
});
|
|
1789
|
+
break;
|
|
1790
|
+
case "cancelled":
|
|
1791
|
+
this.listener?.onCancelled({
|
|
1792
|
+
executionId: snapshot.executionId,
|
|
1793
|
+
log
|
|
1794
|
+
});
|
|
1795
|
+
break;
|
|
1796
|
+
case "timeout":
|
|
1797
|
+
this.listener?.onFailed({
|
|
1798
|
+
executionId: snapshot.executionId,
|
|
1799
|
+
status: "timeout",
|
|
1800
|
+
reason: "timeout",
|
|
1801
|
+
log
|
|
1802
|
+
});
|
|
1803
|
+
break;
|
|
1804
|
+
default:
|
|
1805
|
+
this.listener?.onFailed({
|
|
1806
|
+
executionId: snapshot.executionId,
|
|
1807
|
+
status: "failure",
|
|
1808
|
+
reason: "error",
|
|
1809
|
+
log
|
|
1810
|
+
});
|
|
1811
|
+
break;
|
|
1812
|
+
}
|
|
1813
|
+
} catch (err) {
|
|
1814
|
+
const log = {
|
|
1815
|
+
id: snapshot.executionId,
|
|
1816
|
+
taskId: snapshot.taskId,
|
|
1817
|
+
taskName: snapshot.name,
|
|
1818
|
+
startedAt,
|
|
1819
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1820
|
+
status: ac.signal.aborted ? "cancelled" : "failure",
|
|
1821
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1822
|
+
};
|
|
1823
|
+
if (ac.signal.aborted) {
|
|
1824
|
+
this.listener?.onCancelled({
|
|
1825
|
+
executionId: snapshot.executionId,
|
|
1826
|
+
log
|
|
1827
|
+
});
|
|
1828
|
+
} else {
|
|
1829
|
+
this.listener?.onFailed({
|
|
1830
|
+
executionId: snapshot.executionId,
|
|
1831
|
+
status: "failure",
|
|
1832
|
+
reason: "error",
|
|
1833
|
+
log
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
} finally {
|
|
1837
|
+
this.running.delete(snapshot.executionId);
|
|
1838
|
+
const taskExecIds = this.taskExecutions.get(snapshot.taskId);
|
|
1839
|
+
if (taskExecIds) {
|
|
1840
|
+
taskExecIds.delete(snapshot.executionId);
|
|
1841
|
+
if (taskExecIds.size === 0) {
|
|
1842
|
+
this.taskExecutions.delete(snapshot.taskId);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
cancel(executionId) {
|
|
1848
|
+
const exec = this.running.get(executionId);
|
|
1849
|
+
if (!exec) return false;
|
|
1850
|
+
exec.cancel();
|
|
1851
|
+
return true;
|
|
1852
|
+
}
|
|
1853
|
+
listRunning() {
|
|
1854
|
+
return Array.from(this.running.values()).map((e) => ({
|
|
1855
|
+
executionId: e.executionId,
|
|
1856
|
+
taskId: e.taskId,
|
|
1857
|
+
startedAt: e.startedAt
|
|
1858
|
+
}));
|
|
1859
|
+
}
|
|
1860
|
+
dispose() {
|
|
1861
|
+
for (const exec of this.running.values()) {
|
|
1862
|
+
exec.cancel();
|
|
1863
|
+
}
|
|
1864
|
+
this.running.clear();
|
|
1865
|
+
this.taskExecutions.clear();
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
|
|
1495
1869
|
// src/scheduled-tasks/TaskLogManager.ts
|
|
1496
1870
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1497
|
-
import * as
|
|
1498
|
-
import * as
|
|
1871
|
+
import * as fs8 from "fs";
|
|
1872
|
+
import * as path7 from "path";
|
|
1499
1873
|
var CONFIG_DIR4 = ".serviceme";
|
|
1500
1874
|
var LOG_FILE2 = "scheduled-tasks-log.json";
|
|
1501
1875
|
var MAX_LOGS = 200;
|
|
@@ -1520,17 +1894,17 @@ function validateAndRepairLogFile(raw) {
|
|
|
1520
1894
|
}
|
|
1521
1895
|
var TaskLogManager = class {
|
|
1522
1896
|
constructor(workspacePath) {
|
|
1523
|
-
this.logPath =
|
|
1897
|
+
this.logPath = path7.join(workspacePath, CONFIG_DIR4, LOG_FILE2);
|
|
1524
1898
|
}
|
|
1525
1899
|
getLogPath() {
|
|
1526
1900
|
return this.logPath;
|
|
1527
1901
|
}
|
|
1528
1902
|
readLogFile() {
|
|
1529
|
-
if (!
|
|
1903
|
+
if (!fs8.existsSync(this.logPath)) {
|
|
1530
1904
|
return emptyLogFile();
|
|
1531
1905
|
}
|
|
1532
1906
|
try {
|
|
1533
|
-
const raw =
|
|
1907
|
+
const raw = fs8.readFileSync(this.logPath, "utf-8");
|
|
1534
1908
|
const parsed = JSON.parse(raw);
|
|
1535
1909
|
const file = validateAndRepairLogFile(parsed);
|
|
1536
1910
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.logs) || parsed.logs.length !== file.logs.length) {
|
|
@@ -1546,21 +1920,21 @@ var TaskLogManager = class {
|
|
|
1546
1920
|
}
|
|
1547
1921
|
backupCorruptedFile() {
|
|
1548
1922
|
try {
|
|
1549
|
-
if (
|
|
1923
|
+
if (fs8.existsSync(this.logPath)) {
|
|
1550
1924
|
const backupPath = `${this.logPath}.corrupted.${Date.now()}`;
|
|
1551
|
-
|
|
1925
|
+
fs8.copyFileSync(this.logPath, backupPath);
|
|
1552
1926
|
}
|
|
1553
1927
|
} catch {
|
|
1554
1928
|
}
|
|
1555
1929
|
}
|
|
1556
1930
|
writeLogFile(file) {
|
|
1557
|
-
const dir =
|
|
1558
|
-
if (!
|
|
1559
|
-
|
|
1931
|
+
const dir = path7.dirname(this.logPath);
|
|
1932
|
+
if (!fs8.existsSync(dir)) {
|
|
1933
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1560
1934
|
}
|
|
1561
1935
|
const tmp = `${this.logPath}.tmp`;
|
|
1562
|
-
|
|
1563
|
-
|
|
1936
|
+
fs8.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
|
|
1937
|
+
fs8.renameSync(tmp, this.logPath);
|
|
1564
1938
|
}
|
|
1565
1939
|
appendLog(input) {
|
|
1566
1940
|
const file = this.readLogFile();
|
|
@@ -1630,7 +2004,23 @@ var SchedulerDaemon = class {
|
|
|
1630
2004
|
this.running = true;
|
|
1631
2005
|
this.startTime = Date.now();
|
|
1632
2006
|
this.pidManager.writePid(process.pid);
|
|
1633
|
-
this.
|
|
2007
|
+
const configPath = this.configManager.getConfigPath();
|
|
2008
|
+
const logPath = this.logger.getLogPath();
|
|
2009
|
+
this.logger.log("info", "=".repeat(60));
|
|
2010
|
+
this.logger.log(
|
|
2011
|
+
"info",
|
|
2012
|
+
`Scheduler daemon started
|
|
2013
|
+
PID: ${process.pid}
|
|
2014
|
+
Node: ${process.version}
|
|
2015
|
+
OS: ${os.type()} ${os.release()} (${process.arch})
|
|
2016
|
+
Platform: ${process.platform}
|
|
2017
|
+
Hostname: ${os.hostname()}
|
|
2018
|
+
User: ${os.userInfo().username}
|
|
2019
|
+
Workspace: ${this.workspacePath}
|
|
2020
|
+
Config: ${configPath}
|
|
2021
|
+
LogPath: ${logPath}
|
|
2022
|
+
ExecPath: ${process.execPath}`
|
|
2023
|
+
);
|
|
1634
2024
|
this.tickTimer = setInterval(() => this.tick(), TICK_INTERVAL);
|
|
1635
2025
|
this.setupConfigWatch();
|
|
1636
2026
|
process.on("SIGTERM", () => this.stop());
|
|
@@ -1655,8 +2045,8 @@ var SchedulerDaemon = class {
|
|
|
1655
2045
|
const configPath = this.configManager.getConfigPath();
|
|
1656
2046
|
const dir = configPath.substring(0, configPath.lastIndexOf("/"));
|
|
1657
2047
|
try {
|
|
1658
|
-
if (
|
|
1659
|
-
this.watcher =
|
|
2048
|
+
if (fs9.existsSync(dir)) {
|
|
2049
|
+
this.watcher = fs9.watch(dir, (_eventType, filename) => {
|
|
1660
2050
|
if (filename === "scheduled-tasks.json") {
|
|
1661
2051
|
this.logger.log("info", "Config file changed, reconciling...");
|
|
1662
2052
|
}
|
|
@@ -1672,7 +2062,11 @@ var SchedulerDaemon = class {
|
|
|
1672
2062
|
for (const task of config.tasks) {
|
|
1673
2063
|
if (!task.enabled) continue;
|
|
1674
2064
|
if (this.taskRunning.has(task.id)) continue;
|
|
1675
|
-
|
|
2065
|
+
let lastExec = this.lastRun.get(task.id);
|
|
2066
|
+
if (lastExec === void 0) {
|
|
2067
|
+
this.lastRun.set(task.id, now);
|
|
2068
|
+
lastExec = now;
|
|
2069
|
+
}
|
|
1676
2070
|
if (this.shouldRun(task, lastExec, now)) {
|
|
1677
2071
|
this.executeTask(task, now);
|
|
1678
2072
|
}
|
|
@@ -1694,11 +2088,25 @@ var SchedulerDaemon = class {
|
|
|
1694
2088
|
this.taskRunning.add(task.id);
|
|
1695
2089
|
this.lastRun.set(task.id, now);
|
|
1696
2090
|
const startedAt = new Date(now).toISOString();
|
|
1697
|
-
|
|
2091
|
+
const startMs = Date.now();
|
|
2092
|
+
this.logger.log(
|
|
2093
|
+
"info",
|
|
2094
|
+
`Executing task: ${task.name} (${task.id})
|
|
2095
|
+
type: ${task.taskType}
|
|
2096
|
+
schedule: ${task.schedule} (${task.scheduleType})
|
|
2097
|
+
enabled: ${task.enabled}`
|
|
2098
|
+
);
|
|
1698
2099
|
try {
|
|
2100
|
+
const executionPayload = resolveTaskExecutionPayload(
|
|
2101
|
+
task.taskType,
|
|
2102
|
+
task.payload,
|
|
2103
|
+
this.workspacePath
|
|
2104
|
+
);
|
|
2105
|
+
validateTaskPayload(task.taskType, executionPayload);
|
|
1699
2106
|
const executor = getExecutor(task.taskType);
|
|
1700
|
-
const result = await executor.execute(
|
|
2107
|
+
const result = await executor.execute(executionPayload);
|
|
1701
2108
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2109
|
+
const durationMs = Date.now() - startMs;
|
|
1702
2110
|
this.logManager.appendLog({
|
|
1703
2111
|
taskId: task.id,
|
|
1704
2112
|
taskName: task.name,
|
|
@@ -1708,9 +2116,17 @@ var SchedulerDaemon = class {
|
|
|
1708
2116
|
output: result.output,
|
|
1709
2117
|
error: result.error
|
|
1710
2118
|
});
|
|
1711
|
-
|
|
2119
|
+
const outputBytes = result.output ? Buffer.byteLength(result.output) : 0;
|
|
2120
|
+
this.logger.log(
|
|
2121
|
+
"info",
|
|
2122
|
+
`Task completed: ${task.name} (${task.id})
|
|
2123
|
+
status: ${result.status}
|
|
2124
|
+
duration: ${durationMs}ms
|
|
2125
|
+
outputBytes: ${outputBytes}`
|
|
2126
|
+
);
|
|
1712
2127
|
} catch (err) {
|
|
1713
2128
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2129
|
+
const durationMs = Date.now() - startMs;
|
|
1714
2130
|
const message = err instanceof Error ? err.message : String(err);
|
|
1715
2131
|
this.logManager.appendLog({
|
|
1716
2132
|
taskId: task.id,
|
|
@@ -1720,7 +2136,12 @@ var SchedulerDaemon = class {
|
|
|
1720
2136
|
status: "failure",
|
|
1721
2137
|
error: message
|
|
1722
2138
|
});
|
|
1723
|
-
this.logger.log(
|
|
2139
|
+
this.logger.log(
|
|
2140
|
+
"error",
|
|
2141
|
+
`Task failed: ${task.name} (${task.id})
|
|
2142
|
+
duration: ${durationMs}ms
|
|
2143
|
+
error: ${message}`
|
|
2144
|
+
);
|
|
1724
2145
|
} finally {
|
|
1725
2146
|
this.taskRunning.delete(task.id);
|
|
1726
2147
|
}
|
|
@@ -1777,164 +2198,6 @@ function matchCronField(field, value) {
|
|
|
1777
2198
|
const values = field.split(",");
|
|
1778
2199
|
return values.some((v) => Number.parseInt(v, 10) === value);
|
|
1779
2200
|
}
|
|
1780
|
-
|
|
1781
|
-
// src/scheduled-tasks/TaskExecutionEngine.ts
|
|
1782
|
-
var TaskExecutionEngine = class {
|
|
1783
|
-
constructor(getExecutor2) {
|
|
1784
|
-
this.getExecutor = getExecutor2;
|
|
1785
|
-
this.running = /* @__PURE__ */ new Map();
|
|
1786
|
-
this.taskExecutions = /* @__PURE__ */ new Map();
|
|
1787
|
-
this.listener = null;
|
|
1788
|
-
}
|
|
1789
|
-
setListener(listener) {
|
|
1790
|
-
this.listener = listener;
|
|
1791
|
-
}
|
|
1792
|
-
async execute(snapshot) {
|
|
1793
|
-
const policy = snapshot.concurrencyPolicy ?? "reject";
|
|
1794
|
-
if (policy === "reject") {
|
|
1795
|
-
const existingIds = this.taskExecutions.get(snapshot.taskId);
|
|
1796
|
-
if (existingIds && existingIds.size > 0) {
|
|
1797
|
-
throw new Error(
|
|
1798
|
-
`TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
|
|
1799
|
-
);
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
const executor = this.getExecutor(snapshot.type);
|
|
1803
|
-
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1804
|
-
if (!this.taskExecutions.has(snapshot.taskId)) {
|
|
1805
|
-
this.taskExecutions.set(snapshot.taskId, /* @__PURE__ */ new Set());
|
|
1806
|
-
}
|
|
1807
|
-
this.taskExecutions.get(snapshot.taskId)?.add(snapshot.executionId);
|
|
1808
|
-
const ac = new AbortController();
|
|
1809
|
-
const runningExec = {
|
|
1810
|
-
executionId: snapshot.executionId,
|
|
1811
|
-
taskId: snapshot.taskId,
|
|
1812
|
-
startedAt,
|
|
1813
|
-
cancel: () => ac.abort()
|
|
1814
|
-
};
|
|
1815
|
-
this.running.set(snapshot.executionId, runningExec);
|
|
1816
|
-
this.listener?.onStarted({
|
|
1817
|
-
executionId: snapshot.executionId,
|
|
1818
|
-
taskId: snapshot.taskId,
|
|
1819
|
-
startedAt
|
|
1820
|
-
});
|
|
1821
|
-
try {
|
|
1822
|
-
let result;
|
|
1823
|
-
if (isStreamingTaskExecutor(executor)) {
|
|
1824
|
-
const handle = executor.executeStreaming(
|
|
1825
|
-
snapshot.payload,
|
|
1826
|
-
(stream, data) => {
|
|
1827
|
-
this.listener?.onOutput({
|
|
1828
|
-
executionId: snapshot.executionId,
|
|
1829
|
-
stream,
|
|
1830
|
-
data
|
|
1831
|
-
});
|
|
1832
|
-
},
|
|
1833
|
-
ac.signal
|
|
1834
|
-
);
|
|
1835
|
-
runningExec.cancel = () => {
|
|
1836
|
-
handle.cancel();
|
|
1837
|
-
ac.abort();
|
|
1838
|
-
};
|
|
1839
|
-
result = await handle.result;
|
|
1840
|
-
} else {
|
|
1841
|
-
result = await executor.execute(snapshot.payload, ac.signal);
|
|
1842
|
-
}
|
|
1843
|
-
const log = {
|
|
1844
|
-
id: snapshot.executionId,
|
|
1845
|
-
taskId: snapshot.taskId,
|
|
1846
|
-
taskName: snapshot.name,
|
|
1847
|
-
startedAt,
|
|
1848
|
-
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1849
|
-
status: result.status === "running" ? "failure" : result.status,
|
|
1850
|
-
output: result.output,
|
|
1851
|
-
error: result.error
|
|
1852
|
-
};
|
|
1853
|
-
switch (log.status) {
|
|
1854
|
-
case "success":
|
|
1855
|
-
this.listener?.onCompleted({
|
|
1856
|
-
executionId: snapshot.executionId,
|
|
1857
|
-
log
|
|
1858
|
-
});
|
|
1859
|
-
break;
|
|
1860
|
-
case "cancelled":
|
|
1861
|
-
this.listener?.onCancelled({
|
|
1862
|
-
executionId: snapshot.executionId,
|
|
1863
|
-
log
|
|
1864
|
-
});
|
|
1865
|
-
break;
|
|
1866
|
-
case "timeout":
|
|
1867
|
-
this.listener?.onFailed({
|
|
1868
|
-
executionId: snapshot.executionId,
|
|
1869
|
-
status: "timeout",
|
|
1870
|
-
reason: "timeout",
|
|
1871
|
-
log
|
|
1872
|
-
});
|
|
1873
|
-
break;
|
|
1874
|
-
default:
|
|
1875
|
-
this.listener?.onFailed({
|
|
1876
|
-
executionId: snapshot.executionId,
|
|
1877
|
-
status: "failure",
|
|
1878
|
-
reason: "error",
|
|
1879
|
-
log
|
|
1880
|
-
});
|
|
1881
|
-
break;
|
|
1882
|
-
}
|
|
1883
|
-
} catch (err) {
|
|
1884
|
-
const log = {
|
|
1885
|
-
id: snapshot.executionId,
|
|
1886
|
-
taskId: snapshot.taskId,
|
|
1887
|
-
taskName: snapshot.name,
|
|
1888
|
-
startedAt,
|
|
1889
|
-
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1890
|
-
status: ac.signal.aborted ? "cancelled" : "failure",
|
|
1891
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1892
|
-
};
|
|
1893
|
-
if (ac.signal.aborted) {
|
|
1894
|
-
this.listener?.onCancelled({
|
|
1895
|
-
executionId: snapshot.executionId,
|
|
1896
|
-
log
|
|
1897
|
-
});
|
|
1898
|
-
} else {
|
|
1899
|
-
this.listener?.onFailed({
|
|
1900
|
-
executionId: snapshot.executionId,
|
|
1901
|
-
status: "failure",
|
|
1902
|
-
reason: "error",
|
|
1903
|
-
log
|
|
1904
|
-
});
|
|
1905
|
-
}
|
|
1906
|
-
} finally {
|
|
1907
|
-
this.running.delete(snapshot.executionId);
|
|
1908
|
-
const taskExecIds = this.taskExecutions.get(snapshot.taskId);
|
|
1909
|
-
if (taskExecIds) {
|
|
1910
|
-
taskExecIds.delete(snapshot.executionId);
|
|
1911
|
-
if (taskExecIds.size === 0) {
|
|
1912
|
-
this.taskExecutions.delete(snapshot.taskId);
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
cancel(executionId) {
|
|
1918
|
-
const exec = this.running.get(executionId);
|
|
1919
|
-
if (!exec) return false;
|
|
1920
|
-
exec.cancel();
|
|
1921
|
-
return true;
|
|
1922
|
-
}
|
|
1923
|
-
listRunning() {
|
|
1924
|
-
return Array.from(this.running.values()).map((e) => ({
|
|
1925
|
-
executionId: e.executionId,
|
|
1926
|
-
taskId: e.taskId,
|
|
1927
|
-
startedAt: e.startedAt
|
|
1928
|
-
}));
|
|
1929
|
-
}
|
|
1930
|
-
dispose() {
|
|
1931
|
-
for (const exec of this.running.values()) {
|
|
1932
|
-
exec.cancel();
|
|
1933
|
-
}
|
|
1934
|
-
this.running.clear();
|
|
1935
|
-
this.taskExecutions.clear();
|
|
1936
|
-
}
|
|
1937
|
-
};
|
|
1938
2201
|
export {
|
|
1939
2202
|
DaemonLogger,
|
|
1940
2203
|
EnvironmentInspector,
|
|
@@ -1963,5 +2226,7 @@ export {
|
|
|
1963
2226
|
moveFiles,
|
|
1964
2227
|
noopLogger,
|
|
1965
2228
|
parseIntervalMs,
|
|
1966
|
-
|
|
2229
|
+
resolveTaskExecutionPayload,
|
|
2230
|
+
unzipFile,
|
|
2231
|
+
validateTaskPayload
|
|
1967
2232
|
};
|