@openclaw/crabline 0.1.10 → 0.1.12
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 +99 -12
- 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 +264 -50
- 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/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 +12 -2
- package/dist/src/openclaw/artifact-generation.js +436 -15
- package/dist/src/openclaw/artifact-generation.js.map +1 -1
- package/dist/src/openclaw/bridges/matrix.js +21 -4
- package/dist/src/openclaw/bridges/matrix.js.map +1 -1
- package/dist/src/openclaw/bridges/mattermost.js +13 -8
- 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 +15 -6
- package/dist/src/openclaw/bridges/signal.js.map +1 -1
- package/dist/src/openclaw/bridges/slack.js +116 -23
- package/dist/src/openclaw/bridges/slack.js.map +1 -1
- package/dist/src/openclaw/bridges/telegram.js +45 -19
- package/dist/src/openclaw/bridges/telegram.js.map +1 -1
- package/dist/src/openclaw/bridges/whatsapp.js +32 -16
- package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
- package/dist/src/openclaw/bridges/zalo.js +8 -3
- package/dist/src/openclaw/bridges/zalo.js.map +1 -1
- package/dist/src/openclaw/private-file.d.ts +40 -3
- package/dist/src/openclaw/private-file.js +2070 -95
- package/dist/src/openclaw/private-file.js.map +1 -1
- package/dist/src/openclaw/shared.d.ts +1 -0
- package/dist/src/openclaw/shared.js +48 -13
- package/dist/src/openclaw/shared.js.map +1 -1
- package/dist/src/openclaw/smoke-lock.d.ts +9 -0
- package/dist/src/openclaw/smoke-lock.js +696 -187
- package/dist/src/openclaw/smoke-lock.js.map +1 -1
- package/dist/src/openclaw.js +352 -29
- 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 +532 -42
- 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/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/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 +1241 -22
- 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 +259 -51
- 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 +1193 -118
- 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 -0
- package/dist/src/servers/whatsapp.js +149 -24
- 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 +185 -43
- package/fixtures/examples/crabline.example.yaml +3 -2
- package/package.json +18 -10
- package/fixtures/examples/openclaw-bridge.yaml +0 -125
|
@@ -1,9 +1,13 @@
|
|
|
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
6
|
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
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;
|
|
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;
|
|
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;
|
|
7
11
|
function resolveProviderReadinessArtifactPath(selection) {
|
|
8
12
|
const readinessPath = selection.providerReadinessArtifactPath ?? selection.smokeArtifactPath;
|
|
9
13
|
if (selection.capabilityMatrixPath !== OPENCLAW_CRABLINE_CHANNEL_CAPABILITY_MATRIX_PATH ||
|
|
@@ -15,6 +19,179 @@ function resolveProviderReadinessArtifactPath(selection) {
|
|
|
15
19
|
function isMissingPathError(error) {
|
|
16
20
|
return error.code === "ENOENT";
|
|
17
21
|
}
|
|
22
|
+
function isRecord(value) {
|
|
23
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function hasNonEmptyStringFields(value, fields) {
|
|
26
|
+
return fields.every((field) => typeof value[field] === "string" && value[field].length > 0);
|
|
27
|
+
}
|
|
28
|
+
function isGeneratedManifest(value) {
|
|
29
|
+
if (value.version !== 1 ||
|
|
30
|
+
!hasNonEmptyStringFields(value, ["adminToken", "baseUrl", "provider"]) ||
|
|
31
|
+
!isRecord(value.endpoints) ||
|
|
32
|
+
!isRecord(value.env)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const endpoints = value.endpoints;
|
|
36
|
+
const env = value.env;
|
|
37
|
+
const baseUrl = value.baseUrl;
|
|
38
|
+
switch (value.provider) {
|
|
39
|
+
case "mattermost":
|
|
40
|
+
return (hasNonEmptyStringFields(value, ["botToken", "botUserId"]) &&
|
|
41
|
+
hasNonEmptyStringFields(endpoints, ["adminInboundUrl", "apiRoot", "websocketUrl"]) &&
|
|
42
|
+
hasNonEmptyStringFields(env, ["MATTERMOST_BOT_TOKEN", "MATTERMOST_URL"]) &&
|
|
43
|
+
endpoints.adminInboundUrl === `${baseUrl}/crabline/mattermost/inbound` &&
|
|
44
|
+
endpoints.apiRoot === `${baseUrl}/api/v4` &&
|
|
45
|
+
endpoints.websocketUrl === `${baseUrl.replace(/^http/u, "ws")}/api/v4/websocket` &&
|
|
46
|
+
env.MATTERMOST_BOT_TOKEN === value.botToken &&
|
|
47
|
+
env.MATTERMOST_URL === baseUrl);
|
|
48
|
+
case "matrix":
|
|
49
|
+
return (hasNonEmptyStringFields(value, ["accessToken", "botUserId", "deviceId"]) &&
|
|
50
|
+
hasNonEmptyStringFields(endpoints, ["adminInboundUrl", "clientApiRoot", "syncUrl"]) &&
|
|
51
|
+
hasNonEmptyStringFields(env, [
|
|
52
|
+
"MATRIX_ACCESS_TOKEN",
|
|
53
|
+
"MATRIX_BASE_URL",
|
|
54
|
+
"MATRIX_USER_ID",
|
|
55
|
+
]) &&
|
|
56
|
+
endpoints.adminInboundUrl === `${baseUrl}/crabline/matrix/inbound` &&
|
|
57
|
+
endpoints.clientApiRoot === `${baseUrl}/_matrix/client/v3` &&
|
|
58
|
+
endpoints.syncUrl === `${endpoints.clientApiRoot}/sync` &&
|
|
59
|
+
env.MATRIX_ACCESS_TOKEN === value.accessToken &&
|
|
60
|
+
env.MATRIX_BASE_URL === baseUrl &&
|
|
61
|
+
env.MATRIX_USER_ID === value.botUserId);
|
|
62
|
+
case "signal":
|
|
63
|
+
return (hasNonEmptyStringFields(value, ["account"]) &&
|
|
64
|
+
hasNonEmptyStringFields(endpoints, ["adminInboundUrl", "apiRoot", "eventsUrl", "rpcUrl"]) &&
|
|
65
|
+
endpoints.adminInboundUrl === `${baseUrl}/crabline/signal/inbound` &&
|
|
66
|
+
endpoints.apiRoot === baseUrl &&
|
|
67
|
+
endpoints.eventsUrl === `${baseUrl}/api/v1/events` &&
|
|
68
|
+
endpoints.rpcUrl === `${baseUrl}/api/v1/rpc` &&
|
|
69
|
+
Object.keys(env).length === 0);
|
|
70
|
+
case "slack":
|
|
71
|
+
return (hasNonEmptyStringFields(value, ["botToken", "signingSecret"]) &&
|
|
72
|
+
hasNonEmptyStringFields(endpoints, ["adminInboundUrl", "apiRoot", "eventsUrl"]) &&
|
|
73
|
+
hasNonEmptyStringFields(env, [
|
|
74
|
+
"SLACK_API_URL",
|
|
75
|
+
"SLACK_BOT_TOKEN",
|
|
76
|
+
"SLACK_SIGNING_SECRET",
|
|
77
|
+
]) &&
|
|
78
|
+
endpoints.adminInboundUrl === `${baseUrl}/crabline/slack/inbound` &&
|
|
79
|
+
endpoints.apiRoot === `${baseUrl}/api/` &&
|
|
80
|
+
endpoints.eventsUrl === `${baseUrl}/slack/events` &&
|
|
81
|
+
env.SLACK_API_URL === endpoints.apiRoot &&
|
|
82
|
+
env.SLACK_BOT_TOKEN === value.botToken &&
|
|
83
|
+
env.SLACK_SIGNING_SECRET === value.signingSecret);
|
|
84
|
+
case "telegram":
|
|
85
|
+
return (hasNonEmptyStringFields(value, ["botToken"]) &&
|
|
86
|
+
hasNonEmptyStringFields(endpoints, ["adminInboundUrl", "apiRoot"]) &&
|
|
87
|
+
hasNonEmptyStringFields(env, ["TELEGRAM_BOT_TOKEN"]) &&
|
|
88
|
+
endpoints.adminInboundUrl === `${baseUrl}/crabline/telegram/inbound` &&
|
|
89
|
+
endpoints.apiRoot === baseUrl &&
|
|
90
|
+
env.TELEGRAM_BOT_TOKEN === value.botToken);
|
|
91
|
+
case "whatsapp":
|
|
92
|
+
return (hasNonEmptyStringFields(value, [
|
|
93
|
+
"accessToken",
|
|
94
|
+
"graphVersion",
|
|
95
|
+
"phoneNumberId",
|
|
96
|
+
"selfJid",
|
|
97
|
+
]) &&
|
|
98
|
+
hasNonEmptyStringFields(endpoints, [
|
|
99
|
+
"adminInboundUrl",
|
|
100
|
+
"apiRoot",
|
|
101
|
+
"baileysWebSocketUrl",
|
|
102
|
+
"messagesUrl",
|
|
103
|
+
"phoneNumberUrl",
|
|
104
|
+
"statusUrl",
|
|
105
|
+
]) &&
|
|
106
|
+
hasNonEmptyStringFields(env, [
|
|
107
|
+
"CLOUD_API_ACCESS_TOKEN",
|
|
108
|
+
"CLOUD_API_VERSION",
|
|
109
|
+
"WA_BASE_URL",
|
|
110
|
+
"WA_PHONE_NUMBER_ID",
|
|
111
|
+
]) &&
|
|
112
|
+
endpoints.adminInboundUrl === `${baseUrl}/_crabline/admin/whatsapp/inbound` &&
|
|
113
|
+
endpoints.apiRoot === `${baseUrl}/${value.graphVersion}` &&
|
|
114
|
+
endpoints.phoneNumberUrl ===
|
|
115
|
+
`${endpoints.apiRoot}/${value.phoneNumberId}` &&
|
|
116
|
+
endpoints.messagesUrl === `${endpoints.phoneNumberUrl}/messages` &&
|
|
117
|
+
endpoints.statusUrl === endpoints.messagesUrl &&
|
|
118
|
+
endpoints.baileysWebSocketUrl ===
|
|
119
|
+
`${baseUrl.replace(/^http/u, "ws")}/ws/chat?access_token=${encodeURIComponent(value.accessToken)}` &&
|
|
120
|
+
env.CLOUD_API_ACCESS_TOKEN === value.accessToken &&
|
|
121
|
+
env.CLOUD_API_VERSION === value.graphVersion &&
|
|
122
|
+
env.WA_BASE_URL === baseUrl &&
|
|
123
|
+
env.WA_PHONE_NUMBER_ID === value.phoneNumberId);
|
|
124
|
+
case "zalo":
|
|
125
|
+
return (hasNonEmptyStringFields(value, ["botId", "botToken"]) &&
|
|
126
|
+
hasNonEmptyStringFields(endpoints, ["adminInboundUrl", "apiRoot"]) &&
|
|
127
|
+
hasNonEmptyStringFields(env, ["ZALO_API_URL", "ZALO_BOT_TOKEN"]) &&
|
|
128
|
+
endpoints.adminInboundUrl === `${baseUrl}/crabline/zalo/inbound` &&
|
|
129
|
+
endpoints.apiRoot === baseUrl &&
|
|
130
|
+
env.ZALO_API_URL === baseUrl &&
|
|
131
|
+
env.ZALO_BOT_TOKEN === value.botToken);
|
|
132
|
+
default:
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function isSuccessfulProbe(value, manifest) {
|
|
137
|
+
if (!isRecord(value)) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
switch (manifest.provider) {
|
|
141
|
+
case "mattermost":
|
|
142
|
+
return (value.id === manifest.botUserId &&
|
|
143
|
+
typeof value.username === "string" &&
|
|
144
|
+
value.username.trim().length > 0 &&
|
|
145
|
+
typeof value.update_at === "number" &&
|
|
146
|
+
Number.isSafeInteger(value.update_at) &&
|
|
147
|
+
value.update_at >= 0);
|
|
148
|
+
case "matrix":
|
|
149
|
+
return value.user_id === manifest.botUserId;
|
|
150
|
+
case "signal":
|
|
151
|
+
return (value.ok === true &&
|
|
152
|
+
typeof value.status === "number" &&
|
|
153
|
+
Number.isInteger(value.status) &&
|
|
154
|
+
value.status >= 200 &&
|
|
155
|
+
value.status < 300);
|
|
156
|
+
case "slack":
|
|
157
|
+
return value.ok === true;
|
|
158
|
+
case "telegram": {
|
|
159
|
+
const result = value.result;
|
|
160
|
+
const tokenBotId = typeof manifest.botToken === "string"
|
|
161
|
+
? /^([1-9]\d*):/u.exec(manifest.botToken)?.[1]
|
|
162
|
+
: undefined;
|
|
163
|
+
const expectedBotId = tokenBotId === undefined ? undefined : Number(tokenBotId);
|
|
164
|
+
return (value.ok === true &&
|
|
165
|
+
isRecord(result) &&
|
|
166
|
+
typeof result.id === "number" &&
|
|
167
|
+
Number.isSafeInteger(result.id) &&
|
|
168
|
+
result.id > 0 &&
|
|
169
|
+
Number.isSafeInteger(expectedBotId) &&
|
|
170
|
+
result.id === expectedBotId &&
|
|
171
|
+
result.is_bot === true &&
|
|
172
|
+
typeof result.first_name === "string" &&
|
|
173
|
+
result.first_name.length > 0);
|
|
174
|
+
}
|
|
175
|
+
case "whatsapp":
|
|
176
|
+
return value.id === manifest.phoneNumberId;
|
|
177
|
+
case "zalo":
|
|
178
|
+
return value.ok === true && isRecord(value.result) && value.result.id === manifest.botId;
|
|
179
|
+
default:
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function isReadinessSection(value, manifest, manifestPath, requireCurrentFields) {
|
|
184
|
+
if (!isRecord(value) || value.manifestPath !== manifestPath || !isRecord(value.result)) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
const result = value.result;
|
|
188
|
+
return (result.ok === true &&
|
|
189
|
+
result.provider === manifest.provider &&
|
|
190
|
+
isRecord(result.endpoints) &&
|
|
191
|
+
isDeepStrictEqual(result.endpoints, manifest.endpoints) &&
|
|
192
|
+
isSuccessfulProbe(result.probe, manifest) &&
|
|
193
|
+
(!requireCurrentFields || (result.proof === "provider-api-probe" && result.ready === true)));
|
|
194
|
+
}
|
|
18
195
|
function assertGenerationName(value, field) {
|
|
19
196
|
if (typeof value !== "string" || !GENERATION_NAME_PATTERN.test(value)) {
|
|
20
197
|
throw new Error(`OpenClaw Crabline artifact pointer ${field} is malformed.`);
|
|
@@ -23,15 +200,45 @@ function assertGenerationName(value, field) {
|
|
|
23
200
|
function generationArtifactPath(generation, fileName) {
|
|
24
201
|
return path.join(OPENCLAW_CRABLINE_ARTIFACT_STORE_DIRECTORY, generation, fileName);
|
|
25
202
|
}
|
|
203
|
+
function artifactRemovalTombstoneBaseName(name) {
|
|
204
|
+
const originalName = REMOVAL_TOMBSTONE_PATTERN.exec(name)?.[1];
|
|
205
|
+
return originalName !== undefined &&
|
|
206
|
+
(GENERATION_NAME_PATTERN.test(originalName) || STAGING_NAME_PATTERN.test(originalName))
|
|
207
|
+
? originalName
|
|
208
|
+
: null;
|
|
209
|
+
}
|
|
210
|
+
function withPublishedRecorderPath(providerReadiness, recorderPath) {
|
|
211
|
+
const result = providerReadiness.result;
|
|
212
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
213
|
+
throw new Error("OpenClaw Crabline provider readiness result is malformed.");
|
|
214
|
+
}
|
|
215
|
+
const publishedResult = {
|
|
216
|
+
...result,
|
|
217
|
+
};
|
|
218
|
+
if (recorderPath === undefined) {
|
|
219
|
+
delete publishedResult.recorderPath;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
publishedResult.recorderPath = recorderPath;
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
...providerReadiness,
|
|
226
|
+
result: publishedResult,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
26
229
|
function parseArtifactPointer(contents) {
|
|
27
|
-
let
|
|
230
|
+
let parsed;
|
|
28
231
|
try {
|
|
29
|
-
|
|
232
|
+
parsed = JSON.parse(contents);
|
|
30
233
|
}
|
|
31
234
|
catch (error) {
|
|
32
235
|
throw new Error("OpenClaw Crabline artifact pointer is malformed.", { cause: error });
|
|
33
236
|
}
|
|
34
|
-
if (
|
|
237
|
+
if (!isRecord(parsed)) {
|
|
238
|
+
throw new Error("OpenClaw Crabline artifact pointer is malformed.");
|
|
239
|
+
}
|
|
240
|
+
const value = parsed;
|
|
241
|
+
if (value.version !== 1 && value.version !== 2) {
|
|
35
242
|
throw new Error("OpenClaw Crabline artifact pointer is malformed.");
|
|
36
243
|
}
|
|
37
244
|
assertGenerationName(value.generation, "generation");
|
|
@@ -50,6 +257,8 @@ function parseArtifactPointer(contents) {
|
|
|
50
257
|
if (typeof value.capabilityMatrixPath !== "string" ||
|
|
51
258
|
value.capabilityMatrixPath !== expected.capabilityMatrixPath ||
|
|
52
259
|
value.manifestPath !== expected.manifestPath ||
|
|
260
|
+
(value.version === 2 &&
|
|
261
|
+
value.providerReadinessArtifactPath !== expected.providerReadinessArtifactPath) ||
|
|
53
262
|
(value.providerReadinessArtifactPath !== undefined &&
|
|
54
263
|
value.providerReadinessArtifactPath !== expected.providerReadinessArtifactPath) ||
|
|
55
264
|
(value.smokeArtifactPath !== undefined &&
|
|
@@ -57,15 +266,36 @@ function parseArtifactPointer(contents) {
|
|
|
57
266
|
(value.providerReadinessArtifactPath === undefined && value.smokeArtifactPath === undefined)) {
|
|
58
267
|
throw new Error("OpenClaw Crabline artifact pointer is malformed.");
|
|
59
268
|
}
|
|
60
|
-
|
|
269
|
+
if (value.version === 1 && value.recorderSnapshotPath !== undefined) {
|
|
270
|
+
throw new Error("OpenClaw Crabline artifact pointer is malformed.");
|
|
271
|
+
}
|
|
272
|
+
if (value.version === 2) {
|
|
273
|
+
if (value.recorderSnapshotPath !== null && typeof value.recorderSnapshotPath !== "string") {
|
|
274
|
+
throw new Error("OpenClaw Crabline artifact pointer is malformed.");
|
|
275
|
+
}
|
|
276
|
+
if (typeof value.recorderSnapshotPath === "string") {
|
|
277
|
+
const recorderFileName = path.basename(value.recorderSnapshotPath);
|
|
278
|
+
if (!recorderFileName.endsWith(".jsonl") ||
|
|
279
|
+
value.recorderSnapshotPath !== generationArtifactPath(value.generation, recorderFileName)) {
|
|
280
|
+
throw new Error("OpenClaw Crabline artifact pointer is malformed.");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const pointer = {
|
|
61
285
|
capabilityMatrixPath: value.capabilityMatrixPath,
|
|
62
286
|
generation: value.generation,
|
|
63
287
|
...(value.previousGeneration ? { previousGeneration: value.previousGeneration } : {}),
|
|
64
288
|
manifestPath: value.manifestPath,
|
|
65
289
|
providerReadinessArtifactPath: value.providerReadinessArtifactPath ?? value.smokeArtifactPath,
|
|
66
290
|
smokeArtifactPath: value.smokeArtifactPath ?? value.providerReadinessArtifactPath,
|
|
67
|
-
version: 1,
|
|
68
291
|
};
|
|
292
|
+
return value.version === 1
|
|
293
|
+
? { ...pointer, version: 1 }
|
|
294
|
+
: {
|
|
295
|
+
...pointer,
|
|
296
|
+
recorderSnapshotPath: value.recorderSnapshotPath,
|
|
297
|
+
version: 2,
|
|
298
|
+
};
|
|
69
299
|
}
|
|
70
300
|
export async function readOpenClawCrablineArtifactPointer(outputDir) {
|
|
71
301
|
try {
|
|
@@ -78,34 +308,199 @@ export async function readOpenClawCrablineArtifactPointer(outputDir) {
|
|
|
78
308
|
throw error;
|
|
79
309
|
}
|
|
80
310
|
}
|
|
81
|
-
async function
|
|
311
|
+
async function assertArtifactGenerationExists(outputDir, pointer) {
|
|
312
|
+
const generationDirectory = path.resolve(outputDir, OPENCLAW_CRABLINE_ARTIFACT_STORE_DIRECTORY, pointer.generation);
|
|
313
|
+
let currentGeneration;
|
|
314
|
+
try {
|
|
315
|
+
currentGeneration = await captureDirectoryIdentity(generationDirectory);
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
throw new Error("OpenClaw Crabline current artifact generation is incomplete.", {
|
|
319
|
+
cause: error,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
const assertGenerationIdentity = async () => {
|
|
323
|
+
try {
|
|
324
|
+
await currentGeneration.assertIdentityAt();
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
throw new Error("OpenClaw Crabline current artifact generation is incomplete.", {
|
|
328
|
+
cause: error,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
};
|
|
82
332
|
for (const artifactPath of [
|
|
83
333
|
pointer.manifestPath,
|
|
84
334
|
pointer.capabilityMatrixPath,
|
|
85
335
|
pointer.providerReadinessArtifactPath,
|
|
86
336
|
]) {
|
|
87
|
-
|
|
337
|
+
await assertGenerationIdentity();
|
|
338
|
+
let stats;
|
|
339
|
+
try {
|
|
340
|
+
stats = await fs.lstat(path.join(outputDir, artifactPath));
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
throw new Error("OpenClaw Crabline current artifact generation is incomplete.", {
|
|
344
|
+
cause: error,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
88
347
|
if (!stats.isFile()) {
|
|
89
348
|
throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
|
|
90
349
|
}
|
|
350
|
+
await assertGenerationIdentity();
|
|
351
|
+
}
|
|
352
|
+
const readArtifactObject = async (artifactPath) => {
|
|
353
|
+
try {
|
|
354
|
+
await assertGenerationIdentity();
|
|
355
|
+
const value = JSON.parse(await fs.readFile(path.join(outputDir, artifactPath), "utf8"));
|
|
356
|
+
await assertGenerationIdentity();
|
|
357
|
+
if (!isRecord(value)) {
|
|
358
|
+
throw new Error("artifact is not an object");
|
|
359
|
+
}
|
|
360
|
+
return value;
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
throw new Error("OpenClaw Crabline current artifact generation is incomplete.", {
|
|
364
|
+
cause: error,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
const readNestedRecorderPath = (value) => {
|
|
369
|
+
const result = value.result;
|
|
370
|
+
const recorderPath = result.recorderPath;
|
|
371
|
+
if (recorderPath !== undefined && typeof recorderPath !== "string") {
|
|
372
|
+
throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
|
|
373
|
+
}
|
|
374
|
+
return recorderPath;
|
|
375
|
+
};
|
|
376
|
+
const manifest = await readArtifactObject(pointer.manifestPath);
|
|
377
|
+
const capabilityMatrix = await readArtifactObject(pointer.capabilityMatrixPath);
|
|
378
|
+
const readiness = await readArtifactObject(pointer.providerReadinessArtifactPath);
|
|
379
|
+
const providerReadiness = readiness.providerReadiness;
|
|
380
|
+
const smoke = readiness.smoke;
|
|
381
|
+
if (!isGeneratedManifest(manifest) ||
|
|
382
|
+
capabilityMatrix.version !== 1 ||
|
|
383
|
+
capabilityMatrix.source !== "openclaw/crabline" ||
|
|
384
|
+
capabilityMatrix.manifestPath !== pointer.manifestPath ||
|
|
385
|
+
capabilityMatrix.channelDriver !== "crabline" ||
|
|
386
|
+
typeof capabilityMatrix.selectedChannel !== "string" ||
|
|
387
|
+
capabilityMatrix.selectedChannel.length === 0 ||
|
|
388
|
+
capabilityMatrix.report === null ||
|
|
389
|
+
typeof capabilityMatrix.report !== "object" ||
|
|
390
|
+
Array.isArray(capabilityMatrix.report) ||
|
|
391
|
+
readiness.version !== 1 ||
|
|
392
|
+
readiness.source !== "openclaw/crabline" ||
|
|
393
|
+
readiness.manifestPath !== pointer.manifestPath ||
|
|
394
|
+
readiness.channelDriver !== "crabline" ||
|
|
395
|
+
readiness.selectedChannel !== capabilityMatrix.selectedChannel ||
|
|
396
|
+
manifest.provider !== readiness.selectedChannel ||
|
|
397
|
+
!isReadinessSection(smoke, manifest, pointer.manifestPath, pointer.version === 2 || providerReadiness !== undefined) ||
|
|
398
|
+
(pointer.version === 2 &&
|
|
399
|
+
(!isReadinessSection(providerReadiness, manifest, pointer.manifestPath, true) ||
|
|
400
|
+
!isDeepStrictEqual(providerReadiness, smoke))) ||
|
|
401
|
+
(pointer.version === 1 &&
|
|
402
|
+
providerReadiness !== undefined &&
|
|
403
|
+
(!isReadinessSection(providerReadiness, manifest, pointer.manifestPath, true) ||
|
|
404
|
+
!isDeepStrictEqual(providerReadiness, smoke)))) {
|
|
405
|
+
throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
|
|
406
|
+
}
|
|
407
|
+
const manifestRecorderPath = typeof manifest.recorderPath === "string" ? manifest.recorderPath : undefined;
|
|
408
|
+
const providerReadinessRecorderPath = isRecord(providerReadiness)
|
|
409
|
+
? readNestedRecorderPath(providerReadiness)
|
|
410
|
+
: undefined;
|
|
411
|
+
const smokeRecorderPath = readNestedRecorderPath(smoke);
|
|
412
|
+
const readinessRecorderPaths = [
|
|
413
|
+
...(isRecord(providerReadiness) ? [providerReadinessRecorderPath] : []),
|
|
414
|
+
smokeRecorderPath,
|
|
415
|
+
];
|
|
416
|
+
const recorderPaths = [manifestRecorderPath, ...readinessRecorderPaths];
|
|
417
|
+
if (manifest.recorderPath !== undefined && typeof manifest.recorderPath !== "string") {
|
|
418
|
+
throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
|
|
419
|
+
}
|
|
420
|
+
// The artifact store is owner-only; v1 compatibility covers historical layouts, not hostile same-owner rewrites.
|
|
421
|
+
if (pointer.version === 1 &&
|
|
422
|
+
readinessRecorderPaths.every((recorderPath) => recorderPath === undefined) &&
|
|
423
|
+
(manifestRecorderPath === undefined ||
|
|
424
|
+
path.dirname(path.resolve(outputDir, manifestRecorderPath)) !== generationDirectory)) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (pointer.version === 2 && pointer.recorderSnapshotPath === null) {
|
|
428
|
+
if (recorderPaths.some((recorderPath) => recorderPath !== undefined)) {
|
|
429
|
+
throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
|
|
430
|
+
}
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const resolvedRecorderPaths = recorderPaths.map((recorderPath) => recorderPath === undefined ? undefined : path.resolve(outputDir, recorderPath));
|
|
434
|
+
if (resolvedRecorderPaths.some((recorderPath) => recorderPath === undefined || path.dirname(recorderPath) !== generationDirectory) ||
|
|
435
|
+
new Set(resolvedRecorderPaths).size !== 1) {
|
|
436
|
+
throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
|
|
91
437
|
}
|
|
438
|
+
const recorderPath = resolvedRecorderPaths[0];
|
|
439
|
+
if (pointer.version === 2 &&
|
|
440
|
+
pointer.recorderSnapshotPath !== null &&
|
|
441
|
+
recorderPath !== path.resolve(outputDir, pointer.recorderSnapshotPath)) {
|
|
442
|
+
throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
await assertGenerationIdentity();
|
|
446
|
+
const recorderStats = await fs.lstat(recorderPath, { bigint: true });
|
|
447
|
+
if (recorderStats.isFile() && recorderStats.nlink === 1n) {
|
|
448
|
+
await assertGenerationIdentity();
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
throw new Error("OpenClaw Crabline current artifact generation is incomplete.", {
|
|
454
|
+
cause: error,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
|
|
458
|
+
}
|
|
459
|
+
async function readValidCurrentArtifactGeneration(outputDir, initialPointer) {
|
|
460
|
+
let pointer = initialPointer;
|
|
461
|
+
let lastValidationError;
|
|
462
|
+
for (let attempt = 0; attempt < CURRENT_GENERATION_READ_ATTEMPTS; attempt += 1) {
|
|
463
|
+
try {
|
|
464
|
+
await assertArtifactGenerationExists(outputDir, pointer);
|
|
465
|
+
lastValidationError = undefined;
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
lastValidationError = error;
|
|
469
|
+
}
|
|
470
|
+
const currentPointer = await readOpenClawCrablineArtifactPointer(outputDir);
|
|
471
|
+
if (currentPointer === null || isDeepStrictEqual(currentPointer, pointer)) {
|
|
472
|
+
if (lastValidationError !== undefined) {
|
|
473
|
+
throw lastValidationError;
|
|
474
|
+
}
|
|
475
|
+
if (currentPointer === null) {
|
|
476
|
+
throw new Error("OpenClaw Crabline current artifact generation is incomplete.");
|
|
477
|
+
}
|
|
478
|
+
return pointer;
|
|
479
|
+
}
|
|
480
|
+
pointer = currentPointer;
|
|
481
|
+
}
|
|
482
|
+
throw new Error("OpenClaw Crabline current artifact generation changed too frequently to validate.", lastValidationError === undefined ? undefined : { cause: lastValidationError });
|
|
92
483
|
}
|
|
93
484
|
async function pruneArtifactStore(params) {
|
|
94
485
|
const retainedGenerations = new Set(params.pointer
|
|
95
486
|
? [params.pointer.generation, params.pointer.previousGeneration].filter((generation) => generation !== undefined)
|
|
96
487
|
: []);
|
|
97
488
|
for (const entry of await fs.readdir(params.store.directoryPath, { withFileTypes: true })) {
|
|
98
|
-
const isAbandonedStaging = entry.isDirectory() && entry.name
|
|
489
|
+
const isAbandonedStaging = entry.isDirectory() && STAGING_NAME_PATTERN.test(entry.name);
|
|
99
490
|
const isObsoleteGeneration = entry.isDirectory() &&
|
|
100
491
|
GENERATION_NAME_PATTERN.test(entry.name) &&
|
|
101
492
|
!retainedGenerations.has(entry.name);
|
|
102
|
-
|
|
493
|
+
const removalTombstoneBaseName = entry.isDirectory()
|
|
494
|
+
? artifactRemovalTombstoneBaseName(entry.name)
|
|
495
|
+
: null;
|
|
496
|
+
const isRemovalTombstone = removalTombstoneBaseName !== null;
|
|
497
|
+
if (!isAbandonedStaging && !isObsoleteGeneration && !isRemovalTombstone) {
|
|
103
498
|
continue;
|
|
104
499
|
}
|
|
105
500
|
await params.lock.assertOwned();
|
|
106
501
|
await params.store.assertIdentityAt();
|
|
107
502
|
const obsolete = await securePrivateDirectory(path.join(params.store.directoryPath, entry.name), params.directoryOptions);
|
|
108
|
-
await removeSecuredPrivateDirectory(obsolete);
|
|
503
|
+
await removeSecuredPrivateDirectory(obsolete, undefined, removalTombstoneBaseName ?? entry.name);
|
|
109
504
|
await params.store.assertIdentityAt();
|
|
110
505
|
}
|
|
111
506
|
}
|
|
@@ -121,16 +516,17 @@ export async function publishOpenClawCrablineArtifactGeneration(params, dependen
|
|
|
121
516
|
...(dependencies.secureWindowsDirectory
|
|
122
517
|
? { secureWindowsDirectory: dependencies.secureWindowsDirectory }
|
|
123
518
|
: {}),
|
|
519
|
+
...(dependencies.syncParent ? { syncParent: dependencies.syncParent } : {}),
|
|
124
520
|
};
|
|
125
521
|
const store = await securePrivateDirectory(storePath, directoryOptions);
|
|
126
522
|
await output.assertIdentityAt();
|
|
127
523
|
await store.assertIdentityAt();
|
|
128
524
|
await params.lock.assertOwned();
|
|
129
|
-
|
|
525
|
+
let currentPointer = await readOpenClawCrablineArtifactPointer(outputDir);
|
|
130
526
|
await output.assertIdentityAt();
|
|
131
527
|
await store.assertIdentityAt();
|
|
132
528
|
if (currentPointer) {
|
|
133
|
-
await
|
|
529
|
+
currentPointer = await readValidCurrentArtifactGeneration(outputDir, currentPointer);
|
|
134
530
|
await output.assertIdentityAt();
|
|
135
531
|
await store.assertIdentityAt();
|
|
136
532
|
}
|
|
@@ -166,22 +562,35 @@ export async function publishOpenClawCrablineArtifactGeneration(params, dependen
|
|
|
166
562
|
let primaryError;
|
|
167
563
|
let published;
|
|
168
564
|
try {
|
|
565
|
+
if (params.recorderSnapshot &&
|
|
566
|
+
(path.basename(params.recorderSnapshot.fileName) !== params.recorderSnapshot.fileName ||
|
|
567
|
+
!params.recorderSnapshot.fileName.endsWith(".jsonl"))) {
|
|
568
|
+
throw new Error("OpenClaw Crabline recorder snapshot filename is malformed.");
|
|
569
|
+
}
|
|
570
|
+
const recorderSnapshotPath = params.recorderSnapshot
|
|
571
|
+
? generationArtifactPath(generation, params.recorderSnapshot.fileName)
|
|
572
|
+
: undefined;
|
|
169
573
|
const pointer = {
|
|
170
574
|
capabilityMatrixPath: generationArtifactPath(generation, params.selection.capabilityMatrixPath),
|
|
171
575
|
generation,
|
|
172
576
|
manifestPath: generationArtifactPath(generation, OPENCLAW_CRABLINE_MANIFEST_PATH),
|
|
173
577
|
...(currentPointer ? { previousGeneration: currentPointer.generation } : {}),
|
|
174
578
|
providerReadinessArtifactPath: generationArtifactPath(generation, providerReadinessArtifactPath),
|
|
579
|
+
recorderSnapshotPath: recorderSnapshotPath ?? null,
|
|
175
580
|
smokeArtifactPath: generationArtifactPath(generation, providerReadinessArtifactPath),
|
|
176
|
-
version:
|
|
581
|
+
version: 2,
|
|
177
582
|
};
|
|
583
|
+
const publishedManifest = recorderSnapshotPath
|
|
584
|
+
? { ...params.manifest, recorderPath: recorderSnapshotPath }
|
|
585
|
+
: Object.fromEntries(Object.entries(params.manifest).filter(([key]) => key !== "recorderPath"));
|
|
586
|
+
const providerReadinessBase = withPublishedRecorderPath(params.providerReadiness, recorderSnapshotPath);
|
|
178
587
|
const providerReadiness = {
|
|
179
|
-
...
|
|
588
|
+
...providerReadinessBase,
|
|
180
589
|
manifestPath: pointer.manifestPath,
|
|
181
590
|
};
|
|
182
591
|
const artifactContents = [
|
|
183
592
|
{
|
|
184
|
-
contents: `${JSON.stringify(
|
|
593
|
+
contents: `${JSON.stringify(publishedManifest, null, 2)}\n`,
|
|
185
594
|
fileName: OPENCLAW_CRABLINE_MANIFEST_PATH,
|
|
186
595
|
},
|
|
187
596
|
{
|
|
@@ -207,6 +616,14 @@ export async function publishOpenClawCrablineArtifactGeneration(params, dependen
|
|
|
207
616
|
}, null, 2)}\n`,
|
|
208
617
|
fileName: providerReadinessArtifactPath,
|
|
209
618
|
},
|
|
619
|
+
...(params.recorderSnapshot
|
|
620
|
+
? [
|
|
621
|
+
{
|
|
622
|
+
contents: params.recorderSnapshot.contents,
|
|
623
|
+
fileName: params.recorderSnapshot.fileName,
|
|
624
|
+
},
|
|
625
|
+
]
|
|
626
|
+
: []),
|
|
210
627
|
];
|
|
211
628
|
await params.lock.assertOwned();
|
|
212
629
|
for (const artifact of artifactContents) {
|
|
@@ -231,6 +648,10 @@ export async function publishOpenClawCrablineArtifactGeneration(params, dependen
|
|
|
231
648
|
await output.assertIdentityAt();
|
|
232
649
|
await store.assertIdentityAt();
|
|
233
650
|
await staging.assertIdentityAt(generationPath);
|
|
651
|
+
await assertArtifactGenerationExists(outputDir, pointer);
|
|
652
|
+
await output.assertIdentityAt();
|
|
653
|
+
await store.assertIdentityAt();
|
|
654
|
+
await staging.assertIdentityAt(generationPath);
|
|
234
655
|
await params.lock.commitFileAtomically({
|
|
235
656
|
contents: `${JSON.stringify(pointer, null, 2)}\n`,
|
|
236
657
|
destinationPath: path.join(outputDir, OPENCLAW_CRABLINE_ARTIFACT_POINTER_PATH),
|