@vtxmacro/cli 2026.9.69 → 2026.10.2

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/bin/vtx-mcp.js CHANGED
@@ -53,7 +53,8 @@ async function parseError(response, fallback) {
53
53
  detail = null;
54
54
  }
55
55
  const rawDetail = detail && typeof detail === "object" && "detail" in detail ? detail.detail : detail;
56
- const message = typeof rawDetail === "string" ? rawDetail : fallback;
56
+ const detailMessage = rawDetail && typeof rawDetail === "object" && typeof rawDetail.message === "string" ? String(rawDetail.message).trim() : "";
57
+ const message = typeof rawDetail === "string" ? rawDetail : detailMessage || fallback;
57
58
  const code = rawDetail && typeof rawDetail === "object" && "code" in rawDetail ? String(rawDetail.code || "") : void 0;
58
59
  return new AgentClientError({
59
60
  status: response.status,
@@ -610,7 +611,16 @@ var VTX_MCP_TOOLS = [
610
611
  size: { type: "number" },
611
612
  slippage: { type: "number" },
612
613
  estimatedPrice: { type: "number" },
613
- estimatedPriceTimestampMs: { type: "number" }
614
+ estimatedPriceTimestampMs: { type: "number" },
615
+ reduceOnly: { type: "boolean" },
616
+ protection: {
617
+ type: "object",
618
+ description: 'Optional fixed TP/SL like the AI page Auto SL/TP: take_profit and/or stop_loss, each { mode: "percent" | "usdc", value }. Placed as reduce-only market triggers from the average fill; not allowed with reduceOnly; no trailing stops.',
619
+ properties: {
620
+ take_profit: { type: "object", required: ["mode", "value"], properties: { mode: { type: "string", enum: ["percent", "usdc"] }, value: { type: "number" } } },
621
+ stop_loss: { type: "object", required: ["mode", "value"], properties: { mode: { type: "string", enum: ["percent", "usdc"] }, value: { type: "number" }, stop_type: { type: "string", enum: ["fixed"] } } }
622
+ }
623
+ }
614
624
  }
615
625
  }
616
626
  },
@@ -815,6 +825,15 @@ function marketOrderPayload(input) {
815
825
  if (estimatedPriceTimestampMs !== void 0) {
816
826
  payload.estimated_price_timestamp_ms = estimatedPriceTimestampMs;
817
827
  }
828
+ if (input.reduceOnly === true) {
829
+ payload.reduce_only = true;
830
+ }
831
+ if (input.protection !== void 0) {
832
+ if (input.protection === null || typeof input.protection !== "object" || Array.isArray(input.protection)) {
833
+ throw new Error("protection must be an object with take_profit and/or stop_loss");
834
+ }
835
+ payload.protection = input.protection;
836
+ }
818
837
  return payload;
819
838
  }
