@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,22 +1,213 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { isDeepStrictEqual } from "node:util";
4
5
  import { captureDirectoryIdentity, publishPrivateFileAtomically, removeSecuredPrivateDirectory, securePrivateDirectory, syncParentDirectory, } from "./private-file.js";
5
- import { 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_MANIFEST_PATH, } from "./shared.js";
6
+ import { OPENCLAW_CRABLINE_ARTIFACT_POINTER_PATH, OPENCLAW_CRABLINE_ARTIFACT_STORE_DIRECTORY, OPENCLAW_CRABLINE_CHANNEL_CAPABILITY_MATRIX_PATH, OPENCLAW_CRABLINE_PROVIDER_READINESS_PATH, OPENCLAW_CRABLINE_MANIFEST_PATH, } from "./shared.js";
6
7
  const GENERATION_NAME_PATTERN = /^generation-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
7
8
  const STAGING_NAME_PATTERN = /^\.staging-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
8
9
  const 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;
10
+ const CURRENT_GENERATION_READ_ATTEMPTS = 8;
9
11
  function resolveProviderReadinessArtifactPath(selection) {
10
- const readinessPath = selection.providerReadinessArtifactPath ?? selection.smokeArtifactPath;
11
12
  if (selection.capabilityMatrixPath !== OPENCLAW_CRABLINE_CHANNEL_CAPABILITY_MATRIX_PATH ||
12
- readinessPath !== OPENCLAW_CRABLINE_PROVIDER_READINESS_PATH) {
13
+ selection.providerReadinessArtifactPath !== OPENCLAW_CRABLINE_PROVIDER_READINESS_PATH) {
13
14
  throw new Error("OpenClaw Crabline artifact selection paths are malformed.");
14
15
  }
15
- return readinessPath;
16
+ return selection.providerReadinessArtifactPath;
16
17
  }
17
18
  function isMissingPathError(error) {
18
19
  return error.code === "ENOENT";
19
20
  }
