@serviceme/devtools-core 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -652,6 +652,50 @@ var AccessControl = class {
652
652
  }
653
653
  };
654
654
 
655
+ // src/logger.ts
656
+ var noopLogger = {
657
+ debug() {
658
+ },
659
+ info() {
660
+ },
661
+ warn() {
662
+ },
663
+ error() {
664
+ }
665
+ };
666
+ function formatArgs(args) {
667
+ return args.map((arg) => {
668
+ if (typeof arg === "string") {
669
+ return arg;
670
+ }
671
+ try {
672
+ return JSON.stringify(arg);
673
+ } catch {
674
+ return String(arg);
675
+ }
676
+ }).join(" ");
677
+ }
678
+ function createConsoleLogger(prefix = "serviceme") {
679
+ return {
680
+ debug(message, ...args) {
681
+ process.stderr.write(`[${prefix}] DEBUG ${message} ${formatArgs(args)}
682
+ `);
683
+ },
684
+ info(message, ...args) {
685
+ process.stderr.write(`[${prefix}] INFO ${message} ${formatArgs(args)}
686
+ `);
687
+ },
688
+ warn(message, ...args) {
689
+ process.stderr.write(`[${prefix}] WARN ${message} ${formatArgs(args)}
690
+ `);
691
+ },
692
+ error(message, ...args) {
693
+ process.stderr.write(`[${prefix}] ERROR ${message} ${formatArgs(args)}
694
+ `);
695
+ }
696
+ };
697
+ }
698
+
655
699
  // src/auth/AuthStateManager.ts
656
700
  var import_node_events = require("events");
