@riddledc/openclaw-riddledc 0.9.1 → 0.9.3
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 +7 -2
- package/README.md +10 -0
- package/dist/chunk-LJCZ53PU.js +833 -0
- package/dist/core.cjs +872 -0
- package/dist/core.d.cts +57 -0
- package/dist/core.d.ts +57 -0
- package/dist/core.js +36 -0
- package/dist/index.cjs +427 -155
- package/dist/index.js +191 -127
- package/openclaw.plugin.json +1 -1
- package/package.json +7 -2
package/dist/index.cjs
CHANGED
|
@@ -24,12 +24,231 @@ __export(index_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
26
|
var import_typebox = require("@sinclair/typebox");
|
|
27
|
+
var import_promises2 = require("fs/promises");
|
|
28
|
+
var import_node_path2 = require("path");
|
|
29
|
+
var import_node_child_process2 = require("child_process");
|
|
30
|
+
var import_node_util2 = require("util");
|
|
31
|
+
|
|
32
|
+
// src/core.ts
|
|
33
|
+
var import_node_child_process = require("child_process");
|
|
27
34
|
var import_promises = require("fs/promises");
|
|
28
35
|
var import_node_path = require("path");
|
|
29
|
-
var import_node_child_process = require("child_process");
|
|
30
36
|
var import_node_util = require("util");
|
|
31
37
|
var execFile = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
32
38
|
var INLINE_CAP = 50 * 1024;
|
|
39
|
+
var PREVIEW_REQUEST_TIMEOUT_MS = 3e4;
|
|
40
|
+
var PREVIEW_UPLOAD_TIMEOUT_MS = 5 * 6e4;
|
|
41
|
+
var PREVIEW_RETRY_ATTEMPTS = 3;
|
|
42
|
+
var PREVIEW_RETRY_BASE_DELAY_MS = 750;
|
|
43
|
+
function configFromOpenClawApi(api) {
|
|
44
|
+
const cfg = api?.config ?? {};
|
|
45
|
+
const pluginCfg = cfg?.plugins?.entries?.["openclaw-riddledc"]?.config ?? {};
|
|
46
|
+
return {
|
|
47
|
+
apiKey: process.env.RIDDLE_API_KEY || pluginCfg.apiKey,
|
|
48
|
+
baseUrl: pluginCfg.baseUrl || "https://api.riddledc.com",
|
|
49
|
+
workspace: api?.workspacePath ?? process.cwd()
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function requireConfig(config) {
|
|
53
|
+
const apiKey = config.apiKey || process.env.RIDDLE_API_KEY;
|
|
54
|
+
const baseUrl = config.baseUrl || "https://api.riddledc.com";
|
|
55
|
+
if (!apiKey) {
|
|
56
|
+
throw new Error("Missing Riddle API key. Set RIDDLE_API_KEY env var or configure a Riddle API key.");
|
|
57
|
+
}
|
|
58
|
+
assertAllowedBaseUrl(baseUrl);
|
|
59
|
+
return { apiKey, baseUrl, workspace: config.workspace || process.cwd() };
|
|
60
|
+
}
|
|
61
|
+
function assertAllowedBaseUrl(baseUrl) {
|
|
62
|
+
const url = new URL(baseUrl);
|
|
63
|
+
if (url.protocol !== "https:") throw new Error(`Riddle baseUrl must be https: (${baseUrl})`);
|
|
64
|
+
if (url.hostname !== "api.riddledc.com") {
|
|
65
|
+
throw new Error(`Refusing to use non-official Riddle host: ${url.hostname}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function describeError(err) {
|
|
69
|
+
const anyErr = err;
|
|
70
|
+
const parts = [];
|
|
71
|
+
if (err instanceof Error) parts.push(err.message);
|
|
72
|
+
else parts.push(String(err));
|
|
73
|
+
const cause = anyErr?.cause;
|
|
74
|
+
if (cause) {
|
|
75
|
+
const causeParts = [
|
|
76
|
+
cause.code ? `code=${cause.code}` : "",
|
|
77
|
+
cause.name ? `name=${cause.name}` : "",
|
|
78
|
+
cause.message ? `message=${cause.message}` : ""
|
|
79
|
+
].filter(Boolean);
|
|
80
|
+
if (causeParts.length) parts.push(`cause: ${causeParts.join(" ")}`);
|
|
81
|
+
}
|
|
82
|
+
return parts.join("; ");
|
|
83
|
+
}
|
|
84
|
+
async function fetchWithTimeout(url, init, timeoutMs, label) {
|
|
85
|
+
const controller = new AbortController();
|
|
86
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
87
|
+
try {
|
|
88
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
89
|
+
} catch (err) {
|
|
90
|
+
if (err?.name === "AbortError") {
|
|
91
|
+
throw new Error(`${label} timed out after ${Math.round(timeoutMs / 1e3)}s`);
|
|
92
|
+
}
|
|
93
|
+
throw err;
|
|
94
|
+
} finally {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function isTransientFetchError(err) {
|
|
99
|
+
const text = describeError(err).toLowerCase();
|
|
100
|
+
return [
|
|
101
|
+
"fetch failed",
|
|
102
|
+
"timed out",
|
|
103
|
+
"timeout",
|
|
104
|
+
"econnreset",
|
|
105
|
+
"econnrefused",
|
|
106
|
+
"etimedout",
|
|
107
|
+
"eai_again",
|
|
108
|
+
"socket",
|
|
109
|
+
"network",
|
|
110
|
+
"und_err",
|
|
111
|
+
"terminated"
|
|
112
|
+
].some((needle) => text.includes(needle));
|
|
113
|
+
}
|
|
114
|
+
function sleep(ms) {
|
|
115
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
116
|
+
}
|
|
117
|
+
async function fetchWithRetry(url, init, timeoutMs, label, opts = {}) {
|
|
118
|
+
const attempts = Math.max(1, opts.attempts ?? PREVIEW_RETRY_ATTEMPTS);
|
|
119
|
+
const baseDelayMs = opts.baseDelayMs ?? PREVIEW_RETRY_BASE_DELAY_MS;
|
|
120
|
+
let lastErr;
|
|
121
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
122
|
+
try {
|
|
123
|
+
return await fetchWithTimeout(url, init, timeoutMs, label);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
lastErr = err;
|
|
126
|
+
if (attempt >= attempts || !isTransientFetchError(err)) break;
|
|
127
|
+
const jitterMs = Math.floor(Math.random() * 250);
|
|
128
|
+
const delayMs = Math.min(baseDelayMs * Math.pow(2, attempt - 1) + jitterMs, 5e3);
|
|
129
|
+
console.warn(`[openclaw-riddledc] ${label} attempt ${attempt}/${attempts} failed: ${describeError(err)}; retrying in ${delayMs}ms`);
|
|
130
|
+
await sleep(delayMs);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
throw new Error(`${label} failed after ${attempts} attempts: ${describeError(lastErr)}`);
|
|
134
|
+
}
|
|
135
|
+
async function assertDirectory(dir) {
|
|
136
|
+
if (!dir || typeof dir !== "string") throw new Error("directory must be an absolute path");
|
|
137
|
+
try {
|
|
138
|
+
const st = await (0, import_promises.stat)(dir);
|
|
139
|
+
if (!st.isDirectory()) throw new Error(`Not a directory: ${dir}`);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
return { ok: false, error: `Cannot access directory: ${e.message}` };
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
async function tarDirectory(dir, tarball, excludes, timeout) {
|
|
146
|
+
const excludeArgs = excludes.flatMap((p) => ["--exclude", p]);
|
|
147
|
+
await execFile("tar", ["czf", tarball, ...excludeArgs, "-C", dir, "."], { timeout });
|
|
148
|
+
return (0, import_promises.readFile)(tarball);
|
|
149
|
+
}
|
|
150
|
+
async function createStaticPreview(config, params) {
|
|
151
|
+
let cfg;
|
|
152
|
+
try {
|
|
153
|
+
cfg = requireConfig(config);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
return { ok: false, error: err.message };
|
|
156
|
+
}
|
|
157
|
+
const dirError = await assertDirectory(params.directory);
|
|
158
|
+
if (dirError) return dirError;
|
|
159
|
+
const endpoint = cfg.baseUrl.replace(/\/$/, "");
|
|
160
|
+
let createRes;
|
|
161
|
+
try {
|
|
162
|
+
createRes = await fetchWithRetry(`${endpoint}/v1/preview`, {
|
|
163
|
+
method: "POST",
|
|
164
|
+
headers: { Authorization: `Bearer ${cfg.apiKey}`, "Content-Type": "application/json" },
|
|
165
|
+
body: JSON.stringify({ framework: params.framework || "spa" })
|
|
166
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "preview create");
|
|
167
|
+
} catch (e) {
|
|
168
|
+
return { ok: false, error: `Create failed: ${describeError(e)}` };
|
|
169
|
+
}
|
|
170
|
+
if (!createRes.ok) {
|
|
171
|
+
const err = await createRes.text();
|
|
172
|
+
return { ok: false, error: `Create failed: HTTP ${createRes.status} ${err}` };
|
|
173
|
+
}
|
|
174
|
+
const created = await createRes.json();
|
|
175
|
+
const tarball = `/tmp/riddle-preview-${created.id}.tar.gz`;
|
|
176
|
+
try {
|
|
177
|
+
const tarData = await tarDirectory(params.directory, tarball, [], 6e4);
|
|
178
|
+
let uploadRes;
|
|
179
|
+
try {
|
|
180
|
+
uploadRes = await fetchWithRetry(created.upload_url, {
|
|
181
|
+
method: "PUT",
|
|
182
|
+
headers: { "Content-Type": "application/gzip" },
|
|
183
|
+
body: tarData
|
|
184
|
+
}, PREVIEW_UPLOAD_TIMEOUT_MS, "preview upload");
|
|
185
|
+
} catch (e) {
|
|
186
|
+
return { ok: false, id: created.id, error: `Upload failed: ${describeError(e)}` };
|
|
187
|
+
}
|
|
188
|
+
if (!uploadRes.ok) {
|
|
189
|
+
return { ok: false, id: created.id, error: `Upload failed: HTTP ${uploadRes.status}` };
|
|
190
|
+
}
|
|
191
|
+
} finally {
|
|
192
|
+
try {
|
|
193
|
+
await (0, import_promises.rm)(tarball, { force: true });
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
let publishRes;
|
|
198
|
+
try {
|
|
199
|
+
publishRes = await fetchWithTimeout(`${endpoint}/v1/preview/${created.id}/publish`, {
|
|
200
|
+
method: "POST",
|
|
201
|
+
headers: { Authorization: `Bearer ${cfg.apiKey}` }
|
|
202
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "preview publish");
|
|
203
|
+
} catch (e) {
|
|
204
|
+
return { ok: false, id: created.id, error: `Publish failed: ${describeError(e)}` };
|
|
205
|
+
}
|
|
206
|
+
if (!publishRes.ok) {
|
|
207
|
+
const err = await publishRes.text();
|
|
208
|
+
return { ok: false, id: created.id, error: `Publish failed: HTTP ${publishRes.status} ${err}` };
|
|
209
|
+
}
|
|
210
|
+
const published = await publishRes.json();
|
|
211
|
+
return {
|
|
212
|
+
ok: true,
|
|
213
|
+
id: published.id,
|
|
214
|
+
preview_url: published.preview_url,
|
|
215
|
+
file_count: published.file_count,
|
|
216
|
+
total_bytes: published.total_bytes,
|
|
217
|
+
expires_at: created.expires_at
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async function deleteStaticPreview(config, id) {
|
|
221
|
+
let cfg;
|
|
222
|
+
try {
|
|
223
|
+
cfg = requireConfig(config);
|
|
224
|
+
} catch (err) {
|
|
225
|
+
return { ok: false, error: err.message };
|
|
226
|
+
}
|
|
227
|
+
let res;
|
|
228
|
+
try {
|
|
229
|
+
res = await fetchWithTimeout(`${cfg.baseUrl.replace(/\/$/, "")}/v1/preview/${id}`, {
|
|
230
|
+
method: "DELETE",
|
|
231
|
+
headers: { Authorization: `Bearer ${cfg.apiKey}` }
|
|
232
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS, "preview delete");
|
|
233
|
+
} catch (e) {
|
|
234
|
+
return { ok: false, error: `Delete failed: ${describeError(e)}` };
|
|
235
|
+
}
|
|
236
|
+
if (!res.ok) {
|
|
237
|
+
const err = await res.text();
|
|
238
|
+
return { ok: false, error: `Delete failed: HTTP ${res.status} ${err}` };
|
|
239
|
+
}
|
|
240
|
+
const data = await res.json();
|
|
241
|
+
return { ok: true, deleted: true, files_removed: data.files_removed };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/index.ts
|
|
245
|
+
var execFile2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
246
|
+
var INLINE_CAP2 = 50 * 1024;
|
|
247
|
+
var PREVIEW_REQUEST_TIMEOUT_MS2 = 3e4;
|
|
248
|
+
var PREVIEW_UPLOAD_TIMEOUT_MS2 = 5 * 6e4;
|
|
249
|
+
var PREVIEW_ARTIFACT_TIMEOUT_MS = 6e4;
|
|
250
|
+
var PREVIEW_RETRY_ATTEMPTS2 = 3;
|
|
251
|
+
var PREVIEW_RETRY_BASE_DELAY_MS2 = 750;
|
|
33
252
|
function getCfg(api) {
|
|
34
253
|
const cfg = api?.config ?? {};
|
|
35
254
|
const pluginCfg = cfg?.plugins?.entries?.["openclaw-riddledc"]?.config ?? {};
|
|
@@ -38,7 +257,7 @@ function getCfg(api) {
|
|
|
38
257
|
baseUrl: pluginCfg.baseUrl || "https://api.riddledc.com"
|
|
39
258
|
};
|
|
40
259
|
}
|
|
41
|
-
function
|
|
260
|
+
function assertAllowedBaseUrl2(baseUrl) {
|
|
42
261
|
const url = new URL(baseUrl);
|
|
43
262
|
if (url.protocol !== "https:") throw new Error(`Riddle baseUrl must be https: (${baseUrl})`);
|
|
44
263
|
if (url.hostname !== "api.riddledc.com") {
|
|
@@ -75,20 +294,90 @@ function abToBase64(ab) {
|
|
|
75
294
|
function getWorkspacePath(api) {
|
|
76
295
|
return api?.workspacePath ?? process.cwd();
|
|
77
296
|
}
|
|
297
|
+
function describeError2(err) {
|
|
298
|
+
const anyErr = err;
|
|
299
|
+
const parts = [];
|
|
300
|
+
if (err instanceof Error) parts.push(err.message);
|
|
301
|
+
else parts.push(String(err));
|
|
302
|
+
const cause = anyErr?.cause;
|
|
303
|
+
if (cause) {
|
|
304
|
+
const causeParts = [
|
|
305
|
+
cause.code ? `code=${cause.code}` : "",
|
|
306
|
+
cause.name ? `name=${cause.name}` : "",
|
|
307
|
+
cause.message ? `message=${cause.message}` : ""
|
|
308
|
+
].filter(Boolean);
|
|
309
|
+
if (causeParts.length) parts.push(`cause: ${causeParts.join(" ")}`);
|
|
310
|
+
}
|
|
311
|
+
return parts.join("; ");
|
|
312
|
+
}
|
|
313
|
+
async function fetchWithTimeout2(url, init, timeoutMs, label) {
|
|
314
|
+
const controller = new AbortController();
|
|
315
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
316
|
+
try {
|
|
317
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
318
|
+
} catch (err) {
|
|
319
|
+
if (err?.name === "AbortError") {
|
|
320
|
+
throw new Error(`${label} timed out after ${Math.round(timeoutMs / 1e3)}s`);
|
|
321
|
+
}
|
|
322
|
+
throw err;
|
|
323
|
+
} finally {
|
|
324
|
+
clearTimeout(timer);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function isTransientFetchError2(err) {
|
|
328
|
+
const text = describeError2(err).toLowerCase();
|
|
329
|
+
return [
|
|
330
|
+
"fetch failed",
|
|
331
|
+
"timed out",
|
|
332
|
+
"timeout",
|
|
333
|
+
"econnreset",
|
|
334
|
+
"econnrefused",
|
|
335
|
+
"etimedout",
|
|
336
|
+
"eai_again",
|
|
337
|
+
"socket",
|
|
338
|
+
"network",
|
|
339
|
+
"und_err",
|
|
340
|
+
"terminated"
|
|
341
|
+
].some((needle) => text.includes(needle));
|
|
342
|
+
}
|
|
343
|
+
function sleep2(ms) {
|
|
344
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
345
|
+
}
|
|
346
|
+
async function fetchWithRetry2(url, init, timeoutMs, label, opts = {}) {
|
|
347
|
+
const attempts = Math.max(1, opts.attempts ?? PREVIEW_RETRY_ATTEMPTS2);
|
|
348
|
+
const baseDelayMs = opts.baseDelayMs ?? PREVIEW_RETRY_BASE_DELAY_MS2;
|
|
349
|
+
let lastErr;
|
|
350
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
351
|
+
try {
|
|
352
|
+
return await fetchWithTimeout2(url, init, timeoutMs, label);
|
|
353
|
+
} catch (err) {
|
|
354
|
+
lastErr = err;
|
|
355
|
+
if (attempt >= attempts || !isTransientFetchError2(err)) break;
|
|
356
|
+
const jitterMs = Math.floor(Math.random() * 250);
|
|
357
|
+
const delayMs = Math.min(baseDelayMs * Math.pow(2, attempt - 1) + jitterMs, 5e3);
|
|
358
|
+
console.warn(`[openclaw-riddledc] ${label} attempt ${attempt}/${attempts} failed: ${describeError2(err)}; retrying in ${delayMs}ms`);
|
|
359
|
+
await sleep2(delayMs);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
throw new Error(`${label} failed after ${attempts} attempts: ${describeError2(lastErr)}`);
|
|
363
|
+
}
|
|
364
|
+
function isAlreadyStartedResponse(status, body) {
|
|
365
|
+
return status === 409 && /already in status:\s*(queued|running|complete|completed)/i.test(body);
|
|
366
|
+
}
|
|
78
367
|
async function writeArtifact(workspace, subdir, filename, content) {
|
|
79
|
-
const dir = (0,
|
|
80
|
-
await (0,
|
|
81
|
-
const filePath = (0,
|
|
368
|
+
const dir = (0, import_node_path2.join)(workspace, "riddle", subdir);
|
|
369
|
+
await (0, import_promises2.mkdir)(dir, { recursive: true });
|
|
370
|
+
const filePath = (0, import_node_path2.join)(dir, filename);
|
|
82
371
|
const buf = Buffer.from(content, "utf8");
|
|
83
|
-
await (0,
|
|
372
|
+
await (0, import_promises2.writeFile)(filePath, buf);
|
|
84
373
|
return { path: filePath, sizeBytes: buf.byteLength };
|
|
85
374
|
}
|
|
86
375
|
async function writeArtifactBinary(workspace, subdir, filename, base64Content) {
|
|
87
|
-
const dir = (0,
|
|
88
|
-
await (0,
|
|
89
|
-
const filePath = (0,
|
|
376
|
+
const dir = (0, import_node_path2.join)(workspace, "riddle", subdir);
|
|
377
|
+
await (0, import_promises2.mkdir)(dir, { recursive: true });
|
|
378
|
+
const filePath = (0, import_node_path2.join)(dir, filename);
|
|
90
379
|
const buf = Buffer.from(base64Content, "base64");
|
|
91
|
-
await (0,
|
|
380
|
+
await (0, import_promises2.writeFile)(filePath, buf);
|
|
92
381
|
return { path: filePath, sizeBytes: buf.byteLength };
|
|
93
382
|
}
|
|
94
383
|
async function applySafetySpec(result, opts) {
|
|
@@ -132,10 +421,10 @@ async function applySafetySpec(result, opts) {
|
|
|
132
421
|
if (result.har != null) {
|
|
133
422
|
const harStr = typeof result.har === "string" ? result.har : JSON.stringify(result.har);
|
|
134
423
|
const harBytes = Buffer.byteLength(harStr, "utf8");
|
|
135
|
-
if (opts.harInline && harBytes <=
|
|
424
|
+
if (opts.harInline && harBytes <= INLINE_CAP2) {
|
|
136
425
|
} else {
|
|
137
426
|
const ref = await writeArtifact(opts.workspace, "har", `${jobId}.har.json`, harStr);
|
|
138
|
-
if (opts.harInline && harBytes >
|
|
427
|
+
if (opts.harInline && harBytes > INLINE_CAP2) {
|
|
139
428
|
result.har = { saved: ref.path, sizeBytes: ref.sizeBytes, warning: "Exceeded 50KB inline cap; wrote to file" };
|
|
140
429
|
} else {
|
|
141
430
|
result.har = { saved: ref.path, sizeBytes: ref.sizeBytes };
|
|
@@ -145,7 +434,7 @@ async function applySafetySpec(result, opts) {
|
|
|
145
434
|
if (result.console != null) {
|
|
146
435
|
const consoleStr = typeof result.console === "string" ? result.console : JSON.stringify(result.console);
|
|
147
436
|
const consoleBytes = Buffer.byteLength(consoleStr, "utf8");
|
|
148
|
-
if (consoleBytes >
|
|
437
|
+
if (consoleBytes > INLINE_CAP2) {
|
|
149
438
|
const ref = await writeArtifact(opts.workspace, "console", `${jobId}.log`, consoleStr);
|
|
150
439
|
result.console = { saved: ref.path, sizeBytes: ref.sizeBytes };
|
|
151
440
|
}
|
|
@@ -248,7 +537,7 @@ async function runWithDefaults(api, payload, defaults) {
|
|
|
248
537
|
error: "Missing Riddle API key. Set RIDDLE_API_KEY env var or plugins.entries.riddle.config.apiKey."
|
|
249
538
|
};
|
|
250
539
|
}
|
|
251
|
-
|
|
540
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
252
541
|
const mode = detectMode(payload);
|
|
253
542
|
const userInclude = payload.include ?? [];
|
|
254
543
|
const userRequestedHar = userInclude.includes("har");
|
|
@@ -400,7 +689,7 @@ function register(api) {
|
|
|
400
689
|
if (!apiKey) {
|
|
401
690
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
402
691
|
}
|
|
403
|
-
|
|
692
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
404
693
|
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/jobs/${params.job_id}`, {
|
|
405
694
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
406
695
|
});
|
|
@@ -809,70 +1098,8 @@ function register(api) {
|
|
|
809
1098
|
framework: import_typebox.Type.Optional(import_typebox.Type.String({ description: "Framework hint: 'spa' (default) or 'static'" }))
|
|
810
1099
|
}),
|
|
811
1100
|
async execute(_id, params) {
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
815
|
-
}
|
|
816
|
-
assertAllowedBaseUrl(baseUrl);
|
|
817
|
-
const dir = params.directory;
|
|
818
|
-
if (!dir || typeof dir !== "string") throw new Error("directory must be an absolute path");
|
|
819
|
-
try {
|
|
820
|
-
const st = await (0, import_promises.stat)(dir);
|
|
821
|
-
if (!st.isDirectory()) throw new Error(`Not a directory: ${dir}`);
|
|
822
|
-
} catch (e) {
|
|
823
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Cannot access directory: ${e.message}` }, null, 2) }] };
|
|
824
|
-
}
|
|
825
|
-
const endpoint = baseUrl.replace(/\/$/, "");
|
|
826
|
-
const createRes = await fetch(`${endpoint}/v1/preview`, {
|
|
827
|
-
method: "POST",
|
|
828
|
-
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
829
|
-
body: JSON.stringify({ framework: params.framework || "spa" })
|
|
830
|
-
});
|
|
831
|
-
if (!createRes.ok) {
|
|
832
|
-
const err = await createRes.text();
|
|
833
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: HTTP ${createRes.status} ${err}` }, null, 2) }] };
|
|
834
|
-
}
|
|
835
|
-
const created = await createRes.json();
|
|
836
|
-
const tarball = `/tmp/riddle-preview-${created.id}.tar.gz`;
|
|
837
|
-
try {
|
|
838
|
-
await execFile("tar", ["czf", tarball, "-C", dir, "."], { timeout: 6e4 });
|
|
839
|
-
const tarData = await (0, import_promises.readFile)(tarball);
|
|
840
|
-
const uploadRes = await fetch(created.upload_url, {
|
|
841
|
-
method: "PUT",
|
|
842
|
-
headers: { "Content-Type": "application/gzip" },
|
|
843
|
-
body: tarData
|
|
844
|
-
});
|
|
845
|
-
if (!uploadRes.ok) {
|
|
846
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
847
|
-
}
|
|
848
|
-
} finally {
|
|
849
|
-
try {
|
|
850
|
-
await (0, import_promises.rm)(tarball, { force: true });
|
|
851
|
-
} catch {
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
const publishRes = await fetch(`${endpoint}/v1/preview/${created.id}/publish`, {
|
|
855
|
-
method: "POST",
|
|
856
|
-
headers: { Authorization: `Bearer ${apiKey}` }
|
|
857
|
-
});
|
|
858
|
-
if (!publishRes.ok) {
|
|
859
|
-
const err = await publishRes.text();
|
|
860
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Publish failed: HTTP ${publishRes.status} ${err}` }, null, 2) }] };
|
|
861
|
-
}
|
|
862
|
-
const published = await publishRes.json();
|
|
863
|
-
return {
|
|
864
|
-
content: [{
|
|
865
|
-
type: "text",
|
|
866
|
-
text: JSON.stringify({
|
|
867
|
-
ok: true,
|
|
868
|
-
id: published.id,
|
|
869
|
-
preview_url: published.preview_url,
|
|
870
|
-
file_count: published.file_count,
|
|
871
|
-
total_bytes: published.total_bytes,
|
|
872
|
-
expires_at: created.expires_at
|
|
873
|
-
}, null, 2)
|
|
874
|
-
}]
|
|
875
|
-
};
|
|
1101
|
+
const result = await createStaticPreview(configFromOpenClawApi(api), params);
|
|
1102
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
876
1103
|
}
|
|
877
1104
|
},
|
|
878
1105
|
{ optional: true }
|
|
@@ -885,21 +1112,8 @@ function register(api) {
|
|
|
885
1112
|
id: import_typebox.Type.String({ description: "Preview ID (e.g. pv_a1b2c3d4)" })
|
|
886
1113
|
}),
|
|
887
1114
|
async execute(_id, params) {
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
891
|
-
}
|
|
892
|
-
assertAllowedBaseUrl(baseUrl);
|
|
893
|
-
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/preview/${params.id}`, {
|
|
894
|
-
method: "DELETE",
|
|
895
|
-
headers: { Authorization: `Bearer ${apiKey}` }
|
|
896
|
-
});
|
|
897
|
-
if (!res.ok) {
|
|
898
|
-
const err = await res.text();
|
|
899
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Delete failed: HTTP ${res.status} ${err}` }, null, 2) }] };
|
|
900
|
-
}
|
|
901
|
-
const data = await res.json();
|
|
902
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: true, deleted: true, files_removed: data.files_removed }, null, 2) }] };
|
|
1115
|
+
const result = await deleteStaticPreview(configFromOpenClawApi(api), params.id);
|
|
1116
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
903
1117
|
}
|
|
904
1118
|
},
|
|
905
1119
|
{ optional: true }
|
|
@@ -934,11 +1148,11 @@ function register(api) {
|
|
|
934
1148
|
if (!apiKey) {
|
|
935
1149
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
936
1150
|
}
|
|
937
|
-
|
|
1151
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
938
1152
|
const dir = params.directory;
|
|
939
1153
|
if (!dir || typeof dir !== "string") throw new Error("directory must be an absolute path");
|
|
940
1154
|
try {
|
|
941
|
-
const st = await (0,
|
|
1155
|
+
const st = await (0, import_promises2.stat)(dir);
|
|
942
1156
|
if (!st.isDirectory()) throw new Error(`Not a directory: ${dir}`);
|
|
943
1157
|
} catch (e) {
|
|
944
1158
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Cannot access directory: ${e.message}` }, null, 2) }] };
|
|
@@ -951,11 +1165,16 @@ function register(api) {
|
|
|
951
1165
|
const envBody = {};
|
|
952
1166
|
if (hasSensitiveEnv) envBody.env = params.sensitive_env;
|
|
953
1167
|
if (hasLocalStorage) envBody.localStorage = params.localStorage;
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1168
|
+
let envRes;
|
|
1169
|
+
try {
|
|
1170
|
+
envRes = await fetchWithTimeout2(`${endpoint}/v1/server-preview/env`, {
|
|
1171
|
+
method: "POST",
|
|
1172
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1173
|
+
body: JSON.stringify(envBody)
|
|
1174
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "server preview env store");
|
|
1175
|
+
} catch (e) {
|
|
1176
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1177
|
+
}
|
|
959
1178
|
if (!envRes.ok) {
|
|
960
1179
|
const err = await envRes.text();
|
|
961
1180
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: HTTP ${envRes.status} ${err}` }, null, 2) }] };
|
|
@@ -981,11 +1200,16 @@ function register(api) {
|
|
|
981
1200
|
if (params.navigation_timeout) createBody.navigation_timeout = params.navigation_timeout;
|
|
982
1201
|
if (params.color_scheme) createBody.color_scheme = params.color_scheme;
|
|
983
1202
|
if (params.viewport) createBody.viewport = params.viewport;
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1203
|
+
let createRes;
|
|
1204
|
+
try {
|
|
1205
|
+
createRes = await fetchWithRetry2(`${endpoint}/v1/server-preview`, {
|
|
1206
|
+
method: "POST",
|
|
1207
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1208
|
+
body: JSON.stringify(createBody)
|
|
1209
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "server preview create");
|
|
1210
|
+
} catch (e) {
|
|
1211
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1212
|
+
}
|
|
989
1213
|
if (!createRes.ok) {
|
|
990
1214
|
const err = await createRes.text();
|
|
991
1215
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: HTTP ${createRes.status} ${err}` }, null, 2) }] };
|
|
@@ -995,37 +1219,56 @@ function register(api) {
|
|
|
995
1219
|
try {
|
|
996
1220
|
const excludes = params.exclude || [".git", "*.log"];
|
|
997
1221
|
const excludeArgs = excludes.flatMap((p) => ["--exclude", p]);
|
|
998
|
-
await
|
|
999
|
-
const tarData = await (0,
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1222
|
+
await execFile2("tar", ["czf", tarball, ...excludeArgs, "-C", dir, "."], { timeout: 12e4 });
|
|
1223
|
+
const tarData = await (0, import_promises2.readFile)(tarball);
|
|
1224
|
+
let uploadRes;
|
|
1225
|
+
try {
|
|
1226
|
+
uploadRes = await fetchWithRetry2(created.upload_url, {
|
|
1227
|
+
method: "PUT",
|
|
1228
|
+
headers: { "Content-Type": "application/gzip" },
|
|
1229
|
+
body: tarData
|
|
1230
|
+
}, PREVIEW_UPLOAD_TIMEOUT_MS2, "server preview upload");
|
|
1231
|
+
} catch (e) {
|
|
1232
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1233
|
+
}
|
|
1005
1234
|
if (!uploadRes.ok) {
|
|
1006
1235
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
1007
1236
|
}
|
|
1008
1237
|
} finally {
|
|
1009
1238
|
try {
|
|
1010
|
-
await (0,
|
|
1239
|
+
await (0, import_promises2.rm)(tarball, { force: true });
|
|
1011
1240
|
} catch {
|
|
1012
1241
|
}
|
|
1013
1242
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1243
|
+
let startRes;
|
|
1244
|
+
try {
|
|
1245
|
+
startRes = await fetchWithRetry2(`${endpoint}/v1/server-preview/${created.job_id}/start`, {
|
|
1246
|
+
method: "POST",
|
|
1247
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1248
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "server preview start");
|
|
1249
|
+
} catch (e) {
|
|
1250
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1251
|
+
}
|
|
1018
1252
|
if (!startRes.ok) {
|
|
1019
1253
|
const err = await startRes.text();
|
|
1020
|
-
|
|
1254
|
+
if (isAlreadyStartedResponse(startRes.status, err)) {
|
|
1255
|
+
console.warn(`[openclaw-riddledc] server preview start returned ${startRes.status} for ${created.job_id}; continuing to poll`);
|
|
1256
|
+
} else {
|
|
1257
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: HTTP ${startRes.status} ${err}` }, null, 2) }] };
|
|
1258
|
+
}
|
|
1021
1259
|
}
|
|
1022
1260
|
const timeoutMs = ((params.timeout || 120) + 60) * 1e3;
|
|
1023
1261
|
const pollStart = Date.now();
|
|
1024
1262
|
const POLL_INTERVAL = 3e3;
|
|
1025
1263
|
while (Date.now() - pollStart < timeoutMs) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1264
|
+
let statusRes;
|
|
1265
|
+
try {
|
|
1266
|
+
statusRes = await fetchWithRetry2(`${endpoint}/v1/server-preview/${created.job_id}`, {
|
|
1267
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1268
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "server preview poll", { attempts: 2 });
|
|
1269
|
+
} catch (e) {
|
|
1270
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1271
|
+
}
|
|
1029
1272
|
if (!statusRes.ok) {
|
|
1030
1273
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: HTTP ${statusRes.status}` }, null, 2) }] };
|
|
1031
1274
|
}
|
|
@@ -1044,7 +1287,7 @@ function register(api) {
|
|
|
1044
1287
|
for (const output of result.outputs) {
|
|
1045
1288
|
if (output.name && /\.(png|jpg|jpeg)$/i.test(output.name) && output.url) {
|
|
1046
1289
|
try {
|
|
1047
|
-
const imgRes = await
|
|
1290
|
+
const imgRes = await fetchWithTimeout2(output.url, {}, PREVIEW_ARTIFACT_TIMEOUT_MS, "server preview artifact download");
|
|
1048
1291
|
if (imgRes.ok) {
|
|
1049
1292
|
const buf = await imgRes.arrayBuffer();
|
|
1050
1293
|
const base64 = Buffer.from(buf).toString("base64");
|
|
@@ -1098,17 +1341,17 @@ function register(api) {
|
|
|
1098
1341
|
if (!apiKey) {
|
|
1099
1342
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
1100
1343
|
}
|
|
1101
|
-
|
|
1344
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
1102
1345
|
const dir = params.directory;
|
|
1103
1346
|
if (!dir || typeof dir !== "string") throw new Error("directory must be an absolute path");
|
|
1104
1347
|
try {
|
|
1105
|
-
const st = await (0,
|
|
1348
|
+
const st = await (0, import_promises2.stat)(dir);
|
|
1106
1349
|
if (!st.isDirectory()) throw new Error(`Not a directory: ${dir}`);
|
|
1107
1350
|
} catch (e) {
|
|
1108
1351
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Cannot access directory: ${e.message}` }, null, 2) }] };
|
|
1109
1352
|
}
|
|
1110
1353
|
try {
|
|
1111
|
-
await (0,
|
|
1354
|
+
await (0, import_promises2.stat)(`${dir}/Dockerfile`);
|
|
1112
1355
|
} catch {
|
|
1113
1356
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `No Dockerfile found at ${dir}/Dockerfile. riddle_build_preview requires a Dockerfile at the root of the directory.` }, null, 2) }] };
|
|
1114
1357
|
}
|
|
@@ -1120,11 +1363,16 @@ function register(api) {
|
|
|
1120
1363
|
const envBody = {};
|
|
1121
1364
|
if (hasSensitiveEnv) envBody.env = params.sensitive_env;
|
|
1122
1365
|
if (hasLocalStorage) envBody.localStorage = params.localStorage;
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1366
|
+
let envRes;
|
|
1367
|
+
try {
|
|
1368
|
+
envRes = await fetchWithTimeout2(`${endpoint}/v1/build-preview/env`, {
|
|
1369
|
+
method: "POST",
|
|
1370
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1371
|
+
body: JSON.stringify(envBody)
|
|
1372
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "build preview env store");
|
|
1373
|
+
} catch (e) {
|
|
1374
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1375
|
+
}
|
|
1128
1376
|
if (!envRes.ok) {
|
|
1129
1377
|
const err = await envRes.text();
|
|
1130
1378
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: HTTP ${envRes.status} ${err}` }, null, 2) }] };
|
|
@@ -1152,11 +1400,16 @@ function register(api) {
|
|
|
1152
1400
|
if (params.color_scheme) createBody.color_scheme = params.color_scheme;
|
|
1153
1401
|
if (params.viewport) createBody.viewport = params.viewport;
|
|
1154
1402
|
if (params.audit) createBody.audit = true;
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1403
|
+
let createRes;
|
|
1404
|
+
try {
|
|
1405
|
+
createRes = await fetchWithRetry2(`${endpoint}/v1/build-preview`, {
|
|
1406
|
+
method: "POST",
|
|
1407
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1408
|
+
body: JSON.stringify(createBody)
|
|
1409
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "build preview create");
|
|
1410
|
+
} catch (e) {
|
|
1411
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1412
|
+
}
|
|
1160
1413
|
if (!createRes.ok) {
|
|
1161
1414
|
const err = await createRes.text();
|
|
1162
1415
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: HTTP ${createRes.status} ${err}` }, null, 2) }] };
|
|
@@ -1166,37 +1419,56 @@ function register(api) {
|
|
|
1166
1419
|
try {
|
|
1167
1420
|
const excludes = params.exclude || [".git", "*.log"];
|
|
1168
1421
|
const excludeArgs = excludes.flatMap((p) => ["--exclude", p]);
|
|
1169
|
-
await
|
|
1170
|
-
const tarData = await (0,
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1422
|
+
await execFile2("tar", ["czf", tarball, ...excludeArgs, "-C", dir, "."], { timeout: 12e4 });
|
|
1423
|
+
const tarData = await (0, import_promises2.readFile)(tarball);
|
|
1424
|
+
let uploadRes;
|
|
1425
|
+
try {
|
|
1426
|
+
uploadRes = await fetchWithRetry2(created.upload_url, {
|
|
1427
|
+
method: "PUT",
|
|
1428
|
+
headers: { "Content-Type": "application/gzip" },
|
|
1429
|
+
body: tarData
|
|
1430
|
+
}, PREVIEW_UPLOAD_TIMEOUT_MS2, "build preview upload");
|
|
1431
|
+
} catch (e) {
|
|
1432
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1433
|
+
}
|
|
1176
1434
|
if (!uploadRes.ok) {
|
|
1177
1435
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
1178
1436
|
}
|
|
1179
1437
|
} finally {
|
|
1180
1438
|
try {
|
|
1181
|
-
await (0,
|
|
1439
|
+
await (0, import_promises2.rm)(tarball, { force: true });
|
|
1182
1440
|
} catch {
|
|
1183
1441
|
}
|
|
1184
1442
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1443
|
+
let startRes;
|
|
1444
|
+
try {
|
|
1445
|
+
startRes = await fetchWithRetry2(`${endpoint}/v1/build-preview/${created.job_id}/start`, {
|
|
1446
|
+
method: "POST",
|
|
1447
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1448
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "build preview start");
|
|
1449
|
+
} catch (e) {
|
|
1450
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1451
|
+
}
|
|
1189
1452
|
if (!startRes.ok) {
|
|
1190
1453
|
const err = await startRes.text();
|
|
1191
|
-
|
|
1454
|
+
if (isAlreadyStartedResponse(startRes.status, err)) {
|
|
1455
|
+
console.warn(`[openclaw-riddledc] build preview start returned ${startRes.status} for ${created.job_id}; continuing to poll`);
|
|
1456
|
+
} else {
|
|
1457
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: HTTP ${startRes.status} ${err}` }, null, 2) }] };
|
|
1458
|
+
}
|
|
1192
1459
|
}
|
|
1193
1460
|
const timeoutMs = ((params.timeout || 180) + 120) * 1e3;
|
|
1194
1461
|
const pollStart = Date.now();
|
|
1195
1462
|
const POLL_INTERVAL = 3e3;
|
|
1196
1463
|
while (Date.now() - pollStart < timeoutMs) {
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1464
|
+
let statusRes;
|
|
1465
|
+
try {
|
|
1466
|
+
statusRes = await fetchWithRetry2(`${endpoint}/v1/build-preview/${created.job_id}`, {
|
|
1467
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1468
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "build preview poll", { attempts: 2 });
|
|
1469
|
+
} catch (e) {
|
|
1470
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1471
|
+
}
|
|
1200
1472
|
if (!statusRes.ok) {
|
|
1201
1473
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: HTTP ${statusRes.status}` }, null, 2) }] };
|
|
1202
1474
|
}
|
|
@@ -1219,7 +1491,7 @@ function register(api) {
|
|
|
1219
1491
|
for (const output of result.outputs) {
|
|
1220
1492
|
if (output.name && /\.(png|jpg|jpeg)$/i.test(output.name) && output.url) {
|
|
1221
1493
|
try {
|
|
1222
|
-
const imgRes = await
|
|
1494
|
+
const imgRes = await fetchWithTimeout2(output.url, {}, PREVIEW_ARTIFACT_TIMEOUT_MS, "build preview artifact download");
|
|
1223
1495
|
if (imgRes.ok) {
|
|
1224
1496
|
const buf = await imgRes.arrayBuffer();
|
|
1225
1497
|
const base64 = Buffer.from(buf).toString("base64");
|
|
@@ -1244,7 +1516,7 @@ function register(api) {
|
|
|
1244
1516
|
async function riddleApiFetch(api2, method, path, body) {
|
|
1245
1517
|
const { apiKey, baseUrl } = getCfg(api2);
|
|
1246
1518
|
if (!apiKey) throw new Error("Missing Riddle API key. Set RIDDLE_API_KEY or configure in plugin settings.");
|
|
1247
|
-
|
|
1519
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
1248
1520
|
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
1249
1521
|
const res = await fetch(url, {
|
|
1250
1522
|
method,
|
|
@@ -1312,7 +1584,7 @@ function register(api) {
|
|
|
1312
1584
|
async execute(_id, params) {
|
|
1313
1585
|
const { apiKey, baseUrl } = getCfg(api);
|
|
1314
1586
|
if (!apiKey) return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
1315
|
-
|
|
1587
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
1316
1588
|
const payload = { timeout_sec: params.timeout_sec || 60 };
|
|
1317
1589
|
if (params.stealth) payload.stealth = true;
|
|
1318
1590
|
if (params.custom_storage) payload.custom_storage = params.custom_storage;
|