@serviceme/devtools-cli 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -4,8 +4,9 @@
4
4
  var child_process = require('child_process');
5
5
  var fs2 = require('fs/promises');
6
6
  var path = require('path');
7
- var fs6 = require('fs');
7
+ var fs8 = require('fs');
8
8
  var crypto = require('crypto');
9
+ var os = require('os');
9
10
  var readline = require('readline');
10
11
 
11
12
  function _interopNamespace(e) {
@@ -28,7 +29,8 @@ function _interopNamespace(e) {
28
29
 
29
30
  var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
30
31
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
31
- var fs6__namespace = /*#__PURE__*/_interopNamespace(fs6);
32
+ var fs8__namespace = /*#__PURE__*/_interopNamespace(fs8);
33
+ var os__namespace = /*#__PURE__*/_interopNamespace(os);
32
34
  var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
33
35
 
34
36
  var __create = Object.create;
@@ -360,7 +362,8 @@ async function runCommand(command, options2 = {}) {
360
362
  cwd: options2.cwd,
361
363
  env: options2.env,
362
364
  shell: options2.shell,
363
- stdio: "pipe"
365
+ stdio: "pipe",
366
+ windowsHide: true
364
367
  });
365
368
  let stdout = "";
366
369
  let stderr = "";
@@ -1126,9 +1129,9 @@ var require_esprima = __commonJS({
1126
1129
  });
1127
1130
  };
