@openclaw/crabline 0.1.12 → 0.1.13

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 (53) hide show
  1. package/README.md +38 -11
  2. package/dist/src/cli/program.js +17 -2
  3. package/dist/src/cli/program.js.map +1 -1
  4. package/dist/src/index.d.ts +12 -10
  5. package/dist/src/index.js +6 -5
  6. package/dist/src/index.js.map +1 -1
  7. package/dist/src/openclaw/artifact-generation.d.ts +5 -9
  8. package/dist/src/openclaw/artifact-generation.js +41 -69
  9. package/dist/src/openclaw/artifact-generation.js.map +1 -1
  10. package/dist/src/openclaw/bridges/discord.d.ts +1 -0
  11. package/dist/src/openclaw/bridges/discord.js +149 -0
  12. package/dist/src/openclaw/bridges/discord.js.map +1 -0
  13. package/dist/src/openclaw/bridges/matrix.js +1 -1
  14. package/dist/src/openclaw/bridges/matrix.js.map +1 -1
  15. package/dist/src/openclaw/bridges/mattermost.js +1 -1
  16. package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
  17. package/dist/src/openclaw/bridges/signal.js +1 -1
  18. package/dist/src/openclaw/bridges/signal.js.map +1 -1
  19. package/dist/src/openclaw/bridges/slack.js +1 -1
  20. package/dist/src/openclaw/bridges/slack.js.map +1 -1
  21. package/dist/src/openclaw/bridges/telegram.js +1 -1
  22. package/dist/src/openclaw/bridges/telegram.js.map +1 -1
  23. package/dist/src/openclaw/bridges/whatsapp.js +1 -1
  24. package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
  25. package/dist/src/openclaw/bridges/zalo.js +1 -1
  26. package/dist/src/openclaw/bridges/zalo.js.map +1 -1
  27. package/dist/src/openclaw/outbound-contract.js +3 -0
  28. package/dist/src/openclaw/outbound-contract.js.map +1 -1
  29. package/dist/src/openclaw/{smoke-lock.d.ts → provider-readiness-lock.d.ts} +7 -7
  30. package/dist/src/openclaw/{smoke-lock.js → provider-readiness-lock.js} +122 -260
  31. package/dist/src/openclaw/provider-readiness-lock.js.map +1 -0
  32. package/dist/src/openclaw/shared.d.ts +9 -17
  33. package/dist/src/openclaw/shared.js +5 -6
  34. package/dist/src/openclaw/shared.js.map +1 -1
  35. package/dist/src/openclaw.d.ts +3 -5
  36. package/dist/src/openclaw.js +22 -20
  37. package/dist/src/openclaw.js.map +1 -1
  38. package/dist/src/servers/discord.d.ts +40 -0
  39. package/dist/src/servers/discord.js +842 -0
  40. package/dist/src/servers/discord.js.map +1 -0
  41. package/dist/src/servers/index.d.ts +10 -4
  42. package/dist/src/servers/index.js +4 -0
  43. package/dist/src/servers/index.js.map +1 -1
  44. package/dist/src/servers/slack.js +11 -3
  45. package/dist/src/servers/slack.js.map +1 -1
  46. package/dist/src/servers/telegram.js +51 -2
  47. package/dist/src/servers/telegram.js.map +1 -1
  48. package/dist/src/servers/whatsapp.d.ts +0 -1
  49. package/dist/src/servers/whatsapp.js +0 -1
  50. package/dist/src/servers/whatsapp.js.map +1 -1
  51. package/docs/channel-setup.md +66 -21
  52. package/package.json +1 -1
  53. package/dist/src/openclaw/smoke-lock.js.map +0 -1
@@ -11,7 +11,7 @@ const LOCK_OWNER_FILE = "owner.json";
11
11
  const LOCK_LEASE_FILE_PREFIX = "lease.";
12
12
  const LOCK_COMMIT_STAGE_FILE_PREFIX = "commit-stage.";
13
13
  const LOCK_STAGED_FILE_PREFIX = ".commit-file.";
14
- const LEGACY_RECOVERY_SUFFIX = ".recovering";
14
+ const RECOVERY_CLAIM_SUFFIX = ".recovery";
15
15
  const COMMIT_CLAIM_SUFFIX = ".commit";
16
16
  const OWNED_CLAIM_SUFFIX = ".owned";
17
17
  const RELEASE_CLAIM_SUFFIX = ".release";
