@lousy-agents/cli 5.17.8 → 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.
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "ghcr.io/devcontainers/features/copilot-cli:1.0.0": {},
12
12
  "ghcr.io/anthropics/devcontainer-features/claude-code:1": {
13
- "version": "v2.1.217"
13
+ "version": "v2.1.218"
14
14
  },
15
15
  "ghcr.io/rocker-org/devcontainer-features/apt-packages:1.0.2": {
16
16
  "packages": "yamllint, shellcheck",
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "devDependencies": {
20
20
  "@biomejs/biome": "2.5.3",
21
- "@lousy-agents/mcp": "5.17.7",
21
+ "@lousy-agents/mcp": "5.17.8",
22
22
  "@modelcontextprotocol/server-sequential-thinking": "2026.7.4",
23
23
  "@testcontainers/postgresql": "12.0.4",
24
24
  "@types/node": "24.13.3",
@@ -972,9 +972,9 @@
972
972
  }
973
973
  },
974
974
  "node_modules/@lousy-agents/mcp": {
975
- "version": "5.17.7",
976
- "resolved": "https://registry.npmjs.org/@lousy-agents/mcp/-/mcp-5.17.7.tgz",
977
- "integrity": "sha512-alFrah04RcSr507x7Ygg0AHuRy1wXOtjZpW1DMzKvvFiLQ7cr8vRBX1yKV8zv6GrP3uU+s9O8d3bC5EY67/05A==",
975
+ "version": "5.17.8",
976
+ "resolved": "https://registry.npmjs.org/@lousy-agents/mcp/-/mcp-5.17.8.tgz",
977
+ "integrity": "sha512-4c6NkQAje237D4DzbY1cH+qvYc2uIX1uZc0KBYZtqSMSUfKrGb4crCR8TcS/HRnM8NGqRXqtnnh/mkiyZyvq+A==",
978
978
  "dev": true,
979
979
  "license": "BSD-2-Clause-Patent",
980
980
  "dependencies": {
@@ -3476,9 +3476,9 @@
3476
3476
  }
3477
3477
  },