1128
1131
  CommentHandler2.prototype.visitComment = function(node, metadata) {
1129
- var type = node.type[0] === "L" ? "Line" : "Block";
1132
+ var type2 = node.type[0] === "L" ? "Line" : "Block";
1130
1133
  var comment = {
1131
- type,
1134
+ type: type2,
1132
1135
  value: node.value
1133
1136
  };
1134
1137
  if (node.range) {
@@ -1141,7 +1144,7 @@ var require_esprima = __commonJS({
1141
1144
  if (this.attach) {
1142
1145
  var entry = {
1143
1146
  comment: {
1144
- type,
1147
+ type: type2,
1145
1148
  value: node.value,
1146
1149
  range: [metadata.start.offset, metadata.end.offset]
1147
1150
  },
@@ -1150,7 +1153,7 @@ var require_esprima = __commonJS({
1150
1153
  if (node.loc) {
1151
1154
  entry.comment.loc = node.loc;
1152
1155
  }
1153
- node.type = type;
1156
+ node.type = type2;
1154
1157
  this.leading.push(entry);
1155
1158
  this.trailing.push(entry);
1156
1159
  }
@@ -5965,28 +5968,28 @@ var require_esprima = __commonJS({
5965
5968
  };
5966
5969
  };
5967
5970
  Scanner2.prototype.scanIdentifier = function() {
5968
- var type;
5971
+ var type2;
5969
5972
  var start = this.index;
5970
5973
  var id = this.source.charCodeAt(start) === 92 ? this.getComplexIdentifier() : this.getIdentifier();
5971
5974
  if (id.length === 1) {
5972
- type = 3;
5975
+ type2 = 3;
5973
5976
  } else if (this.isKeyword(id)) {
5974
- type = 4;
5977
+ type2 = 4;
5975
5978
  } else if (id === "null") {
5976
- type = 5;
5979
+ type2 = 5;
5977
5980
  } else if (id === "true" || id === "false") {
5978
- type = 1;
5981
+ type2 = 1;
5979
5982
  } else {
5980
- type = 3;
5983
+ type2 = 3;
5981
5984
  }
5982
- if (type !== 3 && start + id.length !== this.index) {
5985
+ if (type2 !== 3 && start + id.length !== this.index) {
5983
5986
  var restore = this.index;
5984
5987
  this.index = start;
5985
5988
  this.tolerateUnexpectedToken(messages_1.Messages.InvalidEscapedReservedWord);
5986
5989
  this.index = restore;
5987
5990
  }
5988
5991
  return {
5989
- type,
5992
+ type: type2,
5990
5993
  value: id,
5991
5994
  lineNumber: this.lineNumber,
5992
5995
  lineStart: this.lineStart,
@@ -8437,13 +8440,13 @@ var require_parse = __commonJS({
8437
8440
  last = current;
8438
8441
  current = new_token;
8439
8442
  };
8440
- var type = () => {
8443
+ var type2 = () => {
8441
8444
  if (!current) {
8442
8445
  unexpected_end();
8443
8446
  }
8444
8447
  return current.type === "Punctuator" ? current.value : current.type;
8445
8448
  };
8446
- var is = (t) => type() === t;
8449
+ var is = (t) => type2() === t;
8447
8450
  var expect = (a) => {
8448
8451
  if (!is(a)) {
8449
8452
  unexpected();
@@ -8598,7 +8601,7 @@ var require_parse = __commonJS({
8598
8601
  return array;
8599
8602
  };
8600
8603
  function walk() {
8601
- let tt = type();
8604
+ let tt = type2();
8602
8605
  if (tt === CURLY_BRACKET_OPEN) {
8603
8606
  next();
8604
8607
  return {
@@ -8614,7 +8617,7 @@ var require_parse = __commonJS({
8614
8617
  let negative = EMPTY;
8615
8618
  if (tt === MINUS) {
8616
8619
  next();
8617
- tt = type();
8620
+ tt = type2();
8618
8621
  negative = MINUS;
8619
8622
  }
8620
8623
  let v;
@@ -8740,11 +8743,11 @@ var require_stringify = __commonJS({
8740
8743
  let is_line_comment = false;
8741
8744
  const str = comments.reduce((prev, {
8742
8745
  inline,
8743
- type,
8746
+ type: type2,
8744
8747
  value
8745
8748
  }) => {
8746
8749
  const delimiter = inline ? SPACE : LF + deeper_gap;
8747
- is_line_comment = type === "LineComment";
8750
+ is_line_comment = type2 === "LineComment";
8748
8751
  return prev + delimiter + comment_stringify(value, is_line_comment);
8749
8752
  }, EMPTY);
8750
8753
  return display_block || is_line_comment ? str + LF + deeper_gap : str;
@@ -8984,9 +8987,9 @@ function read() {
8984
8987
  }
8985
8988
  return c2;
8986
8989
  }
8987
- function newToken(type, value) {
8990
+ function newToken(type2, value) {
8988
8991
  return {
8989
- type,
8992
+ type: type2,
8990
8993
  value,
8991
8994
  line,
8992
8995
  column
@@ -10192,7 +10195,7 @@ var require_pend = __commonJS({
10192
10195
  // ../../node_modules/.pnpm/yauzl@3.2.0/node_modules/yauzl/fd-slicer.js
10193
10196
  var require_fd_slicer = __commonJS({
10194
10197
  "../../node_modules/.pnpm/yauzl@3.2.0/node_modules/yauzl/fd-slicer.js"(exports$1) {
10195
- var fs11 = __require("fs");
10198
+ var fs13 = __require("fs");
10196
10199
  var util2 = __require("util");
10197
10200
  var stream = __require("stream");
10198
10201
  var Readable = stream.Readable;
@@ -10217,7 +10220,7 @@ var require_fd_slicer = __commonJS({
10217
10220
  FdSlicer.prototype.read = function(buffer2, offset, length, position, callback) {
10218
10221
  var self = this;
10219
10222
  self.pend.go(function(cb) {
10220
- fs11.read(self.fd, buffer2, offset, length, position, function(err, bytesRead, buffer3) {
10223
+ fs13.read(self.fd, buffer2, offset, length, position, function(err, bytesRead, buffer3) {
10221
10224
  cb();
10222
10225
  callback(err, bytesRead, buffer3);
10223
10226
  });
@@ -10226,7 +10229,7 @@ var require_fd_slicer = __commonJS({
10226
10229
  FdSlicer.prototype.write = function(buffer2, offset, length, position, callback) {
10227
10230
  var self = this;
10228
10231
  self.pend.go(function(cb) {
10229
- fs11.write(self.fd, buffer2, offset, length, position, function(err, written, buffer3) {
10232
+ fs13.write(self.fd, buffer2, offset, length, position, function(err, written, buffer3) {
10230
10233
  cb();
10231
10234
  callback(err, written, buffer3);
10232
10235
  });
@@ -10247,7 +10250,7 @@ var require_fd_slicer = __commonJS({
10247
10250
  if (self.refCount > 0) return;
10248
10251
  if (self.refCount < 0) throw new Error("invalid unref");
10249
10252
  if (self.autoClose) {
10250
- fs11.close(self.fd, onCloseDone);
10253
+ fs13.close(self.fd, onCloseDone);
10251
10254
  }
10252
10255
  function onCloseDone(err) {
10253
10256
  if (err) {
@@ -10284,7 +10287,7 @@ var require_fd_slicer = __commonJS({
10284
10287
  self.context.pend.go(function(cb) {
10285
10288
  if (self.destroyed) return cb();
10286
10289
  var buffer2 = Buffer.allocUnsafe(toRead);
10287
- fs11.read(self.context.fd, buffer2, 0, toRead, self.pos, function(err, bytesRead) {
10290
+ fs13.read(self.context.fd, buffer2, 0, toRead, self.pos, function(err, bytesRead) {
10288
10291
  if (err) {
10289
10292
  self.destroy(err);
10290
10293
  } else if (bytesRead === 0) {
@@ -10331,7 +10334,7 @@ var require_fd_slicer = __commonJS({
10331
10334
  }
10332
10335
  self.context.pend.go(function(cb) {
10333
10336
  if (self.destroyed) return cb();
10334
- fs11.write(self.context.fd, buffer2, 0, buffer2.length, self.pos, function(err2, bytes) {
10337
+ fs13.write(self.context.fd, buffer2, 0, buffer2.length, self.pos, function(err2, bytes) {
10335
10338
  if (err2) {
10336
10339
  self.destroy();
10337
10340
  cb();
@@ -10769,7 +10772,7 @@ var require_buffer_crc32 = __commonJS({
10769
10772
  // ../../node_modules/.pnpm/yauzl@3.2.0/node_modules/yauzl/index.js
10770
10773
  var require_yauzl = __commonJS({
10771
10774
  "../../node_modules/.pnpm/yauzl@3.2.0/node_modules/yauzl/index.js"(exports$1) {
10772
- var fs11 = __require("fs");
10775
+ var fs13 = __require("fs");
10773
10776
  var zlib = __require("zlib");
10774
10777
  var fd_slicer = require_fd_slicer();
10775
10778
  var crc32 = require_buffer_crc32();
@@ -10790,7 +10793,7 @@ var require_yauzl = __commonJS({
10790
10793
  exports$1.Entry = Entry;
10791
10794
  exports$1.LocalFileHeader = LocalFileHeader;
10792
10795
  exports$1.RandomAccessReader = RandomAccessReader;
10793
- function open2(path8, options2, callback) {
10796
+ function open2(path9, options2, callback) {
10794
10797
  if (typeof options2 === "function") {
10795
10798
  callback = options2;
10796
10799
  options2 = null;
@@ -10802,10 +10805,10 @@ var require_yauzl = __commonJS({
10802
10805
  if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
10803
10806
  if (options2.strictFileNames == null) options2.strictFileNames = false;
10804
10807
  if (callback == null) callback = defaultCallback;
10805
- fs11.open(path8, "r", function(err, fd) {
10808
+ fs13.open(path9, "r", function(err, fd) {
10806
10809
  if (err) return callback(err);
10807
10810
  fromFd(fd, options2, function(err2, zipfile) {
10808
- if (err2) fs11.close(fd, defaultCallback);
10811
+ if (err2) fs13.close(fd, defaultCallback);
10809
10812
  callback(err2, zipfile);
10810
10813
  });
10811
10814
  });
@@ -10822,7 +10825,7 @@ var require_yauzl = __commonJS({
10822
10825
  if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
10823
10826
  if (options2.strictFileNames == null) options2.strictFileNames = false;
10824
10827
  if (callback == null) callback = defaultCallback;
10825
- fs11.fstat(fd, function(err, stats) {
10828
+ fs13.fstat(fd, function(err, stats) {
10826
10829
  if (err) return callback(err);
10827
10830
  var reader = fd_slicer.createFromFd(fd, { autoClose: true });
10828
10831
  fromRandomAccessReader(reader, stats.size, options2, callback);
@@ -11534,7 +11537,7 @@ var init_fileUtils = __esm({
11534
11537
  return reject(
11535
11538
  new Error("Failed to open zip entry stream.")
11536
11539
  );
11537
- const writeStream = fs6.createWriteStream(outputPath);
11540
+ const writeStream = fs8.createWriteStream(outputPath);
11538
11541
  readStream.on("error", reject);
11539
11542
  writeStream.on("error", reject);
11540
11543
  writeStream.on("close", () => {
@@ -11608,7 +11611,7 @@ var init_fileUtils = __esm({
11608
11611
  const destFile = path.join(destDir, file);
11609
11612
  if (!overwrite) {
11610
11613
  try {
11611
- await fs2.access(destFile, fs6.constants.F_OK);
11614
+ await fs2.access(destFile, fs8.constants.F_OK);
11612
11615
  await fs2.rm(sourceFile, { recursive: true, force: true });
11613
11616
  continue;
11614
11617
  } catch {
@@ -11819,8 +11822,8 @@ var init_DaemonLogger = __esm({
11819
11822
  constructor(workspacePath) {
11820
11823
  this.logPath = path__namespace.join(workspacePath, CONFIG_DIR, LOG_FILE);
11821
11824
  const dir = path__namespace.dirname(this.logPath);
11822
- if (!fs6__namespace.existsSync(dir)) {
11823
- fs6__namespace.mkdirSync(dir, { recursive: true });
11825
+ if (!fs8__namespace.existsSync(dir)) {
11826
+ fs8__namespace.mkdirSync(dir, { recursive: true });
11824
11827
  }
11825
11828
  }
11826
11829
  getLogPath() {
@@ -11831,16 +11834,16 @@ var init_DaemonLogger = __esm({
11831
11834
  const line2 = `[${ts}] [${level.toUpperCase()}] ${message}
11832
11835
  `;
11833
11836
  this.rotateIfNeeded();
11834
- fs6__namespace.appendFileSync(this.logPath, line2, "utf-8");
11837
+ fs8__namespace.appendFileSync(this.logPath, line2, "utf-8");
11835
11838
  }
11836
11839
  rotateIfNeeded() {
11837
11840
  try {
11838
- const stats = fs6__namespace.statSync(this.logPath);
11841
+ const stats = fs8__namespace.statSync(this.logPath);
11839
11842
  if (stats.size > MAX_LOG_SIZE) {
11840
- const content = fs6__namespace.readFileSync(this.logPath, "utf-8");
11843
+ const content = fs8__namespace.readFileSync(this.logPath, "utf-8");
11841
11844
  const halfIdx = content.indexOf("\n", Math.floor(content.length / 2));
11842
11845
  if (halfIdx > 0) {
11843
- fs6__namespace.writeFileSync(this.logPath, content.slice(halfIdx + 1), "utf-8");
11846
+ fs8__namespace.writeFileSync(this.logPath, content.slice(halfIdx + 1), "utf-8");
11844
11847
  }
11845
11848
  }
11846
11849
  } catch {
@@ -11863,20 +11866,20 @@ var init_PidManager = __esm({
11863
11866
  }
11864
11867
  writePid(pid) {
11865
11868
  const dir = path__namespace.dirname(this.pidPath);
11866
- if (!fs6__namespace.existsSync(dir)) {
11867
- fs6__namespace.mkdirSync(dir, { recursive: true });
11869
+ if (!fs8__namespace.existsSync(dir)) {
11870
+ fs8__namespace.mkdirSync(dir, { recursive: true });
11868
11871
  }
11869
- fs6__namespace.writeFileSync(this.pidPath, String(pid), "utf-8");
11872
+ fs8__namespace.writeFileSync(this.pidPath, String(pid), "utf-8");
11870
11873
  }
11871
11874
  readPid() {
11872
- if (!fs6__namespace.existsSync(this.pidPath)) return null;
11873
- const raw = fs6__namespace.readFileSync(this.pidPath, "utf-8").trim();
11875
+ if (!fs8__namespace.existsSync(this.pidPath)) return null;
11876
+ const raw = fs8__namespace.readFileSync(this.pidPath, "utf-8").trim();
11874
11877
  const pid = Number.parseInt(raw, 10);
11875
11878
  return Number.isNaN(pid) ? null : pid;
11876
11879
  }
11877
11880
  removePid() {
11878
- if (fs6__namespace.existsSync(this.pidPath)) {
11879
- fs6__namespace.unlinkSync(this.pidPath);
11881
+ if (fs8__namespace.existsSync(this.pidPath)) {
11882
+ fs8__namespace.unlinkSync(this.pidPath);
11880
11883
  }
11881
11884
  }
11882
11885
  isProcessRunning(pid) {
@@ -11897,12 +11900,52 @@ var init_PidManager = __esm({
11897
11900
  };
11898
11901
  }
11899
11902
  });
11900
- var DEFAULT_TIMEOUT, MAX_OUTPUT_BYTES, GithubCopilotCliExecutor;
11903
+
11904
+ // ../../packages/serviceme-core/src/scheduled-tasks/executors/timeout.ts
11905
+ function resolveConfiguredTimeoutMs(timeoutSeconds, defaultTimeoutMs) {
11906
+ if (timeoutSeconds === 0) {
11907
+ return void 0;
11908
+ }
11909
+ if (typeof timeoutSeconds !== "number" || !Number.isFinite(timeoutSeconds) || timeoutSeconds < 0) {
11910
+ return defaultTimeoutMs;
11911
+ }
11912
+ return timeoutSeconds * 1e3;
11913
+ }
11914
+ var init_timeout = __esm({
11915
+ "../../packages/serviceme-core/src/scheduled-tasks/executors/timeout.ts"() {
11916
+ }
11917
+ });
11918
+ function redactArgs(args) {
11919
+ const redacted = [];
11920
+ let redactNext = false;
11921
+ for (const arg of args) {
11922
+ if (redactNext) {
11923
+ redacted.push("<redacted>");
11924
+ redactNext = false;
11925
+ continue;
11926
+ }
11927
+ redacted.push(arg);
11928
+ if (arg === "--prompt") {
11929
+ redactNext = true;
11930
+ }
11931
+ }
11932
+ return redacted;
11933
+ }
11934
+ function writeDiagnostic(message) {
11935
+ const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
11936
+ if (logPath) {
11937
+ fs8__namespace.appendFileSync(logPath, message);
11938
+ return;
11939
+ }
11940
+ process.stderr.write(message);
11941
+ }
11942
+ var MAX_OUTPUT_BYTES, DEFAULT_TIMEOUT_MS2, GithubCopilotCliExecutor;
11901
11943
  var init_GithubCopilotCliExecutor = __esm({
11902
11944
  "../../packages/serviceme-core/src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts"() {
11903
11945
  init_doctor();
11904
- DEFAULT_TIMEOUT = 3e5;
11946
+ init_timeout();
11905
11947
  MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
11948
+ DEFAULT_TIMEOUT_MS2 = 3e5;
11906
11949
  GithubCopilotCliExecutor = class {
11907
11950
  async execute(payload, abortSignal) {
11908
11951
  const authenticated = await isCopilotAuthenticated();
@@ -11925,7 +11968,7 @@ var init_GithubCopilotCliExecutor = __esm({
11925
11968
  }
11926
11969
  executeStreaming(payload, onOutput, abortSignal) {
11927
11970
  const p = payload;
11928
- const timeout = (p.timeout != null ? p.timeout * 1e3 : null) ?? DEFAULT_TIMEOUT;
11971
+ const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS2);
11929
11972
  let resolve;
11930
11973
  const resultPromise = new Promise((r) => {
11931
11974
  resolve = r;
@@ -11934,7 +11977,7 @@ var init_GithubCopilotCliExecutor = __esm({
11934
11977
  const settle = (result) => {
11935
11978
  if (settled) return;
11936
11979
  settled = true;
11937
- clearTimeout(timer);
11980
+ if (timer) clearTimeout(timer);
11938
11981
  resolve?.(result);
11939
11982
  };
11940
11983
  if (abortSignal?.aborted) {
@@ -11960,23 +12003,32 @@ var init_GithubCopilotCliExecutor = __esm({
11960
12003
  if (p.agent) {
11961
12004
  args.push("--agent", p.agent);
11962
12005
  }
11963
- if (p.timeout != null) {
11964
- args.push("--timeout", String(p.timeout));
11965
- }
12006
+ args.push("--timeout", String(p.timeout ?? 0));
12007
+ writeDiagnostic(
12008
+ `[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}
12009
+ `
12010
+ );
11966
12011
  const child = child_process.spawn("serviceme", args, {
11967
12012
  cwd: p.workspace,
11968
- stdio: ["ignore", "pipe", "pipe"]
12013
+ stdio: ["ignore", "pipe", "pipe"],
12014
+ windowsHide: true
11969
12015
  });
11970
- const timer = setTimeout(() => {
12016
+ if (child.pid) {
12017
+ writeDiagnostic(
12018
+ `[GithubCopilotCliExecutor] spawned child PID=${child.pid}
12019
+ `
12020
+ );
12021
+ }
12022
+ const timer = timeoutMs != null ? setTimeout(() => {
11971
12023
  child.kill("SIGTERM");
11972
12024
  setTimeout(() => {
11973
12025
  if (!child.killed) child.kill("SIGKILL");
11974
12026
  }, 5e3);
11975
12027
  settle({
11976
12028
  status: "timeout",
11977
- error: `Copilot CLI execution timed out after ${timeout / 1e3}s`
12029
+ error: `Copilot CLI execution timed out after ${timeoutMs / 1e3}s`
11978
12030
  });
11979
- }, timeout);
12031
+ }, timeoutMs) : void 0;
11980
12032
  let stdoutBuf = "";
11981
12033
  let stderrBuf = "";
11982
12034
  child.stdout.on("data", (chunk) => {
@@ -12022,20 +12074,23 @@ var init_GithubCopilotCliExecutor = __esm({
12022
12074
  });
12023
12075
 
12024
12076
  // ../../packages/serviceme-core/src/scheduled-tasks/executors/HttpRequestExecutor.ts
12025
- var DEFAULT_TIMEOUT2, HttpRequestExecutor;
12077
+ var DEFAULT_TIMEOUT_MS3, HttpRequestExecutor;
12026
12078
  var init_HttpRequestExecutor = __esm({
12027
12079
  "../../packages/serviceme-core/src/scheduled-tasks/executors/HttpRequestExecutor.ts"() {
12028
- DEFAULT_TIMEOUT2 = 3e4;
12080
+ init_timeout();
12081
+ DEFAULT_TIMEOUT_MS3 = 3e4;
12029
12082
  HttpRequestExecutor = class {
12030
12083
  async execute(payload, abortSignal) {
12031
12084
  const p = payload;
12032
- const timeout = (p.timeout != null ? p.timeout * 1e3 : null) ?? DEFAULT_TIMEOUT2;
12085
+ const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS3);
12033
12086
  const ac = new AbortController();
12034
- const timer = setTimeout(() => {
12087
+ let timedOut = false;
12088
+ const timer = timeoutMs != null ? setTimeout(() => {
12089
+ timedOut = true;
12035
12090
  ac.abort();
12036
- }, timeout);
12091
+ }, timeoutMs) : void 0;
12037
12092
  if (abortSignal?.aborted) {
12038
- clearTimeout(timer);
12093
+ if (timer) clearTimeout(timer);
12039
12094
  return { status: "cancelled", error: "Execution aborted" };
12040
12095
  }
12041
12096
  let externalAbort = false;
@@ -12043,7 +12098,7 @@ var init_HttpRequestExecutor = __esm({
12043
12098
  "abort",
12044
12099
  () => {
12045
12100
  externalAbort = true;
12046
- clearTimeout(timer);
12101
+ if (timer) clearTimeout(timer);
12047
12102
  ac.abort();
12048
12103
  },
12049
12104
  { once: true }
@@ -12055,7 +12110,7 @@ var init_HttpRequestExecutor = __esm({
12055
12110
  body: p.body,
12056
12111
  signal: ac.signal
12057
12112
  });
12058
- clearTimeout(timer);
12113
+ if (timer) clearTimeout(timer);
12059
12114
  const body = await response.text();
12060
12115
  if (response.ok) {
12061
12116
  return {
@@ -12070,14 +12125,17 @@ ${body}`.trim()
12070
12125
  ${body}`.trim()
12071
12126
  };
12072
12127
  } catch (err) {
12073
- clearTimeout(timer);
12128
+ if (timer) clearTimeout(timer);
12074
12129
  if (err instanceof Error && err.name === "AbortError") {
12075
12130
  if (externalAbort) {
12076
12131
  return { status: "cancelled", error: "Execution cancelled" };
12077
12132
  }
12133
+ if (!timedOut || timeoutMs == null) {
12134
+ return { status: "failure", error: err.message };
12135
+ }
12078
12136
  return {
12079
12137
  status: "timeout",
12080
- error: `HTTP request timed out after ${timeout / 1e3}s`
12138
+ error: `HTTP request timed out after ${timeoutMs / 1e3}s`
12081
12139
  };
12082
12140
  }
12083
12141
  const message = err instanceof Error ? err.message : String(err);
@@ -12087,11 +12145,84 @@ ${body}`.trim()
12087
12145
  };
12088
12146
  }
12089
12147
  });
12090
- var DEFAULT_TIMEOUT3, MAX_OUTPUT_BYTES2, ShellExecutor;
12148
+ function resolveShellExecution(script, options2 = {}) {
12149
+ const platform = options2.platform ?? process.platform;
12150
+ const env = options2.env ?? process.env;
12151
+ const fileExists = options2.fileExists ?? fs8__namespace.existsSync;
12152
+ if (platform === "win32") {
12153
+ const posixShell = usesPosixShellSyntax(script) ? findWindowsPosixShell(env, fileExists) : null;
12154
+ if (posixShell) {
12155
+ return {
12156
+ command: posixShell,
12157
+ args: ["-s"],
12158
+ stdinScript: script,
12159
+ outputEncoding: "utf8",
12160
+ shellKind: "posix"
12161
+ };
12162
+ }
12163
+ return {
12164
+ command: env.ComSpec ?? env.COMSPEC ?? "cmd.exe",
12165
+ args: ["/d", "/s", "/c", script],
12166
+ outputEncoding: "utf8",
12167
+ shellKind: "cmd"
12168
+ };
12169
+ }
12170
+ return {
12171
+ command: "sh",
12172
+ args: ["-s"],
12173
+ stdinScript: script,
12174
+ outputEncoding: "utf8",
12175
+ shellKind: "posix"
12176
+ };
12177
+ }
12178
+ function usesPosixShellSyntax(script) {
12179
+ return /\$\([^)]*\)|`[^`]*`/.test(script);
12180
+ }
12181
+ function findWindowsPosixShell(env, fileExists) {
12182
+ const explicitShell = env.SERVICEME_POSIX_SHELL;
12183
+ if (explicitShell && fileExists(explicitShell)) {
12184
+ return explicitShell;
12185
+ }
12186
+ const knownGitBashPaths = [
12187
+ "C:\\Program Files\\Git\\bin\\bash.exe",
12188
+ "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
12189
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
12190
+ "C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
12191
+ ];
12192
+ for (const candidate of knownGitBashPaths) {
12193
+ if (fileExists(candidate)) return candidate;
12194
+ }
12195
+ const pathValue = env.Path ?? env.PATH ?? "";
12196
+ for (const dir of pathValue.split(path__namespace.win32.delimiter)) {
12197
+ if (!dir) continue;
12198
+ for (const executable of POSIX_SHELL_CANDIDATES) {
12199
+ const candidate = path__namespace.win32.join(dir, executable);
12200
+ if (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {
12201
+ return candidate;
12202
+ }
12203
+ }
12204
+ }
12205
+ return null;
12206
+ }
12207
+ function isWindowsWslLauncher(candidate) {
12208
+ const normalized = path__namespace.win32.normalize(candidate).toLowerCase();
12209
+ return normalized.endsWith("\\windows\\system32\\bash.exe") || normalized.endsWith("\\windows\\syswow64\\bash.exe");
12210
+ }
12211
+ function writeDiagnostic2(message) {
12212
+ const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
12213
+ if (logPath) {
12214
+ fs8__namespace.appendFileSync(logPath, message);
12215
+ return;
12216
+ }
12217
+ process.stderr.write(message);
12218
+ }
12219
+ var MAX_OUTPUT_BYTES2, DEFAULT_TIMEOUT_MS4, POSIX_SHELL_CANDIDATES, ShellExecutor;
12091
12220
  var init_ShellExecutor = __esm({
12092
12221
  "../../packages/serviceme-core/src/scheduled-tasks/executors/ShellExecutor.ts"() {
12093
- DEFAULT_TIMEOUT3 = 6e4;
12222
+ init_timeout();
12094
12223
  MAX_OUTPUT_BYTES2 = 1024 * 1024;
12224
+ DEFAULT_TIMEOUT_MS4 = 6e4;
12225
+ POSIX_SHELL_CANDIDATES = ["bash.exe", "sh.exe"];
12095
12226
  ShellExecutor = class {
12096
12227
  async execute(payload, abortSignal) {
12097
12228
  let output = "";
@@ -12107,7 +12238,7 @@ var init_ShellExecutor = __esm({
12107
12238
  }
12108
12239
  executeStreaming(payload, onOutput, abortSignal) {
12109
12240
  const p = payload;
12110
- const timeout = (p.timeout != null ? p.timeout * 1e3 : null) ?? DEFAULT_TIMEOUT3;
12241
+ const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS4);
12111
12242
  let resolve;
12112
12243
  const resultPromise = new Promise((r) => {
12113
12244
  resolve = r;
@@ -12116,7 +12247,7 @@ var init_ShellExecutor = __esm({
12116
12247
  const settle = (result) => {
12117
12248
  if (settled) return;
12118
12249
  settled = true;
12119
- clearTimeout(timer);
12250
+ if (timer) clearTimeout(timer);
12120
12251
  resolve?.(result);
12121
12252
  };
12122
12253
  if (abortSignal?.aborted) {
@@ -12129,37 +12260,65 @@ var init_ShellExecutor = __esm({
12129
12260
  }
12130
12261
  };
12131
12262
  }
12132
- const child = child_process.spawn("sh", ["-c", p.script], {
12263
+ const shellExecution = resolveShellExecution(p.script);
12264
+ const spawnOpts = {
12133
12265
  cwd: p.cwd,
12134
- stdio: ["ignore", "pipe", "pipe"]
12135
- });
12136
- const timer = setTimeout(() => {
12266
+ stdio: [shellExecution.stdinScript ? "pipe" : "ignore", "pipe", "pipe"],
12267
+ windowsHide: true
12268
+ };
12269
+ writeDiagnostic2(
12270
+ `[ShellExecutor] spawn:
12271
+ platform=${process.platform}
12272
+ shell=${shellExecution.shellKind}
12273
+ command=${shellExecution.command}
12274
+ args=${JSON.stringify(shellExecution.args.filter((a) => a !== p.script))}
12275
+ stdin=${shellExecution.stdinScript ? "true" : "false"}
12276
+ cwd=${p.cwd ?? "(default)"}
12277
+ timeout=${timeoutMs != null ? `${timeoutMs}ms` : "unlimited"}
12278
+ scriptLen=${p.script.length}
12279
+ windowsHide=true
12280
+ daemonPid=${process.pid}
12281
+ `
12282
+ );
12283
+ const child = child_process.spawn(shellExecution.command, shellExecution.args, spawnOpts);
12284
+ if (shellExecution.stdinScript && child.stdin) {
12285
+ child.stdin.end(shellExecution.stdinScript);
12286
+ }
12287
+ if (child.pid) {
12288
+ writeDiagnostic2(`[ShellExecutor] spawned child PID=${child.pid}
12289
+ `);
12290
+ }
12291
+ const timer = timeoutMs != null ? setTimeout(() => {
12137
12292
  child.kill("SIGTERM");
12138
12293
  setTimeout(() => {
12139
12294
  if (!child.killed) child.kill("SIGKILL");
12140
12295
  }, 5e3);
12141
12296
  settle({
12142
12297
  status: "timeout",
12143
- error: `Shell execution timed out after ${timeout / 1e3}s`
12298
+ error: `Shell execution timed out after ${timeoutMs / 1e3}s`
12144
12299
  });
12145
- }, timeout);
12300
+ }, timeoutMs) : void 0;
12146
12301
  let stdoutBuf = "";
12147
12302
  let stderrBuf = "";
12148
- child.stdout.on("data", (chunk) => {
12149
- const data = chunk.toString();
12303
+ child.stdout?.on("data", (chunk) => {
12304
+ const data = chunk.toString(shellExecution.outputEncoding);
12150
12305
  stdoutBuf += data;
12151
12306
  if (stdoutBuf.length > MAX_OUTPUT_BYTES2)
12152
12307
  stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES2);
12153
12308
  onOutput("stdout", data);
12154
12309
  });
12155
- child.stderr.on("data", (chunk) => {
12156
- const data = chunk.toString();
12310
+ child.stderr?.on("data", (chunk) => {
12311
+ const data = chunk.toString(shellExecution.outputEncoding);
12157
12312
  stderrBuf += data;
12158
12313
  if (stderrBuf.length > MAX_OUTPUT_BYTES2)
12159
12314
  stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES2);
12160
12315
  onOutput("stderr", data);
12161
12316
  });
12162
12317
  child.on("close", (code) => {
12318
+ writeDiagnostic2(
12319
+ `[ShellExecutor] child PID=${child.pid} exited: code=${code}
12320
+ `
12321
+ );
12163
12322
  if (code === 0) {
12164
12323
  settle({ status: "success", output: stdoutBuf || void 0 });
12165
12324
  } else {
@@ -12198,7 +12357,11 @@ var init_types = __esm({
12198
12357
 
12199
12358
  // ../../packages/serviceme-core/src/scheduled-tasks/executors/index.ts
12200
12359
  function getExecutor(taskType) {
12201
- return executors[taskType];
12360
+ const executor = executors[taskType];
12361
+ if (!executor) {
12362
+ throw new Error(`Unsupported bridge task type: ${taskType}`);
12363
+ }
12364
+ return executor;
12202
12365
  }
12203
12366
  var executors;
12204
12367
  var init_executors = __esm({
@@ -12217,9 +12380,38 @@ var init_executors = __esm({
12217
12380
  function emptyConfig() {
12218
12381
  return { version: 1, tasks: [] };
12219
12382
  }
12383
+ function isRecord3(value) {
12384
+ return value !== null && typeof value === "object" && !Array.isArray(value);
12385
+ }
12386
+ function requireNonEmptyString(payload, field, taskType) {
12387
+ if (!isRecord3(payload) || typeof payload[field] !== "string" || !payload[field].trim()) {
12388
+ throw createServicemeError(
12389
+ "invalid_payload",
12390
+ `${taskType} payload ${field} is required`
12391
+ );
12392
+ }
12393
+ }
12394
+ function validateTaskPayload(taskType, payload) {
12395
+ switch (taskType) {
12396
+ case "command":
12397
+ requireNonEmptyString(payload, "command", taskType);
12398
+ return;
12399
+ case "shell":
12400
+ requireNonEmptyString(payload, "script", taskType);
12401
+ return;
12402
+ case "http_request":
12403
+ requireNonEmptyString(payload, "url", taskType);
12404
+ requireNonEmptyString(payload, "method", taskType);
12405
+ return;
12406
+ case "github_copilot_cli":
12407
+ requireNonEmptyString(payload, "prompt", taskType);
12408
+ return;
12409
+ }
12410
+ }
12220
12411
  var CONFIG_DIR3, CONFIG_FILE, TaskConfigManager;
12221
12412
  var init_TaskConfigManager = __esm({
12222
12413
  "../../packages/serviceme-core/src/scheduled-tasks/TaskConfigManager.ts"() {
12414
+ init_src();
12223
12415
  CONFIG_DIR3 = ".serviceme";
12224
12416
  CONFIG_FILE = "scheduled-tasks.json";
12225
12417
  TaskConfigManager = class {
@@ -12230,21 +12422,21 @@ var init_TaskConfigManager = __esm({
12230
12422
  return this.configPath;
12231
12423
  }
12232
12424
  readConfig() {
12233
- if (!fs6__namespace.existsSync(this.configPath)) {
12425
+ if (!fs8__namespace.existsSync(this.configPath)) {
12234
12426
  return emptyConfig();
12235
12427
  }
12236
- const raw = fs6__namespace.readFileSync(this.configPath, "utf-8");
12428
+ const raw = fs8__namespace.readFileSync(this.configPath, "utf-8");
12237
12429
  const parsed = JSON.parse(raw);
12238
12430
  return parsed;
12239
12431
  }
12240
12432
  writeConfig(config) {
12241
12433
  const dir = path__namespace.dirname(this.configPath);
12242
- if (!fs6__namespace.existsSync(dir)) {
12243
- fs6__namespace.mkdirSync(dir, { recursive: true });
12434
+ if (!fs8__namespace.existsSync(dir)) {
12435
+ fs8__namespace.mkdirSync(dir, { recursive: true });
12244
12436
  }
12245
12437
  const tmp = `${this.configPath}.tmp`;
12246
- fs6__namespace.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
12247
- fs6__namespace.renameSync(tmp, this.configPath);
12438
+ fs8__namespace.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
12439
+ fs8__namespace.renameSync(tmp, this.configPath);
12248
12440
  }
12249
12441
  listTasks() {
12250
12442
  return this.readConfig().tasks;
@@ -12256,6 +12448,7 @@ var init_TaskConfigManager = __esm({
12256
12448
  return this.readConfig().tasks.find((t) => t.name === name);
12257
12449
  }
12258
12450
  createTask(input) {
12451
+ validateTaskPayload(input.taskType, input.payload);
12259
12452
  const config = this.readConfig();
12260
12453
  const now = (/* @__PURE__ */ new Date()).toISOString();
12261
12454
  const task = {
@@ -12284,6 +12477,9 @@ var init_TaskConfigManager = __esm({
12284
12477
  if (!existing) {
12285
12478
  throw new Error(`Task '${id}' not found`);
12286
12479
  }
12480
+ const nextTaskType = input.taskType ?? existing.taskType;
12481
+ const nextPayload = input.payload ?? existing.payload;
12482
+ validateTaskPayload(nextTaskType, nextPayload);
12287
12483
  const updated = {
12288
12484
  ...existing,
12289
12485
  ...input.name !== void 0 && { name: input.name },
@@ -12323,6 +12519,200 @@ var init_TaskConfigManager = __esm({
12323
12519
  };
12324
12520
  }
12325
12521
  });
12522
+
12523
+ // ../../packages/serviceme-core/src/scheduled-tasks/TaskExecutionEngine.ts
12524
+ function hasNonEmptyString(value) {
12525
+ return typeof value === "string" && value.trim().length > 0;
12526
+ }
12527
+ function resolveTaskExecutionPayload(taskType, payload, workspacePath) {
12528
+ if (!hasNonEmptyString(workspacePath)) {
12529
+ return payload;
12530
+ }
12531
+ if (taskType === "shell") {
12532
+ const shellPayload = payload;
12533
+ if (hasNonEmptyString(shellPayload.cwd)) {
12534
+ return shellPayload;
12535
+ }
12536
+ return { ...shellPayload, cwd: workspacePath };
12537
+ }
12538
+ if (taskType === "github_copilot_cli") {
12539
+ const copilotPayload = payload;
12540
+ if (hasNonEmptyString(copilotPayload.workspace)) {
12541
+ return copilotPayload;
12542
+ }
12543
+ return { ...copilotPayload, workspace: workspacePath };
12544
+ }
12545
+ return payload;
12546
+ }
12547
+ var TaskExecutionEngine;
12548
+ var init_TaskExecutionEngine = __esm({
12549
+ "../../packages/serviceme-core/src/scheduled-tasks/TaskExecutionEngine.ts"() {
12550
+ init_types();
12551
+ init_TaskConfigManager();
12552
+ TaskExecutionEngine = class {
12553
+ constructor(getExecutor2) {
12554
+ this.getExecutor = getExecutor2;
12555
+ this.running = /* @__PURE__ */ new Map();
12556
+ this.taskExecutions = /* @__PURE__ */ new Map();
12557
+ this.listener = null;
12558
+ }
12559
+ setListener(listener) {
12560
+ this.listener = listener;
12561
+ }
12562
+ async execute(snapshot) {
12563
+ const policy = snapshot.concurrencyPolicy ?? "reject";
12564
+ if (policy === "reject") {
12565
+ const existingIds = this.taskExecutions.get(snapshot.taskId);
12566
+ if (existingIds && existingIds.size > 0) {
12567
+ throw new Error(
12568
+ `TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
12569
+ );
12570
+ }
12571
+ }
12572
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
12573
+ if (!this.taskExecutions.has(snapshot.taskId)) {
12574
+ this.taskExecutions.set(snapshot.taskId, /* @__PURE__ */ new Set());
12575
+ }
12576
+ this.taskExecutions.get(snapshot.taskId)?.add(snapshot.executionId);
12577
+ const ac = new AbortController();
12578
+ const runningExec = {
12579
+ executionId: snapshot.executionId,
12580
+ taskId: snapshot.taskId,
12581
+ startedAt,
12582
+ cancel: () => ac.abort()
12583
+ };
12584
+ this.running.set(snapshot.executionId, runningExec);
12585
+ this.listener?.onStarted({
12586
+ executionId: snapshot.executionId,
12587
+ taskId: snapshot.taskId,
12588
+ startedAt
12589
+ });
12590
+ try {
12591
+ const executionPayload = resolveTaskExecutionPayload(
12592
+ snapshot.type,
12593
+ snapshot.payload,
12594
+ snapshot.workspacePath
12595
+ );
12596
+ validateTaskPayload(snapshot.type, executionPayload);
12597
+ const executor = this.getExecutor(snapshot.type);
12598
+ let result;
12599
+ if (isStreamingTaskExecutor(executor)) {
12600
+ const handle = executor.executeStreaming(
12601
+ executionPayload,
12602
+ (stream, data) => {
12603
+ this.listener?.onOutput({
12604
+ executionId: snapshot.executionId,
12605
+ stream,
12606
+ data
12607
+ });
12608
+ },
12609
+ ac.signal
12610
+ );
12611
+ runningExec.cancel = () => {
12612
+ handle.cancel();
12613
+ ac.abort();
12614
+ };
12615
+ result = await handle.result;
12616
+ } else {
12617
+ result = await executor.execute(executionPayload, ac.signal);
12618
+ }
12619
+ const log = {
12620
+ id: snapshot.executionId,
12621
+ taskId: snapshot.taskId,
12622
+ taskName: snapshot.name,
12623
+ startedAt,
12624
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
12625
+ status: result.status === "running" ? "failure" : result.status,
12626
+ output: result.output,
12627
+ error: result.error
12628
+ };
12629
+ switch (log.status) {
12630
+ case "success":
12631
+ this.listener?.onCompleted({
12632
+ executionId: snapshot.executionId,
12633
+ log
12634
+ });
12635
+ break;
12636
+ case "cancelled":
12637
+ this.listener?.onCancelled({
12638
+ executionId: snapshot.executionId,
12639
+ log
12640
+ });
12641
+ break;
12642
+ case "timeout":
12643
+ this.listener?.onFailed({
12644
+ executionId: snapshot.executionId,
12645
+ status: "timeout",
12646
+ reason: "timeout",
12647
+ log
12648
+ });
12649
+ break;
12650
+ default:
12651
+ this.listener?.onFailed({
12652
+ executionId: snapshot.executionId,
12653
+ status: "failure",
12654
+ reason: "error",
12655
+ log
12656
+ });
12657
+ break;
12658
+ }
12659
+ } catch (err) {
12660
+ const log = {
12661
+ id: snapshot.executionId,
12662
+ taskId: snapshot.taskId,
12663
+ taskName: snapshot.name,
12664
+ startedAt,
12665
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
12666
+ status: ac.signal.aborted ? "cancelled" : "failure",
12667
+ error: err instanceof Error ? err.message : String(err)
12668
+ };
12669
+ if (ac.signal.aborted) {
12670
+ this.listener?.onCancelled({
12671
+ executionId: snapshot.executionId,
12672
+ log
12673
+ });
12674
+ } else {
12675
+ this.listener?.onFailed({
12676
+ executionId: snapshot.executionId,
12677
+ status: "failure",
12678
+ reason: "error",
12679
+ log
12680
+ });
12681
+ }
12682
+ } finally {
12683
+ this.running.delete(snapshot.executionId);
12684
+ const taskExecIds = this.taskExecutions.get(snapshot.taskId);
12685
+ if (taskExecIds) {
12686
+ taskExecIds.delete(snapshot.executionId);
12687
+ if (taskExecIds.size === 0) {
12688
+ this.taskExecutions.delete(snapshot.taskId);
12689
+ }
12690
+ }
12691
+ }
12692
+ }
12693
+ cancel(executionId) {
12694
+ const exec = this.running.get(executionId);
12695
+ if (!exec) return false;
12696
+ exec.cancel();
12697
+ return true;
12698
+ }
12699
+ listRunning() {
12700
+ return Array.from(this.running.values()).map((e) => ({
12701
+ executionId: e.executionId,
12702
+ taskId: e.taskId,
12703
+ startedAt: e.startedAt
12704
+ }));
12705
+ }
12706
+ dispose() {
12707
+ for (const exec of this.running.values()) {
12708
+ exec.cancel();
12709
+ }
12710
+ this.running.clear();
12711
+ this.taskExecutions.clear();
12712
+ }
12713
+ };
12714
+ }
12715
+ });
12326
12716
  function emptyLogFile() {
12327
12717
  return { logs: [] };
12328
12718
  }
@@ -12356,11 +12746,11 @@ var init_TaskLogManager = __esm({
12356
12746
  return this.logPath;
12357
12747
  }
12358
12748
  readLogFile() {
12359
- if (!fs6__namespace.existsSync(this.logPath)) {
12749
+ if (!fs8__namespace.existsSync(this.logPath)) {
12360
12750
  return emptyLogFile();
12361
12751
  }
12362
12752
  try {
12363
- const raw = fs6__namespace.readFileSync(this.logPath, "utf-8");
12753
+ const raw = fs8__namespace.readFileSync(this.logPath, "utf-8");
12364
12754
  const parsed = JSON.parse(raw);
12365
12755
  const file = validateAndRepairLogFile(parsed);
12366
12756
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.logs) || parsed.logs.length !== file.logs.length) {
@@ -12376,21 +12766,21 @@ var init_TaskLogManager = __esm({
12376
12766
  }
12377
12767
  backupCorruptedFile() {
12378
12768
  try {
12379
- if (fs6__namespace.existsSync(this.logPath)) {
12769
+ if (fs8__namespace.existsSync(this.logPath)) {
12380
12770
  const backupPath = `${this.logPath}.corrupted.${Date.now()}`;
12381
- fs6__namespace.copyFileSync(this.logPath, backupPath);
12771
+ fs8__namespace.copyFileSync(this.logPath, backupPath);
12382
12772
  }
12383
12773
  } catch {
12384
12774
  }
12385
12775
  }
12386
12776
  writeLogFile(file) {
12387
12777
  const dir = path__namespace.dirname(this.logPath);
12388
- if (!fs6__namespace.existsSync(dir)) {
12389
- fs6__namespace.mkdirSync(dir, { recursive: true });
12778
+ if (!fs8__namespace.existsSync(dir)) {
12779
+ fs8__namespace.mkdirSync(dir, { recursive: true });
12390
12780
  }
12391
12781
  const tmp = `${this.logPath}.tmp`;
12392
- fs6__namespace.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
12393
- fs6__namespace.renameSync(tmp, this.logPath);
12782
+ fs8__namespace.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
12783
+ fs8__namespace.renameSync(tmp, this.logPath);
12394
12784
  }
12395
12785
  appendLog(input) {
12396
12786
  const file = this.readLogFile();
@@ -12482,6 +12872,7 @@ var init_SchedulerDaemon = __esm({
12482
12872
  "../../packages/serviceme-core/src/scheduled-tasks/daemon/SchedulerDaemon.ts"() {
12483
12873
  init_executors();
12484
12874
  init_TaskConfigManager();
12875
+ init_TaskExecutionEngine();
12485
12876
  init_TaskLogManager();
12486
12877
  init_DaemonLogger();
12487
12878
  init_PidManager();
@@ -12507,7 +12898,23 @@ var init_SchedulerDaemon = __esm({
12507
12898
  this.running = true;
12508
12899
  this.startTime = Date.now();
12509
12900
  this.pidManager.writePid(process.pid);
12510
- this.logger.log("info", `Scheduler daemon started (PID: ${process.pid})`);
12901
+ const configPath = this.configManager.getConfigPath();
12902
+ const logPath = this.logger.getLogPath();
12903
+ this.logger.log("info", "=".repeat(60));
12904
+ this.logger.log(
12905
+ "info",
12906
+ `Scheduler daemon started
12907
+ PID: ${process.pid}
12908
+ Node: ${process.version}
12909
+ OS: ${os__namespace.type()} ${os__namespace.release()} (${process.arch})
12910
+ Platform: ${process.platform}
12911
+ Hostname: ${os__namespace.hostname()}
12912
+ User: ${os__namespace.userInfo().username}
12913
+ Workspace: ${this.workspacePath}
12914
+ Config: ${configPath}
12915
+ LogPath: ${logPath}
12916
+ ExecPath: ${process.execPath}`
12917
+ );
12511
12918
  this.tickTimer = setInterval(() => this.tick(), TICK_INTERVAL);
12512
12919
  this.setupConfigWatch();
12513
12920
  process.on("SIGTERM", () => this.stop());
@@ -12532,8 +12939,8 @@ var init_SchedulerDaemon = __esm({
12532
12939
  const configPath = this.configManager.getConfigPath();
12533
12940
  const dir = configPath.substring(0, configPath.lastIndexOf("/"));
12534
12941
  try {
12535
- if (fs6__namespace.existsSync(dir)) {
12536
- this.watcher = fs6__namespace.watch(dir, (_eventType, filename) => {
12942
+ if (fs8__namespace.existsSync(dir)) {
12943
+ this.watcher = fs8__namespace.watch(dir, (_eventType, filename) => {
12537
12944
  if (filename === "scheduled-tasks.json") {
12538
12945
  this.logger.log("info", "Config file changed, reconciling...");
12539
12946
  }
@@ -12549,7 +12956,11 @@ var init_SchedulerDaemon = __esm({
12549
12956
  for (const task of config.tasks) {
12550
12957
  if (!task.enabled) continue;
12551
12958
  if (this.taskRunning.has(task.id)) continue;
12552
- const lastExec = this.lastRun.get(task.id) ?? 0;
12959
+ let lastExec = this.lastRun.get(task.id);
12960
+ if (lastExec === void 0) {
12961
+ this.lastRun.set(task.id, now);
12962
+ lastExec = now;
12963
+ }
12553
12964
  if (this.shouldRun(task, lastExec, now)) {
12554
12965
  this.executeTask(task, now);
12555
12966
  }
@@ -12571,11 +12982,25 @@ var init_SchedulerDaemon = __esm({
12571
12982
  this.taskRunning.add(task.id);
12572
12983
  this.lastRun.set(task.id, now);
12573
12984
  const startedAt = new Date(now).toISOString();
12574
- this.logger.log("info", `Executing task: ${task.name} (${task.id})`);
12985
+ const startMs = Date.now();
12986
+ this.logger.log(
12987
+ "info",
12988
+ `Executing task: ${task.name} (${task.id})
12989
+ type: ${task.taskType}
12990
+ schedule: ${task.schedule} (${task.scheduleType})
12991
+ enabled: ${task.enabled}`
12992
+ );
12575
12993
  try {
12994
+ const executionPayload = resolveTaskExecutionPayload(
12995
+ task.taskType,
12996
+ task.payload,
12997
+ this.workspacePath
12998
+ );
12999
+ validateTaskPayload(task.taskType, executionPayload);
12576
13000
  const executor = getExecutor(task.taskType);
12577
- const result = await executor.execute(task.payload);
13001
+ const result = await executor.execute(executionPayload);
12578
13002
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
13003
+ const durationMs = Date.now() - startMs;
12579
13004
  this.logManager.appendLog({
12580
13005
  taskId: task.id,
12581
13006
  taskName: task.name,
@@ -12585,9 +13010,17 @@ var init_SchedulerDaemon = __esm({
12585
13010
  output: result.output,
12586
13011
  error: result.error
12587
13012
  });
12588
- this.logger.log("info", `Task ${task.name} completed: ${result.status}`);
13013
+ const outputBytes = result.output ? Buffer.byteLength(result.output) : 0;
13014
+ this.logger.log(
13015
+ "info",
13016
+ `Task completed: ${task.name} (${task.id})
13017
+ status: ${result.status}
13018
+ duration: ${durationMs}ms
13019
+ outputBytes: ${outputBytes}`
13020
+ );
12589
13021
  } catch (err) {
12590
13022
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
13023
+ const durationMs = Date.now() - startMs;
12591
13024
  const message = err instanceof Error ? err.message : String(err);
12592
13025
  this.logManager.appendLog({
12593
13026
  taskId: task.id,
@@ -12597,7 +13030,12 @@ var init_SchedulerDaemon = __esm({
12597
13030
  status: "failure",
12598
13031
  error: message
12599
13032
  });
12600
- this.logger.log("error", `Task ${task.name} failed: ${message}`);
13033
+ this.logger.log(
13034
+ "error",
13035
+ `Task failed: ${task.name} (${task.id})
13036
+ duration: ${durationMs}ms
13037
+ error: ${message}`
13038
+ );
12601
13039
  } finally {
12602
13040
  this.taskRunning.delete(task.id);
12603
13041
  }
@@ -12627,170 +13065,6 @@ var init_daemon = __esm({
12627
13065
  }
12628
13066
  });
12629
13067
 
12630
- // ../../packages/serviceme-core/src/scheduled-tasks/TaskExecutionEngine.ts
12631
- var TaskExecutionEngine;
12632
- var init_TaskExecutionEngine = __esm({
12633
- "../../packages/serviceme-core/src/scheduled-tasks/TaskExecutionEngine.ts"() {
12634
- init_types();
12635
- TaskExecutionEngine = class {
12636
- constructor(getExecutor2) {
12637
- this.getExecutor = getExecutor2;
12638
- this.running = /* @__PURE__ */ new Map();
12639
- this.taskExecutions = /* @__PURE__ */ new Map();
12640
- this.listener = null;
12641
- }
12642
- setListener(listener) {
12643
- this.listener = listener;
12644
- }
12645
- async execute(snapshot) {
12646
- const policy = snapshot.concurrencyPolicy ?? "reject";
12647
- if (policy === "reject") {
12648
- const existingIds = this.taskExecutions.get(snapshot.taskId);
12649
- if (existingIds && existingIds.size > 0) {
12650
- throw new Error(
12651
- `TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
12652
- );
12653
- }
12654
- }
12655
- const executor = this.getExecutor(snapshot.type);
12656
- const startedAt = (/* @__PURE__ */ new Date()).toISOString();
12657
- if (!this.taskExecutions.has(snapshot.taskId)) {
12658
- this.taskExecutions.set(snapshot.taskId, /* @__PURE__ */ new Set());
12659
- }
12660
- this.taskExecutions.get(snapshot.taskId)?.add(snapshot.executionId);
12661
- const ac = new AbortController();
12662
- const runningExec = {
12663
- executionId: snapshot.executionId,
12664
- taskId: snapshot.taskId,
12665
- startedAt,
12666
- cancel: () => ac.abort()
12667
- };
12668
- this.running.set(snapshot.executionId, runningExec);
12669
- this.listener?.onStarted({
12670
- executionId: snapshot.executionId,
12671
- taskId: snapshot.taskId,
12672
- startedAt
12673
- });
12674
- try {
12675
- let result;
12676
- if (isStreamingTaskExecutor(executor)) {
12677
- const handle = executor.executeStreaming(
12678
- snapshot.payload,
12679
- (stream, data) => {
12680
- this.listener?.onOutput({
12681
- executionId: snapshot.executionId,
12682
- stream,
12683
- data
12684
- });
12685
- },
12686
- ac.signal
12687
- );
12688
- runningExec.cancel = () => {
12689
- handle.cancel();
12690
- ac.abort();
12691
- };
12692
- result = await handle.result;
12693
- } else {
12694
- result = await executor.execute(snapshot.payload, ac.signal);
12695
- }
12696
- const log = {
12697
- id: snapshot.executionId,
12698
- taskId: snapshot.taskId,
12699
- taskName: snapshot.name,
12700
- startedAt,
12701
- finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
12702
- status: result.status === "running" ? "failure" : result.status,
12703
- output: result.output,
12704
- error: result.error
12705
- };
12706
- switch (log.status) {
12707
- case "success":
12708
- this.listener?.onCompleted({
12709
- executionId: snapshot.executionId,
12710
- log
12711
- });
12712
- break;
12713
- case "cancelled":
12714
- this.listener?.onCancelled({
12715
- executionId: snapshot.executionId,
12716
- log
12717
- });
12718
- break;
12719
- case "timeout":
12720
- this.listener?.onFailed({
12721
- executionId: snapshot.executionId,
12722
- status: "timeout",
12723
- reason: "timeout",
12724
- log
12725
- });
12726
- break;
12727
- default:
12728
- this.listener?.onFailed({
12729
- executionId: snapshot.executionId,
12730
- status: "failure",
12731
- reason: "error",
12732
- log
12733
- });
12734
- break;
12735
- }
12736
- } catch (err) {
12737
- const log = {
12738
- id: snapshot.executionId,
12739
- taskId: snapshot.taskId,
12740
- taskName: snapshot.name,
12741
- startedAt,
12742
- finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
12743
- status: ac.signal.aborted ? "cancelled" : "failure",
12744
- error: err instanceof Error ? err.message : String(err)
12745
- };
12746
- if (ac.signal.aborted) {
12747
- this.listener?.onCancelled({
12748
- executionId: snapshot.executionId,
12749
- log
12750
- });
12751
- } else {
12752
- this.listener?.onFailed({
12753
- executionId: snapshot.executionId,
12754
- status: "failure",
12755
- reason: "error",
12756
- log
12757
- });
12758
- }
12759
- } finally {
12760
- this.running.delete(snapshot.executionId);
12761
- const taskExecIds = this.taskExecutions.get(snapshot.taskId);
12762
- if (taskExecIds) {
12763
- taskExecIds.delete(snapshot.executionId);
12764
- if (taskExecIds.size === 0) {
12765
- this.taskExecutions.delete(snapshot.taskId);
12766
- }
12767
- }
12768
- }
12769
- }
12770
- cancel(executionId) {
12771
- const exec = this.running.get(executionId);
12772
- if (!exec) return false;
12773
- exec.cancel();
12774
- return true;
12775
- }
12776
- listRunning() {
12777
- return Array.from(this.running.values()).map((e) => ({
12778
- executionId: e.executionId,
12779
- taskId: e.taskId,
12780
- startedAt: e.startedAt
12781
- }));
12782
- }
12783
- dispose() {
12784
- for (const exec of this.running.values()) {
12785
- exec.cancel();
12786
- }
12787
- this.running.clear();
12788
- this.taskExecutions.clear();
12789
- }
12790
- };
12791
- }
12792
- });
12793
-
12794
13068
  // ../../packages/serviceme-core/src/scheduled-tasks/index.ts
12795
13069
  var init_scheduled_tasks2 = __esm({
12796
13070
  "../../packages/serviceme-core/src/scheduled-tasks/index.ts"() {
@@ -12832,7 +13106,9 @@ __export(src_exports, {
12832
13106
  moveFiles: () => moveFiles,
12833
13107
  noopLogger: () => noopLogger,
12834
13108
  parseIntervalMs: () => parseIntervalMs,
12835
- unzipFile: () => unzipFile
13109
+ resolveTaskExecutionPayload: () => resolveTaskExecutionPayload,
13110
+ unzipFile: () => unzipFile,
13111
+ validateTaskPayload: () => validateTaskPayload
12836
13112
  });
12837
13113
  var init_src2 = __esm({
12838
13114
  "../../packages/serviceme-core/src/index.ts"() {
@@ -12901,7 +13177,7 @@ init_src();
12901
13177
 
12902
13178
  // src/version.ts
12903
13179
  var SERVICEME_CLI_NAME = "serviceme";
12904
- var SERVICEME_CLI_VERSION = "0.1.2";
13180
+ var SERVICEME_CLI_VERSION = "0.1.4";
12905
13181
 
12906
13182
  // src/bridge/ndjson.ts
12907
13183
  function writeBridgeMessage(message) {
@@ -13428,7 +13704,7 @@ function requireWorkspace(parsed) {
13428
13704
  "Missing required flag: --workspacePath"
13429
13705
  );
13430
13706
  }
13431
- if (!fs6__namespace.existsSync(wp)) {
13707
+ if (!fs8__namespace.existsSync(wp)) {
13432
13708
  throw createServicemeError(
13433
13709
  "workspace_not_found",
13434
13710
  `Workspace path does not exist: ${wp}`
@@ -13761,8 +14037,14 @@ async function handleTrigger(parsed) {
13761
14037
  );
13762
14038
  }
13763
14039
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
14040
+ const executionPayload = resolveTaskExecutionPayload(
14041
+ task.taskType,
14042
+ task.payload,
14043
+ wp
14044
+ );
14045
+ validateTaskPayload(task.taskType, executionPayload);
13764
14046
  const executor = getExecutor(task.taskType);
13765
- const result = await executor.execute(task.payload);
14047
+ const result = await executor.execute(executionPayload);
13766
14048
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
13767
14049
  const logMgr = new TaskLogManager(wp);
13768
14050
  const log = logMgr.appendLog({
@@ -13991,7 +14273,7 @@ function requireWorkspace2(parsed) {
13991
14273
  "Missing required flag: --workspacePath"
13992
14274
  );
13993
14275
  }
13994
- if (!fs6__namespace.existsSync(wp)) {
14276
+ if (!fs8__namespace.existsSync(wp)) {
13995
14277
  throw createServicemeError(
13996
14278
  "workspace_not_found",
13997
14279
  `Workspace path does not exist: ${wp}`
@@ -14020,20 +14302,43 @@ function handleStart(parsed) {
14020
14302
  }
14021
14303
  const logPath = path__namespace.join(wp, ".serviceme", "scheduler.log");
14022
14304
  const logDir = path__namespace.dirname(logPath);
14023
- if (!fs6__namespace.existsSync(logDir)) {
14024
- fs6__namespace.mkdirSync(logDir, { recursive: true });
14025
- }
14026
- const out = fs6__namespace.openSync(logPath, "a");
14027
- const err = fs6__namespace.openSync(logPath, "a");
14028
- const child = child_process.spawn(
14029
- process.execPath,
14030
- [cliPath, "scheduler", "__daemon", "--workspacePath", wp],
14031
- {
14032
- detached: true,
14033
- stdio: ["ignore", out, err],
14034
- env: { ...process.env }
14035
- }
14305
+ if (!fs8__namespace.existsSync(logDir)) {
14306
+ fs8__namespace.mkdirSync(logDir, { recursive: true });
14307
+ }
14308
+ const spawnCmd = process.execPath;
14309
+ const spawnArgs = [
14310
+ cliPath,
14311
+ "scheduler",
14312
+ "__daemon",
14313
+ "--workspacePath",
14314
+ wp,
14315
+ "--logPath",
14316
+ logPath
14317
+ ];
14318
+ if (process.platform === "win32") {
14319
+ fs8__namespace.appendFileSync(
14320
+ logPath,
14321
+ `[scheduler:start] spawning daemon via hidden PowerShell Start-Process: cmd=${spawnCmd}, args=${JSON.stringify(spawnArgs)}, platform=${process.platform}, windowsHide=true
14322
+ `
14323
+ );
14324
+ const pid2 = startWindowsDaemon(spawnCmd, spawnArgs);
14325
+ pidMgr.writePid(pid2);
14326
+ writeSuccess({ pid: pid2, status: "started", workspacePath: wp });
14327
+ return;
14328
+ }
14329
+ const out = fs8__namespace.openSync(logPath, "a");
14330
+ const err = fs8__namespace.openSync(logPath, "a");
14331
+ fs8__namespace.appendFileSync(
14332
+ logPath,
14333
+ `[scheduler:start] spawning daemon: cmd=${spawnCmd}, args=${JSON.stringify(spawnArgs)}, platform=${process.platform}, detached=true, windowsHide=true
14334
+ `
14036
14335
  );
14336
+ const child = child_process.spawn(spawnCmd, spawnArgs, {
14337
+ detached: true,
14338
+ stdio: ["ignore", out, err],
14339
+ env: { ...process.env },
14340
+ windowsHide: true
14341
+ });
14037
14342
  child.unref();
14038
14343
  const pid = child.pid;
14039
14344
  if (pid === void 0) {
@@ -14045,6 +14350,60 @@ function handleStart(parsed) {
14045
14350
  pidMgr.writePid(pid);
14046
14351
  writeSuccess({ pid, status: "started", workspacePath: wp });
14047
14352
  }
14353
+ function startWindowsDaemon(command, args) {
14354
+ const argumentLine = args.map(quoteWindowsArgument).join(" ");
14355
+ const script = [
14356
+ "$ErrorActionPreference = 'Stop'",
14357
+ `$process = Start-Process -FilePath ${quotePowerShellString(command)} -ArgumentList ${quotePowerShellString(argumentLine)} -WindowStyle Hidden -PassThru`,
14358
+ "Write-Output $process.Id"
14359
+ ].join("; ");
14360
+ const encodedCommand = Buffer.from(script, "utf16le").toString("base64");
14361
+ const result = child_process.spawnSync(
14362
+ "powershell.exe",
14363
+ [
14364
+ "-NoProfile",
14365
+ "-NonInteractive",
14366
+ "-ExecutionPolicy",
14367
+ "Bypass",
14368
+ "-WindowStyle",
14369
+ "Hidden",
14370
+ "-EncodedCommand",
14371
+ encodedCommand
14372
+ ],
14373
+ {
14374
+ encoding: "utf8",
14375
+ stdio: ["ignore", "pipe", "pipe"],
14376
+ windowsHide: true
14377
+ }
14378
+ );
14379
+ if (result.error) {
14380
+ throw createServicemeError(
14381
+ "internal_error",
14382
+ `Failed to start scheduler daemon: ${result.error.message}`
14383
+ );
14384
+ }
14385
+ if (result.status !== 0) {
14386
+ const stderr = result.stderr.trim();
14387
+ throw createServicemeError(
14388
+ "internal_error",
14389
+ `Failed to start scheduler daemon${stderr ? `: ${stderr}` : ""}`
14390
+ );
14391
+ }
14392
+ const pid = Number.parseInt(result.stdout.trim(), 10);
14393
+ if (!Number.isFinite(pid)) {
14394
+ throw createServicemeError(
14395
+ "internal_error",
14396
+ `Failed to parse scheduler daemon PID: ${result.stdout.trim()}`
14397
+ );
14398
+ }
14399
+ return pid;
14400
+ }
14401
+ function quotePowerShellString(value) {
14402
+ return `'${value.replace(/'/g, "''")}'`;
14403
+ }
14404
+ function quoteWindowsArgument(value) {
14405
+ return `"${value.replace(/(\\*)"/g, '$1$1\\"').replace(/\\+$/g, "$&$&")}"`;
14406
+ }
14048
14407
  function handleStop(parsed) {
14049
14408
  const wp = requireWorkspace2(parsed);
14050
14409
  if (!getBooleanFlag(parsed, "yes")) {
@@ -14080,7 +14439,7 @@ function handleStatus(parsed) {
14080
14439
  let uptimeSeconds = null;
14081
14440
  if (pid !== null) {
14082
14441
  try {
14083
- const stat2 = fs6__namespace.statSync(pidMgr.getPidPath());
14442
+ const stat2 = fs8__namespace.statSync(pidMgr.getPidPath());
14084
14443
  uptimeSeconds = Math.floor((Date.now() - stat2.mtimeMs) / 1e3);
14085
14444
  } catch {
14086
14445
  uptimeSeconds = null;
@@ -14099,6 +14458,16 @@ function handleStatus(parsed) {
14099
14458
  }
14100
14459
  async function runDaemon(parsed) {
14101
14460
  const wp = requireWorkspace2(parsed);
14461
+ const logPath = getStringFlag(parsed, "logPath");
14462
+ if (logPath) {
14463
+ process.env.SERVICEME_SCHEDULER_LOG_PATH = logPath;
14464
+ }
14465
+ const pidMgr = new PidManager(wp);
14466
+ const existingPid = pidMgr.getRunningPid();
14467
+ if (existingPid !== null && existingPid !== process.pid) {
14468
+ process.exit(0);
14469
+ }
14470
+ pidMgr.writePid(process.pid);
14102
14471
  const { SchedulerDaemon: SchedulerDaemon2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
14103
14472
  const daemon = new SchedulerDaemon2(wp);
14104
14473
  daemon.start();