@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.
- package/README.md +38 -11
- package/dist/src/cli/program.js +17 -2
- package/dist/src/cli/program.js.map +1 -1
- package/dist/src/index.d.ts +12 -10
- package/dist/src/index.js +6 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/openclaw/artifact-generation.d.ts +5 -9
- package/dist/src/openclaw/artifact-generation.js +41 -69
- package/dist/src/openclaw/artifact-generation.js.map +1 -1
- package/dist/src/openclaw/bridges/discord.d.ts +1 -0
- package/dist/src/openclaw/bridges/discord.js +149 -0
- package/dist/src/openclaw/bridges/discord.js.map +1 -0
- package/dist/src/openclaw/bridges/matrix.js +1 -1
- package/dist/src/openclaw/bridges/matrix.js.map +1 -1
- package/dist/src/openclaw/bridges/mattermost.js +1 -1
- package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
- package/dist/src/openclaw/bridges/signal.js +1 -1
- package/dist/src/openclaw/bridges/signal.js.map +1 -1
- package/dist/src/openclaw/bridges/slack.js +1 -1
- package/dist/src/openclaw/bridges/slack.js.map +1 -1
- package/dist/src/openclaw/bridges/telegram.js +1 -1
- package/dist/src/openclaw/bridges/telegram.js.map +1 -1
- package/dist/src/openclaw/bridges/whatsapp.js +1 -1
- package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
- package/dist/src/openclaw/bridges/zalo.js +1 -1
- package/dist/src/openclaw/bridges/zalo.js.map +1 -1
- package/dist/src/openclaw/outbound-contract.js +3 -0
- package/dist/src/openclaw/outbound-contract.js.map +1 -1
- package/dist/src/openclaw/{smoke-lock.d.ts → provider-readiness-lock.d.ts} +7 -7
- package/dist/src/openclaw/{smoke-lock.js → provider-readiness-lock.js} +122 -260
- package/dist/src/openclaw/provider-readiness-lock.js.map +1 -0
- package/dist/src/openclaw/shared.d.ts +9 -17
- package/dist/src/openclaw/shared.js +5 -6
- package/dist/src/openclaw/shared.js.map +1 -1
- package/dist/src/openclaw.d.ts +3 -5
- package/dist/src/openclaw.js +22 -20
- package/dist/src/openclaw.js.map +1 -1
- package/dist/src/servers/discord.d.ts +40 -0
- package/dist/src/servers/discord.js +842 -0
- package/dist/src/servers/discord.js.map +1 -0
- package/dist/src/servers/index.d.ts +10 -4
- package/dist/src/servers/index.js +4 -0
- package/dist/src/servers/index.js.map +1 -1
- package/dist/src/servers/slack.js +11 -3
- package/dist/src/servers/slack.js.map +1 -1
- package/dist/src/servers/telegram.js +51 -2
- package/dist/src/servers/telegram.js.map +1 -1
- package/dist/src/servers/whatsapp.d.ts +0 -1
- package/dist/src/servers/whatsapp.js +0 -1
- package/dist/src/servers/whatsapp.js.map +1 -1
- package/docs/channel-setup.md +66 -21
- package/package.json +1 -1
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
|
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 (
|
|
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 (
|
|
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 ? "
|
|
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 ? "
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
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
|
|
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 === "
|
|
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 === "
|
|
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}${
|
|
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: "
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
1004
|
-
? { afterOwnerWrite: dependencies.
|
|
906
|
+
...(dependencies.afterLockReservationWrite
|
|
907
|
+
? { afterOwnerWrite: dependencies.afterLockReservationWrite }
|
|
1005
908
|
: {}),
|
|
1006
|
-
includeLease: false,
|
|
1007
909
|
lockDirectory,
|
|
1008
|
-
owner
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1139
|
-
|
|
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
|
|
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
|
|
1044
|
+
throw new Error("OpenClaw Crabline provider readiness lock ownership was lost.");
|
|
1187
1045
|
}
|
|
1188
|
-
await dependencies.
|
|
1189
|
-
if (!(await
|
|
1190
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1241
|
+
throw new Error("OpenClaw Crabline provider readiness lock commit destination path identity changed.");
|
|
1386
1242
|
}
|
|
1387
|
-
// The owner-only output tree and
|
|
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
|
|
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
|
|
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=
|
|
1320
|
+
//# sourceMappingURL=provider-readiness-lock.js.map
|