@openclaw/crabline 0.1.11 → 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 +137 -23
- package/dist/src/bin/crabline.js +3 -1
- package/dist/src/bin/crabline.js.map +1 -1
- package/dist/src/cli/program.d.ts +5 -1
- package/dist/src/cli/program.js +281 -52
- package/dist/src/cli/program.js.map +1 -1
- package/dist/src/config/load.js +39 -3
- package/dist/src/config/load.js.map +1 -1
- package/dist/src/config/schema.d.ts +20 -0
- package/dist/src/config/schema.js +163 -35
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/core/errors.d.ts +1 -0
- package/dist/src/core/errors.js +6 -3
- package/dist/src/core/errors.js.map +1 -1
- package/dist/src/core/http-path.d.ts +1 -0
- package/dist/src/core/http-path.js +14 -0
- package/dist/src/core/http-path.js.map +1 -0
- package/dist/src/core/matcher.d.ts +3 -1
- package/dist/src/core/matcher.js +58 -2
- package/dist/src/core/matcher.js.map +1 -1
- package/dist/src/core/nonces.d.ts +2 -0
- package/dist/src/core/nonces.js +11 -1
- package/dist/src/core/nonces.js.map +1 -1
- package/dist/src/core/reporters.d.ts +5 -0
- package/dist/src/core/reporters.js +26 -2
- package/dist/src/core/reporters.js.map +1 -1
- package/dist/src/core/run.d.ts +3 -1
- package/dist/src/core/run.js +178 -66
- package/dist/src/core/run.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/matrix-ids.d.ts +5 -0
- package/dist/src/matrix-ids.js +83 -0
- package/dist/src/matrix-ids.js.map +1 -0
- package/dist/src/openclaw/artifact-generation.d.ts +7 -6
- package/dist/src/openclaw/artifact-generation.js +385 -41
- 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 +22 -5
- package/dist/src/openclaw/bridges/matrix.js.map +1 -1
- package/dist/src/openclaw/bridges/mattermost.js +14 -9
- package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
- package/dist/src/openclaw/bridges/probe-response.d.ts +1 -0
- package/dist/src/openclaw/bridges/probe-response.js +5 -0
- package/dist/src/openclaw/bridges/probe-response.js.map +1 -0
- package/dist/src/openclaw/bridges/signal.js +16 -7
- package/dist/src/openclaw/bridges/signal.js.map +1 -1
- package/dist/src/openclaw/bridges/slack.js +117 -24
- package/dist/src/openclaw/bridges/slack.js.map +1 -1
- package/dist/src/openclaw/bridges/telegram.js +46 -20
- package/dist/src/openclaw/bridges/telegram.js.map +1 -1
- package/dist/src/openclaw/bridges/whatsapp.js +33 -17
- package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
- package/dist/src/openclaw/bridges/zalo.js +9 -4
- 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/private-file.d.ts +38 -3
- package/dist/src/openclaw/private-file.js +2008 -87
- package/dist/src/openclaw/private-file.js.map +1 -1
- package/dist/src/openclaw/{smoke-lock.d.ts → provider-readiness-lock.d.ts} +14 -5
- package/dist/src/openclaw/provider-readiness-lock.js +1320 -0
- package/dist/src/openclaw/provider-readiness-lock.js.map +1 -0
- package/dist/src/openclaw/shared.d.ts +10 -17
- package/dist/src/openclaw/shared.js +53 -19
- package/dist/src/openclaw/shared.js.map +1 -1
- package/dist/src/openclaw.d.ts +3 -5
- package/dist/src/openclaw.js +245 -60
- package/dist/src/openclaw.js.map +1 -1
- package/dist/src/platform/process-owned-lock.d.ts +11 -0
- package/dist/src/platform/process-owned-lock.js +1698 -0
- package/dist/src/platform/process-owned-lock.js.map +1 -0
- package/dist/src/platform/recorder-directory.d.ts +1 -0
- package/dist/src/platform/recorder-directory.js +14 -0
- package/dist/src/platform/recorder-directory.js.map +1 -0
- package/dist/src/platform/windows-acl.d.ts +18 -0
- package/dist/src/platform/windows-acl.js +1367 -0
- package/dist/src/platform/windows-acl.js.map +1 -0
- package/dist/src/platform/windows-lock-root.d.ts +13 -0
- package/dist/src/platform/windows-lock-root.js +98 -0
- package/dist/src/platform/windows-lock-root.js.map +1 -0
- package/dist/src/providers/builtin/discord.d.ts +4 -0
- package/dist/src/providers/builtin/discord.js +52 -9
- package/dist/src/providers/builtin/discord.js.map +1 -1
- package/dist/src/providers/builtin/external-webhook-auth.d.ts +1 -0
- package/dist/src/providers/builtin/external-webhook-auth.js +38 -1
- package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -1
- package/dist/src/providers/builtin/feishu.d.ts +1 -0
- package/dist/src/providers/builtin/feishu.js +96 -26
- package/dist/src/providers/builtin/feishu.js.map +1 -1
- package/dist/src/providers/builtin/googlechat.d.ts +1 -0
- package/dist/src/providers/builtin/googlechat.js +95 -26
- package/dist/src/providers/builtin/googlechat.js.map +1 -1
- package/dist/src/providers/builtin/imessage.js +60 -17
- package/dist/src/providers/builtin/imessage.js.map +1 -1
- package/dist/src/providers/builtin/loopback.js +88 -35
- package/dist/src/providers/builtin/loopback.js.map +1 -1
- package/dist/src/providers/builtin/matrix.d.ts +2 -2
- package/dist/src/providers/builtin/matrix.js +78 -21
- package/dist/src/providers/builtin/matrix.js.map +1 -1
- package/dist/src/providers/builtin/mattermost.d.ts +11 -7
- package/dist/src/providers/builtin/mattermost.js +138 -24
- package/dist/src/providers/builtin/mattermost.js.map +1 -1
- package/dist/src/providers/builtin/msteams.d.ts +1 -0
- package/dist/src/providers/builtin/msteams.js +118 -38
- package/dist/src/providers/builtin/msteams.js.map +1 -1
- package/dist/src/providers/builtin/native-local-mock.js +4 -4
- package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
- package/dist/src/providers/builtin/script.js +1057 -146
- package/dist/src/providers/builtin/script.js.map +1 -1
- package/dist/src/providers/builtin/slack.d.ts +8 -1
- package/dist/src/providers/builtin/slack.js +327 -24
- package/dist/src/providers/builtin/slack.js.map +1 -1
- package/dist/src/providers/builtin/telegram.d.ts +1 -1
- package/dist/src/providers/builtin/telegram.js +55 -14
- package/dist/src/providers/builtin/telegram.js.map +1 -1
- package/dist/src/providers/builtin/whatsapp.d.ts +4 -3
- package/dist/src/providers/builtin/whatsapp.js +157 -29
- package/dist/src/providers/builtin/whatsapp.js.map +1 -1
- package/dist/src/providers/builtin/zalo.d.ts +5 -2
- package/dist/src/providers/builtin/zalo.js +31 -7
- package/dist/src/providers/builtin/zalo.js.map +1 -1
- package/dist/src/providers/catalog.d.ts +1 -1
- package/dist/src/providers/catalog.js +1 -1
- package/dist/src/providers/catalog.js.map +1 -1
- package/dist/src/providers/local-mock.d.ts +4 -0
- package/dist/src/providers/local-mock.js +162 -35
- package/dist/src/providers/local-mock.js.map +1 -1
- package/dist/src/providers/native-ids.d.ts +2 -0
- package/dist/src/providers/native-ids.js +3 -0
- package/dist/src/providers/native-ids.js.map +1 -1
- package/dist/src/providers/recorder.d.ts +14 -3
- package/dist/src/providers/recorder.js +521 -41
- package/dist/src/providers/recorder.js.map +1 -1
- package/dist/src/providers/registry.d.ts +1 -0
- package/dist/src/providers/registry.js +65 -20
- package/dist/src/providers/registry.js.map +1 -1
- package/dist/src/providers/signed-jwt.d.ts +6 -1
- package/dist/src/providers/signed-jwt.js +241 -50
- package/dist/src/providers/signed-jwt.js.map +1 -1
- package/dist/src/providers/slack-ids.d.ts +1 -0
- package/dist/src/providers/slack-ids.js +5 -0
- package/dist/src/providers/slack-ids.js.map +1 -1
- package/dist/src/providers/target-normalizers.d.ts +9 -1
- package/dist/src/providers/target-normalizers.js +177 -58
- package/dist/src/providers/target-normalizers.js.map +1 -1
- package/dist/src/providers/webhook-server.d.ts +3 -0
- package/dist/src/providers/webhook-server.js +268 -47
- package/dist/src/providers/webhook-server.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/http.d.ts +14 -2
- package/dist/src/servers/http.js +229 -58
- package/dist/src/servers/http.js.map +1 -1
- 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/matrix.d.ts +3 -0
- package/dist/src/servers/matrix.js +268 -110
- package/dist/src/servers/matrix.js.map +1 -1
- package/dist/src/servers/mattermost.d.ts +5 -0
- package/dist/src/servers/mattermost.js +389 -91
- package/dist/src/servers/mattermost.js.map +1 -1
- package/dist/src/servers/recorder.d.ts +11 -0
- package/dist/src/servers/recorder.js +1072 -69
- package/dist/src/servers/recorder.js.map +1 -1
- package/dist/src/servers/signal.d.ts +1 -0
- package/dist/src/servers/signal.js +279 -44
- package/dist/src/servers/signal.js.map +1 -1
- package/dist/src/servers/slack.d.ts +15 -0
- package/dist/src/servers/slack.js +270 -54
- package/dist/src/servers/slack.js.map +1 -1
- package/dist/src/servers/telegram-identity.d.ts +6 -0
- package/dist/src/servers/telegram-identity.js +27 -0
- package/dist/src/servers/telegram-identity.js.map +1 -0
- package/dist/src/servers/telegram.d.ts +17 -6
- package/dist/src/servers/telegram.js +1218 -107
- package/dist/src/servers/telegram.js.map +1 -1
- package/dist/src/servers/webhook-target.d.ts +12 -4
- package/dist/src/servers/webhook-target.js +226 -28
- package/dist/src/servers/webhook-target.js.map +1 -1
- package/dist/src/servers/whatsapp-baileys-websocket.d.ts +60 -1
- package/dist/src/servers/whatsapp-baileys-websocket.js +782 -112
- package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
- package/dist/src/servers/whatsapp-jid.d.ts +1 -0
- package/dist/src/servers/whatsapp-jid.js +9 -5
- package/dist/src/servers/whatsapp-jid.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/binary-node.d.ts +1 -0
- package/dist/src/servers/whatsapp-wire/binary-node.js +37 -13
- package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/crypto.d.ts +3 -0
- package/dist/src/servers/whatsapp-wire/crypto.js +23 -8
- package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/handshake.d.ts +9 -3
- package/dist/src/servers/whatsapp-wire/handshake.js +173 -12
- package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/signal.js +3 -3
- package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
- package/dist/src/servers/whatsapp.d.ts +7 -1
- package/dist/src/servers/whatsapp.js +149 -25
- package/dist/src/servers/whatsapp.js.map +1 -1
- package/dist/src/servers/zalo.d.ts +1 -0
- package/dist/src/servers/zalo.js +193 -82
- package/dist/src/servers/zalo.js.map +1 -1
- package/docs/channel-setup.md +251 -64
- package/fixtures/examples/crabline.example.yaml +3 -2
- package/package.json +18 -10
- package/dist/src/openclaw/smoke-lock.js +0 -1004
- package/dist/src/openclaw/smoke-lock.js.map +0 -1
- package/fixtures/examples/openclaw-bridge.yaml +0 -125
package/dist/src/openclaw.js
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { CRABLINE_SERVER_CHANNELS, isCrablineServerChannel, startCrablineServer, } from "./servers/index.js";
|
|
3
3
|
import { SLACK_OPENCLAW_CRABLINE_PROVIDER_BRIDGE } from "./openclaw/bridges/slack.js";
|
|
4
|
+
import { DISCORD_OPENCLAW_CRABLINE_PROVIDER_BRIDGE } from "./openclaw/bridges/discord.js";
|
|
4
5
|
import { MATTERMOST_OPENCLAW_CRABLINE_PROVIDER_BRIDGE } from "./openclaw/bridges/mattermost.js";
|
|
5
6
|
import { MATRIX_OPENCLAW_CRABLINE_PROVIDER_BRIDGE } from "./openclaw/bridges/matrix.js";
|
|
6
7
|
import { SIGNAL_OPENCLAW_CRABLINE_PROVIDER_BRIDGE } from "./openclaw/bridges/signal.js";
|
|
7
8
|
import { TELEGRAM_OPENCLAW_CRABLINE_PROVIDER_BRIDGE } from "./openclaw/bridges/telegram.js";
|
|
8
9
|
import { WHATSAPP_OPENCLAW_CRABLINE_PROVIDER_BRIDGE } from "./openclaw/bridges/whatsapp.js";
|
|
9
10
|
import { ZALO_OPENCLAW_CRABLINE_PROVIDER_BRIDGE } from "./openclaw/bridges/zalo.js";
|
|
10
|
-
import { OPENCLAW_CRABLINE_CHANNEL_CAPABILITY_MATRIX_PATH,
|
|
11
|
+
import { OPENCLAW_CRABLINE_CHANNEL_CAPABILITY_MATRIX_PATH, OPENCLAW_CRABLINE_PROVIDER_READINESS_PATH, OPENCLAW_CRABLINE_DEFAULT_CHANNEL, OPENCLAW_CRABLINE_ARTIFACT_POINTER_PATH, OPENCLAW_CRABLINE_ARTIFACT_STORE_DIRECTORY, OPENCLAW_CRABLINE_MANIFEST_PATH, getUnsettledOpenClawCrablineProviderProbe, isRecord, parseQaTarget, runOpenClawCrablineProviderProbe, } from "./openclaw/shared.js";
|
|
11
12
|
import { publishOpenClawCrablineArtifactGeneration } from "./openclaw/artifact-generation.js";
|
|
12
13
|
import { isAcceptedOpenClawCrablineOutbound } from "./openclaw/outbound-contract.js";
|
|
13
|
-
import { syncParentDirectory } from "./openclaw/private-file.js";
|
|
14
|
-
import {
|
|
14
|
+
import { securePrivateDirectory, syncParentDirectory, } from "./openclaw/private-file.js";
|
|
15
|
+
import { acquireOpenClawCrablineProviderReadinessLock, releaseOpenClawCrablineProviderReadinessLock, } from "./openclaw/provider-readiness-lock.js";
|
|
15
16
|
import fs from "node:fs/promises";
|
|
16
17
|
import path from "node:path";
|
|
17
|
-
export { OPENCLAW_CRABLINE_ARTIFACT_POINTER_PATH, OPENCLAW_CRABLINE_ARTIFACT_STORE_DIRECTORY, OPENCLAW_CRABLINE_CHANNEL_CAPABILITY_MATRIX_PATH,
|
|
18
|
+
export { OPENCLAW_CRABLINE_ARTIFACT_POINTER_PATH, OPENCLAW_CRABLINE_ARTIFACT_STORE_DIRECTORY, OPENCLAW_CRABLINE_CHANNEL_CAPABILITY_MATRIX_PATH, OPENCLAW_CRABLINE_PROVIDER_READINESS_PATH, OPENCLAW_CRABLINE_DEFAULT_CHANNEL, OPENCLAW_CRABLINE_MANIFEST_PATH, };
|
|
18
19
|
const OPENCLAW_CRABLINE_PROVIDER_BRIDGES = {
|
|
20
|
+
discord: DISCORD_OPENCLAW_CRABLINE_PROVIDER_BRIDGE,
|
|
19
21
|
mattermost: MATTERMOST_OPENCLAW_CRABLINE_PROVIDER_BRIDGE,
|
|
20
22
|
matrix: MATRIX_OPENCLAW_CRABLINE_PROVIDER_BRIDGE,
|
|
21
23
|
signal: SIGNAL_OPENCLAW_CRABLINE_PROVIDER_BRIDGE,
|
|
@@ -25,12 +27,93 @@ const OPENCLAW_CRABLINE_PROVIDER_BRIDGES = {
|
|
|
25
27
|
zalo: ZALO_OPENCLAW_CRABLINE_PROVIDER_BRIDGE,
|
|
26
28
|
};
|
|
27
29
|
const OPENCLAW_CRABLINE_PROVIDER_BRIDGE_LIST = Object.values(OPENCLAW_CRABLINE_PROVIDER_BRIDGES);
|
|
28
|
-
const RECORDER_TEMP_NAME_PATTERN = /^\.([a-z]+)-
|
|
30
|
+
const RECORDER_TEMP_NAME_PATTERN = /^\.([a-z]+)-provider-server\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl\.tmp$/iu;
|
|
29
31
|
const RECORDER_LOCK_REMOVAL_TOMBSTONE_PATTERN = /^\.(.+)\.\d+\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.remove$/iu;
|
|
32
|
+
const OPENCLAW_CRABLINE_PROVIDER_PROBE_CLEANUP_GRACE_MS = 250;
|
|
30
33
|
function isOpenClawCrablineRecorderTemporary(name) {
|
|
31
34
|
const channel = RECORDER_TEMP_NAME_PATTERN.exec(name)?.[1]?.toLowerCase();
|
|
32
35
|
return channel !== undefined && isCrablineServerChannel(channel);
|
|
33
36
|
}
|
|
37
|
+
function hasStringRecordValues(value) {
|
|
38
|
+
return isRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
39
|
+
}
|
|
40
|
+
function isOpenClawCrablineRecorderEvent(value) {
|
|
41
|
+
return (isRecord(value) &&
|
|
42
|
+
value.accepted === true &&
|
|
43
|
+
typeof value.at === "string" &&
|
|
44
|
+
Number.isFinite(Date.parse(value.at)) &&
|
|
45
|
+
typeof value.method === "string" &&
|
|
46
|
+
value.method.length > 0 &&
|
|
47
|
+
typeof value.path === "string" &&
|
|
48
|
+
value.path.startsWith("/") &&
|
|
49
|
+
hasStringRecordValues(value.query) &&
|
|
50
|
+
(value.type === "admin" || value.type === "api"));
|
|
51
|
+
}
|
|
52
|
+
function openClawCrablineProviderProbeRequest(manifest) {
|
|
53
|
+
switch (manifest.provider) {
|
|
54
|
+
case "discord":
|
|
55
|
+
return {
|
|
56
|
+
method: "GET",
|
|
57
|
+
path: new URL(`${manifest.endpoints.apiRoot}/v10/users/@me`).pathname,
|
|
58
|
+
};
|
|
59
|
+
case "mattermost":
|
|
60
|
+
return {
|
|
61
|
+
method: "GET",
|
|
62
|
+
path: new URL(`${manifest.endpoints.apiRoot}/users/me`).pathname,
|
|
63
|
+
};
|
|
64
|
+
case "matrix":
|
|
65
|
+
return {
|
|
66
|
+
method: "GET",
|
|
67
|
+
path: new URL(`${manifest.endpoints.clientApiRoot}/account/whoami`).pathname,
|
|
68
|
+
};
|
|
69
|
+
case "signal":
|
|
70
|
+
return {
|
|
71
|
+
method: "GET",
|
|
72
|
+
path: new URL("/api/v1/check", manifest.baseUrl).pathname,
|
|
73
|
+
};
|
|
74
|
+
case "slack":
|
|
75
|
+
return {
|
|
76
|
+
method: "POST",
|
|
77
|
+
path: new URL("auth.test", manifest.endpoints.apiRoot).pathname,
|
|
78
|
+
};
|
|
79
|
+
case "telegram":
|
|
80
|
+
return { method: "GET", path: "/bot<redacted>/getMe" };
|
|
81
|
+
case "whatsapp":
|
|
82
|
+
return {
|
|
83
|
+
method: "GET",
|
|
84
|
+
path: new URL(manifest.endpoints.phoneNumberUrl).pathname,
|
|
85
|
+
};
|
|
86
|
+
case "zalo":
|
|
87
|
+
return { method: "POST", path: "/bot<redacted>/getMe" };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function assertOpenClawCrablineRecorderEvidence(contents, manifest) {
|
|
91
|
+
const expectedRequest = openClawCrablineProviderProbeRequest(manifest);
|
|
92
|
+
let recorderEvidence = false;
|
|
93
|
+
for (const line of contents.split(/\r?\n/u)) {
|
|
94
|
+
if (!line.trim()) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
let event;
|
|
98
|
+
try {
|
|
99
|
+
event = JSON.parse(line);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
throw new Error("OpenClaw Crabline provider probe produced no valid JSONL recorder evidence.", { cause: error });
|
|
103
|
+
}
|
|
104
|
+
if (!isOpenClawCrablineRecorderEvent(event)) {
|
|
105
|
+
throw new Error("OpenClaw Crabline provider probe produced no valid JSONL recorder evidence.");
|
|
106
|
+
}
|
|
107
|
+
if (event.type === "api" &&
|
|
108
|
+
event.method === expectedRequest.method &&
|
|
109
|
+
event.path === expectedRequest.path) {
|
|
110
|
+
recorderEvidence = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!recorderEvidence) {
|
|
114
|
+
throw new Error("OpenClaw Crabline provider probe produced no valid JSONL recorder evidence.");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
34
117
|
function isOpenClawCrablineRecorderTemporaryLock(name) {
|
|
35
118
|
return name.endsWith(".lock") && isOpenClawCrablineRecorderTemporary(name.slice(0, -5));
|
|
36
119
|
}
|
|
@@ -80,8 +163,11 @@ async function reclaimOpenClawCrablineRecorderTemporaryLock(directoryPath, name,
|
|
|
80
163
|
await fs.rm(quarantinePath, { force: true, recursive: true });
|
|
81
164
|
await syncParentDirectory(quarantinePath);
|
|
82
165
|
}
|
|
83
|
-
async function reclaimOpenClawCrablineRecorderTemporaries(
|
|
166
|
+
async function reclaimOpenClawCrablineRecorderTemporaries(directory, lock) {
|
|
167
|
+
const directoryPath = directory.directoryPath;
|
|
168
|
+
await directory.assertIdentityAt();
|
|
84
169
|
for (const entry of await fs.readdir(directoryPath, { withFileTypes: true })) {
|
|
170
|
+
await directory.assertIdentityAt();
|
|
85
171
|
const tombstoneBaseName = entry.isDirectory()
|
|
86
172
|
? openClawCrablineRecorderLockTombstoneBaseName(entry.name)
|
|
87
173
|
: null;
|
|
@@ -99,7 +185,60 @@ async function reclaimOpenClawCrablineRecorderTemporaries(directoryPath, lock) {
|
|
|
99
185
|
const temporaryPath = path.join(directoryPath, entry.name);
|
|
100
186
|
await fs.rm(temporaryPath, { force: true });
|
|
101
187
|
await syncParentDirectory(temporaryPath);
|
|
188
|
+
await directory.assertIdentityAt();
|
|
102
189
|
}
|
|
190
|
+
await directory.assertIdentityAt();
|
|
191
|
+
}
|
|
192
|
+
function assertOwnedRecorderStats(stats, expected) {
|
|
193
|
+
const currentUserId = process.geteuid?.();
|
|
194
|
+
if (!stats.isFile() ||
|
|
195
|
+
stats.nlink !== 1n ||
|
|
196
|
+
stats.ino <= 0n ||
|
|
197
|
+
(currentUserId !== undefined && stats.uid !== BigInt(currentUserId)) ||
|
|
198
|
+
(expected !== undefined &&
|
|
199
|
+
(stats.dev !== expected.device ||
|
|
200
|
+
stats.ino !== expected.inode ||
|
|
201
|
+
stats.uid !== expected.userId))) {
|
|
202
|
+
throw new Error("OpenClaw Crabline recorder snapshot identity is invalid.");
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
device: stats.dev,
|
|
206
|
+
inode: stats.ino,
|
|
207
|
+
userId: stats.uid,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
async function readOwnedRecorderSnapshot(recorderPath, directory) {
|
|
211
|
+
await directory.assertIdentityAt();
|
|
212
|
+
const handle = await fs.open(recorderPath, "r");
|
|
213
|
+
try {
|
|
214
|
+
const identity = assertOwnedRecorderStats(await handle.stat({ bigint: true }));
|
|
215
|
+
assertOwnedRecorderStats(await fs.lstat(recorderPath, { bigint: true }), identity);
|
|
216
|
+
const contents = await handle.readFile("utf8");
|
|
217
|
+
await directory.assertIdentityAt();
|
|
218
|
+
assertOwnedRecorderStats(await fs.lstat(recorderPath, { bigint: true }), identity);
|
|
219
|
+
return { contents, identity };
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
await handle.close();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function removeOwnedRecorderTemporary(params) {
|
|
226
|
+
await params.directory.assertIdentityAt();
|
|
227
|
+
let stats;
|
|
228
|
+
try {
|
|
229
|
+
stats = await fs.lstat(params.recorderPath, { bigint: true });
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
if (error.code === "ENOENT") {
|
|
233
|
+
await params.syncParent(params.recorderPath);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
assertOwnedRecorderStats(stats, params.identity);
|
|
239
|
+
await fs.rm(params.recorderPath);
|
|
240
|
+
await params.directory.assertIdentityAt();
|
|
241
|
+
await params.syncParent(params.recorderPath);
|
|
103
242
|
}
|
|
104
243
|
function createOpenClawCrablineProviderAdapter(manifest) {
|
|
105
244
|
const bridge = OPENCLAW_CRABLINE_PROVIDER_BRIDGE_LIST.find((candidate) => candidate.provider === manifest.provider);
|
|
@@ -133,7 +272,6 @@ export function resolveOpenClawCrablineChannelDriverSelection(params) {
|
|
|
133
272
|
channelDriver: "crabline",
|
|
134
273
|
capabilityMatrixPath: OPENCLAW_CRABLINE_CHANNEL_CAPABILITY_MATRIX_PATH,
|
|
135
274
|
providerReadinessArtifactPath: OPENCLAW_CRABLINE_PROVIDER_READINESS_PATH,
|
|
136
|
-
smokeArtifactPath: OPENCLAW_CRABLINE_CHANNEL_SMOKE_PATH,
|
|
137
275
|
};
|
|
138
276
|
}
|
|
139
277
|
export async function probeOpenClawCrablineProvider(manifest) {
|
|
@@ -189,20 +327,55 @@ export async function startOpenClawCrablineAdapter(params, dependencies = {}) {
|
|
|
189
327
|
throw error;
|
|
190
328
|
}
|
|
191
329
|
}
|
|
330
|
+
function attachOpenClawCrablineProbeCleanupFailure(probeFailure, cleanupError) {
|
|
331
|
+
const existingCause = probeFailure.cause;
|
|
332
|
+
try {
|
|
333
|
+
Object.defineProperty(probeFailure, "cause", {
|
|
334
|
+
configurable: true,
|
|
335
|
+
value: existingCause === undefined
|
|
336
|
+
? cleanupError
|
|
337
|
+
: new AggregateError([existingCause, cleanupError], "OpenClaw Crabline provider probe cleanup also failed."),
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
// Some provider errors are frozen; preserving the primary failure is authoritative.
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async function waitForOpenClawCrablineProbeCleanup(probeSettlement) {
|
|
345
|
+
let timeout;
|
|
346
|
+
try {
|
|
347
|
+
await Promise.race([
|
|
348
|
+
probeSettlement,
|
|
349
|
+
new Promise((resolve) => {
|
|
350
|
+
timeout = setTimeout(resolve, OPENCLAW_CRABLINE_PROVIDER_PROBE_CLEANUP_GRACE_MS);
|
|
351
|
+
}),
|
|
352
|
+
]);
|
|
353
|
+
}
|
|
354
|
+
finally {
|
|
355
|
+
if (timeout) {
|
|
356
|
+
clearTimeout(timeout);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
192
360
|
export async function runOpenClawCrablineProviderReadiness(params, dependencies = {}) {
|
|
193
361
|
const outputDir = path.resolve(params.outputDir);
|
|
194
|
-
const releaseLock = dependencies.releaseLock ??
|
|
195
|
-
const
|
|
362
|
+
const releaseLock = dependencies.releaseLock ?? releaseOpenClawCrablineProviderReadinessLock;
|
|
363
|
+
const syncRecorderParent = dependencies.syncParent ?? syncParentDirectory;
|
|
364
|
+
const providerReadinessLock = await (dependencies.acquireLock ?? acquireOpenClawCrablineProviderReadinessLock)({
|
|
196
365
|
channel: params.selection.channel,
|
|
197
366
|
outputDir,
|
|
198
367
|
});
|
|
199
368
|
let outcome;
|
|
369
|
+
let recorderDirectory;
|
|
370
|
+
let recorderIdentity;
|
|
200
371
|
let recorderPath;
|
|
201
372
|
try {
|
|
202
|
-
const
|
|
203
|
-
await
|
|
204
|
-
await
|
|
205
|
-
|
|
373
|
+
const artifactsDirectory = await securePrivateDirectory(path.join(outputDir, "artifacts"));
|
|
374
|
+
recorderDirectory = await securePrivateDirectory(path.join(artifactsDirectory.directoryPath, "crabline"));
|
|
375
|
+
await artifactsDirectory.assertIdentityAt();
|
|
376
|
+
await recorderDirectory.assertIdentityAt();
|
|
377
|
+
await reclaimOpenClawCrablineRecorderTemporaries(recorderDirectory, providerReadinessLock);
|
|
378
|
+
recorderPath = path.join(recorderDirectory.directoryPath, `.${params.selection.channel}-provider-server.${randomUUID()}.jsonl.tmp`);
|
|
206
379
|
const adapter = await (dependencies.startAdapter ?? startOpenClawCrablineAdapter)({
|
|
207
380
|
channel: params.selection.channel,
|
|
208
381
|
openclawConfig: {},
|
|
@@ -211,55 +384,61 @@ export async function runOpenClawCrablineProviderReadiness(params, dependencies
|
|
|
211
384
|
let probe;
|
|
212
385
|
let probeFailed = false;
|
|
213
386
|
let probeFailure;
|
|
387
|
+
let probeSettlement;
|
|
214
388
|
try {
|
|
215
389
|
probe = await adapter.probe();
|
|
216
390
|
}
|
|
217
391
|
catch (error) {
|
|
218
392
|
probeFailed = true;
|
|
219
393
|
probeFailure = error;
|
|
394
|
+
probeSettlement = getUnsettledOpenClawCrablineProviderProbe(error);
|
|
220
395
|
}
|
|
221
|
-
|
|
222
|
-
await
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (!probeFailed) {
|
|
226
|
-
throw cleanupError;
|
|
396
|
+
if (probeSettlement) {
|
|
397
|
+
await waitForOpenClawCrablineProbeCleanup(probeSettlement);
|
|
398
|
+
try {
|
|
399
|
+
await adapter.close();
|
|
227
400
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
401
|
+
catch (cleanupError) {
|
|
402
|
+
if (probeFailure instanceof Error) {
|
|
403
|
+
attachOpenClawCrablineProbeCleanupFailure(probeFailure, cleanupError);
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
const combinedError = new Error("OpenClaw Crabline provider probe and cleanup both failed.", { cause: cleanupError });
|
|
407
|
+
Object.defineProperty(combinedError, "errors", {
|
|
408
|
+
value: [probeFailure, cleanupError],
|
|
236
409
|
});
|
|
410
|
+
throw combinedError;
|
|
237
411
|
}
|
|
238
|
-
|
|
239
|
-
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
try {
|
|
416
|
+
await adapter.close();
|
|
417
|
+
}
|
|
418
|
+
catch (cleanupError) {
|
|
419
|
+
if (!probeFailed) {
|
|
420
|
+
throw cleanupError;
|
|
421
|
+
}
|
|
422
|
+
if (probeFailure instanceof Error) {
|
|
423
|
+
attachOpenClawCrablineProbeCleanupFailure(probeFailure, cleanupError);
|
|
424
|
+
throw probeFailure;
|
|
240
425
|
}
|
|
241
|
-
|
|
426
|
+
const combinedError = new Error("OpenClaw Crabline provider probe and cleanup both failed.", {
|
|
427
|
+
cause: cleanupError,
|
|
428
|
+
});
|
|
429
|
+
Object.defineProperty(combinedError, "errors", {
|
|
430
|
+
value: [probeFailure, cleanupError],
|
|
431
|
+
});
|
|
432
|
+
throw combinedError;
|
|
242
433
|
}
|
|
243
|
-
const combinedError = new Error("OpenClaw Crabline provider probe and cleanup both failed.", {
|
|
244
|
-
cause: cleanupError,
|
|
245
|
-
});
|
|
246
|
-
Object.defineProperty(combinedError, "errors", {
|
|
247
|
-
value: [probeFailure, cleanupError],
|
|
248
|
-
});
|
|
249
|
-
throw combinedError;
|
|
250
434
|
}
|
|
251
435
|
if (probeFailed) {
|
|
252
436
|
throw probeFailure;
|
|
253
437
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
catch (error) {
|
|
259
|
-
if (error.code !== "ENOENT") {
|
|
260
|
-
throw error;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
438
|
+
const recorderSnapshot = await readOwnedRecorderSnapshot(recorderPath, recorderDirectory);
|
|
439
|
+
recorderIdentity = recorderSnapshot.identity;
|
|
440
|
+
const recorderSnapshotContents = recorderSnapshot.contents;
|
|
441
|
+
assertOpenClawCrablineRecorderEvidence(recorderSnapshotContents, adapter.manifest);
|
|
263
442
|
const capabilityReport = {
|
|
264
443
|
result: {
|
|
265
444
|
driver: "crabline",
|
|
@@ -280,19 +459,24 @@ export async function runOpenClawCrablineProviderReadiness(params, dependencies
|
|
|
280
459
|
};
|
|
281
460
|
const generation = await (dependencies.publishGeneration ?? publishOpenClawCrablineArtifactGeneration)({
|
|
282
461
|
capabilityReport,
|
|
283
|
-
lock:
|
|
462
|
+
lock: providerReadinessLock,
|
|
284
463
|
manifest: adapter.manifest,
|
|
285
464
|
outputDir,
|
|
286
465
|
recorderSnapshot: {
|
|
287
466
|
contents: recorderSnapshotContents,
|
|
288
|
-
fileName: `${params.selection.channel}-
|
|
467
|
+
fileName: `${params.selection.channel}-provider-server.jsonl`,
|
|
289
468
|
},
|
|
290
469
|
selection: params.selection,
|
|
291
470
|
providerReadiness,
|
|
292
471
|
});
|
|
293
472
|
let recorderCleanupWarning;
|
|
294
473
|
try {
|
|
295
|
-
await
|
|
474
|
+
await removeOwnedRecorderTemporary({
|
|
475
|
+
directory: recorderDirectory,
|
|
476
|
+
identity: recorderIdentity,
|
|
477
|
+
recorderPath,
|
|
478
|
+
syncParent: syncRecorderParent,
|
|
479
|
+
});
|
|
296
480
|
recorderPath = undefined;
|
|
297
481
|
}
|
|
298
482
|
catch (error) {
|
|
@@ -309,8 +493,6 @@ export async function runOpenClawCrablineProviderReadiness(params, dependencies
|
|
|
309
493
|
manifestPath: generation.manifestPath,
|
|
310
494
|
providerReadiness: generation.providerReadiness,
|
|
311
495
|
providerReadinessArtifactPath: generation.providerReadinessArtifactPath,
|
|
312
|
-
smoke: generation.providerReadiness,
|
|
313
|
-
smokeArtifactPath: generation.providerReadinessArtifactPath,
|
|
314
496
|
...(generation.warnings || recorderCleanupWarning
|
|
315
497
|
? {
|
|
316
498
|
warnings: [
|
|
@@ -324,9 +506,14 @@ export async function runOpenClawCrablineProviderReadiness(params, dependencies
|
|
|
324
506
|
}
|
|
325
507
|
catch (error) {
|
|
326
508
|
let primaryError = error;
|
|
327
|
-
if (recorderPath) {
|
|
509
|
+
if (recorderPath && recorderDirectory) {
|
|
328
510
|
try {
|
|
329
|
-
await
|
|
511
|
+
await removeOwnedRecorderTemporary({
|
|
512
|
+
directory: recorderDirectory,
|
|
513
|
+
...(recorderIdentity ? { identity: recorderIdentity } : {}),
|
|
514
|
+
recorderPath,
|
|
515
|
+
syncParent: syncRecorderParent,
|
|
516
|
+
});
|
|
330
517
|
recorderPath = undefined;
|
|
331
518
|
}
|
|
332
519
|
catch (cleanupError) {
|
|
@@ -356,7 +543,7 @@ export async function runOpenClawCrablineProviderReadiness(params, dependencies
|
|
|
356
543
|
outcome = { committed: false, error: primaryError };
|
|
357
544
|
}
|
|
358
545
|
try {
|
|
359
|
-
await releaseLock(
|
|
546
|
+
await releaseLock(providerReadinessLock);
|
|
360
547
|
}
|
|
361
548
|
catch (cleanupError) {
|
|
362
549
|
// The pointer switch is authoritative; lock removal cannot roll it back.
|
|
@@ -368,7 +555,7 @@ export async function runOpenClawCrablineProviderReadiness(params, dependencies
|
|
|
368
555
|
configurable: true,
|
|
369
556
|
value: existingCause === undefined
|
|
370
557
|
? cleanupError
|
|
371
|
-
: new AggregateError([existingCause, cleanupError], "OpenClaw Crabline
|
|
558
|
+
: new AggregateError([existingCause, cleanupError], "OpenClaw Crabline provider readiness failure cleanup also failed."),
|
|
372
559
|
});
|
|
373
560
|
}
|
|
374
561
|
catch {
|
|
@@ -376,7 +563,7 @@ export async function runOpenClawCrablineProviderReadiness(params, dependencies
|
|
|
376
563
|
}
|
|
377
564
|
throw outcome.error;
|
|
378
565
|
}
|
|
379
|
-
const combinedError = new Error("OpenClaw Crabline
|
|
566
|
+
const combinedError = new Error("OpenClaw Crabline provider readiness and lock cleanup both failed.", {
|
|
380
567
|
cause: cleanupError,
|
|
381
568
|
});
|
|
382
569
|
Object.defineProperty(combinedError, "errors", {
|
|
@@ -389,7 +576,7 @@ export async function runOpenClawCrablineProviderReadiness(params, dependencies
|
|
|
389
576
|
...outcome.result,
|
|
390
577
|
warnings: [
|
|
391
578
|
...(outcome.result.warnings ?? []),
|
|
392
|
-
`OpenClaw Crabline
|
|
579
|
+
`OpenClaw Crabline provider readiness committed but lock cleanup failed: ${detail}`,
|
|
393
580
|
],
|
|
394
581
|
};
|
|
395
582
|
}
|
|
@@ -398,8 +585,6 @@ export async function runOpenClawCrablineProviderReadiness(params, dependencies
|
|
|
398
585
|
}
|
|
399
586
|
return outcome.result;
|
|
400
587
|
}
|
|
401
|
-
/** @deprecated Use runOpenClawCrablineProviderReadiness. */
|
|
402
|
-
export const runOpenClawCrablineChannelDriverSmoke = runOpenClawCrablineProviderReadiness;
|
|
403
588
|
export function createOpenClawCrablineChannelReportNotes(selection) {
|
|
404
589
|
if (!selection) {
|
|
405
590
|
return [];
|
|
@@ -408,7 +593,7 @@ export function createOpenClawCrablineChannelReportNotes(selection) {
|
|
|
408
593
|
`Channel driver: ${selection.channelDriver} local provider for ${selection.channel}.`,
|
|
409
594
|
`Channel artifact pointer: ${OPENCLAW_CRABLINE_ARTIFACT_POINTER_PATH}.`,
|
|
410
595
|
`Generation capability filename: ${selection.capabilityMatrixPath}.`,
|
|
411
|
-
`Generation provider-readiness filename: ${selection.providerReadinessArtifactPath
|
|
596
|
+
`Generation provider-readiness filename: ${selection.providerReadinessArtifactPath}.`,
|
|
412
597
|
"Crabline verifies the local provider API is ready; OpenClaw channel behavior is proven separately by QA scenarios that run the real channel adapter.",
|
|
413
598
|
];
|
|
414
599
|
}
|