21
+ function isRecord(value) {
22
+ return value !== null && typeof value === "object" && !Array.isArray(value);
23
+ }
24
+ function hasNonEmptyStringFields(value, fields) {
25
+ return fields.every((field) => typeof value[field] === "string" && value[field].length > 0);
26
+ }
27
+ function isGeneratedManifest(value) {
28
+ if (value.version !== 1 ||
29
+ !hasNonEmptyStringFields(value, ["adminToken", "baseUrl", "provider"]) ||
30
+ !isRecord(value.endpoints) ||
31
+ !isRecord(value.env)) {
32
+ return false;
33
+ }
34
+ const endpoints = value.endpoints;
35
+ const env = value.env;
36
+ const baseUrl = value.baseUrl;
37
+ switch (value.provider) {
38
+ case "discord":
39
+ return (hasNonEmptyStringFields(value, ["applicationId", "botToken", "botUserId"]) &&
40
+ hasNonEmptyStringFields(endpoints, [
41
+ "adminInboundUrl",
42
+ "apiRoot",
43
+ "gatewayBotUrl",
44
+ "gatewayUrl",
45
+ ]) &&
46
+ hasNonEmptyStringFields(env, ["DISCORD_BOT_TOKEN"]) &&
47
+ endpoints.adminInboundUrl === `${baseUrl}/crabline/discord/inbound` &&
48
+ endpoints.apiRoot === `${baseUrl}/api` &&
49
+ endpoints.gatewayBotUrl === `${baseUrl}/api/v10/gateway/bot` &&
50
+ endpoints.gatewayUrl === `${baseUrl.replace(/^http/u, "ws")}/gateway` &&
51
+ env.DISCORD_BOT_TOKEN === value.botToken);
52
+ case "mattermost":
53
+ return (hasNonEmptyStringFields(value, ["botToken", "botUserId"]) &&
54
+ hasNonEmptyStringFields(endpoints, ["adminInboundUrl", "apiRoot", "websocketUrl"]) &&
55
+ hasNonEmptyStringFields(env, ["MATTERMOST_BOT_TOKEN", "MATTERMOST_URL"]) &&
56
+ endpoints.adminInboundUrl === `${baseUrl}/crabline/mattermost/inbound` &&
57
+ endpoints.apiRoot === `${baseUrl}/api/v4` &&
58
+ endpoints.websocketUrl === `${baseUrl.replace(/^http/u, "ws")}/api/v4/websocket` &&
59
+ env.MATTERMOST_BOT_TOKEN === value.botToken &&
60
+ env.MATTERMOST_URL === baseUrl);
61
+ case "matrix":
62
+ return (hasNonEmptyStringFields(value, ["accessToken", "botUserId", "deviceId"]) &&
63
+ hasNonEmptyStringFields(endpoints, ["adminInboundUrl", "clientApiRoot", "syncUrl"]) &&
64
+ hasNonEmptyStringFields(env, [
65
+ "MATRIX_ACCESS_TOKEN",
66
+ "MATRIX_BASE_URL",
67
+ "MATRIX_USER_ID",
68
+ ]) &&
69
+ endpoints.adminInboundUrl === `${baseUrl}/crabline/matrix/inbound` &&
70
+ endpoints.clientApiRoot === `${baseUrl}/_matrix/client/v3` &&
71
+ endpoints.syncUrl === `${endpoints.clientApiRoot}/sync` &&
72
+ env.MATRIX_ACCESS_TOKEN === value.accessToken &&
73
+ env.MATRIX_BASE_URL === baseUrl &&
74
+ env.MATRIX_USER_ID === value.botUserId);
75
+ case "signal":
76
+ return (hasNonEmptyStringFields(value, ["account"]) &&
77
+ hasNonEmptyStringFields(endpoints, ["adminInboundUrl", "apiRoot", "eventsUrl", "rpcUrl"]) &&
78
+ endpoints.adminInboundUrl === `${baseUrl}/crabline/signal/inbound` &&
79
+ endpoints.apiRoot === baseUrl &&
80
+ endpoints.eventsUrl === `${baseUrl}/api/v1/events` &&
81
+ endpoints.rpcUrl === `${baseUrl}/api/v1/rpc` &&
82
+ Object.keys(env).length === 0);
83
+ case "slack":
84
+ return (hasNonEmptyStringFields(value, ["botToken", "signingSecret"]) &&
85
+ hasNonEmptyStringFields(endpoints, ["adminInboundUrl", "apiRoot", "eventsUrl"]) &&
86
+ hasNonEmptyStringFields(env, [
87
+ "SLACK_API_URL",
88
+ "SLACK_BOT_TOKEN",
89
+ "SLACK_SIGNING_SECRET",
90
+ ]) &&
91
+ endpoints.adminInboundUrl === `${baseUrl}/crabline/slack/inbound` &&
92
+ endpoints.apiRoot === `${baseUrl}/api/` &&
93
+ endpoints.eventsUrl === `${baseUrl}/slack/events` &&
94
+ env.SLACK_API_URL === endpoints.apiRoot &&
95
+ env.SLACK_BOT_TOKEN === value.botToken &&
96
+ env.SLACK_SIGNING_SECRET === value.signingSecret);
97
+ case "telegram":
98
+ return (hasNonEmptyStringFields(value, ["botToken"]) &&
99
+ hasNonEmptyStringFields(endpoints, ["adminInboundUrl", "apiRoot"]) &&
100
+ hasNonEmptyStringFields(env, ["TELEGRAM_BOT_TOKEN"]) &&
101
+ endpoints.adminInboundUrl === `${baseUrl}/crabline/telegram/inbound` &&
102
+ endpoints.apiRoot === baseUrl &&
103
+ env.TELEGRAM_BOT_TOKEN === value.botToken);
104
+ case "whatsapp":
105
+ return (hasNonEmptyStringFields(value, [
106
+ "accessToken",
107
+ "graphVersion",
108
+ "phoneNumberId",
109
+ "selfJid",
110
+ ]) &&
111
+ hasNonEmptyStringFields(endpoints, [
112
+ "adminInboundUrl",
113
+ "apiRoot",
114
+ "baileysWebSocketUrl",
115
+ "messagesUrl",
116
+ "phoneNumberUrl",
117
+ ]) &&
118
+ hasNonEmptyStringFields(env, [
119
+ "CLOUD_API_ACCESS_TOKEN",
120
+ "CLOUD_API_VERSION",
121
+ "WA_BASE_URL",
122
+ "WA_PHONE_NUMBER_ID",
123
+ ]) &&
124
+ endpoints.adminInboundUrl === `${baseUrl}/_crabline/admin/whatsapp/inbound` &&
125
+ endpoints.apiRoot === `${baseUrl}/${value.graphVersion}` &&
126
+ endpoints.phoneNumberUrl ===
127
+ `${endpoints.apiRoot}/${value.phoneNumberId}` &&
128
+ endpoints.messagesUrl === `${endpoints.phoneNumberUrl}/messages` &&
129
+ endpoints.baileysWebSocketUrl ===
130
+ `${baseUrl.replace(/^http/u, "ws")}/ws/chat?access_token=${encodeURIComponent(value.accessToken)}` &&
131
+ env.CLOUD_API_ACCESS_TOKEN === value.accessToken &&
132
+ env.CLOUD_API_VERSION === value.graphVersion &&
133
+ env.WA_BASE_URL === baseUrl &&
134
+ env.WA_PHONE_NUMBER_ID === value.phoneNumberId);
135
+ case "zalo":
136
+ return (hasNonEmptyStringFields(value, ["botId", "botToken"]) &&
137
+ hasNonEmptyStringFields(endpoints, ["adminInboundUrl", "apiRoot"]) &&
138
+ hasNonEmptyStringFields(env, ["ZALO_API_URL", "ZALO_BOT_TOKEN"]) &&
139
+ endpoints.adminInboundUrl === `${baseUrl}/crabline/zalo/inbound` &&
140
+ endpoints.apiRoot === baseUrl &&
141
+ env.ZALO_API_URL === baseUrl &&
142
+ env.ZALO_BOT_TOKEN === value.botToken);
143
+ default:
144
+ return false;
145
+ }
146
+ }
147
+ function isSuccessfulProbe(value, manifest) {
148
+ if (!isRecord(value)) {
149
+ return false;
150
+ }
151
+ switch (manifest.provider) {
152
+ case "discord":
153
+ return (value.id === manifest.botUserId &&
154
+ value.bot === true &&
155
+ typeof value.username === "string" &&
156
+ value.username.trim().length > 0);
157
+ case "mattermost":
158
+ return (value.id === manifest.botUserId &&
159
+ typeof value.username === "string" &&
160
+ value.username.trim().length > 0 &&
161
+ typeof value.update_at === "number" &&
162
+ Number.isSafeInteger(value.update_at) &&
163
+ value.update_at >= 0);
164
+ case "matrix":
165
+ return value.user_id === manifest.botUserId;
166
+ case "signal":
167
+ return (value.ok === true &&
168
+ typeof value.status === "number" &&
169
+ Number.isInteger(value.status) &&
170
+ value.status >= 200 &&
171
+ value.status < 300);
172
+ case "slack":
173
+ return value.ok === true;
174
+ case "telegram": {
175
+ const result = value.result;
176
+ const tokenBotId = typeof manifest.botToken === "string"
177
+ ? /^([1-9]\d*):/u.exec(manifest.botToken)?.[1]
178
+ : undefined;
179
+ const expectedBotId = tokenBotId === undefined ? undefined : Number(tokenBotId);
180
+ return (value.ok === true &&
181
+ isRecord(result) &&
182
+ typeof result.id === "number" &&
183
+ Number.isSafeInteger(result.id) &&
184
+ result.id > 0 &&
185
+ Number.isSafeInteger(expectedBotId) &&
186
+ result.id === expectedBotId &&
187
+ result.is_bot === true &&
188
+ typeof result.first_name === "string" &&
189
+ result.first_name.length > 0);
190
+ }
191
+ case "whatsapp":
192
+ return value.id === manifest.phoneNumberId;
193
+ case "zalo":
194
+ return value.ok === true && isRecord(value.result) && value.result.id === manifest.botId;
195
+ default:
196
+ return false;
197
+ }
198
+ }
199
+ function isReadinessSection(value, manifest, manifestPath, requireCurrentFields) {
200
+ if (!isRecord(value) || value.manifestPath !== manifestPath || !isRecord(value.result)) {
201
+ return false;
202
+ }
203
+ const result = value.result;
204
+ return (result.ok === true &&
205
+ result.provider === manifest.provider &&
206
+ isRecord(result.endpoints) &&
207
+ isDeepStrictEqual(result.endpoints, manifest.endpoints) &&
208
+ isSuccessfulProbe(result.probe, manifest) &&
209
+ (!requireCurrentFields || (result.proof === "provider-api-probe" && result.ready === true)));
210
+ }
20
211
  function assertGenerationName(value, field) {
21
212
  if (typeof value !== "string" || !GENERATION_NAME_PATTERN.test(value)) {
22
213
  throw new Error(`OpenClaw Crabline artifact pointer ${field} is malformed.`);
@@ -32,28 +223,38 @@ function artifactRemovalTombstoneBaseName(name) {
32
223
  ? originalName
33
224
  : null;
34
225
  }
35
- function withRecorderSnapshotPath(providerReadiness, recorderPath) {
226
+ function withPublishedRecorderPath(providerReadiness, recorderPath) {
36
227
  const result = providerReadiness.result;
37
228
  if (!result || typeof result !== "object" || Array.isArray(result)) {
38
229
  throw new Error("OpenClaw Crabline provider readiness result is malformed.");
39
230
  }
231
+ const publishedResult = {
232
+ ...result,
233
+ };
234
+ if (recorderPath === undefined) {
235
+ delete publishedResult.recorderPath;
236
+ }
237
+ else {
238
+ publishedResult.recorderPath = recorderPath;
239
+ }
40
240
  return {
41
241
  ...providerReadiness,
42
- result: {
43
- ...result,
44
- recorderPath,
45
- },
242
+ result: publishedResult,
46
243
  };
47
244
  }
48
245
  function parseArtifactPointer(contents) {
49
- let value;
246
+ let parsed;
50
247
  try {
51
- value = JSON.parse(contents);
248
+ parsed = JSON.parse(contents);
52
249
  }
53
250
  catch (error) {
54
251
  throw new Error("OpenClaw Crabline artifact pointer is malformed.", { cause: error });
55
252
  }
56
- if (value.version !== 1) {
253
+ if (!isRecord(parsed)) {
254
+ throw new Error("OpenClaw Crabline artifact pointer is malformed.");
255
+ }
256
+ const value = parsed;
257
+ if (value.version !== 3) {
57
258
  throw new Error("OpenClaw Crabline artifact pointer is malformed.");
58
259
  }
59
260
  assertGenerationName(value.generation, "generation");
@@ -67,26 +268,31 @@ function parseArtifactPointer(contents) {
67
268
  capabilityMatrixPath: generationArtifactPath(value.generation, OPENCLAW_CRABLINE_CHANNEL_CAPABILITY_MATRIX_PATH),
68
269
  manifestPath: generationArtifactPath(value.generation, OPENCLAW_CRABLINE_MANIFEST_PATH),
69
270
  providerReadinessArtifactPath: generationArtifactPath(value.generation, OPENCLAW_CRABLINE_PROVIDER_READINESS_PATH),
70
- smokeArtifactPath: generationArtifactPath(value.generation, OPENCLAW_CRABLINE_CHANNEL_SMOKE_PATH),
71
271
  };
72
272
  if (typeof value.capabilityMatrixPath !== "string" ||
73
273
  value.capabilityMatrixPath !== expected.capabilityMatrixPath ||
74
274
  value.manifestPath !== expected.manifestPath ||
75
- (value.providerReadinessArtifactPath !== undefined &&
76
- value.providerReadinessArtifactPath !== expected.providerReadinessArtifactPath) ||
77
- (value.smokeArtifactPath !== undefined &&
78
- value.smokeArtifactPath !== expected.smokeArtifactPath) ||
79
- (value.providerReadinessArtifactPath === undefined && value.smokeArtifactPath === undefined)) {
275
+ value.providerReadinessArtifactPath !== expected.providerReadinessArtifactPath) {
80
276
  throw new Error("OpenClaw Crabline artifact pointer is malformed.");
81
277
  }
278
+ if (value.recorderSnapshotPath !== null && typeof value.recorderSnapshotPath !== "string") {
279
+ throw new Error("OpenClaw Crabline artifact pointer is malformed.");
280
+ }
281
+ if (typeof value.recorderSnapshotPath === "string") {
282
+ const recorderFileName = path.basename(value.recorderSnapshotPath);
283
+ if (!recorderFileName.endsWith(".jsonl") ||
284
+ value.recorderSnapshotPath !== generationArtifactPath(value.generation, recorderFileName)) {
285
+ throw new Error("OpenClaw Crabline artifact pointer is malformed.");
286
+ }
287
+ }
82
288
  return {
83
289
  capabilityMatrixPath: value.capabilityMatrixPath,
84
290
  generation: value.generation,
85
291
  ...(value.previousGeneration ? { previousGeneration: value.previousGeneration } : {}),
86
292
  manifestPath: value.manifestPath,
87
- providerReadinessArtifactPath: value.providerReadinessArtifactPath ?? value.smokeArtifactPath,
88
- smokeArtifactPath: value.smokeArtifactPath ?? value.providerReadinessArtifactPath,
89
- version: 1,
293
+ providerReadinessArtifactPath: value.providerReadinessArtifactPath,
294
+ recorderSnapshotPath: value.recorderSnapshotPath,
295
+ version: 3,
90
296
  };
91
297
  }
92
298
  export async function readOpenClawCrablineArtifactPointer(outputDir) {
@@ -100,17 +306,155 @@ export async function readOpenClawCrablineArtifactPointer(outputDir) {
100
306
  throw error;
101
307
  }
102
308
  }
103
- async function assertCurrentGenerationExists(outputDir, pointer) {
309
+ async function assertArtifactGenerationExists(outputDir, pointer) {
310
+ const generationDirectory = path.resolve(outputDir, OPENCLAW_CRABLINE_ARTIFACT_STORE_DIRECTORY, pointer.generation);
311
+ let currentGeneration;
312
+ try {
313
+ currentGeneration = await captureDirectoryIdentity(generationDirectory);
314
+ }
315
+ catch (error) {
316
+ throw new Error("OpenClaw Crabline current artifact generation is incomplete.", {
317
+ cause: error,
318
+ });
319
+ }
320
+ const assertGenerationIdentity = async () => {
321
+ try {
322
+ await currentGeneration.assertIdentityAt();
323
+ }
324
+ catch (error) {
325
+ throw new Error("OpenClaw Crabline current artifact generation is incomplete.", {
326
+ cause: error,
327
+ });
328
+ }
329
+ };
104
330
  for (const artifactPath of [
105
331
  pointer.manifestPath,
106
332
  pointer.capabilityMatrixPath,
107
333
  pointer.providerReadinessArtifactPath,
108
334
  ]) {
109
- const stats = await fs.lstat(path.join(outputDir, artifactPath));
335
+ await assertGenerationIdentity();
336
+ let stats;
337
+ try {
338
+ stats = await fs.lstat(path.join(outputDir, artifactPath));
339
+ }
340
+ catch (error) {
341
+ throw new Error("OpenClaw Crabline current artifact generation is incomplete.", {
342
+ cause: error,
343
+ });
344
+ }
110
345
  if (!stats.isFile()) {
111
346
  throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
112
347
  }
348
+ await assertGenerationIdentity();
113
349
  }
350
+ const readArtifactObject = async (artifactPath) => {
351
+ try {
352
+ await assertGenerationIdentity();
353
+ const value = JSON.parse(await fs.readFile(path.join(outputDir, artifactPath), "utf8"));
354
+ await assertGenerationIdentity();
355
+ if (!isRecord(value)) {
356
+ throw new Error("artifact is not an object");
357
+ }
358
+ return value;
359
+ }
360
+ catch (error) {
361
+ throw new Error("OpenClaw Crabline current artifact generation is incomplete.", {
362
+ cause: error,
363
+ });
364
+ }
365
+ };
366
+ const readNestedRecorderPath = (value) => {
367
+ const result = value.result;
368
+ const recorderPath = result.recorderPath;
369
+ if (recorderPath !== undefined && typeof recorderPath !== "string") {
370
+ throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
371
+ }
372
+ return recorderPath;
373
+ };
374
+ const manifest = await readArtifactObject(pointer.manifestPath);
375
+ const capabilityMatrix = await readArtifactObject(pointer.capabilityMatrixPath);
376
+ const readiness = await readArtifactObject(pointer.providerReadinessArtifactPath);
377
+ const providerReadiness = readiness.providerReadiness;
378
+ if (!isGeneratedManifest(manifest) ||
379
+ capabilityMatrix.version !== 1 ||
380
+ capabilityMatrix.source !== "openclaw/crabline" ||
381
+ capabilityMatrix.manifestPath !== pointer.manifestPath ||
382
+ capabilityMatrix.channelDriver !== "crabline" ||
383
+ typeof capabilityMatrix.selectedChannel !== "string" ||
384
+ capabilityMatrix.selectedChannel.length === 0 ||
385
+ capabilityMatrix.report === null ||
386
+ typeof capabilityMatrix.report !== "object" ||
387
+ Array.isArray(capabilityMatrix.report) ||
388
+ readiness.version !== 2 ||
389
+ readiness.source !== "openclaw/crabline" ||
390
+ readiness.manifestPath !== pointer.manifestPath ||
391
+ readiness.channelDriver !== "crabline" ||
392
+ readiness.selectedChannel !== capabilityMatrix.selectedChannel ||
393
+ manifest.provider !== readiness.selectedChannel ||
394
+ !isReadinessSection(providerReadiness, manifest, pointer.manifestPath, true)) {
395
+ throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
396
+ }
397
+ const manifestRecorderPath = typeof manifest.recorderPath === "string" ? manifest.recorderPath : undefined;
398
+ const providerReadinessRecorderPath = readNestedRecorderPath(providerReadiness);
399
+ const recorderPaths = [manifestRecorderPath, providerReadinessRecorderPath];
400
+ if (manifest.recorderPath !== undefined && typeof manifest.recorderPath !== "string") {
401
+ throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
402
+ }
403
+ if (pointer.recorderSnapshotPath === null) {
404
+ if (recorderPaths.some((recorderPath) => recorderPath !== undefined)) {
405
+ throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
406
+ }
407
+ return;
408
+ }
409
+ const resolvedRecorderPaths = recorderPaths.map((recorderPath) => recorderPath === undefined ? undefined : path.resolve(outputDir, recorderPath));
410
+ if (resolvedRecorderPaths.some((recorderPath) => recorderPath === undefined || path.dirname(recorderPath) !== generationDirectory) ||
411
+ new Set(resolvedRecorderPaths).size !== 1) {
412
+ throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
413
+ }
414
+ const recorderPath = resolvedRecorderPaths[0];
415
+ if (pointer.recorderSnapshotPath !== null &&
416
+ recorderPath !== path.resolve(outputDir, pointer.recorderSnapshotPath)) {
417
+ throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
418
+ }
419
+ try {
420
+ await assertGenerationIdentity();
421
+ const recorderStats = await fs.lstat(recorderPath, { bigint: true });
422
+ if (recorderStats.isFile() && recorderStats.nlink === 1n) {
423
+ await assertGenerationIdentity();
424
+ return;
425
+ }
426
+ }
427
+ catch (error) {
428
+ throw new Error("OpenClaw Crabline current artifact generation is incomplete.", {
429
+ cause: error,
430
+ });
431
+ }
432
+ throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
433
+ }
434
+ async function readValidCurrentArtifactGeneration(outputDir, initialPointer) {
435
+ let pointer = initialPointer;
436
+ let lastValidationError;
437
+ for (let attempt = 0; attempt < CURRENT_GENERATION_READ_ATTEMPTS; attempt += 1) {
438
+ try {
439
+ await assertArtifactGenerationExists(outputDir, pointer);
440
+ lastValidationError = undefined;
441
+ }
442
+ catch (error) {
443
+ lastValidationError = error;
444
+ }
445
+ const currentPointer = await readOpenClawCrablineArtifactPointer(outputDir);
446
+ if (currentPointer === null || isDeepStrictEqual(currentPointer, pointer)) {
447
+ if (lastValidationError !== undefined) {
448
+ throw lastValidationError;
449
+ }
450
+ if (currentPointer === null) {
451
+ throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
452
+ }
453
+ return pointer;
454
+ }
455
+ pointer = currentPointer;
456
+ }
457
+ throw new Error("OpenClaw Crabline current artifact generation changed too frequently to validate.", lastValidationError === undefined ? undefined : { cause: lastValidationError });
114
458
  }
115
459
  async function pruneArtifactStore(params) {
116
460
  const retainedGenerations = new Set(params.pointer
@@ -153,11 +497,11 @@ export async function publishOpenClawCrablineArtifactGeneration(params, dependen
153
497
  await output.assertIdentityAt();
154
498
  await store.assertIdentityAt();
155
499
  await params.lock.assertOwned();
156
- const currentPointer = await readOpenClawCrablineArtifactPointer(outputDir);
500
+ let currentPointer = await readOpenClawCrablineArtifactPointer(outputDir);
157
501
  await output.assertIdentityAt();
158
502
  await store.assertIdentityAt();
159
503
  if (currentPointer) {
160
- await assertCurrentGenerationExists(outputDir, currentPointer);
504
+ currentPointer = await readValidCurrentArtifactGeneration(outputDir, currentPointer);
161
505
  await output.assertIdentityAt();
162
506
  await store.assertIdentityAt();
163
507
  }
@@ -193,15 +537,6 @@ export async function publishOpenClawCrablineArtifactGeneration(params, dependen
193
537
  let primaryError;
194
538
  let published;
195
539
  try {
196
- const pointer = {
197
- capabilityMatrixPath: generationArtifactPath(generation, params.selection.capabilityMatrixPath),
198
- generation,
199
- manifestPath: generationArtifactPath(generation, OPENCLAW_CRABLINE_MANIFEST_PATH),
200
- ...(currentPointer ? { previousGeneration: currentPointer.generation } : {}),
201
- providerReadinessArtifactPath: generationArtifactPath(generation, providerReadinessArtifactPath),
202
- smokeArtifactPath: generationArtifactPath(generation, providerReadinessArtifactPath),
203
- version: 1,
204
- };
205
540
  if (params.recorderSnapshot &&
206
541
  (path.basename(params.recorderSnapshot.fileName) !== params.recorderSnapshot.fileName ||
207
542
  !params.recorderSnapshot.fileName.endsWith(".jsonl"))) {
@@ -210,12 +545,19 @@ export async function publishOpenClawCrablineArtifactGeneration(params, dependen
210
545
  const recorderSnapshotPath = params.recorderSnapshot
211
546
  ? generationArtifactPath(generation, params.recorderSnapshot.fileName)
212
547
  : undefined;
548
+ const pointer = {
549
+ capabilityMatrixPath: generationArtifactPath(generation, params.selection.capabilityMatrixPath),
550
+ generation,
551
+ manifestPath: generationArtifactPath(generation, OPENCLAW_CRABLINE_MANIFEST_PATH),
552
+ ...(currentPointer ? { previousGeneration: currentPointer.generation } : {}),
553
+ providerReadinessArtifactPath: generationArtifactPath(generation, providerReadinessArtifactPath),
554
+ recorderSnapshotPath: recorderSnapshotPath ?? null,
555
+ version: 3,
556
+ };
213
557
  const publishedManifest = recorderSnapshotPath
214
558
  ? { ...params.manifest, recorderPath: recorderSnapshotPath }
215
- : params.manifest;
216
- const providerReadinessBase = recorderSnapshotPath
217
- ? withRecorderSnapshotPath(params.providerReadiness, recorderSnapshotPath)
218
- : params.providerReadiness;
559
+ : Object.fromEntries(Object.entries(params.manifest).filter(([key]) => key !== "recorderPath"));
560
+ const providerReadinessBase = withPublishedRecorderPath(params.providerReadiness, recorderSnapshotPath);
219
561
  const providerReadiness = {
220
562
  ...providerReadinessBase,
221
563
  manifestPath: pointer.manifestPath,
@@ -238,13 +580,12 @@ export async function publishOpenClawCrablineArtifactGeneration(params, dependen
238
580
  },
239
581
  {
240
582
  contents: `${JSON.stringify({
241
- version: 1,
583
+ version: 2,
242
584
  source: "openclaw/crabline",
243
585
  channelDriver: params.selection.channelDriver,
244
586
  selectedChannel: params.selection.channel,
245
587
  manifestPath: pointer.manifestPath,
246
588
  providerReadiness,
247
- smoke: providerReadiness,
248
589
  }, null, 2)}\n`,
249
590
  fileName: providerReadinessArtifactPath,
250
591
  },
@@ -280,6 +621,10 @@ export async function publishOpenClawCrablineArtifactGeneration(params, dependen
280
621
  await output.assertIdentityAt();
281
622
  await store.assertIdentityAt();
282
623
  await staging.assertIdentityAt(generationPath);
624
+ await assertArtifactGenerationExists(outputDir, pointer);
625
+ await output.assertIdentityAt();
626
+ await store.assertIdentityAt();
627
+ await staging.assertIdentityAt(generationPath);
283
628
  await params.lock.commitFileAtomically({
284
629
  contents: `${JSON.stringify(pointer, null, 2)}\n`,
285
630
  destinationPath: path.join(outputDir, OPENCLAW_CRABLINE_ARTIFACT_POINTER_PATH),
@@ -316,7 +661,6 @@ export async function publishOpenClawCrablineArtifactGeneration(params, dependen
316
661
  ...pointer,
317
662
  pointerPath: OPENCLAW_CRABLINE_ARTIFACT_POINTER_PATH,
318
663
  providerReadiness,
319
- smoke: providerReadiness,
320
664
  ...(warnings ? { warnings } : {}),
321
665
  };
322
666
  }