@openclaw/crabline 0.1.14 → 0.1.15
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/dist/src/index.d.ts +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/openclaw/bridges/discord.js +14 -2
- package/dist/src/openclaw/bridges/discord.js.map +1 -1
- package/dist/src/openclaw/bridges/matrix.js +1 -0
- package/dist/src/openclaw/bridges/matrix.js.map +1 -1
- package/dist/src/openclaw/bridges/mattermost.js +69 -18
- package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
- package/dist/src/openclaw/bridges/signal.js +7 -1
- package/dist/src/openclaw/bridges/signal.js.map +1 -1
- package/dist/src/openclaw/bridges/slack.js +2 -1
- package/dist/src/openclaw/bridges/slack.js.map +1 -1
- package/dist/src/openclaw/bridges/telegram.js +49 -25
- package/dist/src/openclaw/bridges/telegram.js.map +1 -1
- package/dist/src/openclaw/bridges/whatsapp.js +2 -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/private-file.d.ts +8 -0
- package/dist/src/openclaw/private-file.js +99 -8
- package/dist/src/openclaw/private-file.js.map +1 -1
- package/dist/src/openclaw/shared.d.ts +26 -1
- package/dist/src/openclaw/shared.js +48 -7
- package/dist/src/openclaw/shared.js.map +1 -1
- package/dist/src/openclaw.d.ts +4 -4
- package/dist/src/openclaw.js +2 -0
- package/dist/src/openclaw.js.map +1 -1
- package/dist/src/servers/mattermost.js +42 -14
- package/dist/src/servers/mattermost.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import { constants as fsConstants, readFileSync } from "node:fs";
|
|
|
4
4
|
import fs, {} from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { performance } from "node:perf_hooks";
|
|
7
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
7
8
|
import { promisify } from "node:util";
|
|
8
9
|
import { applyOwnerOnlyWindowsDirectoryAcl as applyOwnerOnlyWindowsDirectoryAclByHandle, createOwnerOnlyWindowsDirectoryAncestry as createOwnerOnlyWindowsDirectoryAncestryByHandle, } from "../platform/windows-acl.js";
|
|
9
10
|
const execFileAsync = promisify(execFile);
|
|
@@ -1101,6 +1102,38 @@ const PRIVATE_MUTATION_CLAIM_ROOT_FILE = ".crabline-private-mutation.claim";
|
|
|
1101
1102
|
const PRIVATE_MUTATION_CLAIM_OWNER_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
1102
1103
|
const PRIVATE_MUTATION_CLAIM_METADATA_MAX_BYTES = 4096;
|
|
1103
1104
|
const PRIVATE_MUTATION_RESERVED_BASENAME_PATTERN = /^\.crabline-private-mutation(?:\.|$)/iu;
|
|
1105
|
+
const PRIVATE_MUTATION_CLAIM_WAIT_TIMEOUT_MS = 60_000;
|
|
1106
|
+
const PRIVATE_MUTATION_CLAIM_RETRY_DELAY_MS = 50;
|
|
1107
|
+
function preparePrivateMutationClaimWait(options) {
|
|
1108
|
+
const timeoutMs = options.timeoutMs ?? PRIVATE_MUTATION_CLAIM_WAIT_TIMEOUT_MS;
|
|
1109
|
+
const retryDelayMs = options.retryDelayMs ?? PRIVATE_MUTATION_CLAIM_RETRY_DELAY_MS;
|
|
1110
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0) {
|
|
1111
|
+
throw new Error("Private mutation claim wait timeout must be a non-negative safe integer.");
|
|
1112
|
+
}
|
|
1113
|
+
if (!Number.isSafeInteger(retryDelayMs) || retryDelayMs <= 0) {
|
|
1114
|
+
throw new Error("Private mutation claim retry delay must be a positive safe integer.");
|
|
1115
|
+
}
|
|
1116
|
+
const now = options.now ?? performance.now.bind(performance);
|
|
1117
|
+
const sleep = options.sleep ??
|
|
1118
|
+
((delayMs, signal) => delay(delayMs, undefined, { signal }));
|
|
1119
|
+
if (typeof now !== "function" || typeof sleep !== "function") {
|
|
1120
|
+
throw new Error("Private mutation claim wait callbacks must be functions.");
|
|
1121
|
+
}
|
|
1122
|
+
if (options.signal !== undefined && typeof options.signal.throwIfAborted !== "function") {
|
|
1123
|
+
throw new Error("Private mutation claim wait signal is invalid.");
|
|
1124
|
+
}
|
|
1125
|
+
options.signal?.throwIfAborted();
|
|
1126
|
+
if (!Number.isFinite(now())) {
|
|
1127
|
+
throw new Error("Private mutation claim wait clock is invalid.");
|
|
1128
|
+
}
|
|
1129
|
+
return {
|
|
1130
|
+
now,
|
|
1131
|
+
retryDelayMs,
|
|
1132
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
1133
|
+
sleep,
|
|
1134
|
+
timeoutMs,
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1104
1137
|
function assertNotReservedPrivateMutationPath(targetPath) {
|
|
1105
1138
|
if (PRIVATE_MUTATION_RESERVED_BASENAME_PATTERN.test(path.basename(targetPath))) {
|
|
1106
1139
|
throw new Error("Private path uses Crabline's reserved mutation claim namespace.");
|
|
@@ -1345,6 +1378,11 @@ class UnsupportedPrivateMutationHardLinkError extends Error {
|
|
|
1345
1378
|
super("Private mutation claims require a non-hard-link fallback.", { cause });
|
|
1346
1379
|
}
|
|
1347
1380
|
}
|
|
1381
|
+
class ActivePrivateMutationClaimError extends Error {
|
|
1382
|
+
constructor(cause) {
|
|
1383
|
+
super("Private path mutation is already claimed.", { cause });
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1348
1386
|
function isUnsupportedHardLinkError(error) {
|
|
1349
1387
|
const code = error.code;
|
|
1350
1388
|
return code === "EPERM" || code === "ENOTSUP" || code === "EOPNOTSUPP" || code === "ENOSYS";
|
|
@@ -1426,15 +1464,15 @@ async function acquireHardLinkPrivateMutationClaim(parent, options) {
|
|
|
1426
1464
|
if (runtime.isProcessAlive(observed.owner.pid)) {
|
|
1427
1465
|
if (observed.owner.pid === runtime.pid &&
|
|
1428
1466
|
observed.owner.processStartedAtMs === runtime.processStartedAtMs) {
|
|
1429
|
-
throw new
|
|
1467
|
+
throw new ActivePrivateMutationClaimError(error);
|
|
1430
1468
|
}
|
|
1431
1469
|
else if (observed.owner.pid !== runtime.pid) {
|
|
1432
1470
|
if (observed.owner.processIdentity === undefined) {
|
|
1433
|
-
throw new
|
|
1471
|
+
throw new ActivePrivateMutationClaimError(error);
|
|
1434
1472
|
}
|
|
1435
1473
|
const actualIdentity = runtime.getProcessIdentity(observed.owner.pid);
|
|
1436
1474
|
if (actualIdentity === null || actualIdentity === observed.owner.processIdentity) {
|
|
1437
|
-
throw new
|
|
1475
|
+
throw new ActivePrivateMutationClaimError(error);
|
|
1438
1476
|
}
|
|
1439
1477
|
}
|
|
1440
1478
|
}
|
|
@@ -1796,7 +1834,7 @@ async function acquireDirectoryPrivateMutationClaim(parent, options) {
|
|
|
1796
1834
|
throw error;
|
|
1797
1835
|
}
|
|
1798
1836
|
if (privateMutationClaimOwnerIsActive(observed.metadata.owner, runtime)) {
|
|
1799
|
-
throw new
|
|
1837
|
+
throw new ActivePrivateMutationClaimError(error);
|
|
1800
1838
|
}
|
|
1801
1839
|
await parent.assertIdentityAt();
|
|
1802
1840
|
const revalidated = await readPrivateMutationDirectoryClaim(claimPath);
|
|
@@ -2131,8 +2169,7 @@ async function ownerOnlyPrivateClaimAncestry(leaf, platform) {
|
|
|
2131
2169
|
async function acquirePrivateMutationClaimChain(leaf, options) {
|
|
2132
2170
|
const platform = options.platform ?? process.platform;
|
|
2133
2171
|
const ancestry = await ownerOnlyPrivateClaimAncestry(leaf, platform);
|
|
2134
|
-
const
|
|
2135
|
-
const outerBoundary = await captureSafePrivateDirectoryMutationParent(highestClaimDirectory.directoryPath, platform);
|
|
2172
|
+
const outerBoundary = await captureSafePrivateDirectoryMutationParent(ancestry.at(-1).directoryPath, platform);
|
|
2136
2173
|
const claims = [];
|
|
2137
2174
|
let leafClaim;
|
|
2138
2175
|
try {
|
|
@@ -2239,6 +2276,56 @@ async function acquirePrivateMutationClaimChain(leaf, options) {
|
|
|
2239
2276
|
},
|
|
2240
2277
|
};
|
|
2241
2278
|
}
|
|
2279
|
+
function privateMutationClaimTimeoutError(timeoutMs, cause) {
|
|
2280
|
+
return Object.assign(new Error(`Timed out after ${timeoutMs}ms waiting for a private mutation claim.`, { cause }), { code: "ETIMEDOUT" });
|
|
2281
|
+
}
|
|
2282
|
+
async function releaseClaimAfterWaitFailure(claim, error) {
|
|
2283
|
+
try {
|
|
2284
|
+
await claim.release();
|
|
2285
|
+
}
|
|
2286
|
+
catch (releaseError) {
|
|
2287
|
+
const aggregateError = new AggregateError([error, releaseError], "Private mutation claim wait failed and its acquired claim could not be released.");
|
|
2288
|
+
aggregateError.cause = error;
|
|
2289
|
+
throw aggregateError;
|
|
2290
|
+
}
|
|
2291
|
+
throw error;
|
|
2292
|
+
}
|
|
2293
|
+
async function acquirePrivateMutationClaimWithWait(acquire, wait) {
|
|
2294
|
+
if (wait === undefined) {
|
|
2295
|
+
return await acquire();
|
|
2296
|
+
}
|
|
2297
|
+
const deadlineMs = wait.now() + wait.timeoutMs;
|
|
2298
|
+
let lastContentionError;
|
|
2299
|
+
for (;;) {
|
|
2300
|
+
wait.signal?.throwIfAborted();
|
|
2301
|
+
let claim;
|
|
2302
|
+
try {
|
|
2303
|
+
claim = await acquire();
|
|
2304
|
+
}
|
|
2305
|
+
catch (error) {
|
|
2306
|
+
if (!(error instanceof ActivePrivateMutationClaimError) || wait.timeoutMs === 0) {
|
|
2307
|
+
throw error;
|
|
2308
|
+
}
|
|
2309
|
+
lastContentionError = error;
|
|
2310
|
+
const remainingMs = deadlineMs - wait.now();
|
|
2311
|
+
if (remainingMs <= 0) {
|
|
2312
|
+
throw privateMutationClaimTimeoutError(wait.timeoutMs, error);
|
|
2313
|
+
}
|
|
2314
|
+
await wait.sleep(Math.min(wait.retryDelayMs, remainingMs), wait.signal);
|
|
2315
|
+
continue;
|
|
2316
|
+
}
|
|
2317
|
+
try {
|
|
2318
|
+
wait.signal?.throwIfAborted();
|
|
2319
|
+
if (lastContentionError && wait.now() >= deadlineMs) {
|
|
2320
|
+
throw privateMutationClaimTimeoutError(wait.timeoutMs, lastContentionError);
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
catch (error) {
|
|
2324
|
+
return await releaseClaimAfterWaitFailure(claim, error);
|
|
2325
|
+
}
|
|
2326
|
+
return claim;
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2242
2329
|
async function secureOwnerOnlyMutationParent(directoryPath) {
|
|
2243
2330
|
return await securePrivateDirectory(directoryPath, {
|
|
2244
2331
|
markMutationRoot: false,
|
|
@@ -2247,6 +2334,7 @@ async function secureOwnerOnlyMutationParent(directoryPath) {
|
|
|
2247
2334
|
}
|
|
2248
2335
|
export async function publishPrivateFileAtomically(filePath, contents, options = {}) {
|
|
2249
2336
|
assertNotReservedPrivateMutationPath(filePath);
|
|
2337
|
+
const claimWait = preparePrivateMutationClaimWait(options.claimWait ?? {});
|
|
2250
2338
|
const temporaryPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`);
|
|
2251
2339
|
const parentDirectory = path.resolve(path.dirname(filePath));
|
|
2252
2340
|
const platform = options.platform ?? process.platform;
|
|
@@ -2285,11 +2373,14 @@ export async function publishPrivateFileAtomically(filePath, contents, options =
|
|
|
2285
2373
|
await migrationBoundary?.assertIdentityAt();
|
|
2286
2374
|
const parent = await secureOwnerOnlyMutationParent(parentDirectory);
|
|
2287
2375
|
await migrationBoundary?.assertIdentityAt();
|
|
2288
|
-
const
|
|
2376
|
+
const claimOptions = {
|
|
2289
2377
|
...(options.claimRuntime ? { runtime: options.claimRuntime } : {}),
|
|
2290
2378
|
...(options.createWindowsFile ? { createWindowsFile: options.createWindowsFile } : {}),
|
|
2291
2379
|
...(options.platform ? { platform: options.platform } : {}),
|
|
2292
|
-
}
|
|
2380
|
+
};
|
|
2381
|
+
// The full ancestry fence stays held for the temporary file's lifetime so supported removals
|
|
2382
|
+
// cannot quarantine a secret. Active publishers wait here without serializing their whole flow.
|
|
2383
|
+
const claim = await acquirePrivateMutationClaimWithWait(() => acquirePrivateMutationClaimChain(parent, claimOptions), claimWait);
|
|
2293
2384
|
let handle;
|
|
2294
2385
|
let identity;
|
|
2295
2386
|
let publicationFailed = false;
|