@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.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 = "";
@@ -243,8 +244,14 @@ import {
243
244
  createServicemeError as createServicemeError3,
244
245
  KNOWN_ENVIRONMENT_TOOLS
245
246
  } from "@serviceme/devtools-protocol";
246
- var TOOL_CHECK_TIMEOUT_MS = 5e3;
247
+ var DEFAULT_TOOL_CHECK_TIMEOUT_MS = 5e3;
248
+ var TOOL_CHECK_TIMEOUT_MS = {
249
+ nuget: 12e3,
250
+ nvm: 8e3,
251
+ dotnet: 8e3
252
+ };
247
253
  var ERROR_CODE_NOT_FOUND = 127;
254
+ var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
248
255
  var EnvironmentInspector = class {
249
256
  async checkEnvironment() {
250
257
  const results = await Promise.all(
@@ -281,19 +288,18 @@ var EnvironmentInspector = class {
281
288
  const isWindows = process.platform === "win32";
282
289
  const result = await runCommand(isWindows ? "where" : "which", {
283
290
  args: [toolName],
284
- timeoutMs: TOOL_CHECK_TIMEOUT_MS
291
+ timeoutMs: this.getToolTimeout(toolName)
285
292
  });
286
- return result.stdout.trim().split("\n")[0];
293
+ return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
287
294
  } catch {
288
295
  return void 0;
289
296
  }
290
297
  }
291
298
  async getToolVersion(toolName) {
292
- const command = this.getVersionCommand(toolName);
293
- const isWindows = process.platform === "win32";
294
- const result = await runCommand(isWindows ? "cmd.exe" : "/bin/bash", {
295
- args: isWindows ? ["/c", command] : ["-lc", command],
296
- timeoutMs: TOOL_CHECK_TIMEOUT_MS
299
+ const invocation = this.getVersionInvocation(toolName);
300
+ const result = await runCommand(invocation.command, {
301
+ args: invocation.args,
302
+ timeoutMs: this.getToolTimeout(toolName)
297
303
  });
298
304
  return this.parseVersion(toolName, result.stdout || result.stderr);
299
305
  }
@@ -302,7 +308,7 @@ var EnvironmentInspector = class {
302
308
  try {
303
309
  const result = await runCommand("cmd.exe", {
304
310
  args: ["/c", "nvm version"],
305
- timeoutMs: TOOL_CHECK_TIMEOUT_MS
311
+ timeoutMs: this.getToolTimeout("nvm")
306
312
  });
307
313
  return {
308
314
  installed: true,
@@ -322,7 +328,7 @@ var EnvironmentInspector = class {
322
328
  "-lc",
323
329
  'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; nvm --version'
324
330
  ],
325
- timeoutMs: TOOL_CHECK_TIMEOUT_MS
331
+ timeoutMs: this.getToolTimeout("nvm")
326
332
  });
327
333
  return {
328
334
  installed: true,
@@ -347,7 +353,7 @@ var EnvironmentInspector = class {
347
353
  try {
348
354
  const result = await runCommand("dotnet", {
349
355
  args: ["nuget", "list", "source"],
350
- timeoutMs: TOOL_CHECK_TIMEOUT_MS
356
+ timeoutMs: this.getToolTimeout("nuget")
351
357
  });
352
358
  const privateSourceUrl = "http://192.168.20.209:10010/nuget";
353
359
  if (result.stdout.includes(privateSourceUrl)) {
@@ -365,17 +371,28 @@ var EnvironmentInspector = class {
365
371
  return this.handleToolCheckError(error);
366
372
  }
367
373
  }
368
- getVersionCommand(toolName) {
374
+ getVersionInvocation(toolName) {
375
+ const isWindows = process.platform === "win32";
376
+ if (isWindows && ["npm", "pnpm", "nrm"].includes(toolName)) {
377
+ return {
378
+ command: "cmd.exe",
379
+ args: ["/d", "/s", "/c", `${toolName} --version`]
380
+ };
381
+ }
369
382
  const commands = {
370
- git: "git --version",
371
- node: "node --version",
372
- npm: "npm --version",
373
- pnpm: "pnpm --version",
374
- nvm: "nvm --version",
375
- nrm: "nrm --version",
376
- dotnet: "dotnet --version"
383
+ git: { command: "git", args: ["--version"] },
384
+ node: { command: "node", args: ["--version"] },
385
+ npm: { command: "npm", args: ["--version"] },
386
+ pnpm: { command: "pnpm", args: ["--version"] },
387
+ nvm: { command: "nvm", args: ["--version"] },
388
+ nrm: { command: "nrm", args: ["--version"] },
389
+ rtk: { command: "rtk", args: ["--version"] },
390
+ dotnet: { command: "dotnet", args: ["--version"] }
377
391
  };
378
- return commands[toolName] || `${toolName} --version`;
392
+ return commands[toolName] || { command: toolName, args: ["--version"] };
393
+ }
394
+ getToolTimeout(toolName) {
395
+ return TOOL_CHECK_TIMEOUT_MS[toolName] ?? DEFAULT_TOOL_CHECK_TIMEOUT_MS;
379
396
  }
380
397
  parseVersion(toolName, output) {
381
398
  const cleaned = output.trim();
@@ -402,10 +419,11 @@ var EnvironmentInspector = class {
402
419
  const execError = error;
403
420
  const message = execError.message || String(error);
404
421
  const code = execError.code;
405
- const isNotFound = message.includes("command not found") || message.includes("not recognized") || code === ERROR_CODE_NOT_FOUND;
422
+ const isNotFound = message.includes("command not found") || message.includes("not recognized") || message.includes("ENOENT") || code === "ENOENT" || code === ERROR_CODE_NOT_FOUND;
423
+ const isTimeout = code === ERROR_CODE_TIMEOUT || message.toLowerCase().includes("timed out");
406
424
  return {
407
425
  installed: false,
408
- error: isNotFound ? "Not installed" : message
426
+ error: isNotFound ? "Not installed" : isTimeout ? "Check timed out" : message
409
427
  };
410
428
  }
411
429
  };
@@ -1081,12 +1099,75 @@ var PidManager = class {
1081
1099
  };
1082
1100
 
1083
1101
  // src/scheduled-tasks/daemon/SchedulerDaemon.ts
1084
- import * as fs7 from "fs";
1102
+ import * as fs9 from "fs";
1103
+ import * as os from "os";
1085
1104
 
1086
1105
  // src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
1087
1106
  import { spawn as spawn2 } from "child_process";
1088
- var DEFAULT_TIMEOUT = 3e5;
1107
+ import * as fs5 from "fs";
1108
+
1109
+ // src/scheduled-tasks/executors/timeout.ts
1110
+ function resolveConfiguredTimeoutMs(timeoutSeconds, defaultTimeoutMs) {
1111
+ if (timeoutSeconds === 0) {
1112
+ return void 0;
1113
+ }
1114
+ if (typeof timeoutSeconds !== "number" || !Number.isFinite(timeoutSeconds) || timeoutSeconds < 0) {
1115
+ return defaultTimeoutMs;
1116
+ }
1117
+ return timeoutSeconds * 1e3;
1118
+ }
1119
+
1120
+ // src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
1089
1121
  var MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
1122
+ var DEFAULT_TIMEOUT_MS2 = 3e5;
1123
+ function redactArgs(args) {
1124
+ const redacted = [];
1125
+ let redactNext = false;
1126
+ for (const arg of args) {
1127
+ if (redactNext) {
1128
+ redacted.push("<redacted>");
1129
+ redactNext = false;
1130
+ continue;
1131
+ }
1132
+ redacted.push(arg);
1133
+ if (arg === "--prompt") {
1134
+ redactNext = true;
1135
+ }
1136
+ }
1137
+ return redacted;
1138
+ }
1139
+ function writeDiagnostic(message) {
1140
+ const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
1141
+ if (logPath) {
1142
+ fs5.appendFileSync(logPath, message);
1143
+ return;
1144
+ }
1145
+ process.stderr.write(message);
1146
+ }
1147
+ function resolveGithubCopilotCliExecution(payload) {
1148
+ const args = ["copilot", "prompt", "--prompt", payload.prompt];
1149
+ if (payload.autopilot) {
1150
+ args.push("--autopilot");
1151
+ }
1152
+ if (payload.allowTools && payload.allowTools.length > 0) {
1153
+ args.push("--allow-tools", payload.allowTools.join(","));
1154
+ }
1155
+ if (payload.model) {
1156
+ args.push("--model", payload.model);
1157
+ }
1158
+ if (payload.agent) {
1159
+ args.push("--agent", payload.agent);
1160
+ }
1161
+ args.push("--timeout", String(payload.timeout ?? 0));
1162
+ return {
1163
+ command: "serviceme",
1164
+ args,
1165
+ cwd: payload.workspace,
1166
+ timeoutMs: resolveConfiguredTimeoutMs(payload.timeout, DEFAULT_TIMEOUT_MS2),
1167
+ diagnosticArgs: redactArgs(args),
1168
+ promptLen: payload.prompt.length
1169
+ };
1170
+ }
1090
1171
  var GithubCopilotCliExecutor = class {
1091
1172
  async execute(payload, abortSignal) {
1092
1173
  const authenticated = await isCopilotAuthenticated();
@@ -1109,7 +1190,7 @@ var GithubCopilotCliExecutor = class {
1109
1190
  }
1110
1191
  executeStreaming(payload, onOutput, abortSignal) {
1111
1192
  const p = payload;
1112
- const timeout = (p.timeout != null ? p.timeout * 1e3 : null) ?? DEFAULT_TIMEOUT;
1193
+ const execution = resolveGithubCopilotCliExecution(p);
1113
1194
  let resolve;
1114
1195
  const resultPromise = new Promise((r) => {
1115
1196
  resolve = r;
@@ -1118,7 +1199,7 @@ var GithubCopilotCliExecutor = class {
1118
1199
  const settle = (result) => {
1119
1200
  if (settled) return;
1120
1201
  settled = true;
1121
- clearTimeout(timer);
1202
+ if (timer) clearTimeout(timer);
1122
1203
  resolve?.(result);
1123
1204
  };
1124
1205
  if (abortSignal?.aborted) {
@@ -1131,36 +1212,32 @@ var GithubCopilotCliExecutor = class {
1131
1212
  }
1132
1213
  };
1133
1214
  }
1134
- const args = ["copilot", "prompt", "--prompt", p.prompt];
1135
- if (p.autopilot) {
1136
- args.push("--autopilot");
1137
- }
1138
- if (p.allowTools && p.allowTools.length > 0) {
1139
- args.push("--allow-tools", p.allowTools.join(","));
1140
- }
1141
- if (p.model) {
1142
- args.push("--model", p.model);
1143
- }
1144
- if (p.agent) {
1145
- args.push("--agent", p.agent);
1146
- }
1147
- if (p.timeout != null) {
1148
- args.push("--timeout", String(p.timeout));
1149
- }
1150
- const child = spawn2("serviceme", args, {
1151
- cwd: p.workspace,
1152
- stdio: ["ignore", "pipe", "pipe"]
1215
+ writeDiagnostic(
1216
+ `[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}
1217
+ `
1218
+ );
1219
+ const child = spawn2(execution.command, execution.args, {
1220
+ cwd: execution.cwd,
1221
+ stdio: ["ignore", "pipe", "pipe"],
1222
+ windowsHide: true
1153
1223
  });
1154
- const timer = setTimeout(() => {
1224
+ if (child.pid) {
1225
+ writeDiagnostic(
1226
+ `[GithubCopilotCliExecutor] spawned child PID=${child.pid}
1227
+ `
1228
+ );
1229
+ }
1230
+ const timeoutMs = execution.timeoutMs;
1231
+ const timer = timeoutMs != null ? setTimeout(() => {
1155
1232
  child.kill("SIGTERM");
1156
1233
  setTimeout(() => {
1157
1234
  if (!child.killed) child.kill("SIGKILL");
1158
1235
  }, 5e3);
1159
1236
  settle({
1160
1237
  status: "timeout",
1161
- error: `Copilot CLI execution timed out after ${timeout / 1e3}s`
1238
+ error: `Copilot CLI execution timed out after ${timeoutMs / 1e3}s`
1162
1239
  });
1163
- }, timeout);
1240
+ }, timeoutMs) : void 0;
1164
1241
  let stdoutBuf = "";
1165
1242
  let stderrBuf = "";
1166
1243
  child.stdout.on("data", (chunk) => {
@@ -1204,19 +1281,19 @@ var GithubCopilotCliExecutor = class {
1204
1281
  };
1205
1282
 
1206
1283
  // src/scheduled-tasks/executors/HttpRequestExecutor.ts
1207
- var DEFAULT_TIMEOUT2 = 3e4;
1284
+ var DEFAULT_TIMEOUT_MS3 = 3e4;
1208
1285
  var HttpRequestExecutor = class {
1209
1286
  async execute(payload, abortSignal) {
1210
1287
  const p = payload;
1211
- const timeout = (p.timeout != null ? p.timeout * 1e3 : null) ?? DEFAULT_TIMEOUT2;
1288
+ const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS3);
1212
1289
  const ac = new AbortController();
1213
- let _timedOut = false;
1214
- const timer = setTimeout(() => {
1215
- _timedOut = true;
1290
+ let timedOut = false;
1291
+ const timer = timeoutMs != null ? setTimeout(() => {
1292
+ timedOut = true;
1216
1293
  ac.abort();
1217
- }, timeout);
1294
+ }, timeoutMs) : void 0;
1218
1295
  if (abortSignal?.aborted) {
1219
- clearTimeout(timer);
1296
+ if (timer) clearTimeout(timer);
1220
1297
  return { status: "cancelled", error: "Execution aborted" };
1221
1298
  }
1222
1299
  let externalAbort = false;
@@ -1224,7 +1301,7 @@ var HttpRequestExecutor = class {
1224
1301
  "abort",
1225
1302
  () => {
1226
1303
  externalAbort = true;
1227
- clearTimeout(timer);
1304
+ if (timer) clearTimeout(timer);
1228
1305
  ac.abort();
1229
1306
  },
1230
1307
  { once: true }
@@ -1236,7 +1313,7 @@ var HttpRequestExecutor = class {
1236
1313
  body: p.body,
1237
1314
  signal: ac.signal
1238
1315
  });
1239
- clearTimeout(timer);
1316
+ if (timer) clearTimeout(timer);
1240
1317
  const body = await response.text();
1241
1318
  if (response.ok) {
1242
1319
  return {
@@ -1251,14 +1328,17 @@ ${body}`.trim()
1251
1328
  ${body}`.trim()
1252
1329
  };
1253
1330
  } catch (err) {
1254
- clearTimeout(timer);
1331
+ if (timer) clearTimeout(timer);
1255
1332
  if (err instanceof Error && err.name === "AbortError") {
1256
1333
  if (externalAbort) {
1257
1334
  return { status: "cancelled", error: "Execution cancelled" };
1258
1335
  }
1336
+ if (!timedOut || timeoutMs == null) {
1337
+ return { status: "failure", error: err.message };
1338
+ }
1259
1339
  return {
1260
1340
  status: "timeout",
1261
- error: `HTTP request timed out after ${timeout / 1e3}s`
1341
+ error: `HTTP request timed out after ${timeoutMs / 1e3}s`
1262
1342
  };
1263
1343
  }
1264
1344
  const message = err instanceof Error ? err.message : String(err);
@@ -1269,8 +1349,82 @@ ${body}`.trim()
1269
1349
 
1270
1350
  // src/scheduled-tasks/executors/ShellExecutor.ts
1271
1351
  import { spawn as spawn3 } from "child_process";
1272
- var DEFAULT_TIMEOUT3 = 6e4;
1352
+ import * as fs6 from "fs";
1353
+ import * as path5 from "path";
1273
1354
  var MAX_OUTPUT_BYTES2 = 1024 * 1024;
1355
+ var DEFAULT_TIMEOUT_MS4 = 6e4;
1356
+ var POSIX_SHELL_CANDIDATES = ["bash.exe", "sh.exe"];
1357
+ function resolveShellExecution(script, options = {}) {
1358
+ const platform = options.platform ?? process.platform;
1359
+ const env = options.env ?? process.env;
1360
+ const fileExists = options.fileExists ?? fs6.existsSync;
1361
+ if (platform === "win32") {
1362
+ const posixShell = usesPosixShellSyntax(script) ? findWindowsPosixShell(env, fileExists) : null;
1363
+ if (posixShell) {
1364
+ return {
1365
+ command: posixShell,
1366
+ args: ["-s"],
1367
+ stdinScript: script,
1368
+ outputEncoding: "utf8",
1369
+ shellKind: "posix"
1370
+ };
1371
+ }
1372
+ return {
1373
+ command: env.ComSpec ?? env.COMSPEC ?? "cmd.exe",
1374
+ args: ["/d", "/s", "/c", script],
1375
+ outputEncoding: "utf8",
1376
+ shellKind: "cmd"
1377
+ };
1378
+ }
1379
+ return {
1380
+ command: "sh",
1381
+ args: ["-s"],
1382
+ stdinScript: script,
1383
+ outputEncoding: "utf8",
1384
+ shellKind: "posix"
1385
+ };
1386
+ }
1387
+ function usesPosixShellSyntax(script) {
1388
+ return /\$\([^)]*\)|`[^`]*`/.test(script);
1389
+ }
1390
+ function findWindowsPosixShell(env, fileExists) {
1391
+ const explicitShell = env.SERVICEME_POSIX_SHELL;
1392
+ if (explicitShell && fileExists(explicitShell)) {
1393
+ return explicitShell;
1394
+ }
1395
+ const knownGitBashPaths = [
1396
+ "C:\\Program Files\\Git\\bin\\bash.exe",
1397
+ "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
1398
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
1399
+ "C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
1400
+ ];
1401
+ for (const candidate of knownGitBashPaths) {
1402
+ if (fileExists(candidate)) return candidate;
1403
+ }
1404
+ const pathValue = env.Path ?? env.PATH ?? "";
1405
+ for (const dir of pathValue.split(path5.win32.delimiter)) {
1406
+ if (!dir) continue;
1407
+ for (const executable of POSIX_SHELL_CANDIDATES) {
1408
+ const candidate = path5.win32.join(dir, executable);
1409
+ if (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {
1410
+ return candidate;
1411
+ }
1412
+ }
1413
+ }
1414
+ return null;
1415
+ }
1416
+ function isWindowsWslLauncher(candidate) {
1417
+ const normalized = path5.win32.normalize(candidate).toLowerCase();
1418
+ return normalized.endsWith("\\windows\\system32\\bash.exe") || normalized.endsWith("\\windows\\syswow64\\bash.exe");
1419
+ }
1420
+ function writeDiagnostic2(message) {
1421
+ const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
1422
+ if (logPath) {
1423
+ fs6.appendFileSync(logPath, message);
1424
+ return;
1425
+ }
1426
+ process.stderr.write(message);
1427
+ }
1274
1428
  var ShellExecutor = class {
1275
1429
  async execute(payload, abortSignal) {
1276
1430
  let output = "";
@@ -1286,7 +1440,7 @@ var ShellExecutor = class {
1286
1440
  }
1287
1441
  executeStreaming(payload, onOutput, abortSignal) {
1288
1442
  const p = payload;
1289
- const timeout = (p.timeout != null ? p.timeout * 1e3 : null) ?? DEFAULT_TIMEOUT3;
1443
+ const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS4);
1290
1444
  let resolve;
1291
1445
  const resultPromise = new Promise((r) => {
1292
1446
  resolve = r;
@@ -1295,7 +1449,7 @@ var ShellExecutor = class {
1295
1449
  const settle = (result) => {
1296
1450
  if (settled) return;
1297
1451
  settled = true;
1298
- clearTimeout(timer);
1452
+ if (timer) clearTimeout(timer);
1299
1453
  resolve?.(result);
1300
1454
  };
1301
1455
  if (abortSignal?.aborted) {
@@ -1308,37 +1462,65 @@ var ShellExecutor = class {
1308
1462
  }
1309
1463
  };
1310
1464
  }
1311
- const child = spawn3("sh", ["-c", p.script], {
1465
+ const shellExecution = resolveShellExecution(p.script);
1466
+ const spawnOpts = {
1312
1467
  cwd: p.cwd,
1313
- stdio: ["ignore", "pipe", "pipe"]
1314
- });
1315
- const timer = setTimeout(() => {
1468
+ stdio: [shellExecution.stdinScript ? "pipe" : "ignore", "pipe", "pipe"],
1469
+ windowsHide: true
1470
+ };
1471
+ writeDiagnostic2(
1472
+ `[ShellExecutor] spawn:
1473
+ platform=${process.platform}
1474
+ shell=${shellExecution.shellKind}
1475
+ command=${shellExecution.command}
1476
+ args=${JSON.stringify(shellExecution.args.filter((a) => a !== p.script))}
1477
+ stdin=${shellExecution.stdinScript ? "true" : "false"}
1478
+ cwd=${p.cwd ?? "(default)"}
1479
+ timeout=${timeoutMs != null ? `${timeoutMs}ms` : "unlimited"}
1480
+ scriptLen=${p.script.length}
1481
+ windowsHide=true
1482
+ daemonPid=${process.pid}
1483
+ `
1484
+ );
1485
+ const child = spawn3(shellExecution.command, shellExecution.args, spawnOpts);
1486
+ if (shellExecution.stdinScript && child.stdin) {
1487
+ child.stdin.end(shellExecution.stdinScript);
1488
+ }
1489
+ if (child.pid) {
1490
+ writeDiagnostic2(`[ShellExecutor] spawned child PID=${child.pid}
1491
+ `);
1492
+ }
1493
+ const timer = timeoutMs != null ? setTimeout(() => {
1316
1494
  child.kill("SIGTERM");
1317
1495
  setTimeout(() => {
1318
1496
  if (!child.killed) child.kill("SIGKILL");
1319
1497
  }, 5e3);
1320
1498
  settle({
1321
1499
  status: "timeout",
1322
- error: `Shell execution timed out after ${timeout / 1e3}s`
1500
+ error: `Shell execution timed out after ${timeoutMs / 1e3}s`
1323
1501
  });
1324
- }, timeout);
1502
+ }, timeoutMs) : void 0;
1325
1503
  let stdoutBuf = "";
1326
1504
  let stderrBuf = "";
1327
- child.stdout.on("data", (chunk) => {
1328
- const data = chunk.toString();
1505
+ child.stdout?.on("data", (chunk) => {
1506
+ const data = chunk.toString(shellExecution.outputEncoding);
1329
1507
  stdoutBuf += data;
1330
1508
  if (stdoutBuf.length > MAX_OUTPUT_BYTES2)
1331
1509
  stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES2);
1332
1510
  onOutput("stdout", data);
1333
1511
  });
1334
- child.stderr.on("data", (chunk) => {
1335
- const data = chunk.toString();
1512
+ child.stderr?.on("data", (chunk) => {
1513
+ const data = chunk.toString(shellExecution.outputEncoding);
1336
1514
  stderrBuf += data;
1337
1515
  if (stderrBuf.length > MAX_OUTPUT_BYTES2)
1338
1516
  stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES2);
1339
1517
  onOutput("stderr", data);
1340
1518
  });
1341
1519
  child.on("close", (code) => {
1520
+ writeDiagnostic2(
1521
+ `[ShellExecutor] child PID=${child.pid} exited: code=${code}
1522
+ `
1523
+ );
1342
1524
  if (code === 0) {
1343
1525
  settle({ status: "success", output: stdoutBuf || void 0 });
1344
1526
  } else {
@@ -1385,36 +1567,65 @@ function getExecutor(taskType) {
1385
1567
 
1386
1568
  // src/scheduled-tasks/TaskConfigManager.ts
1387
1569
  import { randomUUID } from "crypto";
1388
- import * as fs5 from "fs";
1389
- import * as path5 from "path";
1570
+ import * as fs7 from "fs";
1571
+ import * as path6 from "path";
1572
+ import { createServicemeError as createServicemeError7 } from "@serviceme/devtools-protocol";
1390
1573
  var CONFIG_DIR3 = ".serviceme";
1391
1574
  var CONFIG_FILE = "scheduled-tasks.json";
1392
1575
  function emptyConfig() {
1393
1576
  return { version: 1, tasks: [] };
1394
1577
  }
1578
+ function isRecord(value) {
1579
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1580
+ }
1581
+ function requireNonEmptyString(payload, field, taskType) {
1582
+ if (!isRecord(payload) || typeof payload[field] !== "string" || !payload[field].trim()) {
1583
+ throw createServicemeError7(
1584
+ "invalid_payload",
1585
+ `${taskType} payload ${field} is required`
1586
+ );
1587
+ }
1588
+ }
1589
+ function validateTaskPayload(taskType, payload) {
1590
+ switch (taskType) {
1591
+ case "command":
1592
+ requireNonEmptyString(payload, "command", taskType);
1593
+ return;
1594
+ case "shell":
1595
+ requireNonEmptyString(payload, "script", taskType);
1596
+ return;
1597
+ case "http_request":
1598
+ requireNonEmptyString(payload, "url", taskType);
1599
+ requireNonEmptyString(payload, "method", taskType);
1600
+ return;
1601
+ case "github_copilot_cli":
1602
+ requireNonEmptyString(payload, "prompt", taskType);
1603
+ return;
1604
+ }
1605
+ }
1395
1606
  var TaskConfigManager = class {
1396
1607
  constructor(workspacePath) {
1397
- this.configPath = path5.join(workspacePath, CONFIG_DIR3, CONFIG_FILE);
1608
+ this.configPath = path6.join(workspacePath, CONFIG_DIR3, CONFIG_FILE);
1398
1609
  }
1399
1610
  getConfigPath() {
1400
1611
  return this.configPath;
1401
1612
  }
1402
1613
  readConfig() {
1403
- if (!fs5.existsSync(this.configPath)) {
1614
+ if (!fs7.existsSync(this.configPath)) {
1404
1615
  return emptyConfig();
1405
1616
  }
1406
- const raw = fs5.readFileSync(this.configPath, "utf-8");
1617
+ const raw = fs7.readFileSync(this.configPath, "utf-8");
1407
1618
  const parsed = JSON.parse(raw);
1408
1619
  return parsed;
1409
1620
  }
1410
1621
  writeConfig(config) {
1411
- const dir = path5.dirname(this.configPath);
1412
- if (!fs5.existsSync(dir)) {
1413
- fs5.mkdirSync(dir, { recursive: true });
1622
+ const dir = path6.dirname(this.configPath);
1623
+ if (!fs7.existsSync(dir)) {
1624
+ fs7.mkdirSync(dir, { recursive: true });
1414
1625
  }
1415
1626
  const tmp = `${this.configPath}.tmp`;
1416
- fs5.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
1417
- fs5.renameSync(tmp, this.configPath);
1627
+ fs7.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
1628
+ fs7.renameSync(tmp, this.configPath);
1418
1629
  }
1419
1630
  listTasks() {
1420
1631
  return this.readConfig().tasks;
@@ -1426,6 +1637,7 @@ var TaskConfigManager = class {
1426
1637
  return this.readConfig().tasks.find((t) => t.name === name);
1427
1638
  }
1428
1639
  createTask(input) {
1640
+ validateTaskPayload(input.taskType, input.payload);
1429
1641
  const config = this.readConfig();
1430
1642
  const now = (/* @__PURE__ */ new Date()).toISOString();
1431
1643
  const task = {
@@ -1454,6 +1666,9 @@ var TaskConfigManager = class {
1454
1666
  if (!existing) {
1455
1667
  throw new Error(`Task '${id}' not found`);
1456
1668
  }
1669
+ const nextTaskType = input.taskType ?? existing.taskType;
1670
+ const nextPayload = input.payload ?? existing.payload;
1671
+ validateTaskPayload(nextTaskType, nextPayload);
1457
1672
  const updated = {
1458
1673
  ...existing,
1459
1674
  ...input.name !== void 0 && { name: input.name },
@@ -1492,10 +1707,197 @@ var TaskConfigManager = class {
1492
1707
  }
1493
1708
  };
1494
1709
 
1710
+ // src/scheduled-tasks/TaskExecutionEngine.ts
1711
+ function hasNonEmptyString(value) {
1712
+ return typeof value === "string" && value.trim().length > 0;
1713
+ }
1714
+ function resolveTaskExecutionPayload(taskType, payload, workspacePath) {
1715
+ if (!hasNonEmptyString(workspacePath)) {
1716
+ return payload;
1717
+ }
1718
+ if (taskType === "shell") {
1719
+ const shellPayload = payload;
1720
+ if (hasNonEmptyString(shellPayload.cwd)) {
1721
+ return shellPayload;
1722
+ }
1723
+ return { ...shellPayload, cwd: workspacePath };
1724
+ }
1725
+ if (taskType === "github_copilot_cli") {
1726
+ const copilotPayload = payload;
1727
+ if (hasNonEmptyString(copilotPayload.workspace)) {
1728
+ return copilotPayload;
1729
+ }
1730
+ return { ...copilotPayload, workspace: workspacePath };
1731
+ }
1732
+ return payload;
1733
+ }
1734
+ var TaskExecutionEngine = class {
1735
+ constructor(getExecutor2) {
1736
+ this.getExecutor = getExecutor2;
1737
+ this.running = /* @__PURE__ */ new Map();
1738
+ this.taskExecutions = /* @__PURE__ */ new Map();
1739
+ this.listener = null;
1740
+ }
1741
+ setListener(listener) {
1742
+ this.listener = listener;
1743
+ }
1744
+ async execute(snapshot) {
1745
+ const policy = snapshot.concurrencyPolicy ?? "reject";
1746
+ if (policy === "reject") {
1747
+ const existingIds = this.taskExecutions.get(snapshot.taskId);
1748
+ if (existingIds && existingIds.size > 0) {
1749
+ throw new Error(
1750
+ `TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
1751
+ );
1752
+ }
1753
+ }
1754
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1755
+ if (!this.taskExecutions.has(snapshot.taskId)) {
1756
+ this.taskExecutions.set(snapshot.taskId, /* @__PURE__ */ new Set());
1757
+ }
1758
+ this.taskExecutions.get(snapshot.taskId)?.add(snapshot.executionId);
1759
+ const ac = new AbortController();
1760
+ const runningExec = {
1761
+ executionId: snapshot.executionId,
1762
+ taskId: snapshot.taskId,
1763
+ startedAt,
1764
+ cancel: () => ac.abort()
1765
+ };
1766
+ this.running.set(snapshot.executionId, runningExec);
1767
+ this.listener?.onStarted({
1768
+ executionId: snapshot.executionId,
1769
+ taskId: snapshot.taskId,
1770
+ startedAt
1771
+ });
1772
+ try {
1773
+ const executionPayload = resolveTaskExecutionPayload(
1774
+ snapshot.type,
1775
+ snapshot.payload,
1776
+ snapshot.workspacePath
1777
+ );
1778
+ validateTaskPayload(snapshot.type, executionPayload);
1779
+ const executor = this.getExecutor(snapshot.type);
1780
+ let result;
1781
+ if (isStreamingTaskExecutor(executor)) {
1782
+ const handle = executor.executeStreaming(
1783
+ executionPayload,
1784
+ (stream, data) => {
1785
+ this.listener?.onOutput({
1786
+ executionId: snapshot.executionId,
1787
+ stream,
1788
+ data
1789
+ });
1790
+ },
1791
+ ac.signal
1792
+ );
1793
+ runningExec.cancel = () => {
1794
+ handle.cancel();
1795
+ ac.abort();
1796
+ };
1797
+ result = await handle.result;
1798
+ } else {
1799
+ result = await executor.execute(executionPayload, ac.signal);
1800
+ }
1801
+ const log = {
1802
+ id: snapshot.executionId,
1803
+ taskId: snapshot.taskId,
1804
+ taskName: snapshot.name,
1805
+ startedAt,
1806
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
1807
+ status: result.status === "running" ? "failure" : result.status,
1808
+ output: result.output,
1809
+ error: result.error
1810
+ };
1811
+ switch (log.status) {
1812
+ case "success":
1813
+ this.listener?.onCompleted({
1814
+ executionId: snapshot.executionId,
1815
+ log
1816
+ });
1817
+ break;
1818
+ case "cancelled":
1819
+ this.listener?.onCancelled({
1820
+ executionId: snapshot.executionId,
1821
+ log
1822
+ });
1823
+ break;
1824
+ case "timeout":
1825
+ this.listener?.onFailed({
1826
+ executionId: snapshot.executionId,
1827
+ status: "timeout",
1828
+ reason: "timeout",
1829
+ log
1830
+ });
1831
+ break;
1832
+ default:
1833
+ this.listener?.onFailed({
1834
+ executionId: snapshot.executionId,
1835
+ status: "failure",
1836
+ reason: "error",
1837
+ log
1838
+ });
1839
+ break;
1840
+ }
1841
+ } catch (err) {
1842
+ const log = {
1843
+ id: snapshot.executionId,
1844
+ taskId: snapshot.taskId,
1845
+ taskName: snapshot.name,
1846
+ startedAt,
1847
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
1848
+ status: ac.signal.aborted ? "cancelled" : "failure",
1849
+ error: err instanceof Error ? err.message : String(err)
1850
+ };
1851
+ if (ac.signal.aborted) {
1852
+ this.listener?.onCancelled({
1853
+ executionId: snapshot.executionId,
1854
+ log
1855
+ });
1856
+ } else {
1857
+ this.listener?.onFailed({
1858
+ executionId: snapshot.executionId,
1859
+ status: "failure",
1860
+ reason: "error",
1861
+ log
1862
+ });
1863
+ }
1864
+ } finally {
1865
+ this.running.delete(snapshot.executionId);
1866
+ const taskExecIds = this.taskExecutions.get(snapshot.taskId);
1867
+ if (taskExecIds) {
1868
+ taskExecIds.delete(snapshot.executionId);
1869
+ if (taskExecIds.size === 0) {
1870
+ this.taskExecutions.delete(snapshot.taskId);
1871
+ }
1872
+ }
1873
+ }
1874
+ }
1875
+ cancel(executionId) {
1876
+ const exec = this.running.get(executionId);
1877
+ if (!exec) return false;
1878
+ exec.cancel();
1879
+ return true;
1880
+ }
1881
+ listRunning() {
1882
+ return Array.from(this.running.values()).map((e) => ({
1883
+ executionId: e.executionId,
1884
+ taskId: e.taskId,
1885
+ startedAt: e.startedAt
1886
+ }));
1887
+ }
1888
+ dispose() {
1889
+ for (const exec of this.running.values()) {
1890
+ exec.cancel();
1891
+ }
1892
+ this.running.clear();
1893
+ this.taskExecutions.clear();
1894
+ }
1895
+ };
1896
+
1495
1897
  // src/scheduled-tasks/TaskLogManager.ts
1496
1898
  import { randomUUID as randomUUID2 } from "crypto";
1497
- import * as fs6 from "fs";
1498
- import * as path6 from "path";
1899
+ import * as fs8 from "fs";
1900
+ import * as path7 from "path";
1499
1901
  var CONFIG_DIR4 = ".serviceme";
1500
1902
  var LOG_FILE2 = "scheduled-tasks-log.json";
1501
1903
  var MAX_LOGS = 200;
@@ -1520,17 +1922,17 @@ function validateAndRepairLogFile(raw) {
1520
1922
  }
1521
1923
  var TaskLogManager = class {
1522
1924
  constructor(workspacePath) {
1523
- this.logPath = path6.join(workspacePath, CONFIG_DIR4, LOG_FILE2);
1925
+ this.logPath = path7.join(workspacePath, CONFIG_DIR4, LOG_FILE2);
1524
1926
  }
1525
1927
  getLogPath() {
1526
1928
  return this.logPath;
1527
1929
  }
1528
1930
  readLogFile() {
1529
- if (!fs6.existsSync(this.logPath)) {
1931
+ if (!fs8.existsSync(this.logPath)) {
1530
1932
  return emptyLogFile();
1531
1933
  }
1532
1934
  try {
1533
- const raw = fs6.readFileSync(this.logPath, "utf-8");
1935
+ const raw = fs8.readFileSync(this.logPath, "utf-8");
1534
1936
  const parsed = JSON.parse(raw);
1535
1937
  const file = validateAndRepairLogFile(parsed);
1536
1938
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.logs) || parsed.logs.length !== file.logs.length) {
@@ -1546,21 +1948,21 @@ var TaskLogManager = class {
1546
1948
  }
1547
1949
  backupCorruptedFile() {
1548
1950
  try {
1549
- if (fs6.existsSync(this.logPath)) {
1951
+ if (fs8.existsSync(this.logPath)) {
1550
1952
  const backupPath = `${this.logPath}.corrupted.${Date.now()}`;
1551
- fs6.copyFileSync(this.logPath, backupPath);
1953
+ fs8.copyFileSync(this.logPath, backupPath);
1552
1954
  }
1553
1955
  } catch {
1554
1956
  }
1555
1957
  }
1556
1958
  writeLogFile(file) {
1557
- const dir = path6.dirname(this.logPath);
1558
- if (!fs6.existsSync(dir)) {
1559
- fs6.mkdirSync(dir, { recursive: true });
1959
+ const dir = path7.dirname(this.logPath);
1960
+ if (!fs8.existsSync(dir)) {
1961
+ fs8.mkdirSync(dir, { recursive: true });
1560
1962
  }
1561
1963
  const tmp = `${this.logPath}.tmp`;
1562
- fs6.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
1563
- fs6.renameSync(tmp, this.logPath);
1964
+ fs8.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
1965
+ fs8.renameSync(tmp, this.logPath);
1564
1966
  }
1565
1967
  appendLog(input) {
1566
1968
  const file = this.readLogFile();
@@ -1630,7 +2032,23 @@ var SchedulerDaemon = class {
1630
2032
  this.running = true;
1631
2033
  this.startTime = Date.now();
1632
2034
  this.pidManager.writePid(process.pid);
1633
- this.logger.log("info", `Scheduler daemon started (PID: ${process.pid})`);
2035
+ const configPath = this.configManager.getConfigPath();
2036
+ const logPath = this.logger.getLogPath();
2037
+ this.logger.log("info", "=".repeat(60));
2038
+ this.logger.log(
2039
+ "info",
2040
+ `Scheduler daemon started
2041
+ PID: ${process.pid}
2042
+ Node: ${process.version}
2043
+ OS: ${os.type()} ${os.release()} (${process.arch})
2044
+ Platform: ${process.platform}
2045
+ Hostname: ${os.hostname()}
2046
+ User: ${os.userInfo().username}
2047
+ Workspace: ${this.workspacePath}
2048
+ Config: ${configPath}
2049
+ LogPath: ${logPath}
2050
+ ExecPath: ${process.execPath}`
2051
+ );
1634
2052
  this.tickTimer = setInterval(() => this.tick(), TICK_INTERVAL);
1635
2053
  this.setupConfigWatch();
1636
2054
  process.on("SIGTERM", () => this.stop());
@@ -1655,8 +2073,8 @@ var SchedulerDaemon = class {
1655
2073
  const configPath = this.configManager.getConfigPath();
1656
2074
  const dir = configPath.substring(0, configPath.lastIndexOf("/"));
1657
2075
  try {
1658
- if (fs7.existsSync(dir)) {
1659
- this.watcher = fs7.watch(dir, (_eventType, filename) => {
2076
+ if (fs9.existsSync(dir)) {
2077
+ this.watcher = fs9.watch(dir, (_eventType, filename) => {
1660
2078
  if (filename === "scheduled-tasks.json") {
1661
2079
  this.logger.log("info", "Config file changed, reconciling...");
1662
2080
  }
@@ -1672,7 +2090,11 @@ var SchedulerDaemon = class {
1672
2090
  for (const task of config.tasks) {
1673
2091
  if (!task.enabled) continue;
1674
2092
  if (this.taskRunning.has(task.id)) continue;
1675
- const lastExec = this.lastRun.get(task.id) ?? 0;
2093
+ let lastExec = this.lastRun.get(task.id);
2094
+ if (lastExec === void 0) {
2095
+ this.lastRun.set(task.id, now);
2096
+ lastExec = now;
2097
+ }
1676
2098
  if (this.shouldRun(task, lastExec, now)) {
1677
2099
  this.executeTask(task, now);
1678
2100
  }
@@ -1694,11 +2116,25 @@ var SchedulerDaemon = class {
1694
2116
  this.taskRunning.add(task.id);
1695
2117
  this.lastRun.set(task.id, now);
1696
2118
  const startedAt = new Date(now).toISOString();
1697
- this.logger.log("info", `Executing task: ${task.name} (${task.id})`);
2119
+ const startMs = Date.now();
2120
+ this.logger.log(
2121
+ "info",
2122
+ `Executing task: ${task.name} (${task.id})
2123
+ type: ${task.taskType}
2124
+ schedule: ${task.schedule} (${task.scheduleType})
2125
+ enabled: ${task.enabled}`
2126
+ );
1698
2127
  try {
2128
+ const executionPayload = resolveTaskExecutionPayload(
2129
+ task.taskType,
2130
+ task.payload,
2131
+ this.workspacePath
2132
+ );
2133
+ validateTaskPayload(task.taskType, executionPayload);
1699
2134
  const executor = getExecutor(task.taskType);
1700
- const result = await executor.execute(task.payload);
2135
+ const result = await executor.execute(executionPayload);
1701
2136
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
2137
+ const durationMs = Date.now() - startMs;
1702
2138
  this.logManager.appendLog({
1703
2139
  taskId: task.id,
1704
2140
  taskName: task.name,
@@ -1708,9 +2144,17 @@ var SchedulerDaemon = class {
1708
2144
  output: result.output,
1709
2145
  error: result.error
1710
2146
  });
1711
- this.logger.log("info", `Task ${task.name} completed: ${result.status}`);
2147
+ const outputBytes = result.output ? Buffer.byteLength(result.output) : 0;
2148
+ this.logger.log(
2149
+ "info",
2150
+ `Task completed: ${task.name} (${task.id})
2151
+ status: ${result.status}
2152
+ duration: ${durationMs}ms
2153
+ outputBytes: ${outputBytes}`
2154
+ );
1712
2155
  } catch (err) {
1713
2156
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
2157
+ const durationMs = Date.now() - startMs;
1714
2158
  const message = err instanceof Error ? err.message : String(err);
1715
2159
  this.logManager.appendLog({
1716
2160
  taskId: task.id,
@@ -1720,7 +2164,12 @@ var SchedulerDaemon = class {
1720
2164
  status: "failure",
1721
2165
  error: message
1722
2166
  });
1723
- this.logger.log("error", `Task ${task.name} failed: ${message}`);
2167
+ this.logger.log(
2168
+ "error",
2169
+ `Task failed: ${task.name} (${task.id})
2170
+ duration: ${durationMs}ms
2171
+ error: ${message}`
2172
+ );
1724
2173
  } finally {
1725
2174
  this.taskRunning.delete(task.id);
1726
2175
  }
@@ -1777,164 +2226,6 @@ function matchCronField(field, value) {
1777
2226
  const values = field.split(",");
1778
2227
  return values.some((v) => Number.parseInt(v, 10) === value);
1779
2228
  }
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
2229
  export {
1939
2230
  DaemonLogger,
1940
2231
  EnvironmentInspector,
@@ -1963,5 +2254,7 @@ export {
1963
2254
  moveFiles,
1964
2255
  noopLogger,
1965
2256
  parseIntervalMs,
1966
- unzipFile
2257
+ resolveTaskExecutionPayload,
2258
+ unzipFile,
2259
+ validateTaskPayload
1967
2260
  };