@serviceme/devtools-core 0.1.3 → 0.1.5

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.js CHANGED
@@ -57,7 +57,9 @@ __export(index_exports, {
57
57
  moveFiles: () => moveFiles,
58
58
  noopLogger: () => noopLogger,
59
59
  parseIntervalMs: () => parseIntervalMs,
60
- unzipFile: () => unzipFile
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 = "";
@@ -290,8 +293,14 @@ async function copilotPrompt(options) {
290
293
 
291
294
  // src/env/environmentInspector.ts
292
295
  var import_devtools_protocol3 = require("@serviceme/devtools-protocol");
293
- var TOOL_CHECK_TIMEOUT_MS = 5e3;
296
+ var DEFAULT_TOOL_CHECK_TIMEOUT_MS = 5e3;
297
+ var TOOL_CHECK_TIMEOUT_MS = {
298
+ nuget: 12e3,
299
+ nvm: 8e3,
300
+ dotnet: 8e3
301
+ };
294
302
  var ERROR_CODE_NOT_FOUND = 127;
303
+ var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
295
304
  var EnvironmentInspector = class {
296
305
  async checkEnvironment() {
297
306
  const results = await Promise.all(
@@ -328,19 +337,18 @@ var EnvironmentInspector = class {
328
337
  const isWindows = process.platform === "win32";
329
338
  const result = await runCommand(isWindows ? "where" : "which", {
330
339
  args: [toolName],
331
- timeoutMs: TOOL_CHECK_TIMEOUT_MS
340
+ timeoutMs: this.getToolTimeout(toolName)
332
341
  });
333
- return result.stdout.trim().split("\n")[0];
342
+ return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
334
343
  } catch {
335
344
  return void 0;
336
345
  }
337
346
  }
338
347
  async getToolVersion(toolName) {
339
- const command = this.getVersionCommand(toolName);
340
- const isWindows = process.platform === "win32";
341
- const result = await runCommand(isWindows ? "cmd.exe" : "/bin/bash", {
342
- args: isWindows ? ["/c", command] : ["-lc", command],
343
- timeoutMs: TOOL_CHECK_TIMEOUT_MS
348
+ const invocation = this.getVersionInvocation(toolName);
349
+ const result = await runCommand(invocation.command, {
350
+ args: invocation.args,
351
+ timeoutMs: this.getToolTimeout(toolName)
344
352
  });
345
353
  return this.parseVersion(toolName, result.stdout || result.stderr);
346
354
  }
@@ -349,7 +357,7 @@ var EnvironmentInspector = class {
349
357
  try {
350
358
  const result = await runCommand("cmd.exe", {
351
359
  args: ["/c", "nvm version"],
352
- timeoutMs: TOOL_CHECK_TIMEOUT_MS
360
+ timeoutMs: this.getToolTimeout("nvm")
353
361
  });
354
362
  return {
355
363
  installed: true,
@@ -369,7 +377,7 @@ var EnvironmentInspector = class {
369
377
  "-lc",
370
378
  'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; nvm --version'
371
379
  ],
372
- timeoutMs: TOOL_CHECK_TIMEOUT_MS
380
+ timeoutMs: this.getToolTimeout("nvm")
373
381
  });
374
382
  return {
375
383
  installed: true,
@@ -394,7 +402,7 @@ var EnvironmentInspector = class {
394
402
  try {
395
403
  const result = await runCommand("dotnet", {
396
404
  args: ["nuget", "list", "source"],
397
- timeoutMs: TOOL_CHECK_TIMEOUT_MS
405
+ timeoutMs: this.getToolTimeout("nuget")
398
406
  });
399
407
  const privateSourceUrl = "http://192.168.20.209:10010/nuget";
400
408
  if (result.stdout.includes(privateSourceUrl)) {
@@ -412,17 +420,28 @@ var EnvironmentInspector = class {
412
420
  return this.handleToolCheckError(error);
413
421
  }
414
422
  }
415
- getVersionCommand(toolName) {
423
+ getVersionInvocation(toolName) {
424
+ const isWindows = process.platform === "win32";
425
+ if (isWindows && ["npm", "pnpm", "nrm"].includes(toolName)) {
426
+ return {
427
+ command: "cmd.exe",
428
+ args: ["/d", "/s", "/c", `${toolName} --version`]
429
+ };
430
+ }
416
431
  const commands = {
417
- git: "git --version",
418
- node: "node --version",
419
- npm: "npm --version",
420
- pnpm: "pnpm --version",
421
- nvm: "nvm --version",
422
- nrm: "nrm --version",
423
- dotnet: "dotnet --version"
432
+ git: { command: "git", args: ["--version"] },
433
+ node: { command: "node", args: ["--version"] },
434
+ npm: { command: "npm", args: ["--version"] },
435
+ pnpm: { command: "pnpm", args: ["--version"] },
436
+ nvm: { command: "nvm", args: ["--version"] },
437
+ nrm: { command: "nrm", args: ["--version"] },
438
+ rtk: { command: "rtk", args: ["--version"] },
439
+ dotnet: { command: "dotnet", args: ["--version"] }
424
440
  };
425
- return commands[toolName] || `${toolName} --version`;
441
+ return commands[toolName] || { command: toolName, args: ["--version"] };
442
+ }
443
+ getToolTimeout(toolName) {
444
+ return TOOL_CHECK_TIMEOUT_MS[toolName] ?? DEFAULT_TOOL_CHECK_TIMEOUT_MS;
426
445
  }
427
446
  parseVersion(toolName, output) {
428
447
  const cleaned = output.trim();
@@ -449,10 +468,11 @@ var EnvironmentInspector = class {
449
468
  const execError = error;
450
469
  const message = execError.message || String(error);
451
470
  const code = execError.code;
452
- const isNotFound = message.includes("command not found") || message.includes("not recognized") || code === ERROR_CODE_NOT_FOUND;
471
+ const isNotFound = message.includes("command not found") || message.includes("not recognized") || message.includes("ENOENT") || code === "ENOENT" || code === ERROR_CODE_NOT_FOUND;
472
+ const isTimeout = code === ERROR_CODE_TIMEOUT || message.toLowerCase().includes("timed out");
453
473
  return {
454
474
  installed: false,
455
- error: isNotFound ? "Not installed" : message
475
+ error: isNotFound ? "Not installed" : isTimeout ? "Check timed out" : message
456
476
  };
457
477
  }
458
478
  };
@@ -1113,12 +1133,75 @@ var PidManager = class {
1113
1133
  };
1114
1134
 
1115
1135
  // src/scheduled-tasks/daemon/SchedulerDaemon.ts
1116
- var fs7 = __toESM(require("fs"));
1136
+ var fs9 = __toESM(require("fs"));
1137
+ var os = __toESM(require("os"));
1117
1138
 
1118
1139
  // src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
1119
1140
  var import_node_child_process2 = require("child_process");
1120
- var DEFAULT_TIMEOUT = 3e5;
1141
+ var fs5 = __toESM(require("fs"));
1142
+
1143
+ // src/scheduled-tasks/executors/timeout.ts
1144
+ function resolveConfiguredTimeoutMs(timeoutSeconds, defaultTimeoutMs) {
1145
+ if (timeoutSeconds === 0) {
1146
+ return void 0;
1147
+ }
1148
+ if (typeof timeoutSeconds !== "number" || !Number.isFinite(timeoutSeconds) || timeoutSeconds < 0) {
1149
+ return defaultTimeoutMs;
1150
+ }
1151
+ return timeoutSeconds * 1e3;
1152
+ }
1153
+
1154
+ // src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
1121
1155
  var MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
1156
+ var DEFAULT_TIMEOUT_MS2 = 3e5;
1157
+ function redactArgs(args) {
1158
+ const redacted = [];
1159
+ let redactNext = false;
1160
+ for (const arg of args) {
1161
+ if (redactNext) {
1162
+ redacted.push("<redacted>");
1163
+ redactNext = false;
1164
+ continue;
1165
+ }
1166
+ redacted.push(arg);
1167
+ if (arg === "--prompt") {
1168
+ redactNext = true;
1169
+ }
1170
+ }
1171
+ return redacted;
1172
+ }
1173
+ function writeDiagnostic(message) {
1174
+ const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
1175
+ if (logPath) {
1176
+ fs5.appendFileSync(logPath, message);
1177
+ return;
1178
+ }
1179
+ process.stderr.write(message);
1180
+ }
1181
+ function resolveGithubCopilotCliExecution(payload) {
1182
+ const args = ["copilot", "prompt", "--prompt", payload.prompt];
1183
+ if (payload.autopilot) {
1184
+ args.push("--autopilot");
1185
+ }
1186
+ if (payload.allowTools && payload.allowTools.length > 0) {
1187
+ args.push("--allow-tools", payload.allowTools.join(","));
1188
+ }
1189
+ if (payload.model) {
1190
+ args.push("--model", payload.model);
1191
+ }
1192
+ if (payload.agent) {
1193
+ args.push("--agent", payload.agent);
1194
+ }
1195
+ args.push("--timeout", String(payload.timeout ?? 0));
1196
+ return {
1197
+ command: "serviceme",
1198
+ args,
1199
+ cwd: payload.workspace,
1200
+ timeoutMs: resolveConfiguredTimeoutMs(payload.timeout, DEFAULT_TIMEOUT_MS2),
1201
+ diagnosticArgs: redactArgs(args),
1202
+ promptLen: payload.prompt.length
1203
+ };
1204
+ }
1122
1205
  var GithubCopilotCliExecutor = class {
1123
1206
  async execute(payload, abortSignal) {
1124
1207
  const authenticated = await isCopilotAuthenticated();
@@ -1141,7 +1224,7 @@ var GithubCopilotCliExecutor = class {
1141
1224
  }
1142
1225
  executeStreaming(payload, onOutput, abortSignal) {
1143
1226
  const p = payload;
1144
- const timeout = (p.timeout != null ? p.timeout * 1e3 : null) ?? DEFAULT_TIMEOUT;
1227
+ const execution = resolveGithubCopilotCliExecution(p);
1145
1228
  let resolve;
1146
1229
  const resultPromise = new Promise((r) => {
1147
1230
  resolve = r;
@@ -1150,7 +1233,7 @@ var GithubCopilotCliExecutor = class {
1150
1233
  const settle = (result) => {
1151
1234
  if (settled) return;
1152
1235
  settled = true;
1153
- clearTimeout(timer);
1236
+ if (timer) clearTimeout(timer);
1154
1237
  resolve?.(result);
1155
1238
  };
1156
1239
  if (abortSignal?.aborted) {
@@ -1163,36 +1246,32 @@ var GithubCopilotCliExecutor = class {
1163
1246
  }
1164
1247
  };
1165
1248
  }
1166
- const args = ["copilot", "prompt", "--prompt", p.prompt];
1167
- if (p.autopilot) {
1168
- args.push("--autopilot");
1169
- }
1170
- if (p.allowTools && p.allowTools.length > 0) {
1171
- args.push("--allow-tools", p.allowTools.join(","));
1172
- }
1173
- if (p.model) {
1174
- args.push("--model", p.model);
1175
- }
1176
- if (p.agent) {
1177
- args.push("--agent", p.agent);
1178
- }
1179
- if (p.timeout != null) {
1180
- args.push("--timeout", String(p.timeout));
1181
- }
1182
- const child = (0, import_node_child_process2.spawn)("serviceme", args, {
1183
- cwd: p.workspace,
1184
- stdio: ["ignore", "pipe", "pipe"]
1249
+ writeDiagnostic(
1250
+ `[GithubCopilotCliExecutor] spawn: command=${execution.command}, args=${JSON.stringify(execution.diagnosticArgs)}, promptLen=${execution.promptLen}, cwd=${execution.cwd ?? "(default)"}, platform=${process.platform}, windowsHide=true, pid=${process.pid}
1251
+ `
1252
+ );
1253
+ const child = (0, import_node_child_process2.spawn)(execution.command, execution.args, {
1254
+ cwd: execution.cwd,
1255
+ stdio: ["ignore", "pipe", "pipe"],
1256
+ windowsHide: true
1185
1257
  });
1186
- const timer = setTimeout(() => {
1258
+ if (child.pid) {
1259
+ writeDiagnostic(
1260
+ `[GithubCopilotCliExecutor] spawned child PID=${child.pid}
1261
+ `
1262
+ );
1263
+ }
1264
+ const timeoutMs = execution.timeoutMs;
1265
+ const timer = timeoutMs != null ? setTimeout(() => {
1187
1266
  child.kill("SIGTERM");
1188
1267
  setTimeout(() => {
1189
1268
  if (!child.killed) child.kill("SIGKILL");
1190
1269
  }, 5e3);
1191
1270
  settle({
1192
1271
  status: "timeout",
1193
- error: `Copilot CLI execution timed out after ${timeout / 1e3}s`
1272
+ error: `Copilot CLI execution timed out after ${timeoutMs / 1e3}s`
1194
1273
  });
1195
- }, timeout);
1274
+ }, timeoutMs) : void 0;
1196
1275
  let stdoutBuf = "";
1197
1276
  let stderrBuf = "";
1198
1277
  child.stdout.on("data", (chunk) => {
@@ -1236,19 +1315,19 @@ var GithubCopilotCliExecutor = class {
1236
1315
  };
1237
1316
 
1238
1317
  // src/scheduled-tasks/executors/HttpRequestExecutor.ts
1239
- var DEFAULT_TIMEOUT2 = 3e4;
1318
+ var DEFAULT_TIMEOUT_MS3 = 3e4;
1240
1319
  var HttpRequestExecutor = class {
1241
1320
  async execute(payload, abortSignal) {
1242
1321
  const p = payload;
1243
- const timeout = (p.timeout != null ? p.timeout * 1e3 : null) ?? DEFAULT_TIMEOUT2;
1322
+ const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS3);
1244
1323
  const ac = new AbortController();
1245
- let _timedOut = false;
1246
- const timer = setTimeout(() => {
1247
- _timedOut = true;
1324
+ let timedOut = false;
1325
+ const timer = timeoutMs != null ? setTimeout(() => {
1326
+ timedOut = true;
1248
1327
  ac.abort();
1249
- }, timeout);
1328
+ }, timeoutMs) : void 0;
1250
1329
  if (abortSignal?.aborted) {
1251
- clearTimeout(timer);
1330
+ if (timer) clearTimeout(timer);
1252
1331
  return { status: "cancelled", error: "Execution aborted" };
1253
1332
  }
1254
1333
  let externalAbort = false;
@@ -1256,7 +1335,7 @@ var HttpRequestExecutor = class {
1256
1335
  "abort",
1257
1336
  () => {
1258
1337
  externalAbort = true;
1259
- clearTimeout(timer);
1338
+ if (timer) clearTimeout(timer);
1260
1339
  ac.abort();
1261
1340
  },
1262
1341
  { once: true }
@@ -1268,7 +1347,7 @@ var HttpRequestExecutor = class {
1268
1347
  body: p.body,
1269
1348
  signal: ac.signal
1270
1349
  });
1271
- clearTimeout(timer);
1350
+ if (timer) clearTimeout(timer);
1272
1351
  const body = await response.text();
1273
1352
  if (response.ok) {
1274
1353
  return {
@@ -1283,14 +1362,17 @@ ${body}`.trim()
1283
1362
  ${body}`.trim()
1284
1363
  };
1285
1364
  } catch (err) {
1286
- clearTimeout(timer);
1365
+ if (timer) clearTimeout(timer);
1287
1366
  if (err instanceof Error && err.name === "AbortError") {
1288
1367
  if (externalAbort) {
1289
1368
  return { status: "cancelled", error: "Execution cancelled" };
1290
1369
  }
1370
+ if (!timedOut || timeoutMs == null) {
1371
+ return { status: "failure", error: err.message };
1372
+ }
1291
1373
  return {
1292
1374
  status: "timeout",
1293
- error: `HTTP request timed out after ${timeout / 1e3}s`
1375
+ error: `HTTP request timed out after ${timeoutMs / 1e3}s`
1294
1376
  };
1295
1377
  }
1296
1378
  const message = err instanceof Error ? err.message : String(err);
@@ -1301,8 +1383,82 @@ ${body}`.trim()
1301
1383
 
1302
1384
  // src/scheduled-tasks/executors/ShellExecutor.ts
1303
1385
  var import_node_child_process3 = require("child_process");
1304
- var DEFAULT_TIMEOUT3 = 6e4;
1386
+ var fs6 = __toESM(require("fs"));
1387
+ var path5 = __toESM(require("path"));
1305
1388
  var MAX_OUTPUT_BYTES2 = 1024 * 1024;
1389
+ var DEFAULT_TIMEOUT_MS4 = 6e4;
1390
+ var POSIX_SHELL_CANDIDATES = ["bash.exe", "sh.exe"];
1391
+ function resolveShellExecution(script, options = {}) {
1392
+ const platform = options.platform ?? process.platform;
1393
+ const env = options.env ?? process.env;
1394
+ const fileExists = options.fileExists ?? fs6.existsSync;
1395
+ if (platform === "win32") {
1396
+ const posixShell = usesPosixShellSyntax(script) ? findWindowsPosixShell(env, fileExists) : null;
1397
+ if (posixShell) {
1398
+ return {
1399
+ command: posixShell,
1400
+ args: ["-s"],
1401
+ stdinScript: script,
1402
+ outputEncoding: "utf8",
1403
+ shellKind: "posix"
1404
+ };
1405
+ }
1406
+ return {
1407
+ command: env.ComSpec ?? env.COMSPEC ?? "cmd.exe",
1408
+ args: ["/d", "/s", "/c", script],
1409
+ outputEncoding: "utf8",
1410
+ shellKind: "cmd"
1411
+ };
1412
+ }
1413
+ return {
1414
+ command: "sh",
1415
+ args: ["-s"],
1416
+ stdinScript: script,
1417
+ outputEncoding: "utf8",
1418
+ shellKind: "posix"
1419
+ };
1420
+ }
1421
+ function usesPosixShellSyntax(script) {
1422
+ return /\$\([^)]*\)|`[^`]*`/.test(script);
1423
+ }
1424
+ function findWindowsPosixShell(env, fileExists) {
1425
+ const explicitShell = env.SERVICEME_POSIX_SHELL;
1426
+ if (explicitShell && fileExists(explicitShell)) {
1427
+ return explicitShell;
1428
+ }
1429
+ const knownGitBashPaths = [
1430
+ "C:\\Program Files\\Git\\bin\\bash.exe",
1431
+ "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
1432
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
1433
+ "C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
1434
+ ];
1435
+ for (const candidate of knownGitBashPaths) {
1436
+ if (fileExists(candidate)) return candidate;
1437
+ }
1438
+ const pathValue = env.Path ?? env.PATH ?? "";
1439
+ for (const dir of pathValue.split(path5.win32.delimiter)) {
1440
+ if (!dir) continue;
1441
+ for (const executable of POSIX_SHELL_CANDIDATES) {
1442
+ const candidate = path5.win32.join(dir, executable);
1443
+ if (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {
1444
+ return candidate;
1445
+ }
1446
+ }
1447
+ }
1448
+ return null;
1449
+ }
1450
+ function isWindowsWslLauncher(candidate) {
1451
+ const normalized = path5.win32.normalize(candidate).toLowerCase();
1452
+ return normalized.endsWith("\\windows\\system32\\bash.exe") || normalized.endsWith("\\windows\\syswow64\\bash.exe");
1453
+ }
1454
+ function writeDiagnostic2(message) {
1455
+ const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
1456
+ if (logPath) {
1457
+ fs6.appendFileSync(logPath, message);
1458
+ return;
1459
+ }
1460
+ process.stderr.write(message);
1461
+ }
1306
1462
  var ShellExecutor = class {
1307
1463
  async execute(payload, abortSignal) {
1308
1464
  let output = "";
@@ -1318,7 +1474,7 @@ var ShellExecutor = class {
1318
1474
  }
1319
1475
  executeStreaming(payload, onOutput, abortSignal) {
1320
1476
  const p = payload;
1321
- const timeout = (p.timeout != null ? p.timeout * 1e3 : null) ?? DEFAULT_TIMEOUT3;
1477
+ const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS4);
1322
1478
  let resolve;
1323
1479
  const resultPromise = new Promise((r) => {
1324
1480
  resolve = r;
@@ -1327,7 +1483,7 @@ var ShellExecutor = class {
1327
1483
  const settle = (result) => {
1328
1484
  if (settled) return;
1329
1485
  settled = true;
1330
- clearTimeout(timer);
1486
+ if (timer) clearTimeout(timer);
1331
1487
  resolve?.(result);
1332
1488
  };
1333
1489
  if (abortSignal?.aborted) {
@@ -1340,37 +1496,65 @@ var ShellExecutor = class {
1340
1496
  }
1341
1497
  };
1342
1498
  }
1343
- const child = (0, import_node_child_process3.spawn)("sh", ["-c", p.script], {
1499
+ const shellExecution = resolveShellExecution(p.script);
1500
+ const spawnOpts = {
1344
1501
  cwd: p.cwd,
1345
- stdio: ["ignore", "pipe", "pipe"]
1346
- });
1347
- const timer = setTimeout(() => {
1502
+ stdio: [shellExecution.stdinScript ? "pipe" : "ignore", "pipe", "pipe"],
1503
+ windowsHide: true
1504
+ };
1505
+ writeDiagnostic2(
1506
+ `[ShellExecutor] spawn:
1507
+ platform=${process.platform}
1508
+ shell=${shellExecution.shellKind}
1509
+ command=${shellExecution.command}
1510
+ args=${JSON.stringify(shellExecution.args.filter((a) => a !== p.script))}
1511
+ stdin=${shellExecution.stdinScript ? "true" : "false"}
1512
+ cwd=${p.cwd ?? "(default)"}
1513
+ timeout=${timeoutMs != null ? `${timeoutMs}ms` : "unlimited"}
1514
+ scriptLen=${p.script.length}
1515
+ windowsHide=true
1516
+ daemonPid=${process.pid}
1517
+ `
1518
+ );
1519
+ const child = (0, import_node_child_process3.spawn)(shellExecution.command, shellExecution.args, spawnOpts);
1520
+ if (shellExecution.stdinScript && child.stdin) {
1521
+ child.stdin.end(shellExecution.stdinScript);
1522
+ }
1523
+ if (child.pid) {
1524
+ writeDiagnostic2(`[ShellExecutor] spawned child PID=${child.pid}
1525
+ `);
1526
+ }
1527
+ const timer = timeoutMs != null ? setTimeout(() => {
1348
1528
  child.kill("SIGTERM");
1349
1529
  setTimeout(() => {
1350
1530
  if (!child.killed) child.kill("SIGKILL");
1351
1531
  }, 5e3);
1352
1532
  settle({
1353
1533
  status: "timeout",
1354
- error: `Shell execution timed out after ${timeout / 1e3}s`
1534
+ error: `Shell execution timed out after ${timeoutMs / 1e3}s`
1355
1535
  });
1356
- }, timeout);
1536
+ }, timeoutMs) : void 0;
1357
1537
  let stdoutBuf = "";
1358
1538
  let stderrBuf = "";
1359
- child.stdout.on("data", (chunk) => {
1360
- const data = chunk.toString();
1539
+ child.stdout?.on("data", (chunk) => {
1540
+ const data = chunk.toString(shellExecution.outputEncoding);
1361
1541
  stdoutBuf += data;
1362
1542
  if (stdoutBuf.length > MAX_OUTPUT_BYTES2)
1363
1543
  stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES2);
1364
1544
  onOutput("stdout", data);
1365
1545
  });
1366
- child.stderr.on("data", (chunk) => {
1367
- const data = chunk.toString();
1546
+ child.stderr?.on("data", (chunk) => {
1547
+ const data = chunk.toString(shellExecution.outputEncoding);
1368
1548
  stderrBuf += data;
1369
1549
  if (stderrBuf.length > MAX_OUTPUT_BYTES2)
1370
1550
  stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES2);
1371
1551
  onOutput("stderr", data);
1372
1552
  });
1373
1553
  child.on("close", (code) => {
1554
+ writeDiagnostic2(
1555
+ `[ShellExecutor] child PID=${child.pid} exited: code=${code}
1556
+ `
1557
+ );
1374
1558
  if (code === 0) {
1375
1559
  settle({ status: "success", output: stdoutBuf || void 0 });
1376
1560
  } else {
@@ -1417,36 +1601,65 @@ function getExecutor(taskType) {
1417
1601
 
1418
1602
  // src/scheduled-tasks/TaskConfigManager.ts
1419
1603
  var import_node_crypto = require("crypto");
1420
- var fs5 = __toESM(require("fs"));
1421
- var path5 = __toESM(require("path"));
1604
+ var fs7 = __toESM(require("fs"));
1605
+ var path6 = __toESM(require("path"));
1606
+ var import_devtools_protocol7 = require("@serviceme/devtools-protocol");
1422
1607
  var CONFIG_DIR3 = ".serviceme";
1423
1608
  var CONFIG_FILE = "scheduled-tasks.json";
1424
1609
  function emptyConfig() {
1425
1610
  return { version: 1, tasks: [] };
1426
1611
  }
1612
+ function isRecord(value) {
1613
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1614
+ }
1615
+ function requireNonEmptyString(payload, field, taskType) {
1616
+ if (!isRecord(payload) || typeof payload[field] !== "string" || !payload[field].trim()) {
1617
+ throw (0, import_devtools_protocol7.createServicemeError)(
1618
+ "invalid_payload",
1619
+ `${taskType} payload ${field} is required`
1620
+ );
1621
+ }
1622
+ }
1623
+ function validateTaskPayload(taskType, payload) {
1624
+ switch (taskType) {
1625
+ case "command":
1626
+ requireNonEmptyString(payload, "command", taskType);
1627
+ return;
1628
+ case "shell":
1629
+ requireNonEmptyString(payload, "script", taskType);
1630
+ return;
1631
+ case "http_request":
1632
+ requireNonEmptyString(payload, "url", taskType);
1633
+ requireNonEmptyString(payload, "method", taskType);
1634
+ return;
1635
+ case "github_copilot_cli":
1636
+ requireNonEmptyString(payload, "prompt", taskType);
1637
+ return;
1638
+ }
1639
+ }
1427
1640
  var TaskConfigManager = class {
1428
1641
  constructor(workspacePath) {
1429
- this.configPath = path5.join(workspacePath, CONFIG_DIR3, CONFIG_FILE);
1642
+ this.configPath = path6.join(workspacePath, CONFIG_DIR3, CONFIG_FILE);
1430
1643
  }
1431
1644
  getConfigPath() {
1432
1645
  return this.configPath;
1433
1646
  }
1434
1647
  readConfig() {
1435
- if (!fs5.existsSync(this.configPath)) {
1648
+ if (!fs7.existsSync(this.configPath)) {
1436
1649
  return emptyConfig();
1437
1650
  }
1438
- const raw = fs5.readFileSync(this.configPath, "utf-8");
1651
+ const raw = fs7.readFileSync(this.configPath, "utf-8");
1439
1652
  const parsed = JSON.parse(raw);
1440
1653
  return parsed;
1441
1654
  }
1442
1655
  writeConfig(config) {
1443
- const dir = path5.dirname(this.configPath);
1444
- if (!fs5.existsSync(dir)) {
1445
- fs5.mkdirSync(dir, { recursive: true });
1656
+ const dir = path6.dirname(this.configPath);
1657
+ if (!fs7.existsSync(dir)) {
1658
+ fs7.mkdirSync(dir, { recursive: true });
1446
1659
  }
1447
1660
  const tmp = `${this.configPath}.tmp`;
1448
- fs5.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
1449
- fs5.renameSync(tmp, this.configPath);
1661
+ fs7.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
1662
+ fs7.renameSync(tmp, this.configPath);
1450
1663
  }
1451
1664
  listTasks() {
1452
1665
  return this.readConfig().tasks;
@@ -1458,6 +1671,7 @@ var TaskConfigManager = class {
1458
1671
  return this.readConfig().tasks.find((t) => t.name === name);
1459
1672
  }
1460
1673
  createTask(input) {
1674
+ validateTaskPayload(input.taskType, input.payload);
1461
1675
  const config = this.readConfig();
1462
1676
  const now = (/* @__PURE__ */ new Date()).toISOString();
1463
1677
  const task = {
@@ -1486,6 +1700,9 @@ var TaskConfigManager = class {
1486
1700
  if (!existing) {
1487
1701
  throw new Error(`Task '${id}' not found`);
1488
1702
  }
1703
+ const nextTaskType = input.taskType ?? existing.taskType;
1704
+ const nextPayload = input.payload ?? existing.payload;
1705
+ validateTaskPayload(nextTaskType, nextPayload);
1489
1706
  const updated = {
1490
1707
  ...existing,
1491
1708
  ...input.name !== void 0 && { name: input.name },
@@ -1524,10 +1741,197 @@ var TaskConfigManager = class {
1524
1741
  }
1525
1742
  };
1526
1743
 
1744
+ // src/scheduled-tasks/TaskExecutionEngine.ts
1745
+ function hasNonEmptyString(value) {
1746
+ return typeof value === "string" && value.trim().length > 0;
1747
+ }
1748
+ function resolveTaskExecutionPayload(taskType, payload, workspacePath) {
1749
+ if (!hasNonEmptyString(workspacePath)) {
1750
+ return payload;
1751
+ }
1752
+ if (taskType === "shell") {
1753
+ const shellPayload = payload;
1754
+ if (hasNonEmptyString(shellPayload.cwd)) {
1755
+ return shellPayload;
1756
+ }
1757
+ return { ...shellPayload, cwd: workspacePath };
1758
+ }
1759
+ if (taskType === "github_copilot_cli") {
1760
+ const copilotPayload = payload;
1761
+ if (hasNonEmptyString(copilotPayload.workspace)) {
1762
+ return copilotPayload;
1763
+ }
1764
+ return { ...copilotPayload, workspace: workspacePath };
1765
+ }
1766
+ return payload;
1767
+ }
1768
+ var TaskExecutionEngine = class {
1769
+ constructor(getExecutor2) {
1770
+ this.getExecutor = getExecutor2;
1771
+ this.running = /* @__PURE__ */ new Map();
1772
+ this.taskExecutions = /* @__PURE__ */ new Map();
1773
+ this.listener = null;
1774
+ }
1775
+ setListener(listener) {
1776
+ this.listener = listener;
1777
+ }
1778
+ async execute(snapshot) {
1779
+ const policy = snapshot.concurrencyPolicy ?? "reject";
1780
+ if (policy === "reject") {
1781
+ const existingIds = this.taskExecutions.get(snapshot.taskId);
1782
+ if (existingIds && existingIds.size > 0) {
1783
+ throw new Error(
1784
+ `TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
1785
+ );
1786
+ }
1787
+ }
1788
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1789
+ if (!this.taskExecutions.has(snapshot.taskId)) {
1790
+ this.taskExecutions.set(snapshot.taskId, /* @__PURE__ */ new Set());
1791
+ }
1792
+ this.taskExecutions.get(snapshot.taskId)?.add(snapshot.executionId);
1793
+ const ac = new AbortController();
1794
+ const runningExec = {
1795
+ executionId: snapshot.executionId,
1796
+ taskId: snapshot.taskId,
1797
+ startedAt,
1798
+ cancel: () => ac.abort()
1799
+ };
1800
+ this.running.set(snapshot.executionId, runningExec);
1801
+ this.listener?.onStarted({
1802
+ executionId: snapshot.executionId,
1803
+ taskId: snapshot.taskId,
1804
+ startedAt
1805
+ });
1806
+ try {
1807
+ const executionPayload = resolveTaskExecutionPayload(
1808
+ snapshot.type,
1809
+ snapshot.payload,
1810
+ snapshot.workspacePath
1811
+ );
1812
+ validateTaskPayload(snapshot.type, executionPayload);
1813
+ const executor = this.getExecutor(snapshot.type);
1814
+ let result;
1815
+ if (isStreamingTaskExecutor(executor)) {
1816
+ const handle = executor.executeStreaming(
1817
+ executionPayload,
1818
+ (stream, data) => {
1819
+ this.listener?.onOutput({
1820
+ executionId: snapshot.executionId,
1821
+ stream,
1822
+ data
1823
+ });
1824
+ },
1825
+ ac.signal
1826
+ );
1827
+ runningExec.cancel = () => {
1828
+ handle.cancel();
1829
+ ac.abort();
1830
+ };
1831
+ result = await handle.result;
1832
+ } else {
1833
+ result = await executor.execute(executionPayload, ac.signal);
1834
+ }
1835
+ const log = {
1836
+ id: snapshot.executionId,
1837
+ taskId: snapshot.taskId,
1838
+ taskName: snapshot.name,
1839
+ startedAt,
1840
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
1841
+ status: result.status === "running" ? "failure" : result.status,
1842
+ output: result.output,
1843
+ error: result.error
1844
+ };
1845
+ switch (log.status) {
1846
+ case "success":
1847
+ this.listener?.onCompleted({
1848
+ executionId: snapshot.executionId,
1849
+ log
1850
+ });
1851
+ break;
1852
+ case "cancelled":
1853
+ this.listener?.onCancelled({
1854
+ executionId: snapshot.executionId,
1855
+ log
1856
+ });
1857
+ break;
1858
+ case "timeout":
1859
+ this.listener?.onFailed({
1860
+ executionId: snapshot.executionId,
1861
+ status: "timeout",
1862
+ reason: "timeout",
1863
+ log
1864
+ });
1865
+ break;
1866
+ default:
1867
+ this.listener?.onFailed({
1868
+ executionId: snapshot.executionId,
1869
+ status: "failure",
1870
+ reason: "error",
1871
+ log
1872
+ });
1873
+ break;
1874
+ }
1875
+ } catch (err) {
1876
+ const log = {
1877
+ id: snapshot.executionId,
1878
+ taskId: snapshot.taskId,
1879
+ taskName: snapshot.name,
1880
+ startedAt,
1881
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
1882
+ status: ac.signal.aborted ? "cancelled" : "failure",
1883
+ error: err instanceof Error ? err.message : String(err)
1884
+ };
1885
+ if (ac.signal.aborted) {
1886
+ this.listener?.onCancelled({
1887
+ executionId: snapshot.executionId,
1888
+ log
1889
+ });
1890
+ } else {
1891
+ this.listener?.onFailed({
1892
+ executionId: snapshot.executionId,
1893
+ status: "failure",
1894
+ reason: "error",
1895
+ log
1896
+ });
1897
+ }
1898
+ } finally {
1899
+ this.running.delete(snapshot.executionId);
1900
+ const taskExecIds = this.taskExecutions.get(snapshot.taskId);
1901
+ if (taskExecIds) {
1902
+ taskExecIds.delete(snapshot.executionId);
1903
+ if (taskExecIds.size === 0) {
1904
+ this.taskExecutions.delete(snapshot.taskId);
1905
+ }
1906
+ }
1907
+ }
1908
+ }
1909
+ cancel(executionId) {
1910
+ const exec = this.running.get(executionId);
1911
+ if (!exec) return false;
1912
+ exec.cancel();
1913
+ return true;
1914
+ }
1915
+ listRunning() {
1916
+ return Array.from(this.running.values()).map((e) => ({
1917
+ executionId: e.executionId,
1918
+ taskId: e.taskId,
1919
+ startedAt: e.startedAt
1920
+ }));
1921
+ }
1922
+ dispose() {
1923
+ for (const exec of this.running.values()) {
1924
+ exec.cancel();
1925
+ }
1926
+ this.running.clear();
1927
+ this.taskExecutions.clear();
1928
+ }
1929
+ };
1930
+
1527
1931
  // src/scheduled-tasks/TaskLogManager.ts
1528
1932
  var import_node_crypto2 = require("crypto");
1529
- var fs6 = __toESM(require("fs"));
1530
- var path6 = __toESM(require("path"));
1933
+ var fs8 = __toESM(require("fs"));
1934
+ var path7 = __toESM(require("path"));
1531
1935
  var CONFIG_DIR4 = ".serviceme";
1532
1936
  var LOG_FILE2 = "scheduled-tasks-log.json";
1533
1937
  var MAX_LOGS = 200;
@@ -1552,17 +1956,17 @@ function validateAndRepairLogFile(raw) {
1552
1956
  }
1553
1957
  var TaskLogManager = class {
1554
1958
  constructor(workspacePath) {
1555
- this.logPath = path6.join(workspacePath, CONFIG_DIR4, LOG_FILE2);
1959
+ this.logPath = path7.join(workspacePath, CONFIG_DIR4, LOG_FILE2);
1556
1960
  }
1557
1961
  getLogPath() {
1558
1962
  return this.logPath;
1559
1963
  }
1560
1964
  readLogFile() {
1561
- if (!fs6.existsSync(this.logPath)) {
1965
+ if (!fs8.existsSync(this.logPath)) {
1562
1966
  return emptyLogFile();
1563
1967
  }
1564
1968
  try {
1565
- const raw = fs6.readFileSync(this.logPath, "utf-8");
1969
+ const raw = fs8.readFileSync(this.logPath, "utf-8");
1566
1970
  const parsed = JSON.parse(raw);
1567
1971
  const file = validateAndRepairLogFile(parsed);
1568
1972
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.logs) || parsed.logs.length !== file.logs.length) {
@@ -1578,21 +1982,21 @@ var TaskLogManager = class {
1578
1982
  }
1579
1983
  backupCorruptedFile() {
1580
1984
  try {
1581
- if (fs6.existsSync(this.logPath)) {
1985
+ if (fs8.existsSync(this.logPath)) {
1582
1986
  const backupPath = `${this.logPath}.corrupted.${Date.now()}`;
1583
- fs6.copyFileSync(this.logPath, backupPath);
1987
+ fs8.copyFileSync(this.logPath, backupPath);
1584
1988
  }
1585
1989
  } catch {
1586
1990
  }
1587
1991
  }
1588
1992
  writeLogFile(file) {
1589
- const dir = path6.dirname(this.logPath);
1590
- if (!fs6.existsSync(dir)) {
1591
- fs6.mkdirSync(dir, { recursive: true });
1993
+ const dir = path7.dirname(this.logPath);
1994
+ if (!fs8.existsSync(dir)) {
1995
+ fs8.mkdirSync(dir, { recursive: true });
1592
1996
  }
1593
1997
  const tmp = `${this.logPath}.tmp`;
1594
- fs6.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
1595
- fs6.renameSync(tmp, this.logPath);
1998
+ fs8.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
1999
+ fs8.renameSync(tmp, this.logPath);
1596
2000
  }
1597
2001
  appendLog(input) {
1598
2002
  const file = this.readLogFile();
@@ -1662,7 +2066,23 @@ var SchedulerDaemon = class {
1662
2066
  this.running = true;
1663
2067
  this.startTime = Date.now();
1664
2068
  this.pidManager.writePid(process.pid);
1665
- this.logger.log("info", `Scheduler daemon started (PID: ${process.pid})`);
2069
+ const configPath = this.configManager.getConfigPath();
2070
+ const logPath = this.logger.getLogPath();
2071
+ this.logger.log("info", "=".repeat(60));
2072
+ this.logger.log(
2073
+ "info",
2074
+ `Scheduler daemon started
2075
+ PID: ${process.pid}
2076
+ Node: ${process.version}
2077
+ OS: ${os.type()} ${os.release()} (${process.arch})
2078
+ Platform: ${process.platform}
2079
+ Hostname: ${os.hostname()}
2080
+ User: ${os.userInfo().username}
2081
+ Workspace: ${this.workspacePath}
2082
+ Config: ${configPath}
2083
+ LogPath: ${logPath}
2084
+ ExecPath: ${process.execPath}`
2085
+ );
1666
2086
  this.tickTimer = setInterval(() => this.tick(), TICK_INTERVAL);
1667
2087
  this.setupConfigWatch();
1668
2088
  process.on("SIGTERM", () => this.stop());
@@ -1687,8 +2107,8 @@ var SchedulerDaemon = class {
1687
2107
  const configPath = this.configManager.getConfigPath();
1688
2108
  const dir = configPath.substring(0, configPath.lastIndexOf("/"));
1689
2109
  try {
1690
- if (fs7.existsSync(dir)) {
1691
- this.watcher = fs7.watch(dir, (_eventType, filename) => {
2110
+ if (fs9.existsSync(dir)) {
2111
+ this.watcher = fs9.watch(dir, (_eventType, filename) => {
1692
2112
  if (filename === "scheduled-tasks.json") {
1693
2113
  this.logger.log("info", "Config file changed, reconciling...");
1694
2114
  }
@@ -1704,7 +2124,11 @@ var SchedulerDaemon = class {
1704
2124
  for (const task of config.tasks) {
1705
2125
  if (!task.enabled) continue;
1706
2126
  if (this.taskRunning.has(task.id)) continue;
1707
- const lastExec = this.lastRun.get(task.id) ?? 0;
2127
+ let lastExec = this.lastRun.get(task.id);
2128
+ if (lastExec === void 0) {
2129
+ this.lastRun.set(task.id, now);
2130
+ lastExec = now;
2131
+ }
1708
2132
  if (this.shouldRun(task, lastExec, now)) {
1709
2133
  this.executeTask(task, now);
1710
2134
  }
@@ -1726,11 +2150,25 @@ var SchedulerDaemon = class {
1726
2150
  this.taskRunning.add(task.id);
1727
2151
  this.lastRun.set(task.id, now);
1728
2152
  const startedAt = new Date(now).toISOString();
1729
- this.logger.log("info", `Executing task: ${task.name} (${task.id})`);
2153
+ const startMs = Date.now();
2154
+ this.logger.log(
2155
+ "info",
2156
+ `Executing task: ${task.name} (${task.id})
2157
+ type: ${task.taskType}
2158
+ schedule: ${task.schedule} (${task.scheduleType})
2159
+ enabled: ${task.enabled}`
2160
+ );
1730
2161
  try {
2162
+ const executionPayload = resolveTaskExecutionPayload(
2163
+ task.taskType,
2164
+ task.payload,
2165
+ this.workspacePath
2166
+ );
2167
+ validateTaskPayload(task.taskType, executionPayload);
1731
2168
  const executor = getExecutor(task.taskType);
1732
- const result = await executor.execute(task.payload);
2169
+ const result = await executor.execute(executionPayload);
1733
2170
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
2171
+ const durationMs = Date.now() - startMs;
1734
2172
  this.logManager.appendLog({
1735
2173
  taskId: task.id,
1736
2174
  taskName: task.name,
@@ -1740,9 +2178,17 @@ var SchedulerDaemon = class {
1740
2178
  output: result.output,
1741
2179
  error: result.error
1742
2180
  });
1743
- this.logger.log("info", `Task ${task.name} completed: ${result.status}`);
2181
+ const outputBytes = result.output ? Buffer.byteLength(result.output) : 0;
2182
+ this.logger.log(
2183
+ "info",
2184
+ `Task completed: ${task.name} (${task.id})
2185
+ status: ${result.status}
2186
+ duration: ${durationMs}ms
2187
+ outputBytes: ${outputBytes}`
2188
+ );
1744
2189
  } catch (err) {
1745
2190
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
2191
+ const durationMs = Date.now() - startMs;
1746
2192
  const message = err instanceof Error ? err.message : String(err);
1747
2193
  this.logManager.appendLog({
1748
2194
  taskId: task.id,
@@ -1752,7 +2198,12 @@ var SchedulerDaemon = class {
1752
2198
  status: "failure",
1753
2199
  error: message
1754
2200
  });
1755
- this.logger.log("error", `Task ${task.name} failed: ${message}`);
2201
+ this.logger.log(
2202
+ "error",
2203
+ `Task failed: ${task.name} (${task.id})
2204
+ duration: ${durationMs}ms
2205
+ error: ${message}`
2206
+ );
1756
2207
  } finally {
1757
2208
  this.taskRunning.delete(task.id);
1758
2209
  }
@@ -1809,164 +2260,6 @@ function matchCronField(field, value) {
1809
2260
  const values = field.split(",");
1810
2261
  return values.some((v) => Number.parseInt(v, 10) === value);
1811
2262
  }
1812
-
1813
- // src/scheduled-tasks/TaskExecutionEngine.ts
1814
- var TaskExecutionEngine = class {
1815
- constructor(getExecutor2) {
1816
- this.getExecutor = getExecutor2;
1817
- this.running = /* @__PURE__ */ new Map();
1818
- this.taskExecutions = /* @__PURE__ */ new Map();
1819
- this.listener = null;
1820
- }
1821
- setListener(listener) {
1822
- this.listener = listener;
1823
- }
1824
- async execute(snapshot) {
1825
- const policy = snapshot.concurrencyPolicy ?? "reject";
1826
- if (policy === "reject") {
1827
- const existingIds = this.taskExecutions.get(snapshot.taskId);
1828
- if (existingIds && existingIds.size > 0) {
1829
- throw new Error(
1830
- `TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
1831
- );
1832
- }
1833
- }
1834
- const executor = this.getExecutor(snapshot.type);
1835
- const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1836
- if (!this.taskExecutions.has(snapshot.taskId)) {
1837
- this.taskExecutions.set(snapshot.taskId, /* @__PURE__ */ new Set());
1838
- }
1839
- this.taskExecutions.get(snapshot.taskId)?.add(snapshot.executionId);
1840
- const ac = new AbortController();
1841
- const runningExec = {
1842
- executionId: snapshot.executionId,
1843
- taskId: snapshot.taskId,
1844
- startedAt,
1845
- cancel: () => ac.abort()
1846
- };
1847
- this.running.set(snapshot.executionId, runningExec);
1848
- this.listener?.onStarted({
1849
- executionId: snapshot.executionId,
1850
- taskId: snapshot.taskId,
1851
- startedAt
1852
- });
1853
- try {
1854
- let result;
1855
- if (isStreamingTaskExecutor(executor)) {
1856
- const handle = executor.executeStreaming(
1857
- snapshot.payload,
1858
- (stream, data) => {
1859
- this.listener?.onOutput({
1860
- executionId: snapshot.executionId,
1861
- stream,
1862
- data
1863
- });
1864
- },
1865
- ac.signal
1866
- );
1867
- runningExec.cancel = () => {
1868
- handle.cancel();
1869
- ac.abort();
1870
- };
1871
- result = await handle.result;
1872
- } else {
1873
- result = await executor.execute(snapshot.payload, ac.signal);
1874
- }
1875
- const log = {
1876
- id: snapshot.executionId,
1877
- taskId: snapshot.taskId,
1878
- taskName: snapshot.name,
1879
- startedAt,
1880
- finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
1881
- status: result.status === "running" ? "failure" : result.status,
1882
- output: result.output,
1883
- error: result.error
1884
- };
1885
- switch (log.status) {
1886
- case "success":
1887
- this.listener?.onCompleted({
1888
- executionId: snapshot.executionId,
1889
- log
1890
- });
1891
- break;
1892
- case "cancelled":
1893
- this.listener?.onCancelled({
1894
- executionId: snapshot.executionId,
1895
- log
1896
- });
1897
- break;
1898
- case "timeout":
1899
- this.listener?.onFailed({
1900
- executionId: snapshot.executionId,
1901
- status: "timeout",
1902
- reason: "timeout",
1903
- log
1904
- });
1905
- break;
1906
- default:
1907
- this.listener?.onFailed({
1908
- executionId: snapshot.executionId,
1909
- status: "failure",
1910
- reason: "error",
1911
- log
1912
- });
1913
- break;
1914
- }
1915
- } catch (err) {
1916
- const log = {
1917
- id: snapshot.executionId,
1918
- taskId: snapshot.taskId,
1919
- taskName: snapshot.name,
1920
- startedAt,
1921
- finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
1922
- status: ac.signal.aborted ? "cancelled" : "failure",
1923
- error: err instanceof Error ? err.message : String(err)
1924
- };
1925
- if (ac.signal.aborted) {
1926
- this.listener?.onCancelled({
1927
- executionId: snapshot.executionId,
1928
- log
1929
- });
1930
- } else {
1931
- this.listener?.onFailed({
1932
- executionId: snapshot.executionId,
1933
- status: "failure",
1934
- reason: "error",
1935
- log
1936
- });
1937
- }
1938
- } finally {
1939
- this.running.delete(snapshot.executionId);
1940
- const taskExecIds = this.taskExecutions.get(snapshot.taskId);
1941
- if (taskExecIds) {
1942
- taskExecIds.delete(snapshot.executionId);
1943
- if (taskExecIds.size === 0) {
1944
- this.taskExecutions.delete(snapshot.taskId);
1945
- }
1946
- }
1947
- }
1948
- }
1949
- cancel(executionId) {
1950
- const exec = this.running.get(executionId);
1951
- if (!exec) return false;
1952
- exec.cancel();
1953
- return true;
1954
- }
1955
- listRunning() {
1956
- return Array.from(this.running.values()).map((e) => ({
1957
- executionId: e.executionId,
1958
- taskId: e.taskId,
1959
- startedAt: e.startedAt
1960
- }));
1961
- }
1962
- dispose() {
1963
- for (const exec of this.running.values()) {
1964
- exec.cancel();
1965
- }
1966
- this.running.clear();
1967
- this.taskExecutions.clear();
1968
- }
1969
- };
1970
2263
  // Annotate the CommonJS export names for ESM import in node:
1971
2264
  0 && (module.exports = {
1972
2265
  DaemonLogger,
@@ -1996,5 +2289,7 @@ var TaskExecutionEngine = class {
1996
2289
  moveFiles,
1997
2290
  noopLogger,
1998
2291
  parseIntervalMs,
1999
- unzipFile
2292
+ resolveTaskExecutionPayload,
2293
+ unzipFile,
2294
+ validateTaskPayload
2000
2295
  });