820
839
  function limitOrderPayload(input) {
@@ -6,7 +6,7 @@ import {
6
6
  appendFile,
7
7
  mkdir as mkdir2,
8
8
  readFile as readFile3,
9
- readdir,
9
+ readdir as readdir2,
10
10
  rename as rename2,
11
11
  rm as rm3
12
12
  } from "node:fs/promises";
@@ -16,7 +16,7 @@ import { fileURLToPath } from "node:url";
16
16
  // agent-cli-release.json
17
17
  var agent_cli_release_default = {
18
18
  package_name: "@vtxmacro/cli",
19
- package_version: "2026.9.69",
19
+ package_version: "2026.10.2",
20
20
  codex_package_name: "@openai/codex",
21
21
  codex_version: "0.155.0",
22
22
  copilot_sdk_package_name: "@github/copilot-sdk",
@@ -358,9 +358,9 @@ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, relative
358
358
  import { randomBytes } from "node:crypto";
359
359
  import { spawn } from "node:child_process";
360
360
  import { constants } from "node:fs";
361
- import { lstat, mkdir, open, readFile, realpath, rename, rm } from "node:fs/promises";
361
+ import { lstat, mkdir, open, readdir, readFile, realpath, rename, rm } from "node:fs/promises";
362
362
  import { homedir } from "node:os";
363
- import { dirname, isAbsolute, join, relative, resolve, win32 as windowsPath } from "node:path";
363
+ import { basename, dirname, isAbsolute, join, relative, resolve, win32 as windowsPath } from "node:path";
364
364
  var InferenceHostProcessLockBusyError = class extends Error {
365
365
  constructor() {
366
366
  super("Another inference host process already owns this local host.");
@@ -900,10 +900,22 @@ var defaultProcessIdentity = async (pid) => {
900
900
  if (process.platform === "darwin") return await darwinProcessIdentity(pid);
901
901
  return null;
902
902
  };
903
+ var cachedCurrentProcessIdentity = null;
903
904
  var defaultCurrentProcessIdentity = async () => {
904
- const identity = await defaultProcessIdentity(process.pid);
905
- if (!identity) throw new Error("Inference host could not establish its process-start identity.");
906
- return identity;
905
+ if (!cachedCurrentProcessIdentity) {
906
+ cachedCurrentProcessIdentity = (async () => {
907
+ const identity = await defaultProcessIdentity(process.pid);
908
+ if (!identity) throw new Error("Inference host could not establish its process-start identity.");
909
+ return identity;
910
+ })();
911
+ }
912
+ const pending = cachedCurrentProcessIdentity;
913
+ try {
914
+ return await pending;
915
+ } catch (error) {
916
+ if (cachedCurrentProcessIdentity === pending) cachedCurrentProcessIdentity = null;
917
+ throw error;
918
+ }
907
919
  };
908
920
  var processLockObservationCache = /* @__PURE__ */ new Map();
909
921
  var inferenceProcessIdentitiesMatch = (stored, observed) => stored === observed;
@@ -1052,150 +1064,305 @@ async function writeAtomicInferencePrivateFile(path, contents, dependencies = {}
1052
1064
  throw error;
1053
1065
  }
1054
1066
  }
1055
- async function acquireInferenceHostProcessLock(path, dependencies = {}) {
1056
- const directory = dirname(path);
1057
- await ensureInferencePrivateDirectory(directory);
1058
- const ownerToken = randomBytes(24).toString("base64url");
1059
- const observeIdentity = dependencies.processIdentity ?? defaultProcessIdentity;
1060
- const currentBootIdentity = dependencies.currentBootIdentity ?? (() => readInferenceSystemBootIdentity());
1061
- const isProcessAlive = dependencies.processAlive ?? ((pid) => {
1067
+ var PROCESS_LOCK_PUBLICATION_GRACE_MS = 1e4;
1068
+ var PROCESS_LOCK_MAX_RECORD_BYTES = 64 * 1024;
1069
+ var PROCESS_LOCK_GUARD_ELECTION_LEASE_MS = 5e3;
1070
+ var PROCESS_LOCK_GUARD_ELECTION_STALE_MS = 3e4;
1071
+ var PROCESS_LOCK_GUARD_ELECTION_ATTEMPTS = 8;
1072
+ var PROCESS_LOCK_GUARD_ELECTION_BACKOFF_MS = 10;
1073
+ var PROCESS_LOCK_LABEL = "Inference host process lock";
1074
+ var PROCESS_LOCK_GUARD_LABEL = "Inference host process lock reclamation guard";
1075
+ var sameProcessLockFile = (left, right) => left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs;
1076
+ var openExclusiveProcessLockFile = async (path) => {
1077
+ try {
1078
+ return await open(
1079
+ path,
1080
+ constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW,
1081
+ 384
1082
+ );
1083
+ } catch (error) {
1084
+ if (error.code === "EEXIST") return null;
1085
+ throw error;
1086
+ }
1087
+ };
1088
+ var inspectProcessLockFile = async (path, label, probes) => {
1089
+ let before;
1090
+ try {
1091
+ before = await lstat(path);
1092
+ } catch (error) {
1093
+ if (error.code === "ENOENT") return { state: "missing" };
1094
+ throw error;
1095
+ }
1096
+ if (before.isSymbolicLink() || !before.isFile() || process.platform !== "win32" && (before.mode & 63) !== 0 || typeof process.getuid === "function" && before.uid !== process.getuid()) {
1097
+ throw new Error(`${label} is unsafe.`);
1098
+ }
1099
+ let handle;
1100
+ try {
1101
+ await secureExistingWindowsPath(path, "file");
1102
+ handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
1103
+ } catch (error) {
1104
+ if (error.code === "ENOENT") return { state: "missing" };
1105
+ throw error;
1106
+ }
1107
+ let stale = false;
1108
+ try {
1109
+ const opened = await handle.stat();
1110
+ if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) {
1111
+ return { state: "busy" };
1112
+ }
1113
+ const raw = opened.size > PROCESS_LOCK_MAX_RECORD_BYTES ? "" : await handle.readFile("utf8");
1114
+ const stat = await handle.stat();
1115
+ if (!sameProcessLockFile(opened, stat)) return { state: "busy" };
1116
+ let record = null;
1062
1117
  try {
1063
- process.kill(pid, 0);
1064
- return true;
1118
+ const parsed = JSON.parse(raw);
1119
+ if (parsed && typeof parsed === "object") record = parsed;
1120
+ } catch {
1121
+ record = null;
1122
+ }
1123
+ if (!record || record.schema_version !== 2 || !Number.isSafeInteger(record.pid) || Number(record.pid) < 1 || typeof record.owner_token !== "string" || !record.owner_token || typeof record.process_identity !== "string" || !record.process_identity || typeof record.boot_identity !== "string" || !record.boot_identity) {
1124
+ if (Date.now() - stat.mtimeMs < PROCESS_LOCK_PUBLICATION_GRACE_MS) return { state: "busy" };
1125
+ stale = true;
1126
+ return { state: "stale", handle, stat };
1127
+ }
1128
+ const ownerPid = Number(record.pid);
1129
+ const storedIdentity = record.process_identity;
1130
+ const cacheKey = resolve(path);
1131
+ if (await probes.currentBootIdentity() === record.boot_identity && probes.isProcessAlive(ownerPid)) {
1132
+ const cached = processLockObservationCache.get(cacheKey);
1133
+ const observedIdentity = cached && cached.dev === stat.dev && cached.ino === stat.ino && cached.pid === ownerPid && cached.storedIdentity === storedIdentity && Date.now() - cached.observedAtMs < 5e3 ? cached.observedIdentity : await probes.observeIdentity(ownerPid);
1134
+ if (!observedIdentity) {
1135
+ throw new Error("Inference host could not verify the existing lock owner identity.");
1136
+ }
1137
+ processLockObservationCache.set(cacheKey, {
1138
+ dev: stat.dev,
1139
+ ino: stat.ino,
1140
+ pid: ownerPid,
1141
+ storedIdentity,
1142
+ observedIdentity,
1143
+ observedAtMs: Date.now()
1144
+ });
1145
+ if (inferenceProcessIdentitiesMatch(storedIdentity, observedIdentity)) {
1146
+ return { state: "busy" };
1147
+ }
1148
+ }
1149
+ stale = true;
1150
+ return { state: "stale", handle, stat };
1151
+ } finally {
1152
+ if (!stale) await handle.close().catch(() => void 0);
1153
+ }
1154
+ };
1155
+ var removeVerifiedStaleProcessLockFile = async (path, inspection, assertExclusive) => {
1156
+ try {
1157
+ await assertExclusive();
1158
+ let current;
1159
+ try {
1160
+ current = await lstat(path);
1065
1161
  } catch (error) {
1066
- if (error.code === "ESRCH") return false;
1162
+ if (error.code === "ENOENT") return;
1067
1163
  throw error;
1068
1164
  }
1069
- });
1070
- let handle;
1071
- for (let acquisition = 0; acquisition < 2; acquisition += 1) {
1165
+ const opened = await inspection.handle.stat();
1166
+ if (current.isSymbolicLink() || !sameProcessLockFile(current, opened) || !sameProcessLockFile(opened, inspection.stat)) {
1167
+ throw new InferenceHostProcessLockBusyError();
1168
+ }
1169
+ if (process.platform === "win32") await inspection.handle.close();
1072
1170
  try {
1073
- handle = await open(
1074
- path,
1075
- constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW,
1076
- 384
1077
- );
1078
- break;
1171
+ await rm(path);
1079
1172
  } catch (error) {
1080
- if (error.code !== "EEXIST") throw error;
1081
- if (acquisition > 0) {
1082
- throw new InferenceHostProcessLockBusyError();
1173
+ if (error.code !== "ENOENT") throw error;
1174
+ }
1175
+ } finally {
1176
+ await inspection.handle.close().catch(() => void 0);
1177
+ }
1178
+ processLockObservationCache.delete(resolve(path));
1179
+ invalidateWindowsAclCache(path);
1180
+ await syncInferenceDirectory(dirname(path));
1181
+ };
1182
+ var removeStaleProcessLockReclaimGuard = async (lockPath, guardPath, inspection) => {
1183
+ const directory = dirname(lockPath);
1184
+ const prefix = `${basename(lockPath)}.reclaim-ballot.`;
1185
+ try {
1186
+ for (let attempt = 0; attempt < PROCESS_LOCK_GUARD_ELECTION_ATTEMPTS; attempt += 1) {
1187
+ if (attempt > 0) {
1188
+ await new Promise((resolvePromise) => setTimeout(
1189
+ resolvePromise,
1190
+ Math.floor(Math.random() * PROCESS_LOCK_GUARD_ELECTION_BACKOFF_MS * 2 ** Math.min(attempt, 4))
1191
+ ));
1192
+ let current;
1193
+ try {
1194
+ current = await lstat(guardPath);
1195
+ } catch (error) {
1196
+ if (error.code === "ENOENT") return;
1197
+ throw error;
1198
+ }
1199
+ if (!sameProcessLockFile(current, inspection.stat)) return;
1083
1200
  }
1084
- let before;
1201
+ const ballotName = `${prefix}${randomBytes(16).toString("hex")}`;
1202
+ const ballotPath = join(directory, ballotName);
1203
+ const ballot = await openExclusiveProcessLockFile(ballotPath);
1204
+ if (!ballot) continue;
1205
+ await ballot.close();
1206
+ const electedAt = performance.now();
1085
1207
  try {
1086
- before = await lstat(path);
1087
- } catch (error2) {
1088
- if (error2.code === "ENOENT") continue;
1089
- throw error2;
1208
+ let contested = false;
1209
+ for (const name of await readdir(directory)) {
1210
+ if (!name.startsWith(prefix) || name === ballotName) continue;
1211
+ const otherPath = join(directory, name);
1212
+ let other;
1213
+ try {
1214
+ other = await lstat(otherPath);
1215
+ } catch (error) {
1216
+ if (error.code === "ENOENT") continue;
1217
+ throw error;
1218
+ }
1219
+ if (!other.isDirectory() && Date.now() - other.mtimeMs > PROCESS_LOCK_GUARD_ELECTION_STALE_MS) {
1220
+ await rm(otherPath, { force: true });
1221
+ continue;
1222
+ }
1223
+ contested = true;
1224
+ }
1225
+ if (contested) continue;
1226
+ await removeVerifiedStaleProcessLockFile(guardPath, inspection, async () => {
1227
+ if (performance.now() - electedAt > PROCESS_LOCK_GUARD_ELECTION_LEASE_MS) {
1228
+ throw new InferenceHostProcessLockBusyError();
1229
+ }
1230
+ });
1231
+ return;
1232
+ } finally {
1233
+ await rm(ballotPath, { force: true }).catch(() => void 0);
1090
1234
  }
1091
- if (before.isSymbolicLink() || !before.isFile() || process.platform !== "win32" && (before.mode & 63) !== 0 || typeof process.getuid === "function" && before.uid !== process.getuid()) {
1092
- throw new Error("Inference host process lock is unsafe.");
1235
+ }
1236
+ throw new InferenceHostProcessLockBusyError();
1237
+ } finally {
1238
+ await inspection.handle.close().catch(() => void 0);
1239
+ }
1240
+ };
1241
+ var acquireProcessLockReclaimGuard = async (lockPath, ownerRecord, probes) => {
1242
+ const guardPath = `${lockPath}.reclaim`;
1243
+ const guardToken = randomBytes(24).toString("base64url");
1244
+ for (let attempt = 0; attempt < 2; attempt += 1) {
1245
+ const handle = await openExclusiveProcessLockFile(guardPath);
1246
+ if (handle) {
1247
+ try {
1248
+ await handle.writeFile(ownerRecord(guardToken), "utf8");
1249
+ } catch (error) {
1250
+ await handle.close().catch(() => void 0);
1251
+ await rm(guardPath, { force: true }).catch(() => void 0);
1252
+ throw error;
1093
1253
  }
1094
- const reclaimPublicationResidue = async () => {
1095
- let current2;
1254
+ await handle.close();
1255
+ await secureWindowsPrivatePath(guardPath, "file", "harden");
1256
+ const isHeld = async () => {
1257
+ let reader;
1096
1258
  try {
1097
- current2 = await lstat(path);
1098
- } catch (error2) {
1099
- if (error2.code === "ENOENT") return false;
1100
- throw error2;
1101
- }
1102
- if (current2.isSymbolicLink() || !current2.isFile() || current2.dev !== before.dev || current2.ino !== before.ino || process.platform !== "win32" && (current2.mode & 63) !== 0 || typeof process.getuid === "function" && current2.uid !== process.getuid()) {
1103
- throw new Error("Inference host process lock publication residue changed or is unsafe.");
1259
+ reader = await open(guardPath, constants.O_RDONLY | constants.O_NOFOLLOW);
1260
+ } catch (error) {
1261
+ if (error.code === "ENOENT") return false;
1262
+ throw error;
1104
1263
  }
1105
1264
  try {
1106
- await rm(path);
1107
- } catch (error2) {
1108
- if (error2.code === "ENOENT") return false;
1109
- throw error2;
1265
+ const [opened, current] = await Promise.all([reader.stat(), lstat(guardPath)]);
1266
+ if (current.isSymbolicLink() || current.dev !== opened.dev || current.ino !== opened.ino) {
1267
+ return false;
1268
+ }
1269
+ const parsed = JSON.parse(await reader.readFile("utf8"));
1270
+ return parsed.owner_token === guardToken;
1271
+ } catch {
1272
+ return false;
1273
+ } finally {
1274
+ await reader.close().catch(() => void 0);
1275
+ }
1276
+ };
1277
+ return {
1278
+ assertHeld: async () => {
1279
+ if (!await isHeld()) throw new InferenceHostProcessLockBusyError();
1280
+ },
1281
+ // Best effort: a guard left behind carries this live process identity
1282
+ // and becomes reclaimable once this process exits.
1283
+ release: async () => {
1284
+ try {
1285
+ if (!await isHeld()) return;
1286
+ await rm(guardPath);
1287
+ processLockObservationCache.delete(resolve(guardPath));
1288
+ invalidateWindowsAclCache(guardPath);
1289
+ } catch {
1290
+ }
1110
1291
  }
1111
- processLockObservationCache.delete(resolve(path));
1112
- await syncInferenceDirectory(directory);
1113
- return true;
1114
1292
  };
1293
+ }
1294
+ if (attempt > 0) break;
1295
+ const inspection = await inspectProcessLockFile(guardPath, PROCESS_LOCK_GUARD_LABEL, probes);
1296
+ if (inspection.state === "busy") break;
1297
+ if (inspection.state === "stale") {
1298
+ await removeStaleProcessLockReclaimGuard(lockPath, guardPath, inspection);
1299
+ }
1300
+ }
1301
+ throw new InferenceHostProcessLockBusyError();
1302
+ };
1303
+ async function acquireInferenceHostProcessLock(path, dependencies = {}) {
1304
+ const directory = dirname(path);
1305
+ await ensureInferencePrivateDirectory(directory);
1306
+ const ownerToken = randomBytes(24).toString("base64url");
1307
+ const probes = {
1308
+ currentBootIdentity: dependencies.currentBootIdentity ?? (() => readInferenceSystemBootIdentity()),
1309
+ observeIdentity: dependencies.processIdentity ?? defaultProcessIdentity,
1310
+ isProcessAlive: dependencies.processAlive ?? ((pid) => {
1115
1311
  try {
1116
- await secureExistingWindowsPath(path, "file");
1117
- } catch (error2) {
1118
- if (error2.code === "ENOENT") continue;
1119
- throw error2;
1312
+ process.kill(pid, 0);
1313
+ return true;
1314
+ } catch (error) {
1315
+ if (error.code === "ESRCH") return false;
1316
+ throw error;
1120
1317
  }
1121
- let staleOwner;
1318
+ })
1319
+ };
1320
+ const [ownerIdentity, bootIdentity] = await Promise.all([
1321
+ (dependencies.currentProcessIdentity ?? defaultCurrentProcessIdentity)(),
1322
+ probes.currentBootIdentity()
1323
+ ]);
1324
+ const ownerRecord = (token) => `${JSON.stringify({
1325
+ schema_version: 2,
1326
+ pid: process.pid,
1327
+ owner_token: token,
1328
+ process_identity: ownerIdentity,
1329
+ boot_identity: bootIdentity
1330
+ })}
1331
+ `;
1332
+ let handle = await openExclusiveProcessLockFile(path);
1333
+ if (!handle) {
1334
+ const inspection = await inspectProcessLockFile(path, PROCESS_LOCK_LABEL, probes);
1335
+ if (inspection.state === "busy") throw new InferenceHostProcessLockBusyError();
1336
+ if (inspection.state === "stale") {
1337
+ await inspection.handle.close();
1338
+ const guard = await acquireProcessLockReclaimGuard(path, ownerRecord, probes);
1122
1339
  try {
1123
- staleOwner = JSON.parse(await readFile(path, "utf8"));
1124
- } catch (error2) {
1125
- if (error2.code === "ENOENT") continue;
1126
- if (Date.now() - before.mtimeMs < 1e4) {
1127
- throw new InferenceHostProcessLockBusyError();
1128
- }
1129
- await reclaimPublicationResidue();
1130
- continue;
1131
- }
1132
- if (staleOwner.schema_version !== 2 || !Number.isSafeInteger(staleOwner.pid) || Number(staleOwner.pid) < 1 || typeof staleOwner.owner_token !== "string" || !staleOwner.owner_token || typeof staleOwner.process_identity !== "string" || !staleOwner.process_identity || typeof staleOwner.boot_identity !== "string" || !staleOwner.boot_identity) {
1133
- if (Date.now() - before.mtimeMs < 1e4) {
1134
- throw new InferenceHostProcessLockBusyError();
1135
- }
1136
- await reclaimPublicationResidue();
1137
- continue;
1138
- }
1139
- const stalePid = Number(staleOwner.pid);
1140
- const bootIdentity2 = await currentBootIdentity();
1141
- if (bootIdentity2 === staleOwner.boot_identity && isProcessAlive(stalePid)) {
1142
- const cached = processLockObservationCache.get(resolve(path));
1143
- const observedIdentity = cached && cached.dev === before.dev && cached.ino === before.ino && cached.pid === stalePid && cached.storedIdentity === staleOwner.process_identity && Date.now() - cached.observedAtMs < 5e3 ? cached.observedIdentity : await observeIdentity(stalePid);
1144
- if (!observedIdentity) {
1145
- throw new Error("Inference host could not verify the existing lock owner identity.");
1146
- }
1147
- processLockObservationCache.set(resolve(path), {
1148
- dev: before.dev,
1149
- ino: before.ino,
1150
- pid: stalePid,
1151
- storedIdentity: staleOwner.process_identity,
1152
- observedIdentity,
1153
- observedAtMs: Date.now()
1154
- });
1155
- if (inferenceProcessIdentitiesMatch(staleOwner.process_identity, observedIdentity)) {
1156
- throw new InferenceHostProcessLockBusyError();
1340
+ const guarded = await inspectProcessLockFile(path, PROCESS_LOCK_LABEL, probes);
1341
+ if (guarded.state === "busy") throw new InferenceHostProcessLockBusyError();
1342
+ if (guarded.state === "stale") {
1343
+ await removeVerifiedStaleProcessLockFile(path, guarded, guard.assertHeld);
1157
1344
  }
1345
+ handle = await openExclusiveProcessLockFile(path);
1346
+ } finally {
1347
+ await guard.release();
1158
1348
  }
1159
- let current;
1160
- try {
1161
- current = await lstat(path);
1162
- } catch (error2) {
1163
- if (error2.code === "ENOENT") continue;
1164
- throw error2;
1165
- }
1166
- if (current.dev !== before.dev || current.ino !== before.ino) {
1167
- throw new Error("Inference host process lock ownership changed.");
1168
- }
1169
- await rm(path);
1170
- processLockObservationCache.delete(resolve(path));
1171
- await syncInferenceDirectory(directory);
1349
+ } else {
1350
+ handle = await openExclusiveProcessLockFile(path);
1172
1351
  }
1352
+ if (!handle) throw new InferenceHostProcessLockBusyError();
1173
1353
  }
1174
- if (!handle) throw new Error("Inference host process lock could not be acquired.");
1175
- let ownerIdentity;
1176
- let bootIdentity;
1177
1354
  try {
1178
- [ownerIdentity, bootIdentity] = await Promise.all([
1179
- (dependencies.currentProcessIdentity ?? defaultCurrentProcessIdentity)(),
1180
- currentBootIdentity()
1181
- ]);
1355
+ await handle.writeFile(ownerRecord(ownerToken), "utf8");
1356
+ await handle.sync();
1182
1357
  } catch (error) {
1358
+ const created = await handle.stat().catch(() => null);
1183
1359
  await handle.close().catch(() => void 0);
1184
- await rm(path, { force: true }).catch(() => void 0);
1360
+ const current = await lstat(path).catch(() => null);
1361
+ if (created && current && current.dev === created.dev && current.ino === created.ino) {
1362
+ await rm(path, { force: true }).catch(() => void 0);
1363
+ }
1185
1364
  throw error;
1186
1365
  }
1187
- await handle.writeFile(
1188
- `${JSON.stringify({
1189
- schema_version: 2,
1190
- pid: process.pid,
1191
- owner_token: ownerToken,
1192
- process_identity: ownerIdentity,
1193
- boot_identity: bootIdentity
1194
- })}
1195
- `,
1196
- "utf8"
1197
- );
1198
- await handle.sync();
1199
1366
  await handle.close();
1200
1367
  await secureWindowsPrivatePath(path, "file", "harden");
1201
1368
  await syncInferenceDirectory(directory);
@@ -1513,7 +1680,7 @@ var verifyInstalledRelease = async (prefix, targetVersion, executable, platform,
1513
1680
  return script;
1514
1681
  };
1515
1682
  var cleanupInterruptedReleaseStaging = async (releasesPath) => {
1516
- const entries = await readdir(releasesPath, { withFileTypes: true });
1683
+ const entries = await readdir2(releasesPath, { withFileTypes: true });
1517
1684
  for (const entry of entries) {
1518
1685
  if (entry.isDirectory() && /^\.(?:staging|download)-\d{4}\.\d{1,2}\.\d{1,3}-[0-9a-f-]{36}$/u.test(entry.name)) {
1519
1686
  await rm3(resolve3(releasesPath, entry.name), { recursive: true, force: true });
@@ -1680,7 +1847,7 @@ var prunePrivateReleases = async (manifestPath, keep) => {
1680
1847
  );
1681
1848
  let entries;
1682
1849
  try {
1683
- entries = await readdir(releasesPath, { withFileTypes: true });
1850
+ entries = await readdir2(releasesPath, { withFileTypes: true });
1684
1851
  } catch (error) {
1685
1852
  if (error.code === "ENOENT") return;
1686
1853
  throw error;