@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.
Files changed (214) hide show
  1. package/README.md +137 -23
  2. package/dist/src/bin/crabline.js +3 -1
  3. package/dist/src/bin/crabline.js.map +1 -1
  4. package/dist/src/cli/program.d.ts +5 -1
  5. package/dist/src/cli/program.js +281 -52
  6. package/dist/src/cli/program.js.map +1 -1
  7. package/dist/src/config/load.js +39 -3
  8. package/dist/src/config/load.js.map +1 -1
  9. package/dist/src/config/schema.d.ts +20 -0
  10. package/dist/src/config/schema.js +163 -35
  11. package/dist/src/config/schema.js.map +1 -1
  12. package/dist/src/core/errors.d.ts +1 -0
  13. package/dist/src/core/errors.js +6 -3
  14. package/dist/src/core/errors.js.map +1 -1
  15. package/dist/src/core/http-path.d.ts +1 -0
  16. package/dist/src/core/http-path.js +14 -0
  17. package/dist/src/core/http-path.js.map +1 -0
  18. package/dist/src/core/matcher.d.ts +3 -1
  19. package/dist/src/core/matcher.js +58 -2
  20. package/dist/src/core/matcher.js.map +1 -1
  21. package/dist/src/core/nonces.d.ts +2 -0
  22. package/dist/src/core/nonces.js +11 -1
  23. package/dist/src/core/nonces.js.map +1 -1
  24. package/dist/src/core/reporters.d.ts +5 -0
  25. package/dist/src/core/reporters.js +26 -2
  26. package/dist/src/core/reporters.js.map +1 -1
  27. package/dist/src/core/run.d.ts +3 -1
  28. package/dist/src/core/run.js +178 -66
  29. package/dist/src/core/run.js.map +1 -1
  30. package/dist/src/index.d.ts +12 -10
  31. package/dist/src/index.js +6 -5
  32. package/dist/src/index.js.map +1 -1
  33. package/dist/src/matrix-ids.d.ts +5 -0
  34. package/dist/src/matrix-ids.js +83 -0
  35. package/dist/src/matrix-ids.js.map +1 -0
  36. package/dist/src/openclaw/artifact-generation.d.ts +7 -6
  37. package/dist/src/openclaw/artifact-generation.js +385 -41
  38. package/dist/src/openclaw/artifact-generation.js.map +1 -1
  39. package/dist/src/openclaw/bridges/discord.d.ts +1 -0
  40. package/dist/src/openclaw/bridges/discord.js +149 -0
  41. package/dist/src/openclaw/bridges/discord.js.map +1 -0
  42. package/dist/src/openclaw/bridges/matrix.js +22 -5
  43. package/dist/src/openclaw/bridges/matrix.js.map +1 -1
  44. package/dist/src/openclaw/bridges/mattermost.js +14 -9
  45. package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
  46. package/dist/src/openclaw/bridges/probe-response.d.ts +1 -0
  47. package/dist/src/openclaw/bridges/probe-response.js +5 -0
  48. package/dist/src/openclaw/bridges/probe-response.js.map +1 -0
  49. package/dist/src/openclaw/bridges/signal.js +16 -7
  50. package/dist/src/openclaw/bridges/signal.js.map +1 -1
  51. package/dist/src/openclaw/bridges/slack.js +117 -24
  52. package/dist/src/openclaw/bridges/slack.js.map +1 -1
  53. package/dist/src/openclaw/bridges/telegram.js +46 -20
  54. package/dist/src/openclaw/bridges/telegram.js.map +1 -1
  55. package/dist/src/openclaw/bridges/whatsapp.js +33 -17
  56. package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
  57. package/dist/src/openclaw/bridges/zalo.js +9 -4
  58. package/dist/src/openclaw/bridges/zalo.js.map +1 -1
  59. package/dist/src/openclaw/outbound-contract.js +3 -0
  60. package/dist/src/openclaw/outbound-contract.js.map +1 -1
  61. package/dist/src/openclaw/private-file.d.ts +38 -3
  62. package/dist/src/openclaw/private-file.js +2008 -87
  63. package/dist/src/openclaw/private-file.js.map +1 -1
  64. package/dist/src/openclaw/{smoke-lock.d.ts → provider-readiness-lock.d.ts} +14 -5
  65. package/dist/src/openclaw/provider-readiness-lock.js +1320 -0
  66. package/dist/src/openclaw/provider-readiness-lock.js.map +1 -0
  67. package/dist/src/openclaw/shared.d.ts +10 -17
  68. package/dist/src/openclaw/shared.js +53 -19
  69. package/dist/src/openclaw/shared.js.map +1 -1
  70. package/dist/src/openclaw.d.ts +3 -5
  71. package/dist/src/openclaw.js +245 -60
  72. package/dist/src/openclaw.js.map +1 -1
  73. package/dist/src/platform/process-owned-lock.d.ts +11 -0
  74. package/dist/src/platform/process-owned-lock.js +1698 -0
  75. package/dist/src/platform/process-owned-lock.js.map +1 -0
  76. package/dist/src/platform/recorder-directory.d.ts +1 -0
  77. package/dist/src/platform/recorder-directory.js +14 -0
  78. package/dist/src/platform/recorder-directory.js.map +1 -0
  79. package/dist/src/platform/windows-acl.d.ts +18 -0
  80. package/dist/src/platform/windows-acl.js +1367 -0
  81. package/dist/src/platform/windows-acl.js.map +1 -0
  82. package/dist/src/platform/windows-lock-root.d.ts +13 -0
  83. package/dist/src/platform/windows-lock-root.js +98 -0
  84. package/dist/src/platform/windows-lock-root.js.map +1 -0
  85. package/dist/src/providers/builtin/discord.d.ts +4 -0
  86. package/dist/src/providers/builtin/discord.js +52 -9
  87. package/dist/src/providers/builtin/discord.js.map +1 -1
  88. package/dist/src/providers/builtin/external-webhook-auth.d.ts +1 -0
  89. package/dist/src/providers/builtin/external-webhook-auth.js +38 -1
  90. package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -1
  91. package/dist/src/providers/builtin/feishu.d.ts +1 -0
  92. package/dist/src/providers/builtin/feishu.js +96 -26
  93. package/dist/src/providers/builtin/feishu.js.map +1 -1
  94. package/dist/src/providers/builtin/googlechat.d.ts +1 -0
  95. package/dist/src/providers/builtin/googlechat.js +95 -26
  96. package/dist/src/providers/builtin/googlechat.js.map +1 -1
  97. package/dist/src/providers/builtin/imessage.js +60 -17
  98. package/dist/src/providers/builtin/imessage.js.map +1 -1
  99. package/dist/src/providers/builtin/loopback.js +88 -35
  100. package/dist/src/providers/builtin/loopback.js.map +1 -1
  101. package/dist/src/providers/builtin/matrix.d.ts +2 -2
  102. package/dist/src/providers/builtin/matrix.js +78 -21
  103. package/dist/src/providers/builtin/matrix.js.map +1 -1
  104. package/dist/src/providers/builtin/mattermost.d.ts +11 -7
  105. package/dist/src/providers/builtin/mattermost.js +138 -24
  106. package/dist/src/providers/builtin/mattermost.js.map +1 -1
  107. package/dist/src/providers/builtin/msteams.d.ts +1 -0
  108. package/dist/src/providers/builtin/msteams.js +118 -38
  109. package/dist/src/providers/builtin/msteams.js.map +1 -1
  110. package/dist/src/providers/builtin/native-local-mock.js +4 -4
  111. package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
  112. package/dist/src/providers/builtin/script.js +1057 -146
  113. package/dist/src/providers/builtin/script.js.map +1 -1
  114. package/dist/src/providers/builtin/slack.d.ts +8 -1
  115. package/dist/src/providers/builtin/slack.js +327 -24
  116. package/dist/src/providers/builtin/slack.js.map +1 -1
  117. package/dist/src/providers/builtin/telegram.d.ts +1 -1
  118. package/dist/src/providers/builtin/telegram.js +55 -14
  119. package/dist/src/providers/builtin/telegram.js.map +1 -1
  120. package/dist/src/providers/builtin/whatsapp.d.ts +4 -3
  121. package/dist/src/providers/builtin/whatsapp.js +157 -29
  122. package/dist/src/providers/builtin/whatsapp.js.map +1 -1
  123. package/dist/src/providers/builtin/zalo.d.ts +5 -2
  124. package/dist/src/providers/builtin/zalo.js +31 -7
  125. package/dist/src/providers/builtin/zalo.js.map +1 -1
  126. package/dist/src/providers/catalog.d.ts +1 -1
  127. package/dist/src/providers/catalog.js +1 -1
  128. package/dist/src/providers/catalog.js.map +1 -1
  129. package/dist/src/providers/local-mock.d.ts +4 -0
  130. package/dist/src/providers/local-mock.js +162 -35
  131. package/dist/src/providers/local-mock.js.map +1 -1
  132. package/dist/src/providers/native-ids.d.ts +2 -0
  133. package/dist/src/providers/native-ids.js +3 -0
  134. package/dist/src/providers/native-ids.js.map +1 -1
  135. package/dist/src/providers/recorder.d.ts +14 -3
  136. package/dist/src/providers/recorder.js +521 -41
  137. package/dist/src/providers/recorder.js.map +1 -1
  138. package/dist/src/providers/registry.d.ts +1 -0
  139. package/dist/src/providers/registry.js +65 -20
  140. package/dist/src/providers/registry.js.map +1 -1
  141. package/dist/src/providers/signed-jwt.d.ts +6 -1
  142. package/dist/src/providers/signed-jwt.js +241 -50
  143. package/dist/src/providers/signed-jwt.js.map +1 -1
  144. package/dist/src/providers/slack-ids.d.ts +1 -0
  145. package/dist/src/providers/slack-ids.js +5 -0
  146. package/dist/src/providers/slack-ids.js.map +1 -1
  147. package/dist/src/providers/target-normalizers.d.ts +9 -1
  148. package/dist/src/providers/target-normalizers.js +177 -58
  149. package/dist/src/providers/target-normalizers.js.map +1 -1
  150. package/dist/src/providers/webhook-server.d.ts +3 -0
  151. package/dist/src/providers/webhook-server.js +268 -47
  152. package/dist/src/providers/webhook-server.js.map +1 -1
  153. package/dist/src/servers/discord.d.ts +40 -0
  154. package/dist/src/servers/discord.js +842 -0
  155. package/dist/src/servers/discord.js.map +1 -0
  156. package/dist/src/servers/http.d.ts +14 -2
  157. package/dist/src/servers/http.js +229 -58
  158. package/dist/src/servers/http.js.map +1 -1
  159. package/dist/src/servers/index.d.ts +10 -4
  160. package/dist/src/servers/index.js +4 -0
  161. package/dist/src/servers/index.js.map +1 -1
  162. package/dist/src/servers/matrix.d.ts +3 -0
  163. package/dist/src/servers/matrix.js +268 -110
  164. package/dist/src/servers/matrix.js.map +1 -1
  165. package/dist/src/servers/mattermost.d.ts +5 -0
  166. package/dist/src/servers/mattermost.js +389 -91
  167. package/dist/src/servers/mattermost.js.map +1 -1
  168. package/dist/src/servers/recorder.d.ts +11 -0
  169. package/dist/src/servers/recorder.js +1072 -69
  170. package/dist/src/servers/recorder.js.map +1 -1
  171. package/dist/src/servers/signal.d.ts +1 -0
  172. package/dist/src/servers/signal.js +279 -44
  173. package/dist/src/servers/signal.js.map +1 -1
  174. package/dist/src/servers/slack.d.ts +15 -0
  175. package/dist/src/servers/slack.js +270 -54
  176. package/dist/src/servers/slack.js.map +1 -1
  177. package/dist/src/servers/telegram-identity.d.ts +6 -0
  178. package/dist/src/servers/telegram-identity.js +27 -0
  179. package/dist/src/servers/telegram-identity.js.map +1 -0
  180. package/dist/src/servers/telegram.d.ts +17 -6
  181. package/dist/src/servers/telegram.js +1218 -107
  182. package/dist/src/servers/telegram.js.map +1 -1
  183. package/dist/src/servers/webhook-target.d.ts +12 -4
  184. package/dist/src/servers/webhook-target.js +226 -28
  185. package/dist/src/servers/webhook-target.js.map +1 -1
  186. package/dist/src/servers/whatsapp-baileys-websocket.d.ts +60 -1
  187. package/dist/src/servers/whatsapp-baileys-websocket.js +782 -112
  188. package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
  189. package/dist/src/servers/whatsapp-jid.d.ts +1 -0
  190. package/dist/src/servers/whatsapp-jid.js +9 -5
  191. package/dist/src/servers/whatsapp-jid.js.map +1 -1
  192. package/dist/src/servers/whatsapp-wire/binary-node.d.ts +1 -0
  193. package/dist/src/servers/whatsapp-wire/binary-node.js +37 -13
  194. package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
  195. package/dist/src/servers/whatsapp-wire/crypto.d.ts +3 -0
  196. package/dist/src/servers/whatsapp-wire/crypto.js +23 -8
  197. package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
  198. package/dist/src/servers/whatsapp-wire/handshake.d.ts +9 -3
  199. package/dist/src/servers/whatsapp-wire/handshake.js +173 -12
  200. package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
  201. package/dist/src/servers/whatsapp-wire/signal.js +3 -3
  202. package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
  203. package/dist/src/servers/whatsapp.d.ts +7 -1
  204. package/dist/src/servers/whatsapp.js +149 -25
  205. package/dist/src/servers/whatsapp.js.map +1 -1
  206. package/dist/src/servers/zalo.d.ts +1 -0
  207. package/dist/src/servers/zalo.js +193 -82
  208. package/dist/src/servers/zalo.js.map +1 -1
  209. package/docs/channel-setup.md +251 -64
  210. package/fixtures/examples/crabline.example.yaml +3 -2
  211. package/package.json +18 -10
  212. package/dist/src/openclaw/smoke-lock.js +0 -1004
  213. package/dist/src/openclaw/smoke-lock.js.map +0 -1
  214. package/fixtures/examples/openclaw-bridge.yaml +0 -125