@@ -57,7 +57,9 @@ const startHeartbeat = (renew, intervalMs) => {
57
57
  return {
58
58
  assertHealthy() {
59
59
  if (failure !== undefined) {
60
- throw new Error("OpenClaw Crabline smoke lock heartbeat failed.", { cause: failure });
60
+ throw new Error("OpenClaw Crabline provider readiness lock heartbeat failed.", {
61
+ cause: failure,
62
+ });
61
63
  }
62
64
  },
63
65
  async settle() {
@@ -89,7 +91,7 @@ async function readDirectoryIdentity(directoryPath) {
89
91
  try {
90
92
  const stats = await fs.lstat(directoryPath, { bigint: true });
91
93
  if (!stats.isDirectory() || stats.ino <= 0n) {
92
- throw new Error("OpenClaw Crabline smoke lock claim is not a directory.");
94
+ throw new Error("OpenClaw Crabline provider readiness lock claim is not a directory.");
93
95
  }
94
96
  return {
95
97
  device: stats.dev,
@@ -136,7 +138,7 @@ function isOwnedLockArtifactName(name, token) {
136
138
  }
137
139
  async function removeVerifiedLockDirectory(params) {
138
140
  if (!hasSameDirectoryIdentity(params.expectedIdentity, await readDirectoryIdentity(params.directoryPath))) {
139
- throw new Error("OpenClaw Crabline smoke lock cleanup target changed.");
141
+ throw new Error("OpenClaw Crabline provider readiness lock cleanup target changed.");
140
142
  }
141
143
  await params.beforeRemove?.();
142
144
  const quarantinePath = `${params.directoryPath}.cleanup.${randomUUID()}`;
@@ -147,13 +149,13 @@ async function removeVerifiedLockDirectory(params) {
147
149
  quarantinedIdentity = { device: stats.dev, inode: stats.ino };
148
150
  }
149
151
  if (!hasSameDirectoryIdentity(params.expectedIdentity, quarantinedIdentity)) {
150
- throw new Error("OpenClaw Crabline smoke lock cleanup target changed.");
152
+ throw new Error("OpenClaw Crabline provider readiness lock cleanup target changed.");
151
153
  }
152
154
  const entries = fsSync.readdirSync(quarantinePath);
153
155
  const artifacts = [];
154
156
  for (const entry of entries) {
155
157
  if (!isOwnedLockArtifactName(entry, params.token)) {
156
- throw new Error("OpenClaw Crabline smoke lock cleanup found an unexpected artifact.");
158
+ throw new Error("OpenClaw Crabline provider readiness lock cleanup found an unexpected artifact.");
157
159
  }
158
160
  const artifactPath = path.join(quarantinePath, entry);
159
161
  const artifactStats = fsSync.lstatSync(artifactPath, { bigint: true });
@@ -161,7 +163,7 @@ async function removeVerifiedLockDirectory(params) {
161
163
  artifactStats.isSymbolicLink() ||
162
164
  artifactStats.nlink !== 1n ||
163
165
  artifactStats.ino <= 0n) {
164
- throw new Error("OpenClaw Crabline smoke lock cleanup found an unsafe artifact.");
166
+ throw new Error("OpenClaw Crabline provider readiness lock cleanup found an unsafe artifact.");
165
167
  }
166
168
  artifacts.push({
167
169
  identity: { device: artifactStats.dev, inode: artifactStats.ino },
@@ -182,7 +184,7 @@ async function removeVerifiedLockDirectory(params) {
182
184
  artifactStats.nlink !== 1n ||
183
185
  artifactStats.dev !== artifact.identity.device ||
184
186
  artifactStats.ino !== artifact.identity.inode) {
185
- throw new Error("OpenClaw Crabline smoke lock cleanup artifact changed.");
187
+ throw new Error("OpenClaw Crabline provider readiness lock cleanup artifact changed.");
186
188
  }
187
189
  fsSync.unlinkSync(artifact.path);
188
190
  }
@@ -191,7 +193,7 @@ async function removeVerifiedLockDirectory(params) {
191
193
  finalStats.isSymbolicLink() ||
192
194
  finalStats.dev !== params.expectedIdentity.device ||
193
195
  finalStats.ino !== params.expectedIdentity.inode) {
194
- throw new Error("OpenClaw Crabline smoke lock cleanup target changed.");
196
+ throw new Error("OpenClaw Crabline provider readiness lock cleanup target changed.");
195
197
  }
196
198
  fsSync.rmdirSync(quarantinePath);
197
199
  }
@@ -208,32 +210,22 @@ function parseLockOwner(contents) {
208
210
  parsed = JSON.parse(contents);
209
211
  }
210
212
  catch (error) {
211
- throw new Error("OpenClaw Crabline smoke lock owner metadata is malformed.", {
213
+ throw new Error("OpenClaw Crabline provider readiness lock owner metadata is malformed.", {
212
214
  cause: error,
213
215
  });
214
216
  }
215
217
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
216
- throw new Error("OpenClaw Crabline smoke lock owner metadata is malformed.");
218
+ throw new Error("OpenClaw Crabline provider readiness lock owner metadata is malformed.");
217
219
  }
218
220
  const owner = parsed;
219
221
  if (typeof owner.channel !== "string" ||
220
222
  !isCrablineServerChannel(owner.channel) ||
223
+ !isPositiveSafeInteger(owner.createdAtMs) ||
224
+ owner.lockVersion !== 1 ||
221
225
  !isValidProcessId(owner.pid) ||
222
- typeof owner.token !== "string" ||
223
- !LOCK_TOKEN_PATTERN.test(owner.token)) {
224
- throw new Error("OpenClaw Crabline smoke lock owner metadata is malformed.");
225
- }
226
- const hasCreatedAt = owner.createdAtMs !== undefined;
227
- const hasProcessStartedAt = owner.processStartedAtMs !== undefined;
228
- if (!hasCreatedAt && !hasProcessStartedAt && owner.leaseVersion === undefined) {
229
- return {
230
- channel: owner.channel,
231
- pid: owner.pid,
232
- token: owner.token,
233
- };
234
- }
235
- if (!isPositiveSafeInteger(owner.createdAtMs) ||
236
226
  !isPositiveSafeInteger(owner.processStartedAtMs) ||
227
+ typeof owner.token !== "string" ||
228
+ !LOCK_TOKEN_PATTERN.test(owner.token) ||
237
229
  (owner.processIdentity !== undefined &&
238
230
  (typeof owner.processIdentity !== "string" ||
239
231
  owner.processIdentity.length === 0 ||
@@ -242,26 +234,12 @@ function parseLockOwner(contents) {
242
234
  (typeof owner.processIdentityV2 !== "string" ||
243
235
  owner.processIdentityV2.length === 0 ||
244
236
  owner.processIdentityV2.length > 256))) {
245
- throw new Error("OpenClaw Crabline smoke lock owner metadata is malformed.");
246
- }
247
- if (owner.leaseVersion === undefined) {
248
- return {
249
- channel: owner.channel,
250
- createdAtMs: owner.createdAtMs,
251
- pid: owner.pid,
252
- ...(owner.processIdentity ? { processIdentity: owner.processIdentity } : {}),
253
- ...(owner.processIdentityV2 ? { processIdentityV2: owner.processIdentityV2 } : {}),
254
- processStartedAtMs: owner.processStartedAtMs,
255
- token: owner.token,
256
- };
257
- }
258
- if (owner.leaseVersion !== 1) {
259
- throw new Error("OpenClaw Crabline smoke lock owner metadata is malformed.");
237
+ throw new Error("OpenClaw Crabline provider readiness lock owner metadata is malformed.");
260
238
  }
261
239
  return {
262
240
  channel: owner.channel,
263
241
  createdAtMs: owner.createdAtMs,
264
- leaseVersion: owner.leaseVersion,
242
+ lockVersion: owner.lockVersion,
265
243
  pid: owner.pid,
266
244
  ...(owner.processIdentity ? { processIdentity: owner.processIdentity } : {}),
267
245
  ...(owner.processIdentityV2 ? { processIdentityV2: owner.processIdentityV2 } : {}),
@@ -288,17 +266,15 @@ async function readLockRecord(lockDirectory) {
288
266
  try {
289
267
  handle = await fs.open(path.join(lockDirectory, LOCK_OWNER_FILE), "r");
290
268
  const record = await readLockRecordFromHandle(handle);
291
- if (isRenewableOwner(record.owner)) {
292
- try {
293
- const leaseStats = await fs.stat(path.join(lockDirectory, leaseFileName(record.owner.token)));
294
- if (Number.isFinite(leaseStats.mtimeMs) && leaseStats.mtimeMs > 0) {
295
- record.renewedAtMs = Math.max(record.renewedAtMs, leaseStats.mtimeMs);
296
- }
269
+ try {
270
+ const leaseStats = await fs.stat(path.join(lockDirectory, leaseFileName(record.owner.token)));
271
+ if (Number.isFinite(leaseStats.mtimeMs) && leaseStats.mtimeMs > 0) {
272
+ record.renewedAtMs = Math.max(record.renewedAtMs, leaseStats.mtimeMs);
297
273
  }
298
- catch (error) {
299
- if (!isMissingPathError(error)) {
300
- throw error;
301
- }
274
+ }
275
+ catch (error) {
276
+ if (!isMissingPathError(error)) {
277
+ throw error;
302
278
  }
303
279
  }
304
280
  return { kind: "record", record };
@@ -313,12 +289,6 @@ async function readLockRecord(lockDirectory) {
313
289
  await handle?.close();
314
290
  }
315
291
  }
316
- function hasProcessIdentity(owner) {
317
- return "processStartedAtMs" in owner;
318
- }
319
- function isRenewableOwner(owner) {
320
- return "leaseVersion" in owner && owner.leaseVersion === 1;
321
- }
322
292
  export const isProcessAlive = (pid) => {
323
293
  if (!isValidProcessId(pid)) {
324
294
  return false;
@@ -443,29 +413,22 @@ function getCachedCurrentProcessIdentityV2() {
443
413
  return (cachedCurrentProcessIdentityV2 ??= getProcessIdentityV2(process.pid));
444
414
  }
445
415
  function hasExactProcessIdentity(owner) {
446
- return hasProcessIdentity(owner) && typeof owner.processIdentity === "string";
447
- }
448
- function hasCoarseDarwinProcessIdentity(owner) {
449
- if (!hasExactProcessIdentity(owner)) {
450
- return false;
451
- }
452
- return /^darwin:\d+\.\d+:(?!us:).+$/u.test(owner.processIdentity);
416
+ return typeof owner.processIdentity === "string";
453
417
  }
454
418
  function compareProcessIdentity(owner, runtime) {
455
- if (hasProcessIdentity(owner) &&
456
- owner.pid === runtime.currentPid &&
419
+ if (owner.pid === runtime.currentPid &&
457
420
  owner.processStartedAtMs !== runtime.currentProcessStartedAtMs) {
458
421
  return "mismatch";
459
422
  }
460
423
  if (!hasExactProcessIdentity(owner)) {
461
424
  return "unknown";
462
425
  }
463
- if (hasProcessIdentity(owner) && typeof owner.processIdentityV2 === "string") {
426
+ if (typeof owner.processIdentityV2 === "string") {
464
427
  const actualProcessIdentityV2 = owner.pid === runtime.currentPid
465
428
  ? runtime.currentProcessIdentityV2
466
429
  : runtime.getProcessIdentityV2(owner.pid);
467
430
  if (actualProcessIdentityV2 !== null) {
468
- return owner.processIdentityV2 === actualProcessIdentityV2 ? "v2" : "mismatch";
431
+ return owner.processIdentityV2 === actualProcessIdentityV2 ? "match" : "mismatch";
469
432
  }
470
433
  }
471
434
  const actualProcessIdentity = owner.pid === runtime.currentPid
@@ -474,7 +437,7 @@ function compareProcessIdentity(owner, runtime) {
474
437
  if (actualProcessIdentity === null) {
475
438
  return "unknown";
476
439
  }
477
- return owner.processIdentity === actualProcessIdentity ? "legacy" : "mismatch";
440
+ return owner.processIdentity === actualProcessIdentity ? "match" : "mismatch";
478
441
  }
479
442
  function hasProcessIdentityMismatch(owner, runtime) {
480
443
  return compareProcessIdentity(owner, runtime) === "mismatch";
@@ -488,21 +451,11 @@ function isLockOwnerActive(record, runtime) {
488
451
  if (identityMatch === "mismatch") {
489
452
  return false;
490
453
  }
491
- if (!isRenewableOwner(owner)) {
492
- if (owner.pid !== runtime.currentPid &&
493
- hasCoarseDarwinProcessIdentity(owner) &&
494
- identityMatch !== "v2") {
495
- const ageMs = runtime.now() - Math.max(owner.createdAtMs, record.renewedAtMs);
496
- return ageMs >= -runtime.leaseMs && ageMs <= runtime.leaseMs;
497
- }
498
- return true;
499
- }
500
454
  const ageMs = runtime.now() - Math.max(owner.createdAtMs, record.renewedAtMs);
501
455
  return ageMs >= -runtime.leaseMs && ageMs <= runtime.leaseMs;
502
456
  }
503
457
  function needsLiveOwnerConfirmation(record, runtime) {
504
- return (isRenewableOwner(record.owner) &&
505
- runtime.isProcessAlive(record.owner.pid) &&
458
+ return (runtime.isProcessAlive(record.owner.pid) &&
506
459
  !hasProcessIdentityMismatch(record.owner, runtime) &&
507
460
  !isLockOwnerActive(record, runtime));
508
461
  }
@@ -527,7 +480,7 @@ async function confirmObservedOwner(lockDirectory, observed, runtime) {
527
480
  : "unchanged";
528
481
  }
529
482
  function activeRunError(params) {
530
- return new Error(`OpenClaw Crabline smoke is already running for channel "${params.channel ?? "unknown"}" in "${params.outputDir}"; cannot start channel "${params.requestedChannel}".`, params.cause === undefined ? undefined : { cause: params.cause });
483
+ return new Error(`OpenClaw Crabline provider readiness is already running for channel "${params.channel ?? "unknown"}" in "${params.outputDir}"; cannot start channel "${params.requestedChannel}".`, params.cause === undefined ? undefined : { cause: params.cause });
531
484
  }
532
485
  function isPathConfinedTo(rootPath, candidatePath) {
533
486
  const relative = path.relative(rootPath, candidatePath);
@@ -537,7 +490,7 @@ function isPathConfinedTo(rootPath, candidatePath) {
537
490
  async function resolveConfinedPath(rootPath, candidatePath, description, candidateKind) {
538
491
  const resolvedPath = path.resolve(candidatePath);
539
492
  if (resolvedPath !== candidatePath) {
540
- throw new Error(`OpenClaw Crabline smoke lock ${description} escapes its output directory.`);
493
+ throw new Error(`OpenClaw Crabline provider readiness lock ${description} escapes its output directory.`);
541
494
  }
542
495
  const parentPath = candidateKind === "directory" ? resolvedPath : path.dirname(resolvedPath);
543
496
  let realParentPath;
@@ -545,10 +498,10 @@ async function resolveConfinedPath(rootPath, candidatePath, description, candida
545
498
  realParentPath = await fs.realpath(parentPath);
546
499
  }
547
500
  catch {
548
- throw new Error(`OpenClaw Crabline smoke lock ${description} is invalid.`);
501
+ throw new Error(`OpenClaw Crabline provider readiness lock ${description} is invalid.`);
549
502
  }
550
503
  if (!isPathConfinedTo(rootPath, realParentPath)) {
551
- throw new Error(`OpenClaw Crabline smoke lock ${description} escapes its output directory.`);
504
+ throw new Error(`OpenClaw Crabline provider readiness lock ${description} escapes its output directory.`);
552
505
  }
553
506
  return candidateKind === "directory"
554
507
  ? realParentPath
@@ -616,56 +569,31 @@ async function removeOwnedLock(params) {
616
569
  }
617
570
  return true;
618
571
  }
619
- async function renewCompatibilityMarker(params) {
620
- await params.securedDirectory.assertIdentityAt(params.lockDirectory);
572
+ async function assertReservationOwned(params) {
621
573
  const observed = await readLockRecord(params.lockDirectory);
622
574
  if (observed.kind === "missing" || observed.record.owner.token !== params.token) {
623
- return false;
575
+ throw new Error("OpenClaw Crabline provider readiness lock reservation was lost.");
576
+ }
577
+ try {
578
+ await params.securedDirectory.assertIdentityAt(params.lockDirectory);
579
+ }
580
+ catch (error) {
581
+ throw new Error("OpenClaw Crabline provider readiness lock reservation was lost.", {
582
+ cause: error,
583
+ });
624
584
  }
625
- const renewedAt = new Date(params.renewedAtMs);
626
- await params.handle.utimes(renewedAt, renewedAt);
627
- await params.handle.sync();
628
- await params.securedDirectory.assertIdentityAt(params.lockDirectory);
629
585
  const revalidated = await readLockRecord(params.lockDirectory);
630
- return revalidated.kind === "record" && revalidated.record.owner.token === params.token;
631
- }
632
- async function assertCompatibilityMarkerOwned(params) {
633
- await params.securedDirectory.assertIdentityAt(params.lockDirectory);
634
- const observed = await readLockRecord(params.lockDirectory);
635
- if (observed.kind === "missing" || observed.record.owner.token !== params.token) {
636
- throw new Error("OpenClaw Crabline smoke lock compatibility marker was lost.");
586
+ if (revalidated.kind === "missing" || revalidated.record.owner.token !== params.token) {
587
+ throw new Error("OpenClaw Crabline provider readiness lock reservation was lost.");
637
588
  }
638
589
  }
639
- async function retireCompatibilityMarker(handle, owner) {
640
- const retiredOwner = {
641
- ...owner,
642
- createdAtMs: 1,
643
- pid: MAX_PROCESS_ID,
644
- ...(owner.processIdentity ? { processIdentity: "retired" } : {}),
645
- ...(owner.processIdentityV2 ? { processIdentityV2: "retired" } : {}),
646
- processStartedAtMs: 1,
647
- };
648
- const originalBytes = Buffer.byteLength(`${JSON.stringify(owner)}\n`, "utf8");
649
- const retiredJson = JSON.stringify(retiredOwner);
650
- const retiredBytes = Buffer.byteLength(retiredJson, "utf8") + 1;
651
- const targetBytes = Math.max(originalBytes, retiredBytes);
652
- const paddingBytes = targetBytes - retiredBytes;
653
- const retiredContents = `${retiredJson}${" ".repeat(paddingBytes)}\n`;
654
- const written = await handle.write(retiredContents, 0, "utf8");
655
- if (written.bytesWritten !== targetBytes) {
656
- throw new Error("OpenClaw Crabline smoke lock compatibility marker retirement was incomplete.");
657
- }
658
- const retiredAt = new Date(1);
659
- await handle.utimes(retiredAt, retiredAt);
660
- await handle.sync();
661
- }
662
590
  async function renewOwnedLock(lockDirectory, token, renewedAtMs) {
663
591
  const temporaryLeasePath = path.join(lockDirectory, `.${leaseFileName(token)}.${randomUUID()}.tmp`);
664
592
  try {
665
593
  const observed = await readLockRecord(lockDirectory);
666
594
  if (observed.kind === "missing" ||
667
595
  observed.record.owner.token !== token ||
668
- !isRenewableOwner(observed.record.owner)) {
596
+ observed.record.owner.lockVersion !== 1) {
669
597
  return false;
670
598
  }
671
599
  await fs.writeFile(temporaryLeasePath, `${JSON.stringify({ renewedAtMs, token })}\n`, {
@@ -679,7 +607,7 @@ async function renewOwnedLock(lockDirectory, token, renewedAtMs) {
679
607
  const revalidated = await readLockRecord(lockDirectory);
680
608
  if (revalidated.kind === "missing" ||
681
609
  revalidated.record.owner.token !== token ||
682
- !isRenewableOwner(revalidated.record.owner)) {
610
+ revalidated.record.owner.lockVersion !== 1) {
683
611
  return false;
684
612
  }
685
613
  await fs.rename(temporaryLeasePath, path.join(lockDirectory, leaseFileName(token)));
@@ -701,7 +629,7 @@ function claimPrefix(lockDirectory, suffix) {
701
629
  async function listLockClaims(lockDirectory) {
702
630
  const directory = path.dirname(lockDirectory);
703
631
  const prefixes = [
704
- { kind: "recovering", prefix: claimPrefix(lockDirectory, LEGACY_RECOVERY_SUFFIX) },
632
+ { kind: "recovery", prefix: claimPrefix(lockDirectory, RECOVERY_CLAIM_SUFFIX) },
705
633
  { kind: "commit", prefix: claimPrefix(lockDirectory, COMMIT_CLAIM_SUFFIX) },
706
634
  { kind: "owned", prefix: claimPrefix(lockDirectory, OWNED_CLAIM_SUFFIX) },
707
635
  { kind: "release", prefix: claimPrefix(lockDirectory, RELEASE_CLAIM_SUFFIX) },
@@ -714,7 +642,7 @@ async function listLockClaims(lockDirectory) {
714
642
  continue;
715
643
  }
716
644
  if (!entry.isDirectory()) {
717
- throw new Error("OpenClaw Crabline smoke lock claim is not a directory.");
645
+ throw new Error("OpenClaw Crabline provider readiness lock claim is not a directory.");
718
646
  }
719
647
  claims.push({
720
648
  directoryPath: path.join(directory, entry.name),
@@ -723,26 +651,6 @@ async function listLockClaims(lockDirectory) {
723
651
  }
724
652
  return claims.sort((left, right) => left.directoryPath.localeCompare(right.directoryPath));
725
653
  }
726
- async function claimLegacyRecoveryDirectory(lockDirectory, expectedToken) {
727
- const legacyRecoveryDirectory = `${lockDirectory}${LEGACY_RECOVERY_SUFFIX}`;
728
- if (!(await pathExists(legacyRecoveryDirectory))) {
729
- return;
730
- }
731
- if (expectedToken !== undefined) {
732
- const observed = await readLockRecord(legacyRecoveryDirectory);
733
- if (observed.kind === "missing" || observed.record.owner.token !== expectedToken) {
734
- return;
735
- }
736
- }
737
- try {
738
- await fs.rename(legacyRecoveryDirectory, `${lockDirectory}${LEGACY_RECOVERY_SUFFIX}.${randomUUID()}`);
739
- }
740
- catch (error) {
741
- if (!isMissingPathError(error)) {
742
- throw error;
743
- }
744
- }
745
- }
746
654
  async function resolveLockClaim(params) {
747
655
  const observed = await readLockRecord(params.claim.directoryPath);
748
656
  if (observed.kind === "missing") {
@@ -766,7 +674,7 @@ async function resolveLockClaim(params) {
766
674
  return;
767
675
  }
768
676
  if (isLockOwnerActive(observed.record, params.runtime)) {
769
- if (params.claim.kind === "recovering") {
677
+ if (params.claim.kind === "recovery") {
770
678
  try {
771
679
  await fs.rename(params.claim.directoryPath, params.lockDirectory);
772
680
  }
@@ -784,7 +692,7 @@ async function resolveLockClaim(params) {
784
692
  }
785
693
  const confirmation = await confirmObservedOwner(params.claim.directoryPath, observed.record, params.runtime);
786
694
  if (confirmation === "renewed") {
787
- if (params.claim.kind === "recovering") {
695
+ if (params.claim.kind === "recovery") {
788
696
  try {
789
697
  await fs.rename(params.claim.directoryPath, params.lockDirectory);
790
698
  }
@@ -825,7 +733,6 @@ async function resolveLockClaim(params) {
825
733
  }
826
734
  }
827
735
  async function resolveLockClaims(params) {
828
- await claimLegacyRecoveryDirectory(params.lockDirectory);
829
736
  for (const claim of await listLockClaims(params.lockDirectory)) {
830
737
  await resolveLockClaim({
831
738
  ...params,
@@ -858,7 +765,7 @@ async function resolveLockContention(params) {
858
765
  }
859
766
  }
860
767
  await params.beforeRecoveryClaim();
861
- const claimDirectory = `${params.lockDirectory}${LEGACY_RECOVERY_SUFFIX}.${randomUUID()}`;
768
+ const claimDirectory = `${params.lockDirectory}${RECOVERY_CLAIM_SUFFIX}.${randomUUID()}`;
862
769
  try {
863
770
  await fs.rename(params.lockDirectory, claimDirectory);
864
771
  }
@@ -873,7 +780,7 @@ async function resolveLockContention(params) {
873
780
  ...params,
874
781
  claim: {
875
782
  directoryPath: claimDirectory,
876
- kind: "recovering",
783
+ kind: "recovery",
877
784
  },
878
785
  });
879
786
  }
@@ -918,7 +825,7 @@ async function createLockCandidate(params) {
918
825
  throw error;
919
826
  }
920
827
  }
921
- export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies = {}) {
828
+ export async function acquireOpenClawCrablineProviderReadinessLock(params, dependencies = {}) {
922
829
  const requestedOutputDir = path.resolve(params.outputDir);
923
830
  await fs.mkdir(requestedOutputDir, { recursive: true });
924
831
  const outputDir = await fs.realpath(requestedOutputDir);
@@ -932,7 +839,7 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
932
839
  await securedOutputDirectory.assertIdentityAt(outputDir);
933
840
  const outputDirectoryIdentity = await readDirectoryIdentity(outputDir);
934
841
  if (outputDirectoryIdentity === null) {
935
- throw new Error("OpenClaw Crabline smoke lock output directory is invalid.");
842
+ throw new Error("OpenClaw Crabline provider readiness lock output directory is invalid.");
936
843
  }
937
844
  const lockDirectory = path.join(outputDir, `.${OPENCLAW_CRABLINE_MANIFEST_PATH}.lock`);
938
845
  const token = randomUUID();
@@ -968,12 +875,12 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
968
875
  !isPositiveSafeInteger(runtime.currentProcessStartedAtMs) ||
969
876
  !isPositiveSafeInteger(runtime.leaseMs) ||
970
877
  !isPositiveSafeInteger(createdAtMs)) {
971
- throw new Error("Invalid OpenClaw Crabline smoke lock runtime.");
878
+ throw new Error("Invalid OpenClaw Crabline provider readiness lock runtime.");
972
879
  }
973
880
  const owner = {
974
881
  channel: params.channel,
975
882
  createdAtMs,
976
- leaseVersion: 1,
883
+ lockVersion: 1,
977
884
  pid: runtime.currentPid,
978
885
  ...(runtime.currentProcessIdentity ? { processIdentity: runtime.currentProcessIdentity } : {}),
979
886
  ...(runtime.currentProcessIdentityV2
@@ -982,13 +889,9 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
982
889
  processStartedAtMs: runtime.currentProcessStartedAtMs,
983
890
  token,
984
891
  };
985
- const compatibilityOwner = {
986
- ...owner,
987
- createdAtMs: 1,
988
- };
989
892
  for (;;) {
990
893
  const lockClaims = await listLockClaims(lockDirectory);
991
- if (lockClaims.length > 0 || (await pathExists(`${lockDirectory}${LEGACY_RECOVERY_SUFFIX}`))) {
894
+ if (lockClaims.length > 0) {
992
895
  await resolveLockClaims({
993
896
  beforeRecoveryDeleteClaim: dependencies.beforeRecoveryDeleteClaim ?? (async () => undefined),
994
897
  lockDirectory,
@@ -1000,12 +903,11 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1000
903
  }
1001
904
  const ownerDirectory = `${lockDirectory}${OWNED_CLAIM_SUFFIX}.${token}`;
1002
905
  const { candidateDirectory, securedDirectory } = await createLockCandidate({
1003
- ...(dependencies.afterLockOwnerWrite
1004
- ? { afterOwnerWrite: dependencies.afterLockOwnerWrite }
906
+ ...(dependencies.afterLockReservationWrite
907
+ ? { afterOwnerWrite: dependencies.afterLockReservationWrite }
1005
908
  : {}),
1006
- includeLease: false,
1007
909
  lockDirectory,
1008
- owner: compatibilityOwner,
910
+ owner,
1009
911
  ...(dependencies.platform ? { platform: dependencies.platform } : {}),
1010
912
  ...(dependencies.secureWindowsDirectory
1011
913
  ? { secureWindowsDirectory: dependencies.secureWindowsDirectory }
@@ -1027,34 +929,10 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1027
929
  await fs.rm(candidateDirectory, { force: true, recursive: true }).catch(() => undefined);
1028
930
  throw error;
1029
931
  }
1030
- let compatibilityMarkerHandle;
1031
- try {
1032
- compatibilityMarkerHandle = await fs.open(path.join(candidateDirectory, LOCK_OWNER_FILE), "r+");
1033
- const compatibilityRecord = await readLockRecordFromHandle(compatibilityMarkerHandle);
1034
- if (compatibilityRecord.owner.token !== token) {
1035
- throw new Error("OpenClaw Crabline smoke lock compatibility marker was replaced.");
1036
- }
1037
- const activeAt = new Date(createdAtMs);
1038
- await compatibilityMarkerHandle.utimes(activeAt, activeAt);
1039
- await compatibilityMarkerHandle.sync();
1040
- }
1041
- catch (error) {
1042
- await compatibilityMarkerHandle?.close().catch(() => undefined);
1043
- await fs.rm(candidateDirectory, { force: true, recursive: true }).catch(() => undefined);
1044
- await fs
1045
- .rm(ownerCandidate.candidateDirectory, { force: true, recursive: true })
1046
- .catch(() => undefined);
1047
- throw error;
1048
- }
1049
- if (!compatibilityMarkerHandle) {
1050
- throw new Error("OpenClaw Crabline smoke lock compatibility marker was not opened.");
1051
- }
1052
- const markerHandle = compatibilityMarkerHandle;
1053
932
  try {
1054
933
  await fs.rename(candidateDirectory, lockDirectory);
1055
934
  }
1056
935
  catch (error) {
1057
- await markerHandle.close().catch(() => undefined);
1058
936
  await fs.rm(candidateDirectory, { force: true, recursive: true }).catch(() => undefined);
1059
937
  await fs
1060
938
  .rm(ownerCandidate.candidateDirectory, { force: true, recursive: true })
@@ -1076,8 +954,6 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1076
954
  await securedDirectory.assertIdentityAt(lockDirectory);
1077
955
  const installed = await readLockRecord(lockDirectory);
1078
956
  if (installed.kind !== "record" || installed.record.owner.token !== token) {
1079
- await retireCompatibilityMarker(markerHandle, compatibilityOwner);
1080
- await markerHandle.close();
1081
957
  await fs.rm(ownerCandidate.candidateDirectory, { force: true, recursive: true });
1082
958
  continue;
1083
959
  }
@@ -1099,44 +975,38 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1099
975
  else {
1100
976
  await fs.rm(ownerCandidate.candidateDirectory, { force: true, recursive: true });
1101
977
  }
1102
- await retireCompatibilityMarker(markerHandle, compatibilityOwner);
978
+ await removeOwnedLock({
979
+ lockDirectory,
980
+ ownedDirectory: lockDirectory,
981
+ token,
982
+ });
1103
983
  }
1104
984
  catch (caughtCleanupError) {
1105
985
  cleanupError = caughtCleanupError;
1106
986
  }
1107
- try {
1108
- await markerHandle.close();
1109
- }
1110
- catch (closeError) {
1111
- cleanupError =
1112
- cleanupError === undefined
1113
- ? closeError
1114
- : new AggregateError([cleanupError, closeError], "OpenClaw Crabline smoke lock setup cleanup also failed.");
1115
- }
1116
987
  if (cleanupError !== undefined) {
1117
- const aggregateError = new AggregateError([error, cleanupError], "OpenClaw Crabline smoke lock setup and cleanup failed.");
988
+ const aggregateError = new AggregateError([error, cleanupError], "OpenClaw Crabline provider readiness lock setup and cleanup failed.");
1118
989
  aggregateError.cause = error;
1119
990
  throw aggregateError;
1120
991
  }
1121
992
  throw error;
1122
993
  }
1123
994
  const competingClaims = (await listLockClaims(lockDirectory)).filter((claim) => claim.directoryPath !== ownerDirectory);
1124
- if (competingClaims.length > 0 ||
1125
- (await pathExists(`${lockDirectory}${LEGACY_RECOVERY_SUFFIX}`))) {
995
+ if (competingClaims.length > 0) {
1126
996
  const removed = await removeOwnedLock({
1127
997
  lockDirectory,
1128
998
  ownedDirectory: ownerDirectory,
1129
999
  token,
1130
1000
  });
1131
1001
  if (!removed) {
1132
- await markerHandle.close();
1133
- throw new Error("OpenClaw Crabline smoke lock ownership was lost.");
1134
- }
1135
- try {
1136
- await retireCompatibilityMarker(markerHandle, compatibilityOwner);
1002
+ throw new Error("OpenClaw Crabline provider readiness lock ownership was lost.");
1137
1003
  }
1138
- finally {
1139
- await markerHandle.close();
1004
+ if (!(await removeOwnedLock({
1005
+ lockDirectory,
1006
+ ownedDirectory: lockDirectory,
1007
+ token,
1008
+ }))) {
1009
+ throw new Error("OpenClaw Crabline provider readiness lock reservation was lost.");
1140
1010
  }
1141
1011
  await resolveLockClaims({
1142
1012
  beforeRecoveryDeleteClaim: dependencies.beforeRecoveryDeleteClaim ?? (async () => undefined),
@@ -1153,19 +1023,7 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1153
1023
  let lastRenewedAtMs = createdAtMs;
1154
1024
  let pendingRenewal = Promise.resolve();
1155
1025
  let commitFenceConsumed = false;
1156
- let markerClosed = false;
1157
- let markerRetired = false;
1158
1026
  let heartbeat;
1159
- const retireMarker = async () => {
1160
- if (!markerRetired) {
1161
- await retireCompatibilityMarker(markerHandle, compatibilityOwner);
1162
- markerRetired = true;
1163
- }
1164
- if (!markerClosed) {
1165
- await markerHandle.close();
1166
- markerClosed = true;
1167
- }
1168
- };
1169
1027
  const renew = async () => {
1170
1028
  const renewal = pendingRenewal
1171
1029
  .catch(() => undefined)
@@ -1180,20 +1038,14 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1180
1038
  lastObservedNowMs = nowMs;
1181
1039
  const renewedAtMs = clockRegressed ? lastRenewedAtMs + 1 : nowMs;
1182
1040
  if (!Number.isSafeInteger(renewedAtMs)) {
1183
- throw new Error("OpenClaw Crabline smoke lock renewal timestamp overflowed.");
1041
+ throw new Error("OpenClaw Crabline provider readiness lock renewal timestamp overflowed.");
1184
1042
  }
1185
1043
  if (!(await renewOwnedLock(ownedDirectory, token, renewedAtMs))) {
1186
- throw new Error("OpenClaw Crabline smoke lock ownership was lost.");
1044
+ throw new Error("OpenClaw Crabline provider readiness lock ownership was lost.");
1187
1045
  }
1188
- await dependencies.beforeCompatibilityMarkerRenew?.();
1189
- if (!(await renewCompatibilityMarker({
1190
- handle: markerHandle,
1191
- lockDirectory,
1192
- renewedAtMs,
1193
- securedDirectory,
1194
- token,
1195
- }))) {
1196
- throw new Error("OpenClaw Crabline smoke lock compatibility marker was lost.");
1046
+ await dependencies.beforeReservationRenew?.();
1047
+ if (!(await renewOwnedLock(lockDirectory, token, renewedAtMs))) {
1048
+ throw new Error("OpenClaw Crabline provider readiness lock reservation was lost.");
1197
1049
  }
1198
1050
  lastRenewedAtMs = renewedAtMs;
1199
1051
  });
@@ -1210,10 +1062,14 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1210
1062
  ownedDirectory: ownerDirectory,
1211
1063
  token,
1212
1064
  });
1213
- await retireMarker();
1065
+ await removeOwnedLock({
1066
+ lockDirectory,
1067
+ ownedDirectory: lockDirectory,
1068
+ token,
1069
+ });
1214
1070
  }
1215
1071
  catch (cleanupError) {
1216
- const aggregateError = new AggregateError([error, cleanupError], "OpenClaw Crabline smoke lock heartbeat startup and cleanup failed.");
1072
+ const aggregateError = new AggregateError([error, cleanupError], "OpenClaw Crabline provider readiness lock heartbeat startup and cleanup failed.");
1217
1073
  aggregateError.cause = error;
1218
1074
  throw aggregateError;
1219
1075
  }
@@ -1224,12 +1080,12 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1224
1080
  return {
1225
1081
  async assertOwned() {
1226
1082
  if (released || heartbeatStopped) {
1227
- throw new Error("OpenClaw Crabline smoke lock has already been released.");
1083
+ throw new Error("OpenClaw Crabline provider readiness lock has already been released.");
1228
1084
  }
1229
1085
  heartbeat.assertHealthy();
1230
1086
  await renew();
1231
1087
  heartbeat.assertHealthy();
1232
- await assertCompatibilityMarkerOwned({
1088
+ await assertReservationOwned({
1233
1089
  lockDirectory,
1234
1090
  securedDirectory,
1235
1091
  token,
@@ -1237,10 +1093,10 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1237
1093
  },
1238
1094
  async commitFileAtomically({ contents, destinationPath, stageDirectory, stageFile }) {
1239
1095
  if (released) {
1240
- throw new Error("OpenClaw Crabline smoke lock has already been released.");
1096
+ throw new Error("OpenClaw Crabline provider readiness lock has already been released.");
1241
1097
  }
1242
1098
  if (commitFenceConsumed || heartbeatStopped || ownedDirectory !== ownerDirectory) {
1243
- throw new Error("OpenClaw Crabline smoke lock has already committed its fence.");
1099
+ throw new Error("OpenClaw Crabline provider readiness lock has already committed its fence.");
1244
1100
  }
1245
1101
  heartbeat.assertHealthy();
1246
1102
  await renew();
@@ -1253,10 +1109,10 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1253
1109
  await securedDestinationDirectory.assertIdentityAt(destinationDirectory);
1254
1110
  const destinationDirectoryIdentity = await readDirectoryIdentity(destinationDirectory);
1255
1111
  if (destinationDirectoryIdentity === null) {
1256
- throw new Error("OpenClaw Crabline smoke lock commit destination is invalid.");
1112
+ throw new Error("OpenClaw Crabline provider readiness lock commit destination is invalid.");
1257
1113
  }
1258
1114
  if (destinationDirectoryIdentity.device !== outputDirectoryIdentity.device) {
1259
- throw new Error("OpenClaw Crabline smoke lock commit destination is on another filesystem.");
1115
+ throw new Error("OpenClaw Crabline provider readiness lock commit destination is on another filesystem.");
1260
1116
  }
1261
1117
  const resolvedStageDirectory = stageDirectory
1262
1118
  ? await resolveConfinedPath(outputDir, stageDirectory, "commit stage directory", "directory")
@@ -1267,35 +1123,35 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1267
1123
  stageDirectoryIdentity = await readDirectoryIdentity(resolvedStageDirectory);
1268
1124
  }
1269
1125
  catch (error) {
1270
- throw new Error("OpenClaw Crabline smoke lock commit stage directory is invalid.", {
1126
+ throw new Error("OpenClaw Crabline provider readiness lock commit stage directory is invalid.", {
1271
1127
  cause: error,
1272
1128
  });
1273
1129
  }
1274
1130
  }
1275
1131
  if (stageDirectory && stageDirectoryIdentity === null) {
1276
- throw new Error("OpenClaw Crabline smoke lock commit stage directory is invalid.");
1132
+ throw new Error("OpenClaw Crabline provider readiness lock commit stage directory is invalid.");
1277
1133
  }
1278
1134
  if (stageDirectoryIdentity !== null &&
1279
1135
  stageDirectoryIdentity.device !== outputDirectoryIdentity.device) {
1280
- throw new Error("OpenClaw Crabline smoke lock commit stage directory is on another filesystem.");
1136
+ throw new Error("OpenClaw Crabline provider readiness lock commit stage directory is on another filesystem.");
1281
1137
  }
1282
1138
  const stagedFileName = `.commit-file.${token}.${randomUUID()}.tmp`;
1283
1139
  const stagedFilePath = path.join(ownerDirectory, stagedFileName);
1284
1140
  await stageFile(stagedFilePath, contents);
1285
1141
  if (stageDirectory) {
1286
1142
  if (!hasSameDirectoryIdentity(stageDirectoryIdentity, await readDirectoryIdentity(resolvedStageDirectory))) {
1287
- throw new Error("OpenClaw Crabline smoke lock commit stage directory identity changed.");
1143
+ throw new Error("OpenClaw Crabline provider readiness lock commit stage directory identity changed.");
1288
1144
  }
1289
1145
  }
1290
1146
  const stagedFileIdentity = await readFileIdentity(stagedFilePath);
1291
1147
  if (stagedFileIdentity === null) {
1292
- throw new Error("OpenClaw Crabline smoke lock commit stage file is invalid.");
1148
+ throw new Error("OpenClaw Crabline provider readiness lock commit stage file is invalid.");
1293
1149
  }
1294
1150
  heartbeat.assertHealthy();
1295
1151
  await renew();
1296
1152
  heartbeat.assertHealthy();
1297
1153
  await dependencies.beforeCommitClaim?.();
1298
- await assertCompatibilityMarkerOwned({
1154
+ await assertReservationOwned({
1299
1155
  lockDirectory,
1300
1156
  securedDirectory,
1301
1157
  token,
@@ -1306,14 +1162,14 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1306
1162
  if (observedIdentity === null ||
1307
1163
  observed.kind === "missing" ||
1308
1164
  observed.record.owner.token !== token) {
1309
- throw new Error("OpenClaw Crabline smoke lock ownership was lost.");
1165
+ throw new Error("OpenClaw Crabline provider readiness lock ownership was lost.");
1310
1166
  }
1311
1167
  try {
1312
1168
  await fs.rename(ownerDirectory, commitClaim);
1313
1169
  }
1314
1170
  catch (error) {
1315
1171
  if (isMissingPathError(error)) {
1316
- throw new Error("OpenClaw Crabline smoke lock ownership was lost.", {
1172
+ throw new Error("OpenClaw Crabline provider readiness lock ownership was lost.", {
1317
1173
  cause: error,
1318
1174
  });
1319
1175
  }
@@ -1337,7 +1193,7 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1337
1193
  }
1338
1194
  }
1339
1195
  }
1340
- throw new Error("OpenClaw Crabline smoke lock commit fence was lost.");
1196
+ throw new Error("OpenClaw Crabline provider readiness lock commit fence was lost.");
1341
1197
  }
1342
1198
  await dependencies.beforeCommitFileRename?.();
1343
1199
  heartbeat.assertHealthy();
@@ -1346,7 +1202,7 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1346
1202
  await heartbeat.settle();
1347
1203
  heartbeat.assertHealthy();
1348
1204
  await dependencies.beforeCommitRename?.();
1349
- await assertCompatibilityMarkerOwned({
1205
+ await assertReservationOwned({
1350
1206
  lockDirectory,
1351
1207
  securedDirectory,
1352
1208
  token,
@@ -1357,16 +1213,16 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1357
1213
  const currentFileIdentity = await readFileIdentity(claimedStagedFilePath);
1358
1214
  if (!hasSameDirectoryIdentity(stageDirectoryIdentity, currentDirectoryIdentity) ||
1359
1215
  !hasSameDirectoryIdentity(stagedFileIdentity, currentFileIdentity)) {
1360
- const error = new Error("OpenClaw Crabline smoke lock commit stage identity changed.");
1216
+ const error = new Error("OpenClaw Crabline provider readiness lock commit stage identity changed.");
1361
1217
  Object.assign(error, { code: "ENOENT", path: claimedStagedFilePath });
1362
1218
  throw error;
1363
1219
  }
1364
1220
  }
1365
1221
  else if (!hasSameDirectoryIdentity(stagedFileIdentity, await readFileIdentity(claimedStagedFilePath))) {
1366
- throw new Error("OpenClaw Crabline smoke lock commit stage identity changed.");
1222
+ throw new Error("OpenClaw Crabline provider readiness lock commit stage identity changed.");
1367
1223
  }
1368
1224
  if (!hasSameDirectoryIdentity(outputDirectoryIdentity, await readDirectoryIdentity(outputDir))) {
1369
- throw new Error("OpenClaw Crabline smoke lock output directory identity changed.");
1225
+ throw new Error("OpenClaw Crabline provider readiness lock output directory identity changed.");
1370
1226
  }
1371
1227
  await securedOutputDirectory.assertIdentityAt(outputDir);
1372
1228
  let currentDestinationDirectoryIdentity = null;
@@ -1377,14 +1233,14 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1377
1233
  // A replaced directory or symlink is an identity mismatch.
1378
1234
  }
1379
1235
  if (!hasSameDirectoryIdentity(destinationDirectoryIdentity, currentDestinationDirectoryIdentity)) {
1380
- throw new Error("OpenClaw Crabline smoke lock commit destination directory identity changed.");
1236
+ throw new Error("OpenClaw Crabline provider readiness lock commit destination directory identity changed.");
1381
1237
  }
1382
1238
  await securedDestinationDirectory.assertIdentityAt(destinationDirectory);
1383
1239
  const revalidatedDestinationPath = await resolveConfinedPath(outputDir, resolvedDestinationPath, "commit destination", "file");
1384
1240
  if (revalidatedDestinationPath !== resolvedDestinationPath) {
1385
- throw new Error("OpenClaw Crabline smoke lock commit destination path identity changed.");
1241
+ throw new Error("OpenClaw Crabline provider readiness lock commit destination path identity changed.");
1386
1242
  }
1387
- // The owner-only output tree and smoke lock serialize supported writers; confinement does
1243
+ // The owner-only output tree and provider readiness lock serialize supported writers; confinement does
1388
1244
  // not treat another process running as the same OS user as a lower-privilege adversary.
1389
1245
  await fs.rename(claimedStagedFilePath, revalidatedDestinationPath);
1390
1246
  },
@@ -1424,7 +1280,6 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1424
1280
  : {}),
1425
1281
  token,
1426
1282
  });
1427
- await claimLegacyRecoveryDirectory(lockDirectory, token);
1428
1283
  for (const claim of await listLockClaims(lockDirectory)) {
1429
1284
  await removeOwnedLock({
1430
1285
  lockDirectory,
@@ -1435,13 +1290,20 @@ export async function acquireOpenClawCrablineSmokeRunLock(params, dependencies =
1435
1290
  token,
1436
1291
  });
1437
1292
  }
1438
- await retireMarker();
1293
+ await removeOwnedLock({
1294
+ lockDirectory,
1295
+ ownedDirectory: lockDirectory,
1296
+ ...(dependencies.removeDirectory
1297
+ ? { removeDirectory: dependencies.removeDirectory }
1298
+ : {}),
1299
+ token,
1300
+ });
1439
1301
  released = true;
1440
1302
  },
1441
1303
  };
1442
1304
  }
1443
1305
  }
1444
- export async function releaseOpenClawCrablineSmokeRunLock(lock, dependencies = {}) {
1306
+ export async function releaseOpenClawCrablineProviderReadinessLock(lock, dependencies = {}) {
1445
1307
  for (let attempt = 1;; attempt += 1) {
1446
1308
  try {
1447
1309
  await lock.release();
@@ -1455,4 +1317,4 @@ export async function releaseOpenClawCrablineSmokeRunLock(lock, dependencies = {
1455
1317
  }
1456
1318
  }
1457
1319
  }
1458
- //# sourceMappingURL=smoke-lock.js.map
1320
+ //# sourceMappingURL=provider-readiness-lock.js.map