@lousy-agents/mcp 5.17.7 → 5.17.9

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.
Files changed (2) hide show
  1. package/dist/mcp-server.js +421 -151
  2. package/package.json +1 -1
@@ -6559,7 +6559,7 @@ function escapeJsonPtr(str) {
6559
6559
 
6560
6560
 
6561
6561
  },
6562
- 6887(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
6562
+ 3503(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
6563
6563
  // NAMESPACE OBJECT: ../../node_modules/micromark/lib/constructs.js
6564
6564
  var constructs_namespaceObject = {};
6565
6565
  __webpack_require__.r(constructs_namespaceObject);
@@ -27264,15 +27264,11 @@ var external_node_child_process_ = __webpack_require__(1421);
27264
27264
  const PINNED_PYTHON_WORKER_SOURCE = String.raw `
27265
27265
  import base64, errno, json, os, secrets, stat, sys
27266
27266
  DIR_FLAGS = os.O_RDONLY
27267
- if hasattr(os, "O_DIRECTORY"):
27268
- DIR_FLAGS |= os.O_DIRECTORY
27269
- if hasattr(os, "O_NOFOLLOW"):
27270
- DIR_FLAGS |= os.O_NOFOLLOW
27267
+ if hasattr(os, "O_DIRECTORY"): DIR_FLAGS |= os.O_DIRECTORY
27268
+ if hasattr(os, "O_NOFOLLOW"): DIR_FLAGS |= os.O_NOFOLLOW
27271
27269
  READ_FLAGS = os.O_RDONLY
27272
- if hasattr(os, "O_NONBLOCK"):
27273
- READ_FLAGS |= os.O_NONBLOCK
27274
- if hasattr(os, "O_NOFOLLOW"):
27275
- READ_FLAGS |= os.O_NOFOLLOW
27270
+ if hasattr(os, "O_NONBLOCK"): READ_FLAGS |= os.O_NONBLOCK
27271
+ if hasattr(os, "O_NOFOLLOW"): READ_FLAGS |= os.O_NOFOLLOW
27276
27272
  WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
27277
27273
  if hasattr(os, "O_NOFOLLOW"):
27278
27274
  WRITE_FLAGS |= os.O_NOFOLLOW
@@ -27352,6 +27348,10 @@ def write_all(fd, data):
27352
27348
  if written <= 0:
27353
27349
  raise OSError(errno.EIO, "short write")
27354
27350
  view = view[written:]
27351
+ def fsync_best_effort(fd):
27352
+ try: os.fsync(fd)
27353
+ except OSError as error:
27354
+ if error.errno != errno.EPERM: raise
27355
27355
  def link_unsupported(exc):
27356
27356
  unsupported = (errno.EPERM, errno.EOPNOTSUPP, getattr(errno, "ENOTSUP", errno.EOPNOTSUPP))
27357
27357
  return getattr(exc, "errno", None) in unsupported
@@ -27557,13 +27557,13 @@ def write_path(root_fd, payload):
27557
27557
  temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
27558
27558
  os.fchmod(temp_fd, mode)
27559
27559
  write_all(temp_fd, data)
27560
- os.fsync(temp_fd)
27560
+ fsync_best_effort(temp_fd)
27561
27561
  temp_stat = os.fstat(temp_fd)
27562
27562
  os.close(temp_fd)
27563
27563
  temp_fd = None
27564
27564
  result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
27565
27565
  temp_name = None
27566
- os.fsync(parent_fd)
27566
+ fsync_best_effort(parent_fd)
27567
27567
  return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
27568
27568
  finally:
27569
27569
  if temp_fd is not None:
@@ -27990,29 +27990,34 @@ async function runPinnedPathHelper(params) {
27990
27990
  }
27991
27991
  }
27992
27992
 
27993
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
27994
-
27993
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
27995
27994
 
27996
27995
 
27997
27996
 
27998
- const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
27999
- function getGlobalManagers() {
28000
- const globalWithState = globalThis;
28001
- if (!globalWithState[GLOBAL_STATE_KEY]) {
28002
- globalWithState[GLOBAL_STATE_KEY] = new Map();
27997
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
27998
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS = SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES * 8;
27999
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX = "\t".repeat(8);
28000
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN = new RegExp(`\\n(${SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX}[ \\t]{${SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS}})\\n$`);
28001
+ function createSidecarLockOwnershipToken() {
28002
+ let token = SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX;
28003
+ for (const byte of (0,external_node_crypto_.randomBytes)(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
28004
+ for (let bit = 7; bit >= 0; bit -= 1) {
28005
+ token += byte & (1 << bit) ? "\t" : " ";
28006
+ }
28003
28007
  }
28004
- return globalWithState[GLOBAL_STATE_KEY];
28008
+ return token;
28005
28009
  }
28006
- function resolveManagerState(key) {
28007
- const managers = getGlobalManagers();
28008
- let state = managers.get(key);
28009
- if (!state) {
28010
- state = { cleanupRegistered: false, held: new Map() };
28011
- managers.set(key, state);
28012
- }
28013
- return state;
28010
+ function readSidecarLockOwnershipToken(raw) {
28011
+ return SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN.exec(raw)?.[1];
28012
+ }
28013
+ function serializeSidecarLockPayload(payload) {
28014
+ const ownershipToken = createSidecarLockOwnershipToken();
28015
+ return {
28016
+ raw: `${JSON.stringify(payload, null, 2)}\n${ownershipToken}\n`,
28017
+ ownershipToken,
28018
+ };
28014
28019
  }
28015
- async function readLockSnapshot(lockPath) {
28020
+ async function readSidecarLockSnapshot(lockPath) {
28016
28021
  try {
28017
28022
  const stat = await promises_.lstat(lockPath);
28018
28023
  const raw = await promises_.readFile(lockPath, "utf8");
@@ -28034,7 +28039,15 @@ async function readLockSnapshot(lockPath) {
28034
28039
  throw err;
28035
28040
  }
28036
28041
  }
28037
- function snapshotMatches(current, observed) {
28042
+ function sidecarLockSnapshotMatches(current, observed) {
28043
+ if (observed.ownershipToken !== undefined) {
28044
+ return (current.stat?.isFile() === true &&
28045
+ current.raw !== undefined &&
28046
+ observed.raw !== undefined &&
28047
+ readSidecarLockOwnershipToken(current.raw) === observed.ownershipToken &&
28048
+ readSidecarLockOwnershipToken(observed.raw) === observed.ownershipToken &&
28049
+ current.raw === observed.raw);
28050
+ }
28038
28051
  if (observed.stat && current.stat && !file_identity_sameFileIdentity(observed.stat, current.stat)) {
28039
28052
  return false;
28040
28053
  }
@@ -28043,30 +28056,54 @@ function snapshotMatches(current, observed) {
28043
28056
  }
28044
28057
  return observed.stat !== undefined && current.stat !== undefined;
28045
28058
  }
28046
- async function removeLockIfUnchanged(lockPath, observed) {
28047
- const current = await readLockSnapshot(lockPath);
28048
- if (!current || !observed) {
28049
- return false;
28050
- }
28051
- if (!snapshotMatches(current, observed)) {
28052
- // The lock changed after we decided it was stale. Leave the fresh holder's
28053
- // file alone; deleting by path here would break mutual exclusion.
28059
+ async function removeSidecarLockIfUnchanged(lockPath, observed) {
28060
+ const current = await readSidecarLockSnapshot(lockPath);
28061
+ if (!current || !observed || !sidecarLockSnapshotMatches(current, observed)) {
28054
28062
  return false;
28055
28063
  }
28056
28064
  await promises_.rm(lockPath, { force: true }).catch(() => undefined);
28057
28065
  return true;
28058
28066
  }
28059
- async function lockSnapshotStillPresent(lockPath, observed) {
28060
- const current = await readLockSnapshot(lockPath);
28061
- return !!current && !!observed && snapshotMatches(current, observed);
28067
+ async function sidecarLockSnapshotStillPresent(lockPath, observed) {
28068
+ const current = await readSidecarLockSnapshot(lockPath);
28069
+ return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
28062
28070
  }
28063
- async function removeStaleLockIfAllowed(params) {
28064
- if (!params.shouldRemoveStaleLock) {
28065
- return "not-approved";
28071
+ async function sidecarReclaimGuardExists(pathname) {
28072
+ try {
28073
+ await promises_.lstat(pathname);
28074
+ return true;
28066
28075
  }
28067
- if (params.snapshot.raw === undefined) {
28076
+ catch (err) {
28077
+ if (err.code === "ENOENT") {
28078
+ return false;
28079
+ }
28080
+ throw err;
28081
+ }
28082
+ }
28083
+ async function tryAcquireSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
28084
+ try {
28085
+ await promises_.mkdir(reclaimGuardPath);
28086
+ reclaimGuards.add(reclaimGuardPath);
28087
+ return true;
28088
+ }
28089
+ catch (err) {
28090
+ if (err.code === "EEXIST") {
28091
+ return false;
28092
+ }
28093
+ throw err;
28094
+ }
28095
+ }
28096
+ async function releaseSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
28097
+ await promises_.rmdir(reclaimGuardPath);
28098
+ reclaimGuards.delete(reclaimGuardPath);
28099
+ }
28100
+ async function removeStaleSidecarLockIfAllowed(params) {
28101
+ if (!params.shouldRemoveStaleLock || params.snapshot.raw === undefined) {
28068
28102
  return "not-approved";
28069
28103
  }
28104
+ if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot))) {
28105
+ return "changed";
28106
+ }
28070
28107
  if (!(await params.shouldRemoveStaleLock({
28071
28108
  lockPath: params.lockPath,
28072
28109
  normalizedTargetPath: params.normalizedTargetPath,
@@ -28075,32 +28112,95 @@ async function removeStaleLockIfAllowed(params) {
28075
28112
  }))) {
28076
28113
  return "not-approved";
28077
28114
  }
28078
- const current = await readLockSnapshot(params.lockPath);
28079
- if (!current || !snapshotMatches(current, params.snapshot)) {
28115
+ if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot))) {
28080
28116
  return "changed";
28081
28117
  }
28082
28118
  try {
28083
- await promises_.rm(params.lockPath, { force: true });
28119
+ await promises_.rm(params.lockPath);
28120
+ return "removed";
28084
28121
  }
28085
28122
  catch (err) {
28086
28123
  if (err.code === "ENOENT") {
28087
28124
  return "changed";
28088
28125
  }
28089
- return "not-approved";
28126
+ throw err;
28127
+ }
28128
+ }
28129
+
28130
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
28131
+
28132
+
28133
+
28134
+
28135
+
28136
+ const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
28137
+ function getGlobalManagers() {
28138
+ const globalWithState = globalThis;
28139
+ if (!globalWithState[GLOBAL_STATE_KEY]) {
28140
+ globalWithState[GLOBAL_STATE_KEY] = new Map();
28141
+ }
28142
+ return globalWithState[GLOBAL_STATE_KEY];
28143
+ }
28144
+ function resolveManagerState(key) {
28145
+ const managers = getGlobalManagers();
28146
+ let state = managers.get(key);
28147
+ if (!state) {
28148
+ state = {
28149
+ cleanupRegistered: false,
28150
+ held: new Map(),
28151
+ reclaimCleanupRegistered: false,
28152
+ reclaimGuards: new Set(),
28153
+ };
28154
+ managers.set(key, state);
28155
+ }
28156
+ else {
28157
+ // The global manager symbol is shared across package copies and hot reloads.
28158
+ // Backfill state created by fs-safe versions that predate reclaim guards.
28159
+ state.reclaimCleanupRegistered ??= false;
28160
+ state.reclaimGuards ??= new Set();
28090
28161
  }
28091
- return "removed";
28162
+ return state;
28092
28163
  }
28093
28164
  function snapshotMatchesSync(lockPath, observed) {
28165
+ let fd;
28094
28166
  try {
28095
- const stat = external_node_fs_.lstatSync(lockPath);
28096
- if (observed.stat && !file_identity_sameFileIdentity(observed.stat, stat)) {
28167
+ const beforeStat = external_node_fs_.lstatSync(lockPath);
28168
+ if (!beforeStat.isFile()) {
28169
+ return false;
28170
+ }
28171
+ const openFlags = external_node_fs_.constants.O_RDONLY |
28172
+ (process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
28173
+ ? external_node_fs_.constants.O_NOFOLLOW
28174
+ : 0) |
28175
+ (typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0);
28176
+ fd = external_node_fs_.openSync(lockPath, openFlags);
28177
+ const openedStat = external_node_fs_.fstatSync(fd);
28178
+ if (!openedStat.isFile()) {
28097
28179
  return false;
28098
28180
  }
28099
- return observed.raw === undefined || external_node_fs_.readFileSync(lockPath, "utf8") === observed.raw;
28181
+ if (observed.raw !== undefined && openedStat.size !== Buffer.byteLength(observed.raw)) {
28182
+ return false;
28183
+ }
28184
+ const raw = external_node_fs_.readFileSync(fd, "utf8");
28185
+ const afterStat = external_node_fs_.lstatSync(lockPath);
28186
+ if (!afterStat.isFile() || !file_identity_sameFileIdentity(beforeStat, afterStat)) {
28187
+ return false;
28188
+ }
28189
+ return sidecarLockSnapshotMatches({ raw, payload: null, stat: afterStat }, observed);
28100
28190
  }
28101
28191
  catch {
28102
28192
  return false;
28103
28193
  }
28194
+ finally {
28195
+ if (fd !== undefined) {
28196
+ try {
28197
+ external_node_fs_.closeSync(fd);
28198
+ }
28199
+ catch {
28200
+ // Best-effort process-exit cleanup.
28201
+ }
28202
+ }
28203
+ }
28104
28204
  }
28105
28205
  async function resolveNormalizedTargetPath(targetPath) {
28106
28206
  const resolved = external_node_path_.resolve(targetPath);
@@ -28135,6 +28235,17 @@ async function defaultShouldReclaim(params) {
28135
28235
  return true;
28136
28236
  }
28137
28237
  }
28238
+ function releaseAllReclaimGuardsSync(state) {
28239
+ for (const reclaimGuardPath of state.reclaimGuards) {
28240
+ try {
28241
+ external_node_fs_.rmdirSync(reclaimGuardPath);
28242
+ state.reclaimGuards.delete(reclaimGuardPath);
28243
+ }
28244
+ catch {
28245
+ // Best-effort process-exit cleanup. A surviving guard fails closed.
28246
+ }
28247
+ }
28248
+ }
28138
28249
  function releaseAllLocksSync(state) {
28139
28250
  for (const [normalizedTargetPath, held] of state.held) {
28140
28251
  void held.handle.close().catch(() => undefined);
@@ -28148,6 +28259,7 @@ function releaseAllLocksSync(state) {
28148
28259
  }
28149
28260
  state.held.delete(normalizedTargetPath);
28150
28261
  }
28262
+ releaseAllReclaimGuardsSync(state);
28151
28263
  }
28152
28264
  async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
28153
28265
  const current = state.held.get(normalizedTargetPath);
@@ -28170,7 +28282,7 @@ async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
28170
28282
  state.held.delete(normalizedTargetPath);
28171
28283
  held.releasePromise = (async () => {
28172
28284
  await held.handle.close().catch(() => undefined);
28173
- await removeLockIfUnchanged(held.lockPath, held.snapshot);
28285
+ await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot);
28174
28286
  })();
28175
28287
  try {
28176
28288
  await held.releasePromise;
@@ -28183,11 +28295,16 @@ async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
28183
28295
  function createSidecarLockManager(key) {
28184
28296
  const state = resolveManagerState(key);
28185
28297
  function ensureExitCleanupRegistered() {
28186
- if (state.cleanupRegistered) {
28298
+ if (!state.cleanupRegistered) {
28299
+ state.cleanupRegistered = true;
28300
+ state.reclaimCleanupRegistered = true;
28301
+ process.on("exit", () => releaseAllLocksSync(state));
28187
28302
  return;
28188
28303
  }
28189
- state.cleanupRegistered = true;
28190
- process.on("exit", () => releaseAllLocksSync(state));
28304
+ if (!state.reclaimCleanupRegistered) {
28305
+ state.reclaimCleanupRegistered = true;
28306
+ process.on("exit", () => releaseAllReclaimGuardsSync(state));
28307
+ }
28191
28308
  }
28192
28309
  async function acquire(options) {
28193
28310
  ensureExitCleanupRegistered();
@@ -28207,108 +28324,146 @@ function createSidecarLockManager(key) {
28207
28324
  const startedAt = Date.now();
28208
28325
  const retry = options.retry ?? {};
28209
28326
  const maxRetries = options.timeoutMs === Number.POSITIVE_INFINITY ? undefined : retry.retries;
28327
+ const reclaimGuardPath = `${lockPath}.reclaim`;
28328
+ let ownsReclaimGuard = false;
28210
28329
  let attempt = 0;
28211
- while (true) {
28212
- let handle = null;
28213
- try {
28214
- handle = await promises_.open(lockPath, "wx");
28215
- const payload = await options.payload();
28216
- const raw = `${JSON.stringify(payload, null, 2)}\n`;
28217
- await handle.writeFile(raw, "utf8");
28218
- const snapshot = { raw, payload, stat: await handle.stat() };
28219
- const createdHeld = {
28220
- count: 1,
28221
- handle,
28222
- lockPath,
28223
- snapshot,
28224
- acquiredAt: Date.now(),
28225
- metadata: options.metadata ?? {},
28226
- };
28227
- state.held.set(normalizedTargetPath, createdHeld);
28228
- const release = () => releaseHeldLock(state, normalizedTargetPath, createdHeld).then(() => undefined);
28229
- return {
28330
+ const waitForRetry = async () => {
28331
+ const elapsed = Date.now() - startedAt;
28332
+ if ((options.timeoutMs !== undefined &&
28333
+ options.timeoutMs !== Number.POSITIVE_INFINITY &&
28334
+ elapsed >= options.timeoutMs) ||
28335
+ (maxRetries !== undefined && attempt >= maxRetries)) {
28336
+ throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
28337
+ code: "file_lock_timeout",
28230
28338
  lockPath,
28231
28339
  normalizedTargetPath,
28232
- release,
28233
- [Symbol.asyncDispose]: release,
28234
- };
28340
+ });
28235
28341
  }
28236
- catch (err) {
28237
- if (handle) {
28238
- const failedSnapshot = { payload: null };
28239
- try {
28240
- failedSnapshot.stat = await handle.stat();
28241
- }
28242
- catch {
28243
- // Best-effort cleanup of a failed exclusive create.
28244
- }
28245
- const current = state.held.get(normalizedTargetPath);
28246
- if (current?.handle === handle) {
28247
- state.held.delete(normalizedTargetPath);
28248
- }
28249
- // If payload serialization/write fails, the file may be empty or
28250
- // partial JSON, so remove while our exclusive handle is still open.
28251
- await promises_.rm(lockPath, { force: true }).catch(() => undefined);
28252
- await handle.close().catch(() => undefined);
28253
- // Windows can refuse removing an open file; retry after close but
28254
- // only if the path still points at the file identity we created.
28255
- await removeLockIfUnchanged(lockPath, failedSnapshot);
28256
- }
28257
- if (err.code !== "EEXIST") {
28258
- throw err;
28259
- }
28260
- const nowMs = Date.now();
28261
- const snapshot = await readLockSnapshot(lockPath);
28262
- if (!snapshot) {
28342
+ const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
28343
+ ? Number.POSITIVE_INFINITY
28344
+ : Math.max(0, options.timeoutMs - elapsed);
28345
+ const delay = Math.min(computeDelayMs(retry, attempt), remaining);
28346
+ attempt += 1;
28347
+ await new Promise((resolve) => setTimeout(resolve, delay));
28348
+ };
28349
+ try {
28350
+ while (true) {
28351
+ if (!ownsReclaimGuard && (await sidecarReclaimGuardExists(reclaimGuardPath))) {
28352
+ await waitForRetry();
28263
28353
  continue;
28264
28354
  }
28265
- const shouldReclaim = options.shouldReclaim ?? defaultShouldReclaim;
28266
- if (await shouldReclaim({
28267
- lockPath,
28268
- normalizedTargetPath,
28269
- payload: snapshot?.payload ?? null,
28270
- staleMs: options.staleMs,
28271
- nowMs,
28272
- heldByThisProcess: state.held.has(normalizedTargetPath),
28273
- })) {
28274
- if (!(await lockSnapshotStillPresent(lockPath, snapshot))) {
28275
- continue;
28276
- }
28277
- const staleRecovery = options.staleRecovery ?? "fail-closed";
28278
- if (staleRecovery === "remove-if-unchanged") {
28279
- const removal = await removeStaleLockIfAllowed({
28280
- lockPath,
28281
- normalizedTargetPath,
28282
- snapshot,
28283
- shouldRemoveStaleLock: options.shouldRemoveStaleLock,
28284
- });
28285
- if (removal === "removed" || removal === "changed") {
28286
- continue;
28355
+ let handle = null;
28356
+ try {
28357
+ handle = await promises_.open(lockPath, "wx");
28358
+ const payload = await options.payload();
28359
+ const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
28360
+ await handle.writeFile(raw, "utf8");
28361
+ const snapshot = { raw, payload, stat: await handle.stat(), ownershipToken };
28362
+ const createdHeld = {
28363
+ count: 1,
28364
+ handle,
28365
+ lockPath,
28366
+ snapshot,
28367
+ acquiredAt: Date.now(),
28368
+ metadata: options.metadata ?? {},
28369
+ };
28370
+ state.held.set(normalizedTargetPath, createdHeld);
28371
+ if (ownsReclaimGuard) {
28372
+ try {
28373
+ await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath);
28374
+ ownsReclaimGuard = false;
28375
+ }
28376
+ catch (err) {
28377
+ await releaseHeldLock(state, normalizedTargetPath, createdHeld, { force: true });
28378
+ throw err;
28287
28379
  }
28288
28380
  }
28289
- throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
28290
- code: "file_lock_stale",
28381
+ const release = () => releaseHeldLock(state, normalizedTargetPath, createdHeld).then(() => undefined);
28382
+ return {
28291
28383
  lockPath,
28292
28384
  normalizedTargetPath,
28293
- });
28385
+ release,
28386
+ [Symbol.asyncDispose]: release,
28387
+ };
28294
28388
  }
28295
- const elapsed = Date.now() - startedAt;
28296
- if ((options.timeoutMs !== undefined &&
28297
- options.timeoutMs !== Number.POSITIVE_INFINITY &&
28298
- elapsed >= options.timeoutMs) ||
28299
- (maxRetries !== undefined && attempt >= maxRetries)) {
28300
- throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
28301
- code: "file_lock_timeout",
28389
+ catch (err) {
28390
+ if (handle) {
28391
+ const failedSnapshot = { payload: null };
28392
+ try {
28393
+ failedSnapshot.stat = await handle.stat();
28394
+ }
28395
+ catch {
28396
+ // Best-effort cleanup of a failed exclusive create.
28397
+ }
28398
+ const current = state.held.get(normalizedTargetPath);
28399
+ if (current?.handle === handle) {
28400
+ state.held.delete(normalizedTargetPath);
28401
+ }
28402
+ // If payload serialization/write fails, the file may be empty or
28403
+ // partial JSON, so remove while our exclusive handle is still open.
28404
+ await promises_.rm(lockPath, { force: true }).catch(() => undefined);
28405
+ await handle.close().catch(() => undefined);
28406
+ // Windows can refuse removing an open file; retry after close but
28407
+ // only if the path still points at the file identity we created.
28408
+ await removeSidecarLockIfUnchanged(lockPath, failedSnapshot);
28409
+ }
28410
+ if (err.code !== "EEXIST") {
28411
+ throw err;
28412
+ }
28413
+ if (ownsReclaimGuard) {
28414
+ await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath);
28415
+ ownsReclaimGuard = false;
28416
+ continue;
28417
+ }
28418
+ const nowMs = Date.now();
28419
+ const snapshot = await readSidecarLockSnapshot(lockPath);
28420
+ if (!snapshot) {
28421
+ continue;
28422
+ }
28423
+ const shouldReclaim = options.shouldReclaim ?? defaultShouldReclaim;
28424
+ if (await shouldReclaim({
28302
28425
  lockPath,
28303
28426
  normalizedTargetPath,
28304
- });
28427
+ payload: snapshot?.payload ?? null,
28428
+ staleMs: options.staleMs,
28429
+ nowMs,
28430
+ heldByThisProcess: state.held.has(normalizedTargetPath),
28431
+ })) {
28432
+ if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot))) {
28433
+ continue;
28434
+ }
28435
+ const staleRecovery = options.staleRecovery ?? "fail-closed";
28436
+ if (staleRecovery === "remove-if-unchanged") {
28437
+ if (!(await tryAcquireSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath))) {
28438
+ await waitForRetry();
28439
+ continue;
28440
+ }
28441
+ ownsReclaimGuard = true;
28442
+ const removal = await removeStaleSidecarLockIfAllowed({
28443
+ lockPath,
28444
+ normalizedTargetPath,
28445
+ snapshot,
28446
+ shouldRemoveStaleLock: options.shouldRemoveStaleLock,
28447
+ });
28448
+ if (removal === "removed" || removal === "changed") {
28449
+ continue;
28450
+ }
28451
+ await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath);
28452
+ ownsReclaimGuard = false;
28453
+ }
28454
+ throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
28455
+ code: "file_lock_stale",
28456
+ lockPath,
28457
+ normalizedTargetPath,
28458
+ });
28459
+ }
28460
+ await waitForRetry();
28305
28461
  }
28306
- const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
28307
- ? Number.POSITIVE_INFINITY
28308
- : Math.max(0, options.timeoutMs - elapsed);
28309
- const delay = Math.min(computeDelayMs(retry, attempt), remaining);
28310
- attempt += 1;
28311
- await new Promise((resolve) => setTimeout(resolve, delay));
28462
+ }
28463
+ }
28464
+ finally {
28465
+ if (ownsReclaimGuard) {
28466
+ await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath).catch(() => undefined);
28312
28467
  }
28313
28468
  }
28314
28469
  }
@@ -28395,6 +28550,16 @@ function assertWithinMaxBytes(bytes, maxBytes) {
28395
28550
  throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
28396
28551
  }
28397
28552
  }
28553
+ async function syncFileBestEffort(handle) {
28554
+ try {
28555
+ await handle.sync();
28556
+ }
28557
+ catch (error) {
28558
+ if (error?.code !== "EPERM") {
28559
+ throw error;
28560
+ }
28561
+ }
28562
+ }
28398
28563
  async function writeStreamToHandle(stream, handle, maxBytes) {
28399
28564
  let bytes = 0;
28400
28565
  for await (const chunk of stream) {
@@ -28556,7 +28721,7 @@ async function runPinnedWriteFallback(params) {
28556
28721
  else {
28557
28722
  await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
28558
28723
  }
28559
- await handle.sync();
28724
+ await syncFileBestEffort(handle);
28560
28725
  const stat = await handle.stat();
28561
28726
  await handle.close().catch(() => undefined);
28562
28727
  await syncDirectoryBestEffort(parentPath);
@@ -28601,7 +28766,7 @@ async function runPinnedWriteFallback(params) {
28601
28766
  throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
28602
28767
  }
28603
28768
  const expectedTempStat = tempStat;
28604
- await handle.sync();
28769
+ await syncFileBestEffort(handle);
28605
28770
  await handle.close().catch(() => undefined);
28606
28771
  handle = undefined;
28607
28772
  await withAsyncDirectoryGuards([parentGuard], async () => {
@@ -29510,16 +29675,104 @@ function assertNoUnsafeDeviceReadPath(filePath, options) {
29510
29675
  }
29511
29676
  }
29512
29677
 
29678
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/bounded-read.js
29679
+
29680
+
29681
+ const READ_CHUNK_BYTES = 64 * 1024;
29682
+ function assertMaxBytes(maxBytes) {
29683
+ if (maxBytes === Number.POSITIVE_INFINITY) {
29684
+ return;
29685
+ }
29686
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
29687
+ throw new RangeError("maxBytes must be a non-negative safe integer or Infinity");
29688
+ }
29689
+ }
29690
+ function createScratchBuffer(maxBytes) {
29691
+ const initialReadBytes = Number.isFinite(maxBytes)
29692
+ ? Math.min(READ_CHUNK_BYTES, maxBytes + 1)
29693
+ : READ_CHUNK_BYTES;
29694
+ return Buffer.allocUnsafe(Math.max(1, initialReadBytes));
29695
+ }
29696
+ function nextReadLength(total, maxBytes, capacity) {
29697
+ return Number.isFinite(maxBytes)
29698
+ ? Math.min(capacity, maxBytes - total + 1)
29699
+ : capacity;
29700
+ }
29701
+ function appendChunk(params) {
29702
+ const total = params.total + params.bytesRead;
29703
+ if (total > params.maxBytes) {
29704
+ throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got at least ${total})`);
29705
+ }
29706
+ params.chunks.push(Buffer.from(params.scratch.subarray(0, params.bytesRead)));
29707
+ return total;
29708
+ }
29709
+ async function readBoundedAsync(maxBytes, readChunk) {
29710
+ assertMaxBytes(maxBytes);
29711
+ const chunks = [];
29712
+ const scratch = createScratchBuffer(maxBytes);
29713
+ let total = 0;
29714
+ while (true) {
29715
+ const length = nextReadLength(total, maxBytes, scratch.length);
29716
+ const bytesRead = await readChunk(scratch, length);
29717
+ if (bytesRead === 0) {
29718
+ return Buffer.concat(chunks, total);
29719
+ }
29720
+ total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
29721
+ }
29722
+ }
29723
+ /**
29724
+ * Reads from the handle's current offset without closing it. A bounded read
29725
+ * consumes at most maxBytes + 1 bytes so growth after an earlier stat cannot
29726
+ * force an unbounded allocation.
29727
+ */
29728
+ async function readFileHandleBounded(handle, maxBytes) {
29729
+ return await readBoundedAsync(maxBytes, async (scratch, length) => {
29730
+ return (await handle.read(scratch, 0, length, null)).bytesRead;
29731
+ });
29732
+ }
29733
+ function readDescriptorChunk(fd, scratch, length) {
29734
+ return new Promise((resolve, reject) => {
29735
+ fs.read(fd, scratch, 0, length, null, (error, bytesRead) => {
29736
+ if (error) {
29737
+ reject(error);
29738
+ return;
29739
+ }
29740
+ resolve(bytesRead);
29741
+ });
29742
+ });
29743
+ }
29744
+ /** Async bounded read from a numeric descriptor. The caller owns the descriptor. */
29745
+ async function readFileDescriptorBounded(fd, maxBytes) {
29746
+ return await readBoundedAsync(maxBytes, async (scratch, length) => {
29747
+ return await readDescriptorChunk(fd, scratch, length);
29748
+ });
29749
+ }
29750
+ /** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
29751
+ function readFileDescriptorBoundedSync(fd, maxBytes) {
29752
+ assertMaxBytes(maxBytes);
29753
+ const chunks = [];
29754
+ const scratch = createScratchBuffer(maxBytes);
29755
+ let total = 0;
29756
+ while (true) {
29757
+ const length = nextReadLength(total, maxBytes, scratch.length);
29758
+ const bytesRead = fs.readSync(fd, scratch, 0, length, null);
29759
+ if (bytesRead === 0) {
29760
+ return Buffer.concat(chunks, total);
29761
+ }
29762
+ total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
29763
+ }
29764
+ }
29765
+
29513
29766
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/read-opened-file.js
29514
29767
 
29768
+
29515
29769
  async function read_opened_file_readOpenedFileSafely(params) {
29516
29770
  if (params.maxBytes !== undefined && params.opened.stat.size > params.maxBytes) {
29517
29771
  throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${params.opened.stat.size})`);
29518
29772
  }
29519
- const buffer = await params.opened.handle.readFile();
29520
- if (params.maxBytes !== undefined && buffer.byteLength > params.maxBytes) {
29521
- throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${buffer.byteLength})`);
29522
- }
29773
+ const buffer = params.maxBytes === undefined
29774
+ ? await params.opened.handle.readFile()
29775
+ : await readFileHandleBounded(params.opened.handle, params.maxBytes);
29523
29776
  return {
29524
29777
  buffer,
29525
29778
  realPath: params.opened.realPath,
@@ -59503,6 +59756,10 @@ function serialize (cmpts, opts) {
59503
59756
 
59504
59757
  const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u
59505
59758
 
59759
+ // Captures the authority component (between "//" and the next "/", "?" or "#"),
59760
+ // with or without a scheme prefix, for the literal-backslash rejection below.
59761
+ const AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/
59762
+
59506
59763
  /**
59507
59764
  * @param {import('./types/index').URIComponent} parsed
59508
59765
  * @param {RegExpMatchArray} matches
@@ -59549,6 +59806,19 @@ function parseWithStatus (uri, opts) {
59549
59806
  }
59550
59807
  }
59551
59808
 
59809
+ // A literal backslash (U+005C) is not a valid RFC 3986 URI character and is
59810
+ // not an authority delimiter. Reject it in the authority rather than
59811
+ // rewriting it: normalizing "\" -> "/" (WHATWG error recovery) could silently
59812
+ // change the resource identified by an otherwise-invalid input, and lets "\"
59813
+ // act as a host delimiter here while Node's native URL parses a different
59814
+ // host (SSRF / redirect / origin-allowlist bypass). Percent-encoded %5C is
59815
+ // untouched and remains valid encoded data.
59816
+ const authorityMatch = uri.match(AUTHORITY_PREFIX)
59817
+ if (authorityMatch !== null && authorityMatch[1].indexOf('\\') !== -1) {
59818
+ parsed.error = 'URI authority must not contain a literal backslash.'
59819
+ malformedAuthorityOrPort = true
59820
+ }
59821
+
59552
59822
  const matches = uri.match(URI_PARSE)
59553
59823
 
59554
59824
  if (matches) {
@@ -68989,4 +69259,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
68989
69259
  // module factories are used so entry inlining is disabled
68990
69260
  // startup
68991
69261
  // Load entry module and return exports
68992
- var __webpack_exports__ = __webpack_require__(6887);
69262
+ var __webpack_exports__ = __webpack_require__(3503);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lousy-agents/mcp",
3
- "version": "5.17.7",
3
+ "version": "5.17.9",
4
4
  "description": "MCP server for lousy-agents - provides AI coding assistant tools via the Model Context Protocol",
5
5
  "type": "module",
6
6
  "repository": {