@@ -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, OPENCLAW_CRABLINE_CHANNEL_SMOKE_PATH, OPENCLAW_CRABLINE_PROVIDER_READINESS_PATH, OPENCLAW_CRABLINE_DEFAULT_CHANNEL, OPENCLAW_CRABLINE_ARTIFACT_POINTER_PATH, OPENCLAW_CRABLINE_ARTIFACT_STORE_DIRECTORY, OPENCLAW_CRABLINE_MANIFEST_PATH, parseQaTarget, runOpenClawCrablineProviderProbe, } from "./openclaw/shared.js";
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 { acquireOpenClawCrablineSmokeRunLock, releaseOpenClawCrablineSmokeRunLock, } from "./openclaw/smoke-lock.js";
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, OPENCLAW_CRABLINE_CHANNEL_SMOKE_PATH, OPENCLAW_CRABLINE_PROVIDER_READINESS_PATH, OPENCLAW_CRABLINE_DEFAULT_CHANNEL, OPENCLAW_CRABLINE_MANIFEST_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]+)-fake-provider\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl\.tmp$/iu;
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(directoryPath, lock) {
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 ?? releaseOpenClawCrablineSmokeRunLock;
195
- const smokeLock = await (dependencies.acquireLock ?? acquireOpenClawCrablineSmokeRunLock)({
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 recorderDirectory = path.join(outputDir, "artifacts", "crabline");
203
- await fs.mkdir(recorderDirectory, { recursive: true });
204
- await reclaimOpenClawCrablineRecorderTemporaries(recorderDirectory, smokeLock);
205
- recorderPath = path.join(recorderDirectory, `.${params.selection.channel}-fake-provider.${randomUUID()}.jsonl.tmp`);
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
- try {
222
- await adapter.close();
223
- }
224
- catch (cleanupError) {
225
- if (!probeFailed) {
226
- throw cleanupError;
396
+ if (probeSettlement) {
397
+ await waitForOpenClawCrablineProbeCleanup(probeSettlement);
398
+ try {
399
+ await adapter.close();
227
400
  }
228
- if (probeFailure instanceof Error) {
229
- const existingCause = probeFailure.cause;
230
- try {
231
- Object.defineProperty(probeFailure, "cause", {
232
- configurable: true,
233
- value: existingCause === undefined
234
- ? cleanupError
235
- : new AggregateError([existingCause, cleanupError], "OpenClaw Crabline provider probe cleanup also failed."),
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
- catch {
239
- // Some provider errors are frozen; preserving the primary failure is authoritative.
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
- throw probeFailure;
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
- let recorderSnapshotContents = "";
255
- try {
256
- recorderSnapshotContents = await fs.readFile(recorderPath, "utf8");
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: smokeLock,
462
+ lock: providerReadinessLock,
284
463
  manifest: adapter.manifest,
285
464
  outputDir,
286
465
  recorderSnapshot: {
287
466
  contents: recorderSnapshotContents,
288
- fileName: `${params.selection.channel}-fake-provider.jsonl`,
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 fs.rm(recorderPath, { force: true });
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 fs.rm(recorderPath, { force: true });
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(smokeLock);
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 smoke failure cleanup also failed."),
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 smoke and lock cleanup both failed.", {
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 smoke committed but lock cleanup failed: ${detail}`,
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 ?? selection.smokeArtifactPath}.`,
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
  }