@riddledc/openclaw-riddledc 0.9.2 → 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 +292 -173
- package/dist/index.js +10 -99
- package/openclaw.plugin.json +1 -1
- package/package.json +7 -2
package/dist/index.cjs
CHANGED
|
@@ -24,17 +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;
|
|
33
39
|
var PREVIEW_REQUEST_TIMEOUT_MS = 3e4;
|
|
34
40
|
var PREVIEW_UPLOAD_TIMEOUT_MS = 5 * 6e4;
|
|
35
|
-
var PREVIEW_ARTIFACT_TIMEOUT_MS = 6e4;
|
|
36
41
|
var PREVIEW_RETRY_ATTEMPTS = 3;
|
|
37
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;
|
|
38
252
|
function getCfg(api) {
|
|
39
253
|
const cfg = api?.config ?? {};
|
|
40
254
|
const pluginCfg = cfg?.plugins?.entries?.["openclaw-riddledc"]?.config ?? {};
|
|
@@ -43,7 +257,7 @@ function getCfg(api) {
|
|
|
43
257
|
baseUrl: pluginCfg.baseUrl || "https://api.riddledc.com"
|
|
44
258
|
};
|
|
45
259
|
}
|
|
46
|
-
function
|
|
260
|
+
function assertAllowedBaseUrl2(baseUrl) {
|
|
47
261
|
const url = new URL(baseUrl);
|
|
48
262
|
if (url.protocol !== "https:") throw new Error(`Riddle baseUrl must be https: (${baseUrl})`);
|
|
49
263
|
if (url.hostname !== "api.riddledc.com") {
|
|
@@ -80,7 +294,7 @@ function abToBase64(ab) {
|
|
|
80
294
|
function getWorkspacePath(api) {
|
|
81
295
|
return api?.workspacePath ?? process.cwd();
|
|
82
296
|
}
|
|
83
|
-
function
|
|
297
|
+
function describeError2(err) {
|
|
84
298
|
const anyErr = err;
|
|
85
299
|
const parts = [];
|
|
86
300
|
if (err instanceof Error) parts.push(err.message);
|
|
@@ -96,7 +310,7 @@ function describeError(err) {
|
|
|
96
310
|
}
|
|
97
311
|
return parts.join("; ");
|
|
98
312
|
}
|
|
99
|
-
async function
|
|
313
|
+
async function fetchWithTimeout2(url, init, timeoutMs, label) {
|
|
100
314
|
const controller = new AbortController();
|
|
101
315
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
102
316
|
try {
|
|
@@ -110,8 +324,8 @@ async function fetchWithTimeout(url, init, timeoutMs, label) {
|
|
|
110
324
|
clearTimeout(timer);
|
|
111
325
|
}
|
|
112
326
|
}
|
|
113
|
-
function
|
|
114
|
-
const text =
|
|
327
|
+
function isTransientFetchError2(err) {
|
|
328
|
+
const text = describeError2(err).toLowerCase();
|
|
115
329
|
return [
|
|
116
330
|
"fetch failed",
|
|
117
331
|
"timed out",
|
|
@@ -126,44 +340,44 @@ function isTransientFetchError(err) {
|
|
|
126
340
|
"terminated"
|
|
127
341
|
].some((needle) => text.includes(needle));
|
|
128
342
|
}
|
|
129
|
-
function
|
|
343
|
+
function sleep2(ms) {
|
|
130
344
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
131
345
|
}
|
|
132
|
-
async function
|
|
133
|
-
const attempts = Math.max(1, opts.attempts ??
|
|
134
|
-
const baseDelayMs = opts.baseDelayMs ??
|
|
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;
|
|
135
349
|
let lastErr;
|
|
136
350
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
137
351
|
try {
|
|
138
|
-
return await
|
|
352
|
+
return await fetchWithTimeout2(url, init, timeoutMs, label);
|
|
139
353
|
} catch (err) {
|
|
140
354
|
lastErr = err;
|
|
141
|
-
if (attempt >= attempts || !
|
|
355
|
+
if (attempt >= attempts || !isTransientFetchError2(err)) break;
|
|
142
356
|
const jitterMs = Math.floor(Math.random() * 250);
|
|
143
357
|
const delayMs = Math.min(baseDelayMs * Math.pow(2, attempt - 1) + jitterMs, 5e3);
|
|
144
|
-
console.warn(`[openclaw-riddledc] ${label} attempt ${attempt}/${attempts} failed: ${
|
|
145
|
-
await
|
|
358
|
+
console.warn(`[openclaw-riddledc] ${label} attempt ${attempt}/${attempts} failed: ${describeError2(err)}; retrying in ${delayMs}ms`);
|
|
359
|
+
await sleep2(delayMs);
|
|
146
360
|
}
|
|
147
361
|
}
|
|
148
|
-
throw new Error(`${label} failed after ${attempts} attempts: ${
|
|
362
|
+
throw new Error(`${label} failed after ${attempts} attempts: ${describeError2(lastErr)}`);
|
|
149
363
|
}
|
|
150
364
|
function isAlreadyStartedResponse(status, body) {
|
|
151
365
|
return status === 409 && /already in status:\s*(queued|running|complete|completed)/i.test(body);
|
|
152
366
|
}
|
|
153
367
|
async function writeArtifact(workspace, subdir, filename, content) {
|
|
154
|
-
const dir = (0,
|
|
155
|
-
await (0,
|
|
156
|
-
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);
|
|
157
371
|
const buf = Buffer.from(content, "utf8");
|
|
158
|
-
await (0,
|
|
372
|
+
await (0, import_promises2.writeFile)(filePath, buf);
|
|
159
373
|
return { path: filePath, sizeBytes: buf.byteLength };
|
|
160
374
|
}
|
|
161
375
|
async function writeArtifactBinary(workspace, subdir, filename, base64Content) {
|
|
162
|
-
const dir = (0,
|
|
163
|
-
await (0,
|
|
164
|
-
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);
|
|
165
379
|
const buf = Buffer.from(base64Content, "base64");
|
|
166
|
-
await (0,
|
|
380
|
+
await (0, import_promises2.writeFile)(filePath, buf);
|
|
167
381
|
return { path: filePath, sizeBytes: buf.byteLength };
|
|
168
382
|
}
|
|
169
383
|
async function applySafetySpec(result, opts) {
|
|
@@ -207,10 +421,10 @@ async function applySafetySpec(result, opts) {
|
|
|
207
421
|
if (result.har != null) {
|
|
208
422
|
const harStr = typeof result.har === "string" ? result.har : JSON.stringify(result.har);
|
|
209
423
|
const harBytes = Buffer.byteLength(harStr, "utf8");
|
|
210
|
-
if (opts.harInline && harBytes <=
|
|
424
|
+
if (opts.harInline && harBytes <= INLINE_CAP2) {
|
|
211
425
|
} else {
|
|
212
426
|
const ref = await writeArtifact(opts.workspace, "har", `${jobId}.har.json`, harStr);
|
|
213
|
-
if (opts.harInline && harBytes >
|
|
427
|
+
if (opts.harInline && harBytes > INLINE_CAP2) {
|
|
214
428
|
result.har = { saved: ref.path, sizeBytes: ref.sizeBytes, warning: "Exceeded 50KB inline cap; wrote to file" };
|
|
215
429
|
} else {
|
|
216
430
|
result.har = { saved: ref.path, sizeBytes: ref.sizeBytes };
|
|
@@ -220,7 +434,7 @@ async function applySafetySpec(result, opts) {
|
|
|
220
434
|
if (result.console != null) {
|
|
221
435
|
const consoleStr = typeof result.console === "string" ? result.console : JSON.stringify(result.console);
|
|
222
436
|
const consoleBytes = Buffer.byteLength(consoleStr, "utf8");
|
|
223
|
-
if (consoleBytes >
|
|
437
|
+
if (consoleBytes > INLINE_CAP2) {
|
|
224
438
|
const ref = await writeArtifact(opts.workspace, "console", `${jobId}.log`, consoleStr);
|
|
225
439
|
result.console = { saved: ref.path, sizeBytes: ref.sizeBytes };
|
|
226
440
|
}
|
|
@@ -323,7 +537,7 @@ async function runWithDefaults(api, payload, defaults) {
|
|
|
323
537
|
error: "Missing Riddle API key. Set RIDDLE_API_KEY env var or plugins.entries.riddle.config.apiKey."
|
|
324
538
|
};
|
|
325
539
|
}
|
|
326
|
-
|
|
540
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
327
541
|
const mode = detectMode(payload);
|
|
328
542
|
const userInclude = payload.include ?? [];
|
|
329
543
|
const userRequestedHar = userInclude.includes("har");
|
|
@@ -475,7 +689,7 @@ function register(api) {
|
|
|
475
689
|
if (!apiKey) {
|
|
476
690
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
477
691
|
}
|
|
478
|
-
|
|
692
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
479
693
|
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/jobs/${params.job_id}`, {
|
|
480
694
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
481
695
|
});
|
|
@@ -884,85 +1098,8 @@ function register(api) {
|
|
|
884
1098
|
framework: import_typebox.Type.Optional(import_typebox.Type.String({ description: "Framework hint: 'spa' (default) or 'static'" }))
|
|
885
1099
|
}),
|
|
886
1100
|
async execute(_id, params) {
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
890
|
-
}
|
|
891
|
-
assertAllowedBaseUrl(baseUrl);
|
|
892
|
-
const dir = params.directory;
|
|
893
|
-
if (!dir || typeof dir !== "string") throw new Error("directory must be an absolute path");
|
|
894
|
-
try {
|
|
895
|
-
const st = await (0, import_promises.stat)(dir);
|
|
896
|
-
if (!st.isDirectory()) throw new Error(`Not a directory: ${dir}`);
|
|
897
|
-
} catch (e) {
|
|
898
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Cannot access directory: ${e.message}` }, null, 2) }] };
|
|
899
|
-
}
|
|
900
|
-
const endpoint = baseUrl.replace(/\/$/, "");
|
|
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
|
-
}
|
|
911
|
-
if (!createRes.ok) {
|
|
912
|
-
const err = await createRes.text();
|
|
913
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: HTTP ${createRes.status} ${err}` }, null, 2) }] };
|
|
914
|
-
}
|
|
915
|
-
const created = await createRes.json();
|
|
916
|
-
const tarball = `/tmp/riddle-preview-${created.id}.tar.gz`;
|
|
917
|
-
try {
|
|
918
|
-
await execFile("tar", ["czf", tarball, "-C", dir, "."], { timeout: 6e4 });
|
|
919
|
-
const tarData = await (0, import_promises.readFile)(tarball);
|
|
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
|
-
}
|
|
930
|
-
if (!uploadRes.ok) {
|
|
931
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
932
|
-
}
|
|
933
|
-
} finally {
|
|
934
|
-
try {
|
|
935
|
-
await (0, import_promises.rm)(tarball, { force: true });
|
|
936
|
-
} catch {
|
|
937
|
-
}
|
|
938
|
-
}
|
|
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
|
-
}
|
|
948
|
-
if (!publishRes.ok) {
|
|
949
|
-
const err = await publishRes.text();
|
|
950
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Publish failed: HTTP ${publishRes.status} ${err}` }, null, 2) }] };
|
|
951
|
-
}
|
|
952
|
-
const published = await publishRes.json();
|
|
953
|
-
return {
|
|
954
|
-
content: [{
|
|
955
|
-
type: "text",
|
|
956
|
-
text: JSON.stringify({
|
|
957
|
-
ok: true,
|
|
958
|
-
id: published.id,
|
|
959
|
-
preview_url: published.preview_url,
|
|
960
|
-
file_count: published.file_count,
|
|
961
|
-
total_bytes: published.total_bytes,
|
|
962
|
-
expires_at: created.expires_at
|
|
963
|
-
}, null, 2)
|
|
964
|
-
}]
|
|
965
|
-
};
|
|
1101
|
+
const result = await createStaticPreview(configFromOpenClawApi(api), params);
|
|
1102
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
966
1103
|
}
|
|
967
1104
|
},
|
|
968
1105
|
{ optional: true }
|
|
@@ -975,26 +1112,8 @@ function register(api) {
|
|
|
975
1112
|
id: import_typebox.Type.String({ description: "Preview ID (e.g. pv_a1b2c3d4)" })
|
|
976
1113
|
}),
|
|
977
1114
|
async execute(_id, params) {
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
981
|
-
}
|
|
982
|
-
assertAllowedBaseUrl(baseUrl);
|
|
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
|
-
}
|
|
992
|
-
if (!res.ok) {
|
|
993
|
-
const err = await res.text();
|
|
994
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Delete failed: HTTP ${res.status} ${err}` }, null, 2) }] };
|
|
995
|
-
}
|
|
996
|
-
const data = await res.json();
|
|
997
|
-
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) }] };
|
|
998
1117
|
}
|
|
999
1118
|
},
|
|
1000
1119
|
{ optional: true }
|
|
@@ -1029,11 +1148,11 @@ function register(api) {
|
|
|
1029
1148
|
if (!apiKey) {
|
|
1030
1149
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
1031
1150
|
}
|
|
1032
|
-
|
|
1151
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
1033
1152
|
const dir = params.directory;
|
|
1034
1153
|
if (!dir || typeof dir !== "string") throw new Error("directory must be an absolute path");
|
|
1035
1154
|
try {
|
|
1036
|
-
const st = await (0,
|
|
1155
|
+
const st = await (0, import_promises2.stat)(dir);
|
|
1037
1156
|
if (!st.isDirectory()) throw new Error(`Not a directory: ${dir}`);
|
|
1038
1157
|
} catch (e) {
|
|
1039
1158
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Cannot access directory: ${e.message}` }, null, 2) }] };
|
|
@@ -1048,13 +1167,13 @@ function register(api) {
|
|
|
1048
1167
|
if (hasLocalStorage) envBody.localStorage = params.localStorage;
|
|
1049
1168
|
let envRes;
|
|
1050
1169
|
try {
|
|
1051
|
-
envRes = await
|
|
1170
|
+
envRes = await fetchWithTimeout2(`${endpoint}/v1/server-preview/env`, {
|
|
1052
1171
|
method: "POST",
|
|
1053
1172
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1054
1173
|
body: JSON.stringify(envBody)
|
|
1055
|
-
},
|
|
1174
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "server preview env store");
|
|
1056
1175
|
} catch (e) {
|
|
1057
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: ${
|
|
1176
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1058
1177
|
}
|
|
1059
1178
|
if (!envRes.ok) {
|
|
1060
1179
|
const err = await envRes.text();
|
|
@@ -1083,13 +1202,13 @@ function register(api) {
|
|
|
1083
1202
|
if (params.viewport) createBody.viewport = params.viewport;
|
|
1084
1203
|
let createRes;
|
|
1085
1204
|
try {
|
|
1086
|
-
createRes = await
|
|
1205
|
+
createRes = await fetchWithRetry2(`${endpoint}/v1/server-preview`, {
|
|
1087
1206
|
method: "POST",
|
|
1088
1207
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1089
1208
|
body: JSON.stringify(createBody)
|
|
1090
|
-
},
|
|
1209
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "server preview create");
|
|
1091
1210
|
} catch (e) {
|
|
1092
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${
|
|
1211
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1093
1212
|
}
|
|
1094
1213
|
if (!createRes.ok) {
|
|
1095
1214
|
const err = await createRes.text();
|
|
@@ -1100,35 +1219,35 @@ function register(api) {
|
|
|
1100
1219
|
try {
|
|
1101
1220
|
const excludes = params.exclude || [".git", "*.log"];
|
|
1102
1221
|
const excludeArgs = excludes.flatMap((p) => ["--exclude", p]);
|
|
1103
|
-
await
|
|
1104
|
-
const tarData = await (0,
|
|
1222
|
+
await execFile2("tar", ["czf", tarball, ...excludeArgs, "-C", dir, "."], { timeout: 12e4 });
|
|
1223
|
+
const tarData = await (0, import_promises2.readFile)(tarball);
|
|
1105
1224
|
let uploadRes;
|
|
1106
1225
|
try {
|
|
1107
|
-
uploadRes = await
|
|
1226
|
+
uploadRes = await fetchWithRetry2(created.upload_url, {
|
|
1108
1227
|
method: "PUT",
|
|
1109
1228
|
headers: { "Content-Type": "application/gzip" },
|
|
1110
1229
|
body: tarData
|
|
1111
|
-
},
|
|
1230
|
+
}, PREVIEW_UPLOAD_TIMEOUT_MS2, "server preview upload");
|
|
1112
1231
|
} catch (e) {
|
|
1113
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: ${
|
|
1232
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1114
1233
|
}
|
|
1115
1234
|
if (!uploadRes.ok) {
|
|
1116
1235
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
1117
1236
|
}
|
|
1118
1237
|
} finally {
|
|
1119
1238
|
try {
|
|
1120
|
-
await (0,
|
|
1239
|
+
await (0, import_promises2.rm)(tarball, { force: true });
|
|
1121
1240
|
} catch {
|
|
1122
1241
|
}
|
|
1123
1242
|
}
|
|
1124
1243
|
let startRes;
|
|
1125
1244
|
try {
|
|
1126
|
-
startRes = await
|
|
1245
|
+
startRes = await fetchWithRetry2(`${endpoint}/v1/server-preview/${created.job_id}/start`, {
|
|
1127
1246
|
method: "POST",
|
|
1128
1247
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1129
|
-
},
|
|
1248
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "server preview start");
|
|
1130
1249
|
} catch (e) {
|
|
1131
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: ${
|
|
1250
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1132
1251
|
}
|
|
1133
1252
|
if (!startRes.ok) {
|
|
1134
1253
|
const err = await startRes.text();
|
|
@@ -1144,11 +1263,11 @@ function register(api) {
|
|
|
1144
1263
|
while (Date.now() - pollStart < timeoutMs) {
|
|
1145
1264
|
let statusRes;
|
|
1146
1265
|
try {
|
|
1147
|
-
statusRes = await
|
|
1266
|
+
statusRes = await fetchWithRetry2(`${endpoint}/v1/server-preview/${created.job_id}`, {
|
|
1148
1267
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1149
|
-
},
|
|
1268
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "server preview poll", { attempts: 2 });
|
|
1150
1269
|
} catch (e) {
|
|
1151
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: ${
|
|
1270
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1152
1271
|
}
|
|
1153
1272
|
if (!statusRes.ok) {
|
|
1154
1273
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: HTTP ${statusRes.status}` }, null, 2) }] };
|
|
@@ -1168,7 +1287,7 @@ function register(api) {
|
|
|
1168
1287
|
for (const output of result.outputs) {
|
|
1169
1288
|
if (output.name && /\.(png|jpg|jpeg)$/i.test(output.name) && output.url) {
|
|
1170
1289
|
try {
|
|
1171
|
-
const imgRes = await
|
|
1290
|
+
const imgRes = await fetchWithTimeout2(output.url, {}, PREVIEW_ARTIFACT_TIMEOUT_MS, "server preview artifact download");
|
|
1172
1291
|
if (imgRes.ok) {
|
|
1173
1292
|
const buf = await imgRes.arrayBuffer();
|
|
1174
1293
|
const base64 = Buffer.from(buf).toString("base64");
|
|
@@ -1222,17 +1341,17 @@ function register(api) {
|
|
|
1222
1341
|
if (!apiKey) {
|
|
1223
1342
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
1224
1343
|
}
|
|
1225
|
-
|
|
1344
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
1226
1345
|
const dir = params.directory;
|
|
1227
1346
|
if (!dir || typeof dir !== "string") throw new Error("directory must be an absolute path");
|
|
1228
1347
|
try {
|
|
1229
|
-
const st = await (0,
|
|
1348
|
+
const st = await (0, import_promises2.stat)(dir);
|
|
1230
1349
|
if (!st.isDirectory()) throw new Error(`Not a directory: ${dir}`);
|
|
1231
1350
|
} catch (e) {
|
|
1232
1351
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Cannot access directory: ${e.message}` }, null, 2) }] };
|
|
1233
1352
|
}
|
|
1234
1353
|
try {
|
|
1235
|
-
await (0,
|
|
1354
|
+
await (0, import_promises2.stat)(`${dir}/Dockerfile`);
|
|
1236
1355
|
} catch {
|
|
1237
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) }] };
|
|
1238
1357
|
}
|
|
@@ -1246,13 +1365,13 @@ function register(api) {
|
|
|
1246
1365
|
if (hasLocalStorage) envBody.localStorage = params.localStorage;
|
|
1247
1366
|
let envRes;
|
|
1248
1367
|
try {
|
|
1249
|
-
envRes = await
|
|
1368
|
+
envRes = await fetchWithTimeout2(`${endpoint}/v1/build-preview/env`, {
|
|
1250
1369
|
method: "POST",
|
|
1251
1370
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1252
1371
|
body: JSON.stringify(envBody)
|
|
1253
|
-
},
|
|
1372
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "build preview env store");
|
|
1254
1373
|
} catch (e) {
|
|
1255
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: ${
|
|
1374
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1256
1375
|
}
|
|
1257
1376
|
if (!envRes.ok) {
|
|
1258
1377
|
const err = await envRes.text();
|
|
@@ -1283,13 +1402,13 @@ function register(api) {
|
|
|
1283
1402
|
if (params.audit) createBody.audit = true;
|
|
1284
1403
|
let createRes;
|
|
1285
1404
|
try {
|
|
1286
|
-
createRes = await
|
|
1405
|
+
createRes = await fetchWithRetry2(`${endpoint}/v1/build-preview`, {
|
|
1287
1406
|
method: "POST",
|
|
1288
1407
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1289
1408
|
body: JSON.stringify(createBody)
|
|
1290
|
-
},
|
|
1409
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "build preview create");
|
|
1291
1410
|
} catch (e) {
|
|
1292
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${
|
|
1411
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1293
1412
|
}
|
|
1294
1413
|
if (!createRes.ok) {
|
|
1295
1414
|
const err = await createRes.text();
|
|
@@ -1300,35 +1419,35 @@ function register(api) {
|
|
|
1300
1419
|
try {
|
|
1301
1420
|
const excludes = params.exclude || [".git", "*.log"];
|
|
1302
1421
|
const excludeArgs = excludes.flatMap((p) => ["--exclude", p]);
|
|
1303
|
-
await
|
|
1304
|
-
const tarData = await (0,
|
|
1422
|
+
await execFile2("tar", ["czf", tarball, ...excludeArgs, "-C", dir, "."], { timeout: 12e4 });
|
|
1423
|
+
const tarData = await (0, import_promises2.readFile)(tarball);
|
|
1305
1424
|
let uploadRes;
|
|
1306
1425
|
try {
|
|
1307
|
-
uploadRes = await
|
|
1426
|
+
uploadRes = await fetchWithRetry2(created.upload_url, {
|
|
1308
1427
|
method: "PUT",
|
|
1309
1428
|
headers: { "Content-Type": "application/gzip" },
|
|
1310
1429
|
body: tarData
|
|
1311
|
-
},
|
|
1430
|
+
}, PREVIEW_UPLOAD_TIMEOUT_MS2, "build preview upload");
|
|
1312
1431
|
} catch (e) {
|
|
1313
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: ${
|
|
1432
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1314
1433
|
}
|
|
1315
1434
|
if (!uploadRes.ok) {
|
|
1316
1435
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
1317
1436
|
}
|
|
1318
1437
|
} finally {
|
|
1319
1438
|
try {
|
|
1320
|
-
await (0,
|
|
1439
|
+
await (0, import_promises2.rm)(tarball, { force: true });
|
|
1321
1440
|
} catch {
|
|
1322
1441
|
}
|
|
1323
1442
|
}
|
|
1324
1443
|
let startRes;
|
|
1325
1444
|
try {
|
|
1326
|
-
startRes = await
|
|
1445
|
+
startRes = await fetchWithRetry2(`${endpoint}/v1/build-preview/${created.job_id}/start`, {
|
|
1327
1446
|
method: "POST",
|
|
1328
1447
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1329
|
-
},
|
|
1448
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "build preview start");
|
|
1330
1449
|
} catch (e) {
|
|
1331
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: ${
|
|
1450
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1332
1451
|
}
|
|
1333
1452
|
if (!startRes.ok) {
|
|
1334
1453
|
const err = await startRes.text();
|
|
@@ -1344,11 +1463,11 @@ function register(api) {
|
|
|
1344
1463
|
while (Date.now() - pollStart < timeoutMs) {
|
|
1345
1464
|
let statusRes;
|
|
1346
1465
|
try {
|
|
1347
|
-
statusRes = await
|
|
1466
|
+
statusRes = await fetchWithRetry2(`${endpoint}/v1/build-preview/${created.job_id}`, {
|
|
1348
1467
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1349
|
-
},
|
|
1468
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "build preview poll", { attempts: 2 });
|
|
1350
1469
|
} catch (e) {
|
|
1351
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: ${
|
|
1470
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1352
1471
|
}
|
|
1353
1472
|
if (!statusRes.ok) {
|
|
1354
1473
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: HTTP ${statusRes.status}` }, null, 2) }] };
|
|
@@ -1372,7 +1491,7 @@ function register(api) {
|
|
|
1372
1491
|
for (const output of result.outputs) {
|
|
1373
1492
|
if (output.name && /\.(png|jpg|jpeg)$/i.test(output.name) && output.url) {
|
|
1374
1493
|
try {
|
|
1375
|
-
const imgRes = await
|
|
1494
|
+
const imgRes = await fetchWithTimeout2(output.url, {}, PREVIEW_ARTIFACT_TIMEOUT_MS, "build preview artifact download");
|
|
1376
1495
|
if (imgRes.ok) {
|
|
1377
1496
|
const buf = await imgRes.arrayBuffer();
|
|
1378
1497
|
const base64 = Buffer.from(buf).toString("base64");
|
|
@@ -1397,7 +1516,7 @@ function register(api) {
|
|
|
1397
1516
|
async function riddleApiFetch(api2, method, path, body) {
|
|
1398
1517
|
const { apiKey, baseUrl } = getCfg(api2);
|
|
1399
1518
|
if (!apiKey) throw new Error("Missing Riddle API key. Set RIDDLE_API_KEY or configure in plugin settings.");
|
|
1400
|
-
|
|
1519
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
1401
1520
|
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
1402
1521
|
const res = await fetch(url, {
|
|
1403
1522
|
method,
|
|
@@ -1465,7 +1584,7 @@ function register(api) {
|
|
|
1465
1584
|
async execute(_id, params) {
|
|
1466
1585
|
const { apiKey, baseUrl } = getCfg(api);
|
|
1467
1586
|
if (!apiKey) return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
1468
|
-
|
|
1587
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
1469
1588
|
const payload = { timeout_sec: params.timeout_sec || 60 };
|
|
1470
1589
|
if (params.stealth) payload.stealth = true;
|
|
1471
1590
|
if (params.custom_storage) payload.custom_storage = params.custom_storage;
|