@riddledc/openclaw-riddledc 0.9.1 → 0.9.2
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/CHECKSUMS.txt +2 -2
- package/dist/index.cjs +219 -66
- package/dist/index.js +219 -66
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/CHECKSUMS.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
d6ac8bd939d92590308b0293b254e197ab326064044435f645373ca289923cc7 dist/index.cjs
|
|
2
2
|
94ce04f0e2d84bf64dd68f0500dfdd2f951287a3deccec87f197261961927f6f dist/index.d.cts
|
|
3
3
|
94ce04f0e2d84bf64dd68f0500dfdd2f951287a3deccec87f197261961927f6f dist/index.d.ts
|
|
4
|
-
|
|
4
|
+
4b17886fc543f50ded46e6240427ec6652dde694285642ae10463e03aa10c364 dist/index.js
|
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,11 @@ var import_node_child_process = require("child_process");
|
|
|
30
30
|
var import_node_util = require("util");
|
|
31
31
|
var execFile = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
32
32
|
var INLINE_CAP = 50 * 1024;
|
|
33
|
+
var PREVIEW_REQUEST_TIMEOUT_MS = 3e4;
|
|
34
|
+
var PREVIEW_UPLOAD_TIMEOUT_MS = 5 * 6e4;
|
|
35
|
+
var PREVIEW_ARTIFACT_TIMEOUT_MS = 6e4;
|
|
36
|
+
var PREVIEW_RETRY_ATTEMPTS = 3;
|
|
37
|
+
var PREVIEW_RETRY_BASE_DELAY_MS = 750;
|
|
33
38
|
function getCfg(api) {
|
|
34
39
|
const cfg = api?.config ?? {};
|
|
35
40
|
const pluginCfg = cfg?.plugins?.entries?.["openclaw-riddledc"]?.config ?? {};
|
|
@@ -75,6 +80,76 @@ function abToBase64(ab) {
|
|
|
75
80
|
function getWorkspacePath(api) {
|
|
76
81
|
return api?.workspacePath ?? process.cwd();
|
|
77
82
|
}
|
|
83
|
+
function describeError(err) {
|
|
84
|
+
const anyErr = err;
|
|
85
|
+
const parts = [];
|
|
86
|
+
if (err instanceof Error) parts.push(err.message);
|
|
87
|
+
else parts.push(String(err));
|
|
88
|
+
const cause = anyErr?.cause;
|
|
89
|
+
if (cause) {
|
|
90
|
+
const causeParts = [
|
|
91
|
+
cause.code ? `code=${cause.code}` : "",
|
|
92
|
+
cause.name ? `name=${cause.name}` : "",
|
|
93
|
+
cause.message ? `message=${cause.message}` : ""
|
|
94
|
+
].filter(Boolean);
|
|
95
|
+
if (causeParts.length) parts.push(`cause: ${causeParts.join(" ")}`);
|
|
96
|
+
}
|
|
97
|
+
return parts.join("; ");
|
|
98
|
+
}
|
|
99
|
+
async function fetchWithTimeout(url, init, timeoutMs, label) {
|
|
100
|
+
const controller = new AbortController();
|
|
101
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
102
|
+
try {
|
|
103
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if (err?.name === "AbortError") {
|
|
106
|
+
throw new Error(`${label} timed out after ${Math.round(timeoutMs / 1e3)}s`);
|
|
107
|
+
}
|
|
108
|
+
throw err;
|
|
109
|
+
} finally {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function isTransientFetchError(err) {
|
|
114
|
+
const text = describeError(err).toLowerCase();
|
|
115
|
+
return [
|
|
116
|
+
"fetch failed",
|
|
117
|
+
"timed out",
|
|
118
|
+
"timeout",
|
|
119
|
+
"econnreset",
|
|
120
|
+
"econnrefused",
|
|
121
|
+
"etimedout",
|
|
122
|
+
"eai_again",
|
|
123
|
+
"socket",
|
|
124
|
+
"network",
|
|
125
|
+
"und_err",
|
|
126
|
+
"terminated"
|
|
127
|
+
].some((needle) => text.includes(needle));
|
|
128
|
+
}
|
|
129
|
+
function sleep(ms) {
|
|
130
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
131
|
+
}
|
|
132
|
+
async function fetchWithRetry(url, init, timeoutMs, label, opts = {}) {
|
|
133
|
+
const attempts = Math.max(1, opts.attempts ?? PREVIEW_RETRY_ATTEMPTS);
|
|
134
|
+
const baseDelayMs = opts.baseDelayMs ?? PREVIEW_RETRY_BASE_DELAY_MS;
|
|
135
|
+
let lastErr;
|
|
136
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
137
|
+
try {
|
|
138
|
+
return await fetchWithTimeout(url, init, timeoutMs, label);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
lastErr = err;
|
|
141
|
+
if (attempt >= attempts || !isTransientFetchError(err)) break;
|
|
142
|
+
const jitterMs = Math.floor(Math.random() * 250);
|
|
143
|
+
const delayMs = Math.min(baseDelayMs * Math.pow(2, attempt - 1) + jitterMs, 5e3);
|
|
144
|
+
console.warn(`[openclaw-riddledc] ${label} attempt ${attempt}/${attempts} failed: ${describeError(err)}; retrying in ${delayMs}ms`);
|
|
145
|
+
await sleep(delayMs);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
throw new Error(`${label} failed after ${attempts} attempts: ${describeError(lastErr)}`);
|
|
149
|
+
}
|
|
150
|
+
function isAlreadyStartedResponse(status, body) {
|
|
151
|
+
return status === 409 && /already in status:\s*(queued|running|complete|completed)/i.test(body);
|
|
152
|
+
}
|
|
78
153
|
async function writeArtifact(workspace, subdir, filename, content) {
|
|
79
154
|
const dir = (0, import_node_path.join)(workspace, "riddle", subdir);
|
|
80
155
|
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
@@ -823,11 +898,16 @@ function register(api) {
|
|
|
823
898
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Cannot access directory: ${e.message}` }, null, 2) }] };
|
|
824
899
|
}
|
|
825
900
|
const endpoint = baseUrl.replace(/\/$/, "");
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
901
|
+
let createRes;
|
|
902
|
+
try {
|
|
903
|
+
createRes = await fetchWithRetry(`${endpoint}/v1/preview`, {
|
|
904
|
+
method: "POST",
|
|
905
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
906
|
+
body: JSON.stringify({ framework: params.framework || "spa" })
|
|
907
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "preview create");
|
|
908
|
+
} catch (e) {
|
|
909
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${describeError(e)}` }, null, 2) }] };
|
|
910
|
+
}
|
|
831
911
|
if (!createRes.ok) {
|
|
832
912
|
const err = await createRes.text();
|
|
833
913
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: HTTP ${createRes.status} ${err}` }, null, 2) }] };
|
|
@@ -837,11 +917,16 @@ function register(api) {
|
|
|
837
917
|
try {
|
|
838
918
|
await execFile("tar", ["czf", tarball, "-C", dir, "."], { timeout: 6e4 });
|
|
839
919
|
const tarData = await (0, import_promises.readFile)(tarball);
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
920
|
+
let uploadRes;
|
|
921
|
+
try {
|
|
922
|
+
uploadRes = await fetchWithRetry(created.upload_url, {
|
|
923
|
+
method: "PUT",
|
|
924
|
+
headers: { "Content-Type": "application/gzip" },
|
|
925
|
+
body: tarData
|
|
926
|
+
}, PREVIEW_UPLOAD_TIMEOUT_MS, "preview upload");
|
|
927
|
+
} catch (e) {
|
|
928
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Upload failed: ${describeError(e)}` }, null, 2) }] };
|
|
929
|
+
}
|
|
845
930
|
if (!uploadRes.ok) {
|
|
846
931
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
847
932
|
}
|
|
@@ -851,10 +936,15 @@ function register(api) {
|
|
|
851
936
|
} catch {
|
|
852
937
|
}
|
|
853
938
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
939
|
+
let publishRes;
|
|
940
|
+
try {
|
|
941
|
+
publishRes = await fetchWithTimeout(`${endpoint}/v1/preview/${created.id}/publish`, {
|
|
942
|
+
method: "POST",
|
|
943
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
944
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "preview publish");
|
|
945
|
+
} catch (e) {
|
|
946
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Publish failed: ${describeError(e)}` }, null, 2) }] };
|
|
947
|
+
}
|
|
858
948
|
if (!publishRes.ok) {
|
|
859
949
|
const err = await publishRes.text();
|
|
860
950
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Publish failed: HTTP ${publishRes.status} ${err}` }, null, 2) }] };
|
|
@@ -890,10 +980,15 @@ function register(api) {
|
|
|
890
980
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
891
981
|
}
|
|
892
982
|
assertAllowedBaseUrl(baseUrl);
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
983
|
+
let res;
|
|
984
|
+
try {
|
|
985
|
+
res = await fetchWithTimeout(`${baseUrl.replace(/\/$/, "")}/v1/preview/${params.id}`, {
|
|
986
|
+
method: "DELETE",
|
|
987
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
988
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "preview delete");
|
|
989
|
+
} catch (e) {
|
|
990
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Delete failed: ${describeError(e)}` }, null, 2) }] };
|
|
991
|
+
}
|
|
897
992
|
if (!res.ok) {
|
|
898
993
|
const err = await res.text();
|
|
899
994
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Delete failed: HTTP ${res.status} ${err}` }, null, 2) }] };
|
|
@@ -951,11 +1046,16 @@ function register(api) {
|
|
|
951
1046
|
const envBody = {};
|
|
952
1047
|
if (hasSensitiveEnv) envBody.env = params.sensitive_env;
|
|
953
1048
|
if (hasLocalStorage) envBody.localStorage = params.localStorage;
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1049
|
+
let envRes;
|
|
1050
|
+
try {
|
|
1051
|
+
envRes = await fetchWithTimeout(`${endpoint}/v1/server-preview/env`, {
|
|
1052
|
+
method: "POST",
|
|
1053
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1054
|
+
body: JSON.stringify(envBody)
|
|
1055
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "server preview env store");
|
|
1056
|
+
} catch (e) {
|
|
1057
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: ${describeError(e)}` }, null, 2) }] };
|
|
1058
|
+
}
|
|
959
1059
|
if (!envRes.ok) {
|
|
960
1060
|
const err = await envRes.text();
|
|
961
1061
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: HTTP ${envRes.status} ${err}` }, null, 2) }] };
|
|
@@ -981,11 +1081,16 @@ function register(api) {
|
|
|
981
1081
|
if (params.navigation_timeout) createBody.navigation_timeout = params.navigation_timeout;
|
|
982
1082
|
if (params.color_scheme) createBody.color_scheme = params.color_scheme;
|
|
983
1083
|
if (params.viewport) createBody.viewport = params.viewport;
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1084
|
+
let createRes;
|
|
1085
|
+
try {
|
|
1086
|
+
createRes = await fetchWithRetry(`${endpoint}/v1/server-preview`, {
|
|
1087
|
+
method: "POST",
|
|
1088
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1089
|
+
body: JSON.stringify(createBody)
|
|
1090
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "server preview create");
|
|
1091
|
+
} catch (e) {
|
|
1092
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${describeError(e)}` }, null, 2) }] };
|
|
1093
|
+
}
|
|
989
1094
|
if (!createRes.ok) {
|
|
990
1095
|
const err = await createRes.text();
|
|
991
1096
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: HTTP ${createRes.status} ${err}` }, null, 2) }] };
|
|
@@ -997,11 +1102,16 @@ function register(api) {
|
|
|
997
1102
|
const excludeArgs = excludes.flatMap((p) => ["--exclude", p]);
|
|
998
1103
|
await execFile("tar", ["czf", tarball, ...excludeArgs, "-C", dir, "."], { timeout: 12e4 });
|
|
999
1104
|
const tarData = await (0, import_promises.readFile)(tarball);
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1105
|
+
let uploadRes;
|
|
1106
|
+
try {
|
|
1107
|
+
uploadRes = await fetchWithRetry(created.upload_url, {
|
|
1108
|
+
method: "PUT",
|
|
1109
|
+
headers: { "Content-Type": "application/gzip" },
|
|
1110
|
+
body: tarData
|
|
1111
|
+
}, PREVIEW_UPLOAD_TIMEOUT_MS, "server preview upload");
|
|
1112
|
+
} catch (e) {
|
|
1113
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: ${describeError(e)}` }, null, 2) }] };
|
|
1114
|
+
}
|
|
1005
1115
|
if (!uploadRes.ok) {
|
|
1006
1116
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
1007
1117
|
}
|
|
@@ -1011,21 +1121,35 @@ function register(api) {
|
|
|
1011
1121
|
} catch {
|
|
1012
1122
|
}
|
|
1013
1123
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1124
|
+
let startRes;
|
|
1125
|
+
try {
|
|
1126
|
+
startRes = await fetchWithRetry(`${endpoint}/v1/server-preview/${created.job_id}/start`, {
|
|
1127
|
+
method: "POST",
|
|
1128
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1129
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "server preview start");
|
|
1130
|
+
} catch (e) {
|
|
1131
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: ${describeError(e)}` }, null, 2) }] };
|
|
1132
|
+
}
|
|
1018
1133
|
if (!startRes.ok) {
|
|
1019
1134
|
const err = await startRes.text();
|
|
1020
|
-
|
|
1135
|
+
if (isAlreadyStartedResponse(startRes.status, err)) {
|
|
1136
|
+
console.warn(`[openclaw-riddledc] server preview start returned ${startRes.status} for ${created.job_id}; continuing to poll`);
|
|
1137
|
+
} else {
|
|
1138
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: HTTP ${startRes.status} ${err}` }, null, 2) }] };
|
|
1139
|
+
}
|
|
1021
1140
|
}
|
|
1022
1141
|
const timeoutMs = ((params.timeout || 120) + 60) * 1e3;
|
|
1023
1142
|
const pollStart = Date.now();
|
|
1024
1143
|
const POLL_INTERVAL = 3e3;
|
|
1025
1144
|
while (Date.now() - pollStart < timeoutMs) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1145
|
+
let statusRes;
|
|
1146
|
+
try {
|
|
1147
|
+
statusRes = await fetchWithRetry(`${endpoint}/v1/server-preview/${created.job_id}`, {
|
|
1148
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1149
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "server preview poll", { attempts: 2 });
|
|
1150
|
+
} catch (e) {
|
|
1151
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: ${describeError(e)}` }, null, 2) }] };
|
|
1152
|
+
}
|
|
1029
1153
|
if (!statusRes.ok) {
|
|
1030
1154
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: HTTP ${statusRes.status}` }, null, 2) }] };
|
|
1031
1155
|
}
|
|
@@ -1044,7 +1168,7 @@ function register(api) {
|
|
|
1044
1168
|
for (const output of result.outputs) {
|
|
1045
1169
|
if (output.name && /\.(png|jpg|jpeg)$/i.test(output.name) && output.url) {
|
|
1046
1170
|
try {
|
|
1047
|
-
const imgRes = await
|
|
1171
|
+
const imgRes = await fetchWithTimeout(output.url, {}, PREVIEW_ARTIFACT_TIMEOUT_MS, "server preview artifact download");
|
|
1048
1172
|
if (imgRes.ok) {
|
|
1049
1173
|
const buf = await imgRes.arrayBuffer();
|
|
1050
1174
|
const base64 = Buffer.from(buf).toString("base64");
|
|
@@ -1120,11 +1244,16 @@ function register(api) {
|
|
|
1120
1244
|
const envBody = {};
|
|
1121
1245
|
if (hasSensitiveEnv) envBody.env = params.sensitive_env;
|
|
1122
1246
|
if (hasLocalStorage) envBody.localStorage = params.localStorage;
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1247
|
+
let envRes;
|
|
1248
|
+
try {
|
|
1249
|
+
envRes = await fetchWithTimeout(`${endpoint}/v1/build-preview/env`, {
|
|
1250
|
+
method: "POST",
|
|
1251
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1252
|
+
body: JSON.stringify(envBody)
|
|
1253
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "build preview env store");
|
|
1254
|
+
} catch (e) {
|
|
1255
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: ${describeError(e)}` }, null, 2) }] };
|
|
1256
|
+
}
|
|
1128
1257
|
if (!envRes.ok) {
|
|
1129
1258
|
const err = await envRes.text();
|
|
1130
1259
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: HTTP ${envRes.status} ${err}` }, null, 2) }] };
|
|
@@ -1152,11 +1281,16 @@ function register(api) {
|
|
|
1152
1281
|
if (params.color_scheme) createBody.color_scheme = params.color_scheme;
|
|
1153
1282
|
if (params.viewport) createBody.viewport = params.viewport;
|
|
1154
1283
|
if (params.audit) createBody.audit = true;
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1284
|
+
let createRes;
|
|
1285
|
+
try {
|
|
1286
|
+
createRes = await fetchWithRetry(`${endpoint}/v1/build-preview`, {
|
|
1287
|
+
method: "POST",
|
|
1288
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1289
|
+
body: JSON.stringify(createBody)
|
|
1290
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "build preview create");
|
|
1291
|
+
} catch (e) {
|
|
1292
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${describeError(e)}` }, null, 2) }] };
|
|
1293
|
+
}
|
|
1160
1294
|
if (!createRes.ok) {
|
|
1161
1295
|
const err = await createRes.text();
|
|
1162
1296
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: HTTP ${createRes.status} ${err}` }, null, 2) }] };
|
|
@@ -1168,11 +1302,16 @@ function register(api) {
|
|
|
1168
1302
|
const excludeArgs = excludes.flatMap((p) => ["--exclude", p]);
|
|
1169
1303
|
await execFile("tar", ["czf", tarball, ...excludeArgs, "-C", dir, "."], { timeout: 12e4 });
|
|
1170
1304
|
const tarData = await (0, import_promises.readFile)(tarball);
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1305
|
+
let uploadRes;
|
|
1306
|
+
try {
|
|
1307
|
+
uploadRes = await fetchWithRetry(created.upload_url, {
|
|
1308
|
+
method: "PUT",
|
|
1309
|
+
headers: { "Content-Type": "application/gzip" },
|
|
1310
|
+
body: tarData
|
|
1311
|
+
}, PREVIEW_UPLOAD_TIMEOUT_MS, "build preview upload");
|
|
1312
|
+
} catch (e) {
|
|
1313
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: ${describeError(e)}` }, null, 2) }] };
|
|
1314
|
+
}
|
|
1176
1315
|
if (!uploadRes.ok) {
|
|
1177
1316
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
1178
1317
|
}
|
|
@@ -1182,21 +1321,35 @@ function register(api) {
|
|
|
1182
1321
|
} catch {
|
|
1183
1322
|
}
|
|
1184
1323
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1324
|
+
let startRes;
|
|
1325
|
+
try {
|
|
1326
|
+
startRes = await fetchWithRetry(`${endpoint}/v1/build-preview/${created.job_id}/start`, {
|
|
1327
|
+
method: "POST",
|
|
1328
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1329
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "build preview start");
|
|
1330
|
+
} catch (e) {
|
|
1331
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: ${describeError(e)}` }, null, 2) }] };
|
|
1332
|
+
}
|
|
1189
1333
|
if (!startRes.ok) {
|
|
1190
1334
|
const err = await startRes.text();
|
|
1191
|
-
|
|
1335
|
+
if (isAlreadyStartedResponse(startRes.status, err)) {
|
|
1336
|
+
console.warn(`[openclaw-riddledc] build preview start returned ${startRes.status} for ${created.job_id}; continuing to poll`);
|
|
1337
|
+
} else {
|
|
1338
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: HTTP ${startRes.status} ${err}` }, null, 2) }] };
|
|
1339
|
+
}
|
|
1192
1340
|
}
|
|
1193
1341
|
const timeoutMs = ((params.timeout || 180) + 120) * 1e3;
|
|
1194
1342
|
const pollStart = Date.now();
|
|
1195
1343
|
const POLL_INTERVAL = 3e3;
|
|
1196
1344
|
while (Date.now() - pollStart < timeoutMs) {
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1345
|
+
let statusRes;
|
|
1346
|
+
try {
|
|
1347
|
+
statusRes = await fetchWithRetry(`${endpoint}/v1/build-preview/${created.job_id}`, {
|
|
1348
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1349
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "build preview poll", { attempts: 2 });
|
|
1350
|
+
} catch (e) {
|
|
1351
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: ${describeError(e)}` }, null, 2) }] };
|
|
1352
|
+
}
|
|
1200
1353
|
if (!statusRes.ok) {
|
|
1201
1354
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: HTTP ${statusRes.status}` }, null, 2) }] };
|
|
1202
1355
|
}
|
|
@@ -1219,7 +1372,7 @@ function register(api) {
|
|
|
1219
1372
|
for (const output of result.outputs) {
|
|
1220
1373
|
if (output.name && /\.(png|jpg|jpeg)$/i.test(output.name) && output.url) {
|
|
1221
1374
|
try {
|
|
1222
|
-
const imgRes = await
|
|
1375
|
+
const imgRes = await fetchWithTimeout(output.url, {}, PREVIEW_ARTIFACT_TIMEOUT_MS, "build preview artifact download");
|
|
1223
1376
|
if (imgRes.ok) {
|
|
1224
1377
|
const buf = await imgRes.arrayBuffer();
|
|
1225
1378
|
const base64 = Buffer.from(buf).toString("base64");
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,11 @@ import { execFile as execFileCb } from "child_process";
|
|
|
6
6
|
import { promisify } from "util";
|
|
7
7
|
var execFile = promisify(execFileCb);
|
|
8
8
|
var INLINE_CAP = 50 * 1024;
|
|
9
|
+
var PREVIEW_REQUEST_TIMEOUT_MS = 3e4;
|
|
10
|
+
var PREVIEW_UPLOAD_TIMEOUT_MS = 5 * 6e4;
|
|
11
|
+
var PREVIEW_ARTIFACT_TIMEOUT_MS = 6e4;
|
|
12
|
+
var PREVIEW_RETRY_ATTEMPTS = 3;
|
|
13
|
+
var PREVIEW_RETRY_BASE_DELAY_MS = 750;
|
|
9
14
|
function getCfg(api) {
|
|
10
15
|
const cfg = api?.config ?? {};
|
|
11
16
|
const pluginCfg = cfg?.plugins?.entries?.["openclaw-riddledc"]?.config ?? {};
|
|
@@ -51,6 +56,76 @@ function abToBase64(ab) {
|
|
|
51
56
|
function getWorkspacePath(api) {
|
|
52
57
|
return api?.workspacePath ?? process.cwd();
|
|
53
58
|
}
|
|
59
|
+
function describeError(err) {
|
|
60
|
+
const anyErr = err;
|
|
61
|
+
const parts = [];
|
|
62
|
+
if (err instanceof Error) parts.push(err.message);
|
|
63
|
+
else parts.push(String(err));
|
|
64
|
+
const cause = anyErr?.cause;
|
|
65
|
+
if (cause) {
|
|
66
|
+
const causeParts = [
|
|
67
|
+
cause.code ? `code=${cause.code}` : "",
|
|
68
|
+
cause.name ? `name=${cause.name}` : "",
|
|
69
|
+
cause.message ? `message=${cause.message}` : ""
|
|
70
|
+
].filter(Boolean);
|
|
71
|
+
if (causeParts.length) parts.push(`cause: ${causeParts.join(" ")}`);
|
|
72
|
+
}
|
|
73
|
+
return parts.join("; ");
|
|
74
|
+
}
|
|
75
|
+
async function fetchWithTimeout(url, init, timeoutMs, label) {
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
78
|
+
try {
|
|
79
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
80
|
+
} catch (err) {
|
|
81
|
+
if (err?.name === "AbortError") {
|
|
82
|
+
throw new Error(`${label} timed out after ${Math.round(timeoutMs / 1e3)}s`);
|
|
83
|
+
}
|
|
84
|
+
throw err;
|
|
85
|
+
} finally {
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function isTransientFetchError(err) {
|
|
90
|
+
const text = describeError(err).toLowerCase();
|
|
91
|
+
return [
|
|
92
|
+
"fetch failed",
|
|
93
|
+
"timed out",
|
|
94
|
+
"timeout",
|
|
95
|
+
"econnreset",
|
|
96
|
+
"econnrefused",
|
|
97
|
+
"etimedout",
|
|
98
|
+
"eai_again",
|
|
99
|
+
"socket",
|
|
100
|
+
"network",
|
|
101
|
+
"und_err",
|
|
102
|
+
"terminated"
|
|
103
|
+
].some((needle) => text.includes(needle));
|
|
104
|
+
}
|
|
105
|
+
function sleep(ms) {
|
|
106
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
107
|
+
}
|
|
108
|
+
async function fetchWithRetry(url, init, timeoutMs, label, opts = {}) {
|
|
109
|
+
const attempts = Math.max(1, opts.attempts ?? PREVIEW_RETRY_ATTEMPTS);
|
|
110
|
+
const baseDelayMs = opts.baseDelayMs ?? PREVIEW_RETRY_BASE_DELAY_MS;
|
|
111
|
+
let lastErr;
|
|
112
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
113
|
+
try {
|
|
114
|
+
return await fetchWithTimeout(url, init, timeoutMs, label);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
lastErr = err;
|
|
117
|
+
if (attempt >= attempts || !isTransientFetchError(err)) break;
|
|
118
|
+
const jitterMs = Math.floor(Math.random() * 250);
|
|
119
|
+
const delayMs = Math.min(baseDelayMs * Math.pow(2, attempt - 1) + jitterMs, 5e3);
|
|
120
|
+
console.warn(`[openclaw-riddledc] ${label} attempt ${attempt}/${attempts} failed: ${describeError(err)}; retrying in ${delayMs}ms`);
|
|
121
|
+
await sleep(delayMs);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
throw new Error(`${label} failed after ${attempts} attempts: ${describeError(lastErr)}`);
|
|
125
|
+
}
|
|
126
|
+
function isAlreadyStartedResponse(status, body) {
|
|
127
|
+
return status === 409 && /already in status:\s*(queued|running|complete|completed)/i.test(body);
|
|
128
|
+
}
|
|
54
129
|
async function writeArtifact(workspace, subdir, filename, content) {
|
|
55
130
|
const dir = join(workspace, "riddle", subdir);
|
|
56
131
|
await mkdir(dir, { recursive: true });
|
|
@@ -799,11 +874,16 @@ function register(api) {
|
|
|
799
874
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Cannot access directory: ${e.message}` }, null, 2) }] };
|
|
800
875
|
}
|
|
801
876
|
const endpoint = baseUrl.replace(/\/$/, "");
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
877
|
+
let createRes;
|
|
878
|
+
try {
|
|
879
|
+
createRes = await fetchWithRetry(`${endpoint}/v1/preview`, {
|
|
880
|
+
method: "POST",
|
|
881
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
882
|
+
body: JSON.stringify({ framework: params.framework || "spa" })
|
|
883
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "preview create");
|
|
884
|
+
} catch (e) {
|
|
885
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${describeError(e)}` }, null, 2) }] };
|
|
886
|
+
}
|
|
807
887
|
if (!createRes.ok) {
|
|
808
888
|
const err = await createRes.text();
|
|
809
889
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: HTTP ${createRes.status} ${err}` }, null, 2) }] };
|
|
@@ -813,11 +893,16 @@ function register(api) {
|
|
|
813
893
|
try {
|
|
814
894
|
await execFile("tar", ["czf", tarball, "-C", dir, "."], { timeout: 6e4 });
|
|
815
895
|
const tarData = await readFile(tarball);
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
896
|
+
let uploadRes;
|
|
897
|
+
try {
|
|
898
|
+
uploadRes = await fetchWithRetry(created.upload_url, {
|
|
899
|
+
method: "PUT",
|
|
900
|
+
headers: { "Content-Type": "application/gzip" },
|
|
901
|
+
body: tarData
|
|
902
|
+
}, PREVIEW_UPLOAD_TIMEOUT_MS, "preview upload");
|
|
903
|
+
} catch (e) {
|
|
904
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Upload failed: ${describeError(e)}` }, null, 2) }] };
|
|
905
|
+
}
|
|
821
906
|
if (!uploadRes.ok) {
|
|
822
907
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
823
908
|
}
|
|
@@ -827,10 +912,15 @@ function register(api) {
|
|
|
827
912
|
} catch {
|
|
828
913
|
}
|
|
829
914
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
915
|
+
let publishRes;
|
|
916
|
+
try {
|
|
917
|
+
publishRes = await fetchWithTimeout(`${endpoint}/v1/preview/${created.id}/publish`, {
|
|
918
|
+
method: "POST",
|
|
919
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
920
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "preview publish");
|
|
921
|
+
} catch (e) {
|
|
922
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Publish failed: ${describeError(e)}` }, null, 2) }] };
|
|
923
|
+
}
|
|
834
924
|
if (!publishRes.ok) {
|
|
835
925
|
const err = await publishRes.text();
|
|
836
926
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Publish failed: HTTP ${publishRes.status} ${err}` }, null, 2) }] };
|
|
@@ -866,10 +956,15 @@ function register(api) {
|
|
|
866
956
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
867
957
|
}
|
|
868
958
|
assertAllowedBaseUrl(baseUrl);
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
959
|
+
let res;
|
|
960
|
+
try {
|
|
961
|
+
res = await fetchWithTimeout(`${baseUrl.replace(/\/$/, "")}/v1/preview/${params.id}`, {
|
|
962
|
+
method: "DELETE",
|
|
963
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
964
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "preview delete");
|
|
965
|
+
} catch (e) {
|
|
966
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Delete failed: ${describeError(e)}` }, null, 2) }] };
|
|
967
|
+
}
|
|
873
968
|
if (!res.ok) {
|
|
874
969
|
const err = await res.text();
|
|
875
970
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Delete failed: HTTP ${res.status} ${err}` }, null, 2) }] };
|
|
@@ -927,11 +1022,16 @@ function register(api) {
|
|
|
927
1022
|
const envBody = {};
|
|
928
1023
|
if (hasSensitiveEnv) envBody.env = params.sensitive_env;
|
|
929
1024
|
if (hasLocalStorage) envBody.localStorage = params.localStorage;
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1025
|
+
let envRes;
|
|
1026
|
+
try {
|
|
1027
|
+
envRes = await fetchWithTimeout(`${endpoint}/v1/server-preview/env`, {
|
|
1028
|
+
method: "POST",
|
|
1029
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1030
|
+
body: JSON.stringify(envBody)
|
|
1031
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "server preview env store");
|
|
1032
|
+
} catch (e) {
|
|
1033
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: ${describeError(e)}` }, null, 2) }] };
|
|
1034
|
+
}
|
|
935
1035
|
if (!envRes.ok) {
|
|
936
1036
|
const err = await envRes.text();
|
|
937
1037
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: HTTP ${envRes.status} ${err}` }, null, 2) }] };
|
|
@@ -957,11 +1057,16 @@ function register(api) {
|
|
|
957
1057
|
if (params.navigation_timeout) createBody.navigation_timeout = params.navigation_timeout;
|
|
958
1058
|
if (params.color_scheme) createBody.color_scheme = params.color_scheme;
|
|
959
1059
|
if (params.viewport) createBody.viewport = params.viewport;
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1060
|
+
let createRes;
|
|
1061
|
+
try {
|
|
1062
|
+
createRes = await fetchWithRetry(`${endpoint}/v1/server-preview`, {
|
|
1063
|
+
method: "POST",
|
|
1064
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1065
|
+
body: JSON.stringify(createBody)
|
|
1066
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "server preview create");
|
|
1067
|
+
} catch (e) {
|
|
1068
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${describeError(e)}` }, null, 2) }] };
|
|
1069
|
+
}
|
|
965
1070
|
if (!createRes.ok) {
|
|
966
1071
|
const err = await createRes.text();
|
|
967
1072
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: HTTP ${createRes.status} ${err}` }, null, 2) }] };
|
|
@@ -973,11 +1078,16 @@ function register(api) {
|
|
|
973
1078
|
const excludeArgs = excludes.flatMap((p) => ["--exclude", p]);
|
|
974
1079
|
await execFile("tar", ["czf", tarball, ...excludeArgs, "-C", dir, "."], { timeout: 12e4 });
|
|
975
1080
|
const tarData = await readFile(tarball);
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1081
|
+
let uploadRes;
|
|
1082
|
+
try {
|
|
1083
|
+
uploadRes = await fetchWithRetry(created.upload_url, {
|
|
1084
|
+
method: "PUT",
|
|
1085
|
+
headers: { "Content-Type": "application/gzip" },
|
|
1086
|
+
body: tarData
|
|
1087
|
+
}, PREVIEW_UPLOAD_TIMEOUT_MS, "server preview upload");
|
|
1088
|
+
} catch (e) {
|
|
1089
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: ${describeError(e)}` }, null, 2) }] };
|
|
1090
|
+
}
|
|
981
1091
|
if (!uploadRes.ok) {
|
|
982
1092
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
983
1093
|
}
|
|
@@ -987,21 +1097,35 @@ function register(api) {
|
|
|
987
1097
|
} catch {
|
|
988
1098
|
}
|
|
989
1099
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1100
|
+
let startRes;
|
|
1101
|
+
try {
|
|
1102
|
+
startRes = await fetchWithRetry(`${endpoint}/v1/server-preview/${created.job_id}/start`, {
|
|
1103
|
+
method: "POST",
|
|
1104
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1105
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "server preview start");
|
|
1106
|
+
} catch (e) {
|
|
1107
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: ${describeError(e)}` }, null, 2) }] };
|
|
1108
|
+
}
|
|
994
1109
|
if (!startRes.ok) {
|
|
995
1110
|
const err = await startRes.text();
|
|
996
|
-
|
|
1111
|
+
if (isAlreadyStartedResponse(startRes.status, err)) {
|
|
1112
|
+
console.warn(`[openclaw-riddledc] server preview start returned ${startRes.status} for ${created.job_id}; continuing to poll`);
|
|
1113
|
+
} else {
|
|
1114
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: HTTP ${startRes.status} ${err}` }, null, 2) }] };
|
|
1115
|
+
}
|
|
997
1116
|
}
|
|
998
1117
|
const timeoutMs = ((params.timeout || 120) + 60) * 1e3;
|
|
999
1118
|
const pollStart = Date.now();
|
|
1000
1119
|
const POLL_INTERVAL = 3e3;
|
|
1001
1120
|
while (Date.now() - pollStart < timeoutMs) {
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1121
|
+
let statusRes;
|
|
1122
|
+
try {
|
|
1123
|
+
statusRes = await fetchWithRetry(`${endpoint}/v1/server-preview/${created.job_id}`, {
|
|
1124
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1125
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "server preview poll", { attempts: 2 });
|
|
1126
|
+
} catch (e) {
|
|
1127
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: ${describeError(e)}` }, null, 2) }] };
|
|
1128
|
+
}
|
|
1005
1129
|
if (!statusRes.ok) {
|
|
1006
1130
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: HTTP ${statusRes.status}` }, null, 2) }] };
|
|
1007
1131
|
}
|
|
@@ -1020,7 +1144,7 @@ function register(api) {
|
|
|
1020
1144
|
for (const output of result.outputs) {
|
|
1021
1145
|
if (output.name && /\.(png|jpg|jpeg)$/i.test(output.name) && output.url) {
|
|
1022
1146
|
try {
|
|
1023
|
-
const imgRes = await
|
|
1147
|
+
const imgRes = await fetchWithTimeout(output.url, {}, PREVIEW_ARTIFACT_TIMEOUT_MS, "server preview artifact download");
|
|
1024
1148
|
if (imgRes.ok) {
|
|
1025
1149
|
const buf = await imgRes.arrayBuffer();
|
|
1026
1150
|
const base64 = Buffer.from(buf).toString("base64");
|
|
@@ -1096,11 +1220,16 @@ function register(api) {
|
|
|
1096
1220
|
const envBody = {};
|
|
1097
1221
|
if (hasSensitiveEnv) envBody.env = params.sensitive_env;
|
|
1098
1222
|
if (hasLocalStorage) envBody.localStorage = params.localStorage;
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1223
|
+
let envRes;
|
|
1224
|
+
try {
|
|
1225
|
+
envRes = await fetchWithTimeout(`${endpoint}/v1/build-preview/env`, {
|
|
1226
|
+
method: "POST",
|
|
1227
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1228
|
+
body: JSON.stringify(envBody)
|
|
1229
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "build preview env store");
|
|
1230
|
+
} catch (e) {
|
|
1231
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: ${describeError(e)}` }, null, 2) }] };
|
|
1232
|
+
}
|
|
1104
1233
|
if (!envRes.ok) {
|
|
1105
1234
|
const err = await envRes.text();
|
|
1106
1235
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: HTTP ${envRes.status} ${err}` }, null, 2) }] };
|
|
@@ -1128,11 +1257,16 @@ function register(api) {
|
|
|
1128
1257
|
if (params.color_scheme) createBody.color_scheme = params.color_scheme;
|
|
1129
1258
|
if (params.viewport) createBody.viewport = params.viewport;
|
|
1130
1259
|
if (params.audit) createBody.audit = true;
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1260
|
+
let createRes;
|
|
1261
|
+
try {
|
|
1262
|
+
createRes = await fetchWithRetry(`${endpoint}/v1/build-preview`, {
|
|
1263
|
+
method: "POST",
|
|
1264
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1265
|
+
body: JSON.stringify(createBody)
|
|
1266
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "build preview create");
|
|
1267
|
+
} catch (e) {
|
|
1268
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${describeError(e)}` }, null, 2) }] };
|
|
1269
|
+
}
|
|
1136
1270
|
if (!createRes.ok) {
|
|
1137
1271
|
const err = await createRes.text();
|
|
1138
1272
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: HTTP ${createRes.status} ${err}` }, null, 2) }] };
|
|
@@ -1144,11 +1278,16 @@ function register(api) {
|
|
|
1144
1278
|
const excludeArgs = excludes.flatMap((p) => ["--exclude", p]);
|
|
1145
1279
|
await execFile("tar", ["czf", tarball, ...excludeArgs, "-C", dir, "."], { timeout: 12e4 });
|
|
1146
1280
|
const tarData = await readFile(tarball);
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1281
|
+
let uploadRes;
|
|
1282
|
+
try {
|
|
1283
|
+
uploadRes = await fetchWithRetry(created.upload_url, {
|
|
1284
|
+
method: "PUT",
|
|
1285
|
+
headers: { "Content-Type": "application/gzip" },
|
|
1286
|
+
body: tarData
|
|
1287
|
+
}, PREVIEW_UPLOAD_TIMEOUT_MS, "build preview upload");
|
|
1288
|
+
} catch (e) {
|
|
1289
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: ${describeError(e)}` }, null, 2) }] };
|
|
1290
|
+
}
|
|
1152
1291
|
if (!uploadRes.ok) {
|
|
1153
1292
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
1154
1293
|
}
|
|
@@ -1158,21 +1297,35 @@ function register(api) {
|
|
|
1158
1297
|
} catch {
|
|
1159
1298
|
}
|
|
1160
1299
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1300
|
+
let startRes;
|
|
1301
|
+
try {
|
|
1302
|
+
startRes = await fetchWithRetry(`${endpoint}/v1/build-preview/${created.job_id}/start`, {
|
|
1303
|
+
method: "POST",
|
|
1304
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1305
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "build preview start");
|
|
1306
|
+
} catch (e) {
|
|
1307
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: ${describeError(e)}` }, null, 2) }] };
|
|
1308
|
+
}
|
|
1165
1309
|
if (!startRes.ok) {
|
|
1166
1310
|
const err = await startRes.text();
|
|
1167
|
-
|
|
1311
|
+
if (isAlreadyStartedResponse(startRes.status, err)) {
|
|
1312
|
+
console.warn(`[openclaw-riddledc] build preview start returned ${startRes.status} for ${created.job_id}; continuing to poll`);
|
|
1313
|
+
} else {
|
|
1314
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: HTTP ${startRes.status} ${err}` }, null, 2) }] };
|
|
1315
|
+
}
|
|
1168
1316
|
}
|
|
1169
1317
|
const timeoutMs = ((params.timeout || 180) + 120) * 1e3;
|
|
1170
1318
|
const pollStart = Date.now();
|
|
1171
1319
|
const POLL_INTERVAL = 3e3;
|
|
1172
1320
|
while (Date.now() - pollStart < timeoutMs) {
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1321
|
+
let statusRes;
|
|
1322
|
+
try {
|
|
1323
|
+
statusRes = await fetchWithRetry(`${endpoint}/v1/build-preview/${created.job_id}`, {
|
|
1324
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1325
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "build preview poll", { attempts: 2 });
|
|
1326
|
+
} catch (e) {
|
|
1327
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: ${describeError(e)}` }, null, 2) }] };
|
|
1328
|
+
}
|
|
1176
1329
|
if (!statusRes.ok) {
|
|
1177
1330
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: HTTP ${statusRes.status}` }, null, 2) }] };
|
|
1178
1331
|
}
|
|
@@ -1195,7 +1348,7 @@ function register(api) {
|
|
|
1195
1348
|
for (const output of result.outputs) {
|
|
1196
1349
|
if (output.name && /\.(png|jpg|jpeg)$/i.test(output.name) && output.url) {
|
|
1197
1350
|
try {
|
|
1198
|
-
const imgRes = await
|
|
1351
|
+
const imgRes = await fetchWithTimeout(output.url, {}, PREVIEW_ARTIFACT_TIMEOUT_MS, "build preview artifact download");
|
|
1199
1352
|
if (imgRes.ok) {
|
|
1200
1353
|
const buf = await imgRes.arrayBuffer();
|
|
1201
1354
|
const base64 = Buffer.from(buf).toString("base64");
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "openclaw-riddledc",
|
|
3
3
|
"name": "Riddle",
|
|
4
4
|
"description": "Riddle (riddledc.com) hosted browser API tools for OpenClaw agents.",
|
|
5
|
-
"version": "0.9.
|
|
5
|
+
"version": "0.9.2",
|
|
6
6
|
"notes": "0.8.0: Added riddle_build_preview for Dockerfile-based builds with image caching.",
|
|
7
7
|
"type": "plugin",
|
|
8
8
|
"bundledSkills": [],
|