3478
3478
  "node_modules/fast-json-stringify/node_modules/fast-uri": {
3479
- "version": "4.1.0",
3480
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.0.tgz",
3481
- "integrity": "sha512-ZodJ2cRiLVWGi9IgPb3mbgSqM4CD3LexCHkuv0FfBXHJI1ADfucTD06m6clO2Cy5RZYsw/SiCVl/dyrFI/SYWA==",
3479
+ "version": "4.1.1",
3480
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.1.tgz",
3481
+ "integrity": "sha512-YPOs1zD5TG2+EZt+r88LwF6mclA7TPkpwMP7ZN3TO2HiHS8TXvq7QA/17iJsV9dubcLo/f8eEYqMBruyQV21hQ==",
3482
3482
  "funding": [
3483
3483
  {
3484
3484
  "type": "github",
@@ -3501,9 +3501,9 @@
3501
3501
  }
3502
3502
  },
3503
3503
  "node_modules/fast-uri": {
3504
- "version": "3.1.3",
3505
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
3506
- "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",
3504
+ "version": "3.1.4",
3505
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
3506
+ "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
3507
3507
  "funding": [
3508
3508
  {
3509
3509
  "type": "github",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "devDependencies": {
31
31
  "@biomejs/biome": "2.5.3",
32
- "@lousy-agents/mcp": "5.17.7",
32
+ "@lousy-agents/mcp": "5.17.8",
33
33
  "@modelcontextprotocol/server-sequential-thinking": "2026.7.4",
34
34
  "@testcontainers/postgresql": "12.0.4",
35
35
  "@types/node": "24.13.3",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "ghcr.io/devcontainers/features/copilot-cli:1.0.0": {},
12
12
  "ghcr.io/anthropics/devcontainer-features/claude-code:1": {
13
- "version": "v2.1.217"
13
+ "version": "v2.1.218"
14
14
  },
15
15
  "ghcr.io/rocker-org/devcontainer-features/apt-packages:1.0.2": {
16
16
  "packages": "yamllint, shellcheck",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "devDependencies": {
22
22
  "@biomejs/biome": "2.5.3",
23
- "@lousy-agents/mcp": "5.17.7",
23
+ "@lousy-agents/mcp": "5.17.8",
24
24
  "@modelcontextprotocol/server-sequential-thinking": "2026.7.4",
25
25
  "@types/node": "24.13.3",
26
26
  "@upstash/context7-mcp": "3.2.4",
package/dist/index.js CHANGED
@@ -3032,7 +3032,7 @@ exports.basename = (path, { windows } = {}) => {
3032
3032
 
3033
3033
 
3034
3034
  },
3035
- 889(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
3035
+ 580(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
3036
3036
  // NAMESPACE OBJECT: ../../node_modules/micromark/lib/constructs.js
3037
3037
  var constructs_namespaceObject = {};
3038
3038
  __webpack_require__.r(constructs_namespaceObject);
@@ -3790,15 +3790,11 @@ var external_node_child_process_ = __webpack_require__(1421);
3790
3790
  const PINNED_PYTHON_WORKER_SOURCE = String.raw `
3791
3791
  import base64, errno, json, os, secrets, stat, sys
3792
3792
  DIR_FLAGS = os.O_RDONLY
3793
- if hasattr(os, "O_DIRECTORY"):
3794
- DIR_FLAGS |= os.O_DIRECTORY
3795
- if hasattr(os, "O_NOFOLLOW"):
3796
- DIR_FLAGS |= os.O_NOFOLLOW
3793
+ if hasattr(os, "O_DIRECTORY"): DIR_FLAGS |= os.O_DIRECTORY
3794
+ if hasattr(os, "O_NOFOLLOW"): DIR_FLAGS |= os.O_NOFOLLOW
3797
3795
  READ_FLAGS = os.O_RDONLY
3798
- if hasattr(os, "O_NONBLOCK"):
3799
- READ_FLAGS |= os.O_NONBLOCK
3800
- if hasattr(os, "O_NOFOLLOW"):
3801
- READ_FLAGS |= os.O_NOFOLLOW
3796
+ if hasattr(os, "O_NONBLOCK"): READ_FLAGS |= os.O_NONBLOCK
3797
+ if hasattr(os, "O_NOFOLLOW"): READ_FLAGS |= os.O_NOFOLLOW
3802
3798
  WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
3803
3799
  if hasattr(os, "O_NOFOLLOW"):
3804
3800
  WRITE_FLAGS |= os.O_NOFOLLOW
@@ -3878,6 +3874,10 @@ def write_all(fd, data):
3878
3874
  if written <= 0:
3879
3875
  raise OSError(errno.EIO, "short write")
3880
3876
  view = view[written:]
3877
+ def fsync_best_effort(fd):
3878
+ try: os.fsync(fd)
3879
+ except OSError as error:
3880
+ if error.errno != errno.EPERM: raise
3881
3881
  def link_unsupported(exc):
3882
3882
  unsupported = (errno.EPERM, errno.EOPNOTSUPP, getattr(errno, "ENOTSUP", errno.EOPNOTSUPP))
3883
3883
  return getattr(exc, "errno", None) in unsupported
@@ -4083,13 +4083,13 @@ def write_path(root_fd, payload):
4083
4083
  temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
4084
4084
  os.fchmod(temp_fd, mode)
4085
4085
  write_all(temp_fd, data)
4086
- os.fsync(temp_fd)
4086
+ fsync_best_effort(temp_fd)
4087
4087
  temp_stat = os.fstat(temp_fd)
4088
4088
  os.close(temp_fd)
4089
4089
  temp_fd = None
4090
4090
  result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
4091
4091
  temp_name = None
4092
- os.fsync(parent_fd)
4092
+ fsync_best_effort(parent_fd)
4093
4093
  return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
4094
4094
  finally:
4095
4095
  if temp_fd is not None:
@@ -4516,29 +4516,34 @@ async function runPinnedPathHelper(params) {
4516
4516
  }
4517
4517
  }
4518
4518
 
4519
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
4520
-
4519
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
4521
4520
 
4522
4521
 
4523
4522
 
4524
- const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
4525
- function getGlobalManagers() {
4526
- const globalWithState = globalThis;
4527
- if (!globalWithState[GLOBAL_STATE_KEY]) {
4528
- globalWithState[GLOBAL_STATE_KEY] = new Map();
4523
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
4524
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS = SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES * 8;
4525
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX = "\t".repeat(8);
4526
+ const SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN = new RegExp(`\\n(${SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX}[ \\t]{${SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS}})\\n$`);
4527
+ function createSidecarLockOwnershipToken() {
4528
+ let token = SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX;
4529
+ for (const byte of (0,external_node_crypto_.randomBytes)(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
4530
+ for (let bit = 7; bit >= 0; bit -= 1) {
4531
+ token += byte & (1 << bit) ? "\t" : " ";
4532
+ }
4529
4533
  }
4530
- return globalWithState[GLOBAL_STATE_KEY];
4534
+ return token;
4531
4535
  }
4532
- function resolveManagerState(key) {
4533
- const managers = getGlobalManagers();
4534
- let state = managers.get(key);
4535
- if (!state) {
4536
- state = { cleanupRegistered: false, held: new Map() };
4537
- managers.set(key, state);
4538
- }
4539
- return state;
4536
+ function readSidecarLockOwnershipToken(raw) {
4537
+ return SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN.exec(raw)?.[1];
4538
+ }
4539
+ function serializeSidecarLockPayload(payload) {
4540
+ const ownershipToken = createSidecarLockOwnershipToken();
4541
+ return {
4542
+ raw: `${JSON.stringify(payload, null, 2)}\n${ownershipToken}\n`,
4543
+ ownershipToken,
4544
+ };
4540
4545
  }
4541
- async function readLockSnapshot(lockPath) {
4546
+ async function readSidecarLockSnapshot(lockPath) {
4542
4547
  try {
4543
4548
  const stat = await promises_.lstat(lockPath);
4544
4549
  const raw = await promises_.readFile(lockPath, "utf8");
@@ -4560,7 +4565,15 @@ async function readLockSnapshot(lockPath) {
4560
4565
  throw err;
4561
4566
  }
4562
4567
  }
4563
- function snapshotMatches(current, observed) {
4568
+ function sidecarLockSnapshotMatches(current, observed) {
4569
+ if (observed.ownershipToken !== undefined) {
4570
+ return (current.stat?.isFile() === true &&
4571
+ current.raw !== undefined &&
4572
+ observed.raw !== undefined &&
4573
+ readSidecarLockOwnershipToken(current.raw) === observed.ownershipToken &&
4574
+ readSidecarLockOwnershipToken(observed.raw) === observed.ownershipToken &&
4575
+ current.raw === observed.raw);
4576
+ }
4564
4577
  if (observed.stat && current.stat && !file_identity_sameFileIdentity(observed.stat, current.stat)) {
4565
4578
  return false;
4566
4579
  }
@@ -4569,30 +4582,54 @@ function snapshotMatches(current, observed) {
4569
4582
  }
4570
4583
  return observed.stat !== undefined && current.stat !== undefined;
4571
4584
  }
4572
- async function removeLockIfUnchanged(lockPath, observed) {
4573
- const current = await readLockSnapshot(lockPath);
4574
- if (!current || !observed) {
4575
- return false;
4576
- }
4577
- if (!snapshotMatches(current, observed)) {
4578
- // The lock changed after we decided it was stale. Leave the fresh holder's
4579
- // file alone; deleting by path here would break mutual exclusion.
4585
+ async function removeSidecarLockIfUnchanged(lockPath, observed) {
4586
+ const current = await readSidecarLockSnapshot(lockPath);
4587
+ if (!current || !observed || !sidecarLockSnapshotMatches(current, observed)) {
4580
4588
  return false;
4581
4589
  }
4582
4590
  await promises_.rm(lockPath, { force: true }).catch(() => undefined);
4583
4591
  return true;
4584
4592
  }
4585
- async function lockSnapshotStillPresent(lockPath, observed) {
4586
- const current = await readLockSnapshot(lockPath);
4587
- return !!current && !!observed && snapshotMatches(current, observed);
4593
+ async function sidecarLockSnapshotStillPresent(lockPath, observed) {
4594
+ const current = await readSidecarLockSnapshot(lockPath);
4595
+ return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
4588
4596
  }
4589
- async function removeStaleLockIfAllowed(params) {
4590
- if (!params.shouldRemoveStaleLock) {
4591
- return "not-approved";
4597
+ async function sidecarReclaimGuardExists(pathname) {
4598
+ try {
4599
+ await promises_.lstat(pathname);
4600
+ return true;
4601
+ }
4602
+ catch (err) {
4603
+ if (err.code === "ENOENT") {
4604
+ return false;
4605
+ }
4606
+ throw err;
4592
4607
  }
4593
- if (params.snapshot.raw === undefined) {
4608
+ }
4609
+ async function tryAcquireSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
4610
+ try {
4611
+ await promises_.mkdir(reclaimGuardPath);
4612
+ reclaimGuards.add(reclaimGuardPath);
4613
+ return true;
4614
+ }
4615
+ catch (err) {
4616
+ if (err.code === "EEXIST") {
4617
+ return false;
4618
+ }
4619
+ throw err;
4620
+ }
4621
+ }
4622
+ async function releaseSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
4623
+ await promises_.rmdir(reclaimGuardPath);
4624
+ reclaimGuards.delete(reclaimGuardPath);
4625
+ }
4626
+ async function removeStaleSidecarLockIfAllowed(params) {
4627
+ if (!params.shouldRemoveStaleLock || params.snapshot.raw === undefined) {
4594
4628
  return "not-approved";
4595
4629
  }
4630
+ if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot))) {
4631
+ return "changed";
4632
+ }
4596
4633
  if (!(await params.shouldRemoveStaleLock({
4597
4634
  lockPath: params.lockPath,
4598
4635
  normalizedTargetPath: params.normalizedTargetPath,
@@ -4601,32 +4638,95 @@ async function removeStaleLockIfAllowed(params) {
4601
4638
  }))) {
4602
4639
  return "not-approved";
4603
4640
  }
4604
- const current = await readLockSnapshot(params.lockPath);
4605
- if (!current || !snapshotMatches(current, params.snapshot)) {
4641
+ if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot))) {
4606
4642
  return "changed";
4607
4643
  }
4608
4644
  try {
4609
- await promises_.rm(params.lockPath, { force: true });
4645
+ await promises_.rm(params.lockPath);
4646
+ return "removed";
4610
4647
  }
4611
4648
  catch (err) {
4612
4649
  if (err.code === "ENOENT") {
4613
4650
  return "changed";
4614
4651
  }
4615
- return "not-approved";
4652
+ throw err;
4653
+ }
4654
+ }
4655
+
4656
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
4657
+
4658
+
4659
+
4660
+
4661
+
4662
+ const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
4663
+ function getGlobalManagers() {
4664
+ const globalWithState = globalThis;
4665
+ if (!globalWithState[GLOBAL_STATE_KEY]) {
4666
+ globalWithState[GLOBAL_STATE_KEY] = new Map();
4616
4667
  }
4617
- return "removed";
4668
+ return globalWithState[GLOBAL_STATE_KEY];
4669
+ }
4670
+ function resolveManagerState(key) {
4671
+ const managers = getGlobalManagers();
4672
+ let state = managers.get(key);
4673
+ if (!state) {
4674
+ state = {
4675
+ cleanupRegistered: false,
4676
+ held: new Map(),
4677
+ reclaimCleanupRegistered: false,
4678
+ reclaimGuards: new Set(),
4679
+ };
4680
+ managers.set(key, state);
4681
+ }
4682
+ else {
4683
+ // The global manager symbol is shared across package copies and hot reloads.
4684
+ // Backfill state created by fs-safe versions that predate reclaim guards.
4685
+ state.reclaimCleanupRegistered ??= false;
4686
+ state.reclaimGuards ??= new Set();
4687
+ }
4688
+ return state;
4618
4689
  }
4619
4690
  function snapshotMatchesSync(lockPath, observed) {
4691
+ let fd;
4620
4692
  try {
4621
- const stat = external_node_fs_.lstatSync(lockPath);
4622
- if (observed.stat && !file_identity_sameFileIdentity(observed.stat, stat)) {
4693
+ const beforeStat = external_node_fs_.lstatSync(lockPath);
4694
+ if (!beforeStat.isFile()) {
4695
+ return false;
4696
+ }
4697
+ const openFlags = external_node_fs_.constants.O_RDONLY |
4698
+ (process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
4699
+ ? external_node_fs_.constants.O_NOFOLLOW
4700
+ : 0) |
4701
+ (typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0);
4702
+ fd = external_node_fs_.openSync(lockPath, openFlags);
4703
+ const openedStat = external_node_fs_.fstatSync(fd);
4704
+ if (!openedStat.isFile()) {
4705
+ return false;
4706
+ }
4707
+ if (observed.raw !== undefined && openedStat.size !== Buffer.byteLength(observed.raw)) {
4623
4708
  return false;
4624
4709
  }
4625
- return observed.raw === undefined || external_node_fs_.readFileSync(lockPath, "utf8") === observed.raw;
4710
+ const raw = external_node_fs_.readFileSync(fd, "utf8");
4711
+ const afterStat = external_node_fs_.lstatSync(lockPath);
4712
+ if (!afterStat.isFile() || !file_identity_sameFileIdentity(beforeStat, afterStat)) {
4713
+ return false;
4714
+ }
4715
+ return sidecarLockSnapshotMatches({ raw, payload: null, stat: afterStat }, observed);
4626
4716
  }
4627
4717
  catch {
4628
4718
  return false;
4629
4719
  }
4720
+ finally {
4721
+ if (fd !== undefined) {
4722
+ try {
4723
+ external_node_fs_.closeSync(fd);
4724
+ }
4725
+ catch {
4726
+ // Best-effort process-exit cleanup.
4727
+ }
4728
+ }
4729
+ }
4630
4730
  }
4631
4731
  async function resolveNormalizedTargetPath(targetPath) {
4632
4732
  const resolved = external_node_path_.resolve(targetPath);
@@ -4661,6 +4761,17 @@ async function defaultShouldReclaim(params) {
4661
4761
  return true;
4662
4762
  }
4663
4763
  }
4764
+ function releaseAllReclaimGuardsSync(state) {
4765
+ for (const reclaimGuardPath of state.reclaimGuards) {
4766
+ try {
4767
+ external_node_fs_.rmdirSync(reclaimGuardPath);
4768
+ state.reclaimGuards.delete(reclaimGuardPath);
4769
+ }
4770
+ catch {
4771
+ // Best-effort process-exit cleanup. A surviving guard fails closed.
4772
+ }
4773
+ }
4774
+ }
4664
4775
  function releaseAllLocksSync(state) {
4665
4776
  for (const [normalizedTargetPath, held] of state.held) {
4666
4777
  void held.handle.close().catch(() => undefined);
@@ -4674,6 +4785,7 @@ function releaseAllLocksSync(state) {
4674
4785
  }
4675
4786
  state.held.delete(normalizedTargetPath);
4676
4787
  }
4788
+ releaseAllReclaimGuardsSync(state);
4677
4789
  }
4678
4790
  async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
4679
4791
  const current = state.held.get(normalizedTargetPath);
@@ -4696,7 +4808,7 @@ async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
4696
4808
  state.held.delete(normalizedTargetPath);
4697
4809
  held.releasePromise = (async () => {
4698
4810
  await held.handle.close().catch(() => undefined);
4699
- await removeLockIfUnchanged(held.lockPath, held.snapshot);
4811
+ await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot);
4700
4812
  })();
4701
4813
  try {
4702
4814
  await held.releasePromise;
@@ -4709,11 +4821,16 @@ async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
4709
4821
  function createSidecarLockManager(key) {
4710
4822
  const state = resolveManagerState(key);
4711
4823
  function ensureExitCleanupRegistered() {
4712
- if (state.cleanupRegistered) {
4824
+ if (!state.cleanupRegistered) {
4825
+ state.cleanupRegistered = true;
4826
+ state.reclaimCleanupRegistered = true;
4827
+ process.on("exit", () => releaseAllLocksSync(state));
4713
4828
  return;
4714
4829
  }
4715
- state.cleanupRegistered = true;
4716
- process.on("exit", () => releaseAllLocksSync(state));
4830
+ if (!state.reclaimCleanupRegistered) {
4831
+ state.reclaimCleanupRegistered = true;
4832
+ process.on("exit", () => releaseAllReclaimGuardsSync(state));
4833
+ }
4717
4834
  }
4718
4835
  async function acquire(options) {
4719
4836
  ensureExitCleanupRegistered();
@@ -4733,108 +4850,146 @@ function createSidecarLockManager(key) {
4733
4850
  const startedAt = Date.now();
4734
4851
  const retry = options.retry ?? {};
4735
4852
  const maxRetries = options.timeoutMs === Number.POSITIVE_INFINITY ? undefined : retry.retries;
4853
+ const reclaimGuardPath = `${lockPath}.reclaim`;
4854
+ let ownsReclaimGuard = false;
4736
4855
  let attempt = 0;
4737
- while (true) {
4738
- let handle = null;
4739
- try {
4740
- handle = await promises_.open(lockPath, "wx");
4741
- const payload = await options.payload();
4742
- const raw = `${JSON.stringify(payload, null, 2)}\n`;
4743
- await handle.writeFile(raw, "utf8");
4744
- const snapshot = { raw, payload, stat: await handle.stat() };
4745
- const createdHeld = {
4746
- count: 1,
4747
- handle,
4748
- lockPath,
4749
- snapshot,
4750
- acquiredAt: Date.now(),
4751
- metadata: options.metadata ?? {},
4752
- };
4753
- state.held.set(normalizedTargetPath, createdHeld);
4754
- const release = () => releaseHeldLock(state, normalizedTargetPath, createdHeld).then(() => undefined);
4755
- return {
4856
+ const waitForRetry = async () => {
4857
+ const elapsed = Date.now() - startedAt;
4858
+ if ((options.timeoutMs !== undefined &&
4859
+ options.timeoutMs !== Number.POSITIVE_INFINITY &&
4860
+ elapsed >= options.timeoutMs) ||
4861
+ (maxRetries !== undefined && attempt >= maxRetries)) {
4862
+ throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
4863
+ code: "file_lock_timeout",
4756
4864
  lockPath,
4757
4865
  normalizedTargetPath,
4758
- release,
4759
- [Symbol.asyncDispose]: release,
4760
- };
4866
+ });
4761
4867
  }
4762
- catch (err) {
4763
- if (handle) {
4764
- const failedSnapshot = { payload: null };
4765
- try {
4766
- failedSnapshot.stat = await handle.stat();
4767
- }
4768
- catch {
4769
- // Best-effort cleanup of a failed exclusive create.
4770
- }
4771
- const current = state.held.get(normalizedTargetPath);
4772
- if (current?.handle === handle) {
4773
- state.held.delete(normalizedTargetPath);
4774
- }
4775
- // If payload serialization/write fails, the file may be empty or
4776
- // partial JSON, so remove while our exclusive handle is still open.
4777
- await promises_.rm(lockPath, { force: true }).catch(() => undefined);
4778
- await handle.close().catch(() => undefined);
4779
- // Windows can refuse removing an open file; retry after close but
4780
- // only if the path still points at the file identity we created.
4781
- await removeLockIfUnchanged(lockPath, failedSnapshot);
4782
- }
4783
- if (err.code !== "EEXIST") {
4784
- throw err;
4785
- }
4786
- const nowMs = Date.now();
4787
- const snapshot = await readLockSnapshot(lockPath);
4788
- if (!snapshot) {
4868
+ const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
4869
+ ? Number.POSITIVE_INFINITY
4870
+ : Math.max(0, options.timeoutMs - elapsed);
4871
+ const delay = Math.min(computeDelayMs(retry, attempt), remaining);
4872
+ attempt += 1;
4873
+ await new Promise((resolve) => setTimeout(resolve, delay));
4874
+ };
4875
+ try {
4876
+ while (true) {
4877
+ if (!ownsReclaimGuard && (await sidecarReclaimGuardExists(reclaimGuardPath))) {
4878
+ await waitForRetry();
4789
4879
  continue;
4790
4880
  }
4791
- const shouldReclaim = options.shouldReclaim ?? defaultShouldReclaim;
4792
- if (await shouldReclaim({
4793
- lockPath,
4794
- normalizedTargetPath,
4795
- payload: snapshot?.payload ?? null,
4796
- staleMs: options.staleMs,
4797
- nowMs,
4798
- heldByThisProcess: state.held.has(normalizedTargetPath),
4799
- })) {
4800
- if (!(await lockSnapshotStillPresent(lockPath, snapshot))) {
4801
- continue;
4802
- }
4803
- const staleRecovery = options.staleRecovery ?? "fail-closed";
4804
- if (staleRecovery === "remove-if-unchanged") {
4805
- const removal = await removeStaleLockIfAllowed({
4806
- lockPath,
4807
- normalizedTargetPath,
4808
- snapshot,
4809
- shouldRemoveStaleLock: options.shouldRemoveStaleLock,
4810
- });
4811
- if (removal === "removed" || removal === "changed") {
4812
- continue;
4881
+ let handle = null;
4882
+ try {
4883
+ handle = await promises_.open(lockPath, "wx");
4884
+ const payload = await options.payload();
4885
+ const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
4886
+ await handle.writeFile(raw, "utf8");
4887
+ const snapshot = { raw, payload, stat: await handle.stat(), ownershipToken };
4888
+ const createdHeld = {
4889
+ count: 1,
4890
+ handle,
4891
+ lockPath,
4892
+ snapshot,
4893
+ acquiredAt: Date.now(),
4894
+ metadata: options.metadata ?? {},
4895
+ };
4896
+ state.held.set(normalizedTargetPath, createdHeld);
4897
+ if (ownsReclaimGuard) {
4898
+ try {
4899
+ await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath);
4900
+ ownsReclaimGuard = false;
4901
+ }
4902
+ catch (err) {
4903
+ await releaseHeldLock(state, normalizedTargetPath, createdHeld, { force: true });
4904
+ throw err;
4813
4905
  }
4814
4906
  }
4815
- throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
4816
- code: "file_lock_stale",
4907
+ const release = () => releaseHeldLock(state, normalizedTargetPath, createdHeld).then(() => undefined);
4908
+ return {
4817
4909
  lockPath,
4818
4910
  normalizedTargetPath,
4819
- });
4911
+ release,
4912
+ [Symbol.asyncDispose]: release,
4913
+ };
4820
4914
  }
4821
- const elapsed = Date.now() - startedAt;
4822
- if ((options.timeoutMs !== undefined &&
4823
- options.timeoutMs !== Number.POSITIVE_INFINITY &&
4824
- elapsed >= options.timeoutMs) ||
4825
- (maxRetries !== undefined && attempt >= maxRetries)) {
4826
- throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
4827
- code: "file_lock_timeout",
4915
+ catch (err) {
4916
+ if (handle) {
4917
+ const failedSnapshot = { payload: null };
4918
+ try {
4919
+ failedSnapshot.stat = await handle.stat();
4920
+ }
4921
+ catch {
4922
+ // Best-effort cleanup of a failed exclusive create.
4923
+ }
4924
+ const current = state.held.get(normalizedTargetPath);
4925
+ if (current?.handle === handle) {
4926
+ state.held.delete(normalizedTargetPath);
4927
+ }
4928
+ // If payload serialization/write fails, the file may be empty or
4929
+ // partial JSON, so remove while our exclusive handle is still open.
4930
+ await promises_.rm(lockPath, { force: true }).catch(() => undefined);
4931
+ await handle.close().catch(() => undefined);
4932
+ // Windows can refuse removing an open file; retry after close but
4933
+ // only if the path still points at the file identity we created.
4934
+ await removeSidecarLockIfUnchanged(lockPath, failedSnapshot);
4935
+ }
4936
+ if (err.code !== "EEXIST") {
4937
+ throw err;
4938
+ }
4939
+ if (ownsReclaimGuard) {
4940
+ await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath);
4941
+ ownsReclaimGuard = false;
4942
+ continue;
4943
+ }
4944
+ const nowMs = Date.now();
4945
+ const snapshot = await readSidecarLockSnapshot(lockPath);
4946
+ if (!snapshot) {
4947
+ continue;
4948
+ }
4949
+ const shouldReclaim = options.shouldReclaim ?? defaultShouldReclaim;
4950
+ if (await shouldReclaim({
4828
4951
  lockPath,
4829
4952
  normalizedTargetPath,
4830
- });
4953
+ payload: snapshot?.payload ?? null,
4954
+ staleMs: options.staleMs,
4955
+ nowMs,
4956
+ heldByThisProcess: state.held.has(normalizedTargetPath),
4957
+ })) {
4958
+ if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot))) {
4959
+ continue;
4960
+ }
4961
+ const staleRecovery = options.staleRecovery ?? "fail-closed";
4962
+ if (staleRecovery === "remove-if-unchanged") {
4963
+ if (!(await tryAcquireSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath))) {
4964
+ await waitForRetry();
4965
+ continue;
4966
+ }
4967
+ ownsReclaimGuard = true;
4968
+ const removal = await removeStaleSidecarLockIfAllowed({
4969
+ lockPath,
4970
+ normalizedTargetPath,
4971
+ snapshot,
4972
+ shouldRemoveStaleLock: options.shouldRemoveStaleLock,
4973
+ });
4974
+ if (removal === "removed" || removal === "changed") {
4975
+ continue;
4976
+ }
4977
+ await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath);
4978
+ ownsReclaimGuard = false;
4979
+ }
4980
+ throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
4981
+ code: "file_lock_stale",
4982
+ lockPath,
4983
+ normalizedTargetPath,
4984
+ });
4985
+ }
4986
+ await waitForRetry();
4831
4987
  }
4832
- const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
4833
- ? Number.POSITIVE_INFINITY
4834
- : Math.max(0, options.timeoutMs - elapsed);
4835
- const delay = Math.min(computeDelayMs(retry, attempt), remaining);
4836
- attempt += 1;
4837
- await new Promise((resolve) => setTimeout(resolve, delay));
4988
+ }
4989
+ }
4990
+ finally {
4991
+ if (ownsReclaimGuard) {
4992
+ await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath).catch(() => undefined);
4838
4993
  }
4839
4994
  }
4840
4995
  }
@@ -4921,6 +5076,16 @@ function assertWithinMaxBytes(bytes, maxBytes) {
4921
5076
  throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
4922
5077
  }
4923
5078
  }
5079
+ async function syncFileBestEffort(handle) {
5080
+ try {
5081
+ await handle.sync();
5082
+ }
5083
+ catch (error) {
5084
+ if (error?.code !== "EPERM") {
5085
+ throw error;
5086
+ }
5087
+ }
5088
+ }
4924
5089
  async function writeStreamToHandle(stream, handle, maxBytes) {
4925
5090
  let bytes = 0;
4926
5091
  for await (const chunk of stream) {
@@ -5082,7 +5247,7 @@ async function runPinnedWriteFallback(params) {
5082
5247
  else {
5083
5248
  await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
5084
5249
  }
5085
- await handle.sync();
5250
+ await syncFileBestEffort(handle);
5086
5251
  const stat = await handle.stat();
5087
5252
  await handle.close().catch(() => undefined);
5088
5253
  await syncDirectoryBestEffort(parentPath);
@@ -5127,7 +5292,7 @@ async function runPinnedWriteFallback(params) {
5127
5292
  throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
5128
5293
  }
5129
5294
  const expectedTempStat = tempStat;
5130
- await handle.sync();
5295
+ await syncFileBestEffort(handle);
5131
5296
  await handle.close().catch(() => undefined);
5132
5297
  handle = undefined;
5133
5298
  await withAsyncDirectoryGuards([parentGuard], async () => {
@@ -6036,16 +6201,104 @@ function assertNoUnsafeDeviceReadPath(filePath, options) {
6036
6201
  }
6037
6202
  }
6038
6203
 
6204
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/bounded-read.js
6205
+
6206
+
6207
+ const READ_CHUNK_BYTES = 64 * 1024;
6208
+ function assertMaxBytes(maxBytes) {
6209
+ if (maxBytes === Number.POSITIVE_INFINITY) {
6210
+ return;
6211
+ }
6212
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
6213
+ throw new RangeError("maxBytes must be a non-negative safe integer or Infinity");
6214
+ }
6215
+ }
6216
+ function createScratchBuffer(maxBytes) {
6217
+ const initialReadBytes = Number.isFinite(maxBytes)
6218
+ ? Math.min(READ_CHUNK_BYTES, maxBytes + 1)
6219
+ : READ_CHUNK_BYTES;
6220
+ return Buffer.allocUnsafe(Math.max(1, initialReadBytes));
6221
+ }
6222
+ function nextReadLength(total, maxBytes, capacity) {
6223
+ return Number.isFinite(maxBytes)
6224
+ ? Math.min(capacity, maxBytes - total + 1)
6225
+ : capacity;
6226
+ }
6227
+ function appendChunk(params) {
6228
+ const total = params.total + params.bytesRead;
6229
+ if (total > params.maxBytes) {
6230
+ throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got at least ${total})`);
6231
+ }
6232
+ params.chunks.push(Buffer.from(params.scratch.subarray(0, params.bytesRead)));
6233
+ return total;
6234
+ }
6235
+ async function readBoundedAsync(maxBytes, readChunk) {
6236
+ assertMaxBytes(maxBytes);
6237
+ const chunks = [];
6238
+ const scratch = createScratchBuffer(maxBytes);
6239
+ let total = 0;
6240
+ while (true) {
6241
+ const length = nextReadLength(total, maxBytes, scratch.length);
6242
+ const bytesRead = await readChunk(scratch, length);
6243
+ if (bytesRead === 0) {
6244
+ return Buffer.concat(chunks, total);
6245
+ }
6246
+ total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
6247
+ }
6248
+ }
6249
+ /**
6250
+ * Reads from the handle's current offset without closing it. A bounded read
6251
+ * consumes at most maxBytes + 1 bytes so growth after an earlier stat cannot
6252
+ * force an unbounded allocation.
6253
+ */
6254
+ async function readFileHandleBounded(handle, maxBytes) {
6255
+ return await readBoundedAsync(maxBytes, async (scratch, length) => {
6256
+ return (await handle.read(scratch, 0, length, null)).bytesRead;
6257
+ });
6258
+ }
6259
+ function readDescriptorChunk(fd, scratch, length) {
6260
+ return new Promise((resolve, reject) => {
6261
+ fs.read(fd, scratch, 0, length, null, (error, bytesRead) => {
6262
+ if (error) {
6263
+ reject(error);
6264
+ return;
6265
+ }
6266
+ resolve(bytesRead);
6267
+ });
6268
+ });
6269
+ }
6270
+ /** Async bounded read from a numeric descriptor. The caller owns the descriptor. */
6271
+ async function readFileDescriptorBounded(fd, maxBytes) {
6272
+ return await readBoundedAsync(maxBytes, async (scratch, length) => {
6273
+ return await readDescriptorChunk(fd, scratch, length);
6274
+ });
6275
+ }
6276
+ /** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
6277
+ function readFileDescriptorBoundedSync(fd, maxBytes) {
6278
+ assertMaxBytes(maxBytes);
6279
+ const chunks = [];
6280
+ const scratch = createScratchBuffer(maxBytes);
6281
+ let total = 0;
6282
+ while (true) {
6283
+ const length = nextReadLength(total, maxBytes, scratch.length);
6284
+ const bytesRead = fs.readSync(fd, scratch, 0, length, null);
6285
+ if (bytesRead === 0) {
6286
+ return Buffer.concat(chunks, total);
6287
+ }
6288
+ total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
6289
+ }
6290
+ }
6291
+
6039
6292
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/read-opened-file.js
6040
6293
 
6294
+
6041
6295
  async function read_opened_file_readOpenedFileSafely(params) {
6042
6296
  if (params.maxBytes !== undefined && params.opened.stat.size > params.maxBytes) {
6043
6297
  throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${params.opened.stat.size})`);
6044
6298
  }
6045
- const buffer = await params.opened.handle.readFile();
6046
- if (params.maxBytes !== undefined && buffer.byteLength > params.maxBytes) {
6047
- throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${buffer.byteLength})`);
6048
- }
6299
+ const buffer = params.maxBytes === undefined
6300
+ ? await params.opened.handle.readFile()
6301
+ : await readFileHandleBounded(params.opened.handle, params.maxBytes);
6049
6302
  return {
6050
6303
  buffer,
6051
6304
  realPath: params.opened.realPath,
@@ -62073,4 +62326,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
62073
62326
  // module factories are used so entry inlining is disabled
62074
62327
  // startup
62075
62328
  // Load entry module and return exports
62076
- var __webpack_exports__ = __webpack_require__(889);
62329
+ var __webpack_exports__ = __webpack_require__(580);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lousy-agents/cli",
3
- "version": "5.17.8",
3
+ "version": "5.17.9",
4
4
  "description": "CLI scaffolding tool that sets up projects with structure, instructions, and feedback loops for AI coding assistants",
5
5
  "type": "module",
6
6
  "repository": {
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "ghcr.io/devcontainers/features/copilot-cli:1.0.0": {},
12
12
  "ghcr.io/anthropics/devcontainer-features/claude-code:1": {
13
- "version": "v2.1.217"
13
+ "version": "v2.1.218"
14
14
  },
15
15
  "ghcr.io/rocker-org/devcontainer-features/apt-packages:1.0.2": {
16
16
  "packages": "libnss3, libnspr4, libatk1.0-0t64, libatk-bridge2.0-0t64, libcups2t64, libdrm2, libdbus-1-3, libatspi2.0-0t64, libxcomposite1, libxdamage1, libxfixes3, libxrandr2, libgbm1, libxkbcommon0, libasound2t64, yamllint, shellcheck",
@@ -23,7 +23,7 @@
23
23
  "devDependencies": {
24
24
  "@biomejs/biome": "2.5.3",
25
25
  "@eslint/eslintrc": "3.3.6",
26
- "@lousy-agents/mcp": "5.17.7",
26
+ "@lousy-agents/mcp": "5.17.8",
27
27
  "@modelcontextprotocol/server-sequential-thinking": "2026.7.4",
28
28
  "@testing-library/jest-dom": "7.0.0",
29
29
  "@testing-library/react": "16.3.2",
@@ -35,7 +35,7 @@
35
35
  "@vitest/ui": "4.1.10",
36
36
  "eslint": "10.7.0",
37
37
  "eslint-config-next": "16.2.11",
38
- "happy-dom": "20.11.0",
38
+ "happy-dom": "20.11.1",
39
39
  "jsdom": "29.1.1",
40
40
  "typescript": "7.0.2",
41
41
  "vitest": "4.1.10",