657
701
  var AuthStateManager = class {
@@ -814,6 +858,7 @@ var AuthCore = class {
814
858
  this.state = opts.stateManager ?? new AuthStateManager();
815
859
  this.tokenStore = opts.tokenStore;
816
860
  this.accessControl = opts.accessControl;
861
+ this.logger = opts.logger ?? noopLogger;
817
862
  }
818
863
  /** Snapshot of every account, the active provider, and the last error. */
819
864
  status() {
@@ -835,16 +880,28 @@ var AuthCore = class {
835
880
  */
836
881
  async login(provider, ui, shouldContinue) {
837
882
  const providerImpl = this.registry.get(provider);
883
+ this.logger.info("[AuthCore] Starting device-flow login", { provider });
838
884
  try {
839
885
  const initial = await providerImpl.requestDeviceFlow();
840
886
  await ui(initial);
887
+ if (!initial.deviceCode) {
888
+ this.logger.error("[AuthCore] Provider did not return deviceCode", provider);
889
+ throw new Error("Auth provider did not return deviceCode for device flow completion");
890
+ }
841
891
  const session = await providerImpl.completeDeviceFlow(
842
- initial.userCode ? initial.deviceCode ?? "" : "",
843
- shouldContinue
892
+ initial.deviceCode,
893
+ shouldContinue,
894
+ initial.pollIntervalMs
844
895
  );
845
- return await this.persistSession(providerImpl, session);
896
+ const result = await this.persistSession(providerImpl, session);
897
+ this.logger.info("[AuthCore] Device-flow login completed", { provider });
898
+ return result;
846
899
  } catch (err) {
847
- this.state.recordError(err instanceof Error ? err.message : String(err));
900
+ const message = err instanceof Error ? err.message : String(err);
901
+ this.logger.error("[AuthCore] Device-flow login failed", message, {
902
+ provider
903
+ });
904
+ this.state.recordError(message);
848
905
  throw err;
849
906
  }
850
907
  }
@@ -869,7 +926,10 @@ var AuthCore = class {
869
926
  if (!provider) return null;
870
927
  const account = this.state.getActiveAccount();
871
928
  if (!account) return null;
872
- const envelope = await this.tokenStore.get({ provider, accountId: account.id });
929
+ const envelope = await this.tokenStore.get({
930
+ provider,
931
+ accountId: account.id
932
+ });
873
933
  if (!envelope) return null;
874
934
  return { provider, account, token: envelope.token };
875
935
  }
@@ -879,9 +939,15 @@ var AuthCore = class {
879
939
  * every account.
880
940
  */
881
941
  async logout(provider) {
942
+ this.logger.info("[AuthCore] Logout requested", {
943
+ provider: provider ?? "all"
944
+ });
882
945
  if (!provider) {
883
946
  for (const account of this.state.listAccounts()) {
884
- await this.tokenStore.delete({ provider: account.provider, accountId: account.id });
947
+ await this.tokenStore.delete({
948
+ provider: account.provider,
949
+ accountId: account.id
950
+ });
885
951
  this.state.removeAccount(account.provider, account.id);
886
952
  }
887
953
  this.state.clearAll();
@@ -956,6 +1022,10 @@ var AuthCore = class {
956
1022
  avatarUrl: meta.avatarUrl,
957
1023
  expiresAt: meta.expiresAt ?? expiresAt
958
1024
  };
1025
+ this.logger.debug("[AuthCore] Persisting session", {
1026
+ provider: meta.provider,
1027
+ accountId: meta.id
1028
+ });
959
1029
  await this.tokenStore.set({ provider: meta.provider, accountId: meta.id }, session.token, {
960
1030
  expiresAt
961
1031
  });
@@ -1058,11 +1128,13 @@ var GitHubAuthProvider = class {
1058
1128
  userUrl: config.userUrl ?? DEFAULT_USER_URL,
1059
1129
  scope: config.scope ?? DEFAULT_SCOPE,
1060
1130
  minPollIntervalMs: config.minPollIntervalMs ?? 1e3,
1061
- maxPollIntervalMs: config.maxPollIntervalMs ?? 15e3,
1131
+ maxPollIntervalMs: config.maxPollIntervalMs ?? 6e4,
1062
1132
  maxWaitMs: config.maxWaitMs,
1063
1133
  fetchImpl: config.fetchImpl ?? fetch,
1064
1134
  deviceCodeRetryBaseDelayMs: config.deviceCodeRetryBaseDelayMs ?? DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS
1065
1135
  };
1136
+ this.logger = config.logger ?? noopLogger;
1137
+ this.sleepImpl = config.sleepImpl ?? sleep;
1066
1138
  }
1067
1139
  async requestDeviceFlow(opts) {
1068
1140
  const body = JSON.stringify({
@@ -1071,6 +1143,10 @@ var GitHubAuthProvider = class {
1071
1143
  });
1072
1144
  let lastNetworkError;
1073
1145
  for (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {
1146
+ this.logger.debug("[GitHubAuthProvider] Requesting device code", {
1147
+ attempt,
1148
+ maxAttempts: DEVICE_CODE_MAX_ATTEMPTS
1149
+ });
1074
1150
  let resp;
1075
1151
  try {
1076
1152
  resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {
@@ -1084,45 +1160,99 @@ var GitHubAuthProvider = class {
1084
1160
  });
1085
1161
  } catch (error) {
1086
1162
  if (!isTransientNetworkError(error) || attempt === DEVICE_CODE_MAX_ATTEMPTS) {
1163
+ this.logger.error(
1164
+ "[GitHubAuthProvider] Device-code request failed (non-retryable)",
1165
+ error instanceof Error ? error.message : String(error),
1166
+ { attempt }
1167
+ );
1087
1168
  throw error;
1088
1169
  }
1170
+ const delayMs = this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1);
1171
+ this.logger.warn(
1172
+ "[GitHubAuthProvider] Transient network error requesting device code, retrying",
1173
+ {
1174
+ attempt,
1175
+ delayMs,
1176
+ error: error instanceof Error ? error.message : String(error)
1177
+ }
1178
+ );
1089
1179
  lastNetworkError = error;
1090
- await sleep(this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1));
1180
+ await this.sleepImpl(delayMs);
1091
1181
  continue;
1092
1182
  }
1093
1183
  if (!resp.ok) {
1184
+ this.logger.error(
1185
+ "[GitHubAuthProvider] Device-code request rejected by GitHub",
1186
+ `HTTP ${resp.status}`
1187
+ );
1094
1188
  throw new Error(`GitHub device-code request failed: HTTP ${resp.status}`);
1095
1189
  }
1096
1190
  const data = await resp.json();
1097
1191
  if (!data.device_code || !data.user_code || !data.verification_uri) {
1192
+ this.logger.error(
1193
+ "[GitHubAuthProvider] Device-code response missing required fields",
1194
+ JSON.stringify(Object.keys(data))
1195
+ );
1098
1196
  throw new Error("GitHub device-code response missing required fields");
1099
1197
  }
1198
+ this.logger.info("[GitHubAuthProvider] Device code obtained", {
1199
+ userCode: data.user_code,
1200
+ verificationUri: data.verification_uri,
1201
+ expiresInSec: data.expires_in,
1202
+ pollIntervalSec: data.interval
1203
+ });
1100
1204
  return {
1101
1205
  provider: this.providerId,
1206
+ deviceCode: data.device_code,
1102
1207
  userCode: data.user_code,
1103
1208
  verificationUrl: data.verification_uri,
1104
1209
  expiresAt: Date.now() + data.expires_in * 1e3,
1210
+ pollIntervalMs: data.interval * 1e3,
1105
1211
  message: `Open ${data.verification_uri} and enter ${data.user_code}`
1106
1212
  };
1107
1213
  }
1108
1214
  throw lastNetworkError ?? new Error("GitHub device-code request failed");
1109
1215
  }
1110
- async completeDeviceFlow(deviceCode, shouldContinue) {
1216
+ async completeDeviceFlow(deviceCode, shouldContinue, initialPollIntervalMs) {
1111
1217
  const start = Date.now();
1112
- let pollIntervalMs = this.cfg.minPollIntervalMs;
1218
+ let pollIntervalMs = Math.max(this.cfg.minPollIntervalMs, initialPollIntervalMs ?? 0);
1113
1219
  let consecutiveSlowDown = 0;
1220
+ let pollCount = 0;
1221
+ this.logger.info("[GitHubAuthProvider] Starting device-flow polling", {
1222
+ initialPollIntervalMs: pollIntervalMs,
1223
+ maxWaitMs: this.cfg.maxWaitMs
1224
+ });
1114
1225
  while (true) {
1115
1226
  if (shouldContinue && !shouldContinue()) {
1227
+ this.logger.info("[GitHubAuthProvider] Device flow cancelled by caller", {
1228
+ elapsedMs: Date.now() - start,
1229
+ pollCount
1230
+ });
1116
1231
  throw new Error("GitHub device flow cancelled by caller");
1117
1232
  }
1118
1233
  const elapsed = Date.now() - start;
1119
1234
  if (this.cfg.maxWaitMs !== void 0 && elapsed > this.cfg.maxWaitMs) {
1235
+ this.logger.warn("[GitHubAuthProvider] Device flow exceeded max wait time", {
1236
+ elapsedMs: elapsed,
1237
+ maxWaitMs: this.cfg.maxWaitMs,
1238
+ pollCount
1239
+ });
1120
1240
  throw new Error("GitHub device flow exceeded max wait time");
1121
1241
  }
1122
- await sleep(pollIntervalMs);
1242
+ await this.sleepImpl(pollIntervalMs);
1123
1243
  if (shouldContinue && !shouldContinue()) {
1244
+ this.logger.info("[GitHubAuthProvider] Device flow cancelled by caller", {
1245
+ elapsedMs: Date.now() - start,
1246
+ pollCount
1247
+ });
1124
1248
  throw new Error("GitHub device flow cancelled by caller");
1125
1249
  }
1250
+ pollCount++;
1251
+ this.logger.debug("[GitHubAuthProvider] Polling for authorization", {
1252
+ pollCount,
1253
+ elapsedMs: Date.now() - start,
1254
+ pollIntervalMs
1255
+ });
1126
1256
  let resp;
1127
1257
  try {
1128
1258
  resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {
@@ -1140,19 +1270,59 @@ var GitHubAuthProvider = class {
1140
1270
  });
1141
1271
  } catch (error) {
1142
1272
  if (!isTransientNetworkError(error)) {
1273
+ this.logger.error(
1274
+ "[GitHubAuthProvider] Non-retryable error while polling token endpoint",
1275
+ error instanceof Error ? error.message : String(error),
1276
+ { pollCount }
1277
+ );
1143
1278
  throw error;
1144
1279
  }
1145
- pollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), this.cfg.maxPollIntervalMs);
1280
+ const nextPollIntervalMs = Math.min(
1281
+ Math.round(pollIntervalMs * 1.5),
1282
+ this.cfg.maxPollIntervalMs
1283
+ );
1284
+ this.logger.warn(
1285
+ "[GitHubAuthProvider] Transient network error while polling, backing off",
1286
+ {
1287
+ pollCount,
1288
+ error: error instanceof Error ? error.message : String(error),
1289
+ previousPollIntervalMs: pollIntervalMs,
1290
+ nextPollIntervalMs
1291
+ }
1292
+ );
1293
+ pollIntervalMs = nextPollIntervalMs;
1146
1294
  continue;
1147
1295
  }
1148
1296
  if (!resp.ok) {
1149
- pollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), this.cfg.maxPollIntervalMs);
1297
+ const nextPollIntervalMs = Math.min(
1298
+ Math.round(pollIntervalMs * 1.5),
1299
+ this.cfg.maxPollIntervalMs
1300
+ );
1301
+ this.logger.warn(
1302
+ "[GitHubAuthProvider] Token endpoint returned non-OK status, backing off",
1303
+ {
1304
+ pollCount,
1305
+ status: resp.status,
1306
+ previousPollIntervalMs: pollIntervalMs,
1307
+ nextPollIntervalMs
1308
+ }
1309
+ );
1310
+ pollIntervalMs = nextPollIntervalMs;
1150
1311
  continue;
1151
1312
  }
1152
1313
  const data = await resp.json();
1153
1314
  if (data.access_token) {
1315
+ this.logger.info("[GitHubAuthProvider] Authorization granted, fetching user profile", {
1316
+ pollCount,
1317
+ elapsedMs: Date.now() - start
1318
+ });
1154
1319
  const rawUser = await this.fetchGitHubUser(data.access_token);
1155
1320
  const email = await this.resolveEmail(data.access_token, rawUser);
1321
+ this.logger.info("[GitHubAuthProvider] Device-flow login completed", {
1322
+ pollCount,
1323
+ elapsedMs: Date.now() - start,
1324
+ login: rawUser.login
1325
+ });
1156
1326
  return {
1157
1327
  token: data.access_token,
1158
1328
  refreshToken: data.refresh_token,
@@ -1167,22 +1337,42 @@ var GitHubAuthProvider = class {
1167
1337
  };
1168
1338
  }
1169
1339
  if (data.error === "authorization_pending") {
1340
+ this.logger.debug("[GitHubAuthProvider] Authorization still pending", {
1341
+ pollCount,
1342
+ elapsedMs: Date.now() - start
1343
+ });
1170
1344
  continue;
1171
1345
  }
1172
1346
  if (data.error === "slow_down") {
1173
1347
  consecutiveSlowDown++;
1174
- pollIntervalMs = Math.min(
1175
- Math.round(pollIntervalMs * 1.5 + Math.min(consecutiveSlowDown * 500, 2e3)),
1176
- this.cfg.maxPollIntervalMs
1177
- );
1348
+ const nextPollIntervalMs = Math.min(pollIntervalMs + 5e3, this.cfg.maxPollIntervalMs);
1349
+ this.logger.warn("[GitHubAuthProvider] GitHub requested slower polling (slow_down)", {
1350
+ pollCount,
1351
+ consecutiveSlowDown,
1352
+ previousPollIntervalMs: pollIntervalMs,
1353
+ nextPollIntervalMs
1354
+ });
1355
+ pollIntervalMs = nextPollIntervalMs;
1178
1356
  continue;
1179
1357
  }
1180
1358
  if (data.error === "access_denied") {
1359
+ this.logger.warn("[GitHubAuthProvider] User denied authorization", {
1360
+ pollCount
1361
+ });
1181
1362
  throw new Error("User denied authorization");
1182
1363
  }
1183
1364
  if (data.error === "expired_token") {
1365
+ this.logger.warn(
1366
+ "[GitHubAuthProvider] Device code expired before authorization completed",
1367
+ { pollCount, elapsedMs: Date.now() - start }
1368
+ );
1184
1369
  throw new Error("GitHub device code expired \u2014 restart the flow");
1185
1370
  }
1371
+ this.logger.error(
1372
+ "[GitHubAuthProvider] Unexpected device-flow error from token endpoint",
1373
+ data.error ?? "unknown",
1374
+ { pollCount }
1375
+ );
1186
1376
  throw new Error(`GitHub device flow error: ${data.error ?? "unknown"}`);
1187
1377
  }
1188
1378
  }
@@ -2057,10 +2247,20 @@ function isRecord(value) {
2057
2247
  function isNodeError(value) {
2058
2248
  return value instanceof Error && typeof value.code === "string";
2059
2249
  }
2250
+ var LOCK_PID_FILE = "pid";
2251
+ function isProcessAlive(pid) {
2252
+ try {
2253
+ process.kill(pid, 0);
2254
+ return true;
2255
+ } catch {
2256
+ return false;
2257
+ }
2258
+ }
2060
2259
  var FileLock = class {
2061
2260
  constructor(filePath, timeoutMs, retryMs) {
2062
2261
  this.acquired = false;
2063
2262
  this.dirPath = `${filePath}.lock`;
2263
+ this.pidFilePath = path3.join(this.dirPath, LOCK_PID_FILE);
2064
2264
  this.timeoutMs = timeoutMs;
2065
2265
  this.retryMs = retryMs;
2066
2266
  }
@@ -2069,12 +2269,18 @@ var FileLock = class {
2069
2269
  while (true) {
2070
2270
  try {
2071
2271
  await fsp.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });
2272
+ await fsp.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
2072
2273
  this.acquired = true;
2073
2274
  return;
2074
2275
  } catch (err) {
2075
2276
  if (!isNodeError(err) || err.code !== "EEXIST") {
2076
2277
  throw err;
2077
2278
  }
2279
+ const stale = await this.isStaleLock();
2280
+ if (stale) {
2281
+ await fsp.rm(this.dirPath, { recursive: true, force: true });
2282
+ continue;
2283
+ }
2078
2284
  if (Date.now() - start >= this.timeoutMs) {
2079
2285
  throw new Error(`IdentityStore lock acquisition timed out for ${this.dirPath}`);
2080
2286
  }
@@ -2082,6 +2288,16 @@ var FileLock = class {
2082
2288
  }
2083
2289
  }
2084
2290
  }
2291
+ async isStaleLock() {
2292
+ try {
2293
+ const pidStr = await fsp.readFile(this.pidFilePath, "utf8");
2294
+ const pid = Number.parseInt(pidStr.trim(), 10);
2295
+ if (!Number.isFinite(pid) || pid <= 0) return true;
2296
+ return !isProcessAlive(pid);
2297
+ } catch {
2298
+ return true;
2299
+ }
2300
+ }
2085
2301
  async release() {
2086
2302
  if (!this.acquired) return;
2087
2303
  this.acquired = false;
@@ -2507,6 +2723,14 @@ var TOOL_CHECK_TIMEOUT_MS = {
2507
2723
  pnpm: 15e3,
2508
2724
  nrm: 15e3
2509
2725
  };
2726
+ var POSIX_LOGIN_SHELL_FALLBACK_TOOLS = /* @__PURE__ */ new Set([
2727
+ "npm",
2728
+ "pnpm",
2729
+ "nrm",
2730
+ "node",
2731
+ "dotnet",
2732
+ "rtk"
2733
+ ]);
2510
2734
  var ERROR_CODE_NOT_FOUND = 127;
2511
2735
  var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
2512
2736
  var EnvironmentInspector = class {
@@ -2550,6 +2774,23 @@ var EnvironmentInspector = class {
2550
2774
  timeoutMs: this.getToolTimeout(toolName)
2551
2775
  });
2552
2776
  return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
2777
+ } catch {
2778
+ if (this.shouldUsePosixLoginShellFallback(toolName)) {
2779
+ return this.getToolPathFromLoginShell(toolName);
2780
+ }
2781
+ return void 0;
2782
+ }
2783
+ }
2784
+ async getToolPathFromLoginShell(toolName) {
2785
+ try {
2786
+ const result = await this.runCommandFn("/bin/bash", {
2787
+ args: [
2788
+ "-lc",
2789
+ 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; command -v ' + toolName
2790
+ ],
2791
+ timeoutMs: this.getToolTimeout(toolName)
2792
+ });
2793
+ return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
2553
2794
  } catch {
2554
2795
  return void 0;
2555
2796
  }
@@ -2574,12 +2815,33 @@ var EnvironmentInspector = class {
2574
2815
  }
2575
2816
  }
2576
2817
  async getToolVersion(toolName) {
2577
- const invocation = await this.getVersionInvocation(toolName);
2578
- const result = await this.runCommandFn(invocation.command, {
2579
- args: invocation.args,
2580
- timeoutMs: this.getToolTimeout(toolName)
2581
- });
2582
- return this.parseVersion(toolName, result.stdout || result.stderr);
2818
+ try {
2819
+ const invocation = await this.getVersionInvocation(toolName);
2820
+ const result = await this.runCommandFn(invocation.command, {
2821
+ args: invocation.args,
2822
+ timeoutMs: this.getToolTimeout(toolName)
2823
+ });
2824
+ return this.parseVersion(toolName, result.stdout || result.stderr);
2825
+ } catch (error) {
2826
+ if (!this.shouldUsePosixLoginShellFallback(toolName)) {
2827
+ throw error;
2828
+ }
2829
+ const fallbackResult = await this.runCommandFn("/bin/bash", {
2830
+ args: [
2831
+ "-lc",
2832
+ `export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; ${toolName} --version`
2833
+ ],
2834
+ timeoutMs: this.getToolTimeout(toolName)
2835
+ });
2836
+ const fallbackOutput = (fallbackResult.stdout || fallbackResult.stderr).trim();
2837
+ if (!fallbackOutput) {
2838
+ throw error;
2839
+ }
2840
+ return this.parseVersion(toolName, fallbackOutput);
2841
+ }
2842
+ }
2843
+ shouldUsePosixLoginShellFallback(toolName) {
2844
+ return this.platform !== "win32" && POSIX_LOGIN_SHELL_FALLBACK_TOOLS.has(toolName);
2583
2845
  }
2584
2846
  async checkNvm() {
2585
2847
  if (this.platform === "win32") {
@@ -2629,7 +2891,7 @@ var EnvironmentInspector = class {
2629
2891
  };
2630
2892
  }
2631
2893
  try {
2632
- const result = await this.runCommandFn("dotnet", {
2894
+ const result = await this.runCommandFn(dotnetPath, {
2633
2895
  args: ["nuget", "list", "source"],
2634
2896
  timeoutMs: this.getToolTimeout("nuget")
2635
2897
  });
@@ -3160,50 +3422,6 @@ function detectIndent(text) {
3160
3422
  return 2;
3161
3423
  }
3162
3424
 
3163
- // src/logger.ts
3164
- var noopLogger = {
3165
- debug() {
3166
- },
3167
- info() {
3168
- },
3169
- warn() {
3170
- },
3171
- error() {
3172
- }
3173
- };
3174
- function formatArgs(args) {
3175
- return args.map((arg) => {
3176
- if (typeof arg === "string") {
3177
- return arg;
3178
- }
3179
- try {
3180
- return JSON.stringify(arg);
3181
- } catch {
3182
- return String(arg);
3183
- }
3184
- }).join(" ");
3185
- }
3186
- function createConsoleLogger(prefix = "serviceme") {
3187
- return {
3188
- debug(message, ...args) {
3189
- process.stderr.write(`[${prefix}] DEBUG ${message} ${formatArgs(args)}
3190
- `);
3191
- },
3192
- info(message, ...args) {
3193
- process.stderr.write(`[${prefix}] INFO ${message} ${formatArgs(args)}
3194
- `);
3195
- },
3196
- warn(message, ...args) {
3197
- process.stderr.write(`[${prefix}] WARN ${message} ${formatArgs(args)}
3198
- `);
3199
- },
3200
- error(message, ...args) {
3201
- process.stderr.write(`[${prefix}] ERROR ${message} ${formatArgs(args)}
3202
- `);
3203
- }
3204
- };
3205
- }
3206
-
3207
3425
  // src/paths/serverProxyGlobal.ts
3208
3426
  var import_node_crypto4 = require("crypto");
3209
3427
  var fs6 = __toESM(require("fs/promises"));
@@ -6853,10 +7071,19 @@ function coercePersistedToolbox(parsed) {
6853
7071
  function isNodeError2(value) {
6854
7072
  return value instanceof Error && typeof value.code === "string";
6855
7073
  }
7074
+ function isProcessAlive2(pid) {
7075
+ try {
7076
+ process.kill(pid, 0);
7077
+ return true;
7078
+ } catch {
7079
+ return false;
7080
+ }
7081
+ }
6856
7082
  var ToolboxFileLock = class {
6857
7083
  constructor(filePath, timeoutMs, retryMs) {
6858
7084
  this.acquired = false;
6859
7085
  this.dirPath = `${filePath}.lock`;
7086
+ this.pidFilePath = path25.join(this.dirPath, "pid");
6860
7087
  this.timeoutMs = timeoutMs;
6861
7088
  this.retryMs = retryMs;
6862
7089
  }
@@ -6865,10 +7092,16 @@ var ToolboxFileLock = class {
6865
7092
  while (true) {
6866
7093
  try {
6867
7094
  await fsp2.mkdir(this.dirPath, { mode: LOCK_DIR_MODE2 });
7095
+ await fsp2.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
6868
7096
  this.acquired = true;
6869
7097
  return;
6870
7098
  } catch (err) {
6871
7099
  if (!isNodeError2(err) || err.code !== "EEXIST") throw err;
7100
+ const stale = await this.isStaleLock();
7101
+ if (stale) {
7102
+ await fsp2.rm(this.dirPath, { recursive: true, force: true });
7103
+ continue;
7104
+ }
6872
7105
  if (Date.now() - start >= this.timeoutMs) {
6873
7106
  throw new Error(`ToolboxStore lock acquisition timed out for ${this.dirPath}`);
6874
7107
  }
@@ -6876,6 +7109,16 @@ var ToolboxFileLock = class {
6876
7109
  }
6877
7110
  }
6878
7111
  }
7112
+ async isStaleLock() {
7113
+ try {
7114
+ const pidStr = await fsp2.readFile(this.pidFilePath, "utf8");
7115
+ const pid = Number.parseInt(pidStr.trim(), 10);
7116
+ if (!Number.isFinite(pid) || pid <= 0) return true;
7117
+ return !isProcessAlive2(pid);
7118
+ } catch {
7119
+ return true;
7120
+ }
7121
+ }
6879
7122
  async release() {
6880
7123
  if (!this.acquired) return;
6881
7124
  this.acquired = false;