@riddledc/openclaw-riddledc 0.9.2 → 0.9.4
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-SNGACMAL.js +858 -0
- package/dist/core.cjs +897 -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 +318 -175
- package/dist/index.js +36 -101
- 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,59 @@ 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
|
}
|
|
367
|
+
function previewTimeoutToolResult(jobId, timeoutMs, lastStatusData, extras = {}) {
|
|
368
|
+
const lastStatus = lastStatusData?.status ?? "unknown";
|
|
369
|
+
const result = {
|
|
370
|
+
ok: false,
|
|
371
|
+
job_id: jobId,
|
|
372
|
+
status: lastStatus,
|
|
373
|
+
outputs: lastStatusData?.outputs || [],
|
|
374
|
+
compute_seconds: lastStatusData?.compute_seconds,
|
|
375
|
+
egress_bytes: lastStatusData?.egress_bytes,
|
|
376
|
+
error: `Job did not complete within ${timeoutMs / 1e3}s; last status was ${lastStatus}`,
|
|
377
|
+
...extras
|
|
378
|
+
};
|
|
379
|
+
if (lastStatusData?.error) result.server_error = lastStatusData.error;
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
153
382
|
async function writeArtifact(workspace, subdir, filename, content) {
|
|
154
|
-
const dir = (0,
|
|
155
|
-
await (0,
|
|
156
|
-
const filePath = (0,
|
|
383
|
+
const dir = (0, import_node_path2.join)(workspace, "riddle", subdir);
|
|
384
|
+
await (0, import_promises2.mkdir)(dir, { recursive: true });
|
|
385
|
+
const filePath = (0, import_node_path2.join)(dir, filename);
|
|
157
386
|
const buf = Buffer.from(content, "utf8");
|
|
158
|
-
await (0,
|
|
387
|
+
await (0, import_promises2.writeFile)(filePath, buf);
|
|
159
388
|
return { path: filePath, sizeBytes: buf.byteLength };
|
|
160
389
|
}
|
|
161
390
|
async function writeArtifactBinary(workspace, subdir, filename, base64Content) {
|
|
162
|
-
const dir = (0,
|
|
163
|
-
await (0,
|
|
164
|
-
const filePath = (0,
|
|
391
|
+
const dir = (0, import_node_path2.join)(workspace, "riddle", subdir);
|
|
392
|
+
await (0, import_promises2.mkdir)(dir, { recursive: true });
|
|
393
|
+
const filePath = (0, import_node_path2.join)(dir, filename);
|
|
165
394
|
const buf = Buffer.from(base64Content, "base64");
|
|
166
|
-
await (0,
|
|
395
|
+
await (0, import_promises2.writeFile)(filePath, buf);
|
|
167
396
|
return { path: filePath, sizeBytes: buf.byteLength };
|
|
168
397
|
}
|
|
169
398
|
async function applySafetySpec(result, opts) {
|
|
@@ -207,10 +436,10 @@ async function applySafetySpec(result, opts) {
|
|
|
207
436
|
if (result.har != null) {
|
|
208
437
|
const harStr = typeof result.har === "string" ? result.har : JSON.stringify(result.har);
|
|
209
438
|
const harBytes = Buffer.byteLength(harStr, "utf8");
|
|
210
|
-
if (opts.harInline && harBytes <=
|
|
439
|
+
if (opts.harInline && harBytes <= INLINE_CAP2) {
|
|
211
440
|
} else {
|
|
212
441
|
const ref = await writeArtifact(opts.workspace, "har", `${jobId}.har.json`, harStr);
|
|
213
|
-
if (opts.harInline && harBytes >
|
|
442
|
+
if (opts.harInline && harBytes > INLINE_CAP2) {
|
|
214
443
|
result.har = { saved: ref.path, sizeBytes: ref.sizeBytes, warning: "Exceeded 50KB inline cap; wrote to file" };
|
|
215
444
|
} else {
|
|
216
445
|
result.har = { saved: ref.path, sizeBytes: ref.sizeBytes };
|
|
@@ -220,7 +449,7 @@ async function applySafetySpec(result, opts) {
|
|
|
220
449
|
if (result.console != null) {
|
|
221
450
|
const consoleStr = typeof result.console === "string" ? result.console : JSON.stringify(result.console);
|
|
222
451
|
const consoleBytes = Buffer.byteLength(consoleStr, "utf8");
|
|
223
|
-
if (consoleBytes >
|
|
452
|
+
if (consoleBytes > INLINE_CAP2) {
|
|
224
453
|
const ref = await writeArtifact(opts.workspace, "console", `${jobId}.log`, consoleStr);
|
|
225
454
|
result.console = { saved: ref.path, sizeBytes: ref.sizeBytes };
|
|
226
455
|
}
|
|
@@ -323,7 +552,7 @@ async function runWithDefaults(api, payload, defaults) {
|
|
|
323
552
|
error: "Missing Riddle API key. Set RIDDLE_API_KEY env var or plugins.entries.riddle.config.apiKey."
|
|
324
553
|
};
|
|
325
554
|
}
|
|
326
|
-
|
|
555
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
327
556
|
const mode = detectMode(payload);
|
|
328
557
|
const userInclude = payload.include ?? [];
|
|
329
558
|
const userRequestedHar = userInclude.includes("har");
|
|
@@ -475,7 +704,7 @@ function register(api) {
|
|
|
475
704
|
if (!apiKey) {
|
|
476
705
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
477
706
|
}
|
|
478
|
-
|
|
707
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
479
708
|
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/jobs/${params.job_id}`, {
|
|
480
709
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
481
710
|
});
|
|
@@ -884,85 +1113,8 @@ function register(api) {
|
|
|
884
1113
|
framework: import_typebox.Type.Optional(import_typebox.Type.String({ description: "Framework hint: 'spa' (default) or 'static'" }))
|
|
885
1114
|
}),
|
|
886
1115
|
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
|
-
};
|
|
1116
|
+
const result = await createStaticPreview(configFromOpenClawApi(api), params);
|
|
1117
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
966
1118
|
}
|
|
967
1119
|
},
|
|
968
1120
|
{ optional: true }
|
|
@@ -975,26 +1127,8 @@ function register(api) {
|
|
|
975
1127
|
id: import_typebox.Type.String({ description: "Preview ID (e.g. pv_a1b2c3d4)" })
|
|
976
1128
|
}),
|
|
977
1129
|
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) }] };
|
|
1130
|
+
const result = await deleteStaticPreview(configFromOpenClawApi(api), params.id);
|
|
1131
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
998
1132
|
}
|
|
999
1133
|
},
|
|
1000
1134
|
{ optional: true }
|
|
@@ -1029,11 +1163,11 @@ function register(api) {
|
|
|
1029
1163
|
if (!apiKey) {
|
|
1030
1164
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
1031
1165
|
}
|
|
1032
|
-
|
|
1166
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
1033
1167
|
const dir = params.directory;
|
|
1034
1168
|
if (!dir || typeof dir !== "string") throw new Error("directory must be an absolute path");
|
|
1035
1169
|
try {
|
|
1036
|
-
const st = await (0,
|
|
1170
|
+
const st = await (0, import_promises2.stat)(dir);
|
|
1037
1171
|
if (!st.isDirectory()) throw new Error(`Not a directory: ${dir}`);
|
|
1038
1172
|
} catch (e) {
|
|
1039
1173
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Cannot access directory: ${e.message}` }, null, 2) }] };
|
|
@@ -1048,13 +1182,13 @@ function register(api) {
|
|
|
1048
1182
|
if (hasLocalStorage) envBody.localStorage = params.localStorage;
|
|
1049
1183
|
let envRes;
|
|
1050
1184
|
try {
|
|
1051
|
-
envRes = await
|
|
1185
|
+
envRes = await fetchWithTimeout2(`${endpoint}/v1/server-preview/env`, {
|
|
1052
1186
|
method: "POST",
|
|
1053
1187
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1054
1188
|
body: JSON.stringify(envBody)
|
|
1055
|
-
},
|
|
1189
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "server preview env store");
|
|
1056
1190
|
} catch (e) {
|
|
1057
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: ${
|
|
1191
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1058
1192
|
}
|
|
1059
1193
|
if (!envRes.ok) {
|
|
1060
1194
|
const err = await envRes.text();
|
|
@@ -1083,13 +1217,13 @@ function register(api) {
|
|
|
1083
1217
|
if (params.viewport) createBody.viewport = params.viewport;
|
|
1084
1218
|
let createRes;
|
|
1085
1219
|
try {
|
|
1086
|
-
createRes = await
|
|
1220
|
+
createRes = await fetchWithRetry2(`${endpoint}/v1/server-preview`, {
|
|
1087
1221
|
method: "POST",
|
|
1088
1222
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1089
1223
|
body: JSON.stringify(createBody)
|
|
1090
|
-
},
|
|
1224
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "server preview create");
|
|
1091
1225
|
} catch (e) {
|
|
1092
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${
|
|
1226
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1093
1227
|
}
|
|
1094
1228
|
if (!createRes.ok) {
|
|
1095
1229
|
const err = await createRes.text();
|
|
@@ -1100,35 +1234,35 @@ function register(api) {
|
|
|
1100
1234
|
try {
|
|
1101
1235
|
const excludes = params.exclude || [".git", "*.log"];
|
|
1102
1236
|
const excludeArgs = excludes.flatMap((p) => ["--exclude", p]);
|
|
1103
|
-
await
|
|
1104
|
-
const tarData = await (0,
|
|
1237
|
+
await execFile2("tar", ["czf", tarball, ...excludeArgs, "-C", dir, "."], { timeout: 12e4 });
|
|
1238
|
+
const tarData = await (0, import_promises2.readFile)(tarball);
|
|
1105
1239
|
let uploadRes;
|
|
1106
1240
|
try {
|
|
1107
|
-
uploadRes = await
|
|
1241
|
+
uploadRes = await fetchWithRetry2(created.upload_url, {
|
|
1108
1242
|
method: "PUT",
|
|
1109
1243
|
headers: { "Content-Type": "application/gzip" },
|
|
1110
1244
|
body: tarData
|
|
1111
|
-
},
|
|
1245
|
+
}, PREVIEW_UPLOAD_TIMEOUT_MS2, "server preview upload");
|
|
1112
1246
|
} catch (e) {
|
|
1113
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: ${
|
|
1247
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1114
1248
|
}
|
|
1115
1249
|
if (!uploadRes.ok) {
|
|
1116
1250
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
1117
1251
|
}
|
|
1118
1252
|
} finally {
|
|
1119
1253
|
try {
|
|
1120
|
-
await (0,
|
|
1254
|
+
await (0, import_promises2.rm)(tarball, { force: true });
|
|
1121
1255
|
} catch {
|
|
1122
1256
|
}
|
|
1123
1257
|
}
|
|
1124
1258
|
let startRes;
|
|
1125
1259
|
try {
|
|
1126
|
-
startRes = await
|
|
1260
|
+
startRes = await fetchWithRetry2(`${endpoint}/v1/server-preview/${created.job_id}/start`, {
|
|
1127
1261
|
method: "POST",
|
|
1128
1262
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1129
|
-
},
|
|
1263
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "server preview start");
|
|
1130
1264
|
} catch (e) {
|
|
1131
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: ${
|
|
1265
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1132
1266
|
}
|
|
1133
1267
|
if (!startRes.ok) {
|
|
1134
1268
|
const err = await startRes.text();
|
|
@@ -1141,19 +1275,21 @@ function register(api) {
|
|
|
1141
1275
|
const timeoutMs = ((params.timeout || 120) + 60) * 1e3;
|
|
1142
1276
|
const pollStart = Date.now();
|
|
1143
1277
|
const POLL_INTERVAL = 3e3;
|
|
1278
|
+
let lastStatusData = null;
|
|
1144
1279
|
while (Date.now() - pollStart < timeoutMs) {
|
|
1145
1280
|
let statusRes;
|
|
1146
1281
|
try {
|
|
1147
|
-
statusRes = await
|
|
1282
|
+
statusRes = await fetchWithRetry2(`${endpoint}/v1/server-preview/${created.job_id}`, {
|
|
1148
1283
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1149
|
-
},
|
|
1284
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "server preview poll", { attempts: 2 });
|
|
1150
1285
|
} catch (e) {
|
|
1151
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: ${
|
|
1286
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1152
1287
|
}
|
|
1153
1288
|
if (!statusRes.ok) {
|
|
1154
1289
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: HTTP ${statusRes.status}` }, null, 2) }] };
|
|
1155
1290
|
}
|
|
1156
1291
|
const statusData = await statusRes.json();
|
|
1292
|
+
lastStatusData = statusData;
|
|
1157
1293
|
if (statusData.status === "complete" || statusData.status === "completed" || statusData.status === "failed") {
|
|
1158
1294
|
const result = {
|
|
1159
1295
|
ok: statusData.status === "complete" || statusData.status === "completed",
|
|
@@ -1168,7 +1304,7 @@ function register(api) {
|
|
|
1168
1304
|
for (const output of result.outputs) {
|
|
1169
1305
|
if (output.name && /\.(png|jpg|jpeg)$/i.test(output.name) && output.url) {
|
|
1170
1306
|
try {
|
|
1171
|
-
const imgRes = await
|
|
1307
|
+
const imgRes = await fetchWithTimeout2(output.url, {}, PREVIEW_ARTIFACT_TIMEOUT_MS, "server preview artifact download");
|
|
1172
1308
|
if (imgRes.ok) {
|
|
1173
1309
|
const buf = await imgRes.arrayBuffer();
|
|
1174
1310
|
const base64 = Buffer.from(buf).toString("base64");
|
|
@@ -1185,7 +1321,7 @@ function register(api) {
|
|
|
1185
1321
|
}
|
|
1186
1322
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
|
1187
1323
|
}
|
|
1188
|
-
return { content: [{ type: "text", text: JSON.stringify(
|
|
1324
|
+
return { content: [{ type: "text", text: JSON.stringify(previewTimeoutToolResult(created.job_id, timeoutMs, lastStatusData), null, 2) }] };
|
|
1189
1325
|
}
|
|
1190
1326
|
},
|
|
1191
1327
|
{ optional: true }
|
|
@@ -1222,17 +1358,17 @@ function register(api) {
|
|
|
1222
1358
|
if (!apiKey) {
|
|
1223
1359
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
1224
1360
|
}
|
|
1225
|
-
|
|
1361
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
1226
1362
|
const dir = params.directory;
|
|
1227
1363
|
if (!dir || typeof dir !== "string") throw new Error("directory must be an absolute path");
|
|
1228
1364
|
try {
|
|
1229
|
-
const st = await (0,
|
|
1365
|
+
const st = await (0, import_promises2.stat)(dir);
|
|
1230
1366
|
if (!st.isDirectory()) throw new Error(`Not a directory: ${dir}`);
|
|
1231
1367
|
} catch (e) {
|
|
1232
1368
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Cannot access directory: ${e.message}` }, null, 2) }] };
|
|
1233
1369
|
}
|
|
1234
1370
|
try {
|
|
1235
|
-
await (0,
|
|
1371
|
+
await (0, import_promises2.stat)(`${dir}/Dockerfile`);
|
|
1236
1372
|
} catch {
|
|
1237
1373
|
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
1374
|
}
|
|
@@ -1246,13 +1382,13 @@ function register(api) {
|
|
|
1246
1382
|
if (hasLocalStorage) envBody.localStorage = params.localStorage;
|
|
1247
1383
|
let envRes;
|
|
1248
1384
|
try {
|
|
1249
|
-
envRes = await
|
|
1385
|
+
envRes = await fetchWithTimeout2(`${endpoint}/v1/build-preview/env`, {
|
|
1250
1386
|
method: "POST",
|
|
1251
1387
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1252
1388
|
body: JSON.stringify(envBody)
|
|
1253
|
-
},
|
|
1389
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "build preview env store");
|
|
1254
1390
|
} catch (e) {
|
|
1255
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: ${
|
|
1391
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Store env failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1256
1392
|
}
|
|
1257
1393
|
if (!envRes.ok) {
|
|
1258
1394
|
const err = await envRes.text();
|
|
@@ -1283,13 +1419,13 @@ function register(api) {
|
|
|
1283
1419
|
if (params.audit) createBody.audit = true;
|
|
1284
1420
|
let createRes;
|
|
1285
1421
|
try {
|
|
1286
|
-
createRes = await
|
|
1422
|
+
createRes = await fetchWithRetry2(`${endpoint}/v1/build-preview`, {
|
|
1287
1423
|
method: "POST",
|
|
1288
1424
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1289
1425
|
body: JSON.stringify(createBody)
|
|
1290
|
-
},
|
|
1426
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "build preview create");
|
|
1291
1427
|
} catch (e) {
|
|
1292
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${
|
|
1428
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1293
1429
|
}
|
|
1294
1430
|
if (!createRes.ok) {
|
|
1295
1431
|
const err = await createRes.text();
|
|
@@ -1300,35 +1436,35 @@ function register(api) {
|
|
|
1300
1436
|
try {
|
|
1301
1437
|
const excludes = params.exclude || [".git", "*.log"];
|
|
1302
1438
|
const excludeArgs = excludes.flatMap((p) => ["--exclude", p]);
|
|
1303
|
-
await
|
|
1304
|
-
const tarData = await (0,
|
|
1439
|
+
await execFile2("tar", ["czf", tarball, ...excludeArgs, "-C", dir, "."], { timeout: 12e4 });
|
|
1440
|
+
const tarData = await (0, import_promises2.readFile)(tarball);
|
|
1305
1441
|
let uploadRes;
|
|
1306
1442
|
try {
|
|
1307
|
-
uploadRes = await
|
|
1443
|
+
uploadRes = await fetchWithRetry2(created.upload_url, {
|
|
1308
1444
|
method: "PUT",
|
|
1309
1445
|
headers: { "Content-Type": "application/gzip" },
|
|
1310
1446
|
body: tarData
|
|
1311
|
-
},
|
|
1447
|
+
}, PREVIEW_UPLOAD_TIMEOUT_MS2, "build preview upload");
|
|
1312
1448
|
} catch (e) {
|
|
1313
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: ${
|
|
1449
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1314
1450
|
}
|
|
1315
1451
|
if (!uploadRes.ok) {
|
|
1316
1452
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
1317
1453
|
}
|
|
1318
1454
|
} finally {
|
|
1319
1455
|
try {
|
|
1320
|
-
await (0,
|
|
1456
|
+
await (0, import_promises2.rm)(tarball, { force: true });
|
|
1321
1457
|
} catch {
|
|
1322
1458
|
}
|
|
1323
1459
|
}
|
|
1324
1460
|
let startRes;
|
|
1325
1461
|
try {
|
|
1326
|
-
startRes = await
|
|
1462
|
+
startRes = await fetchWithRetry2(`${endpoint}/v1/build-preview/${created.job_id}/start`, {
|
|
1327
1463
|
method: "POST",
|
|
1328
1464
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1329
|
-
},
|
|
1465
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "build preview start");
|
|
1330
1466
|
} catch (e) {
|
|
1331
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: ${
|
|
1467
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Start failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1332
1468
|
}
|
|
1333
1469
|
if (!startRes.ok) {
|
|
1334
1470
|
const err = await startRes.text();
|
|
@@ -1341,19 +1477,21 @@ function register(api) {
|
|
|
1341
1477
|
const timeoutMs = ((params.timeout || 180) + 120) * 1e3;
|
|
1342
1478
|
const pollStart = Date.now();
|
|
1343
1479
|
const POLL_INTERVAL = 3e3;
|
|
1480
|
+
let lastStatusData = null;
|
|
1344
1481
|
while (Date.now() - pollStart < timeoutMs) {
|
|
1345
1482
|
let statusRes;
|
|
1346
1483
|
try {
|
|
1347
|
-
statusRes = await
|
|
1484
|
+
statusRes = await fetchWithRetry2(`${endpoint}/v1/build-preview/${created.job_id}`, {
|
|
1348
1485
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1349
|
-
},
|
|
1486
|
+
}, PREVIEW_REQUEST_TIMEOUT_MS2, "build preview poll", { attempts: 2 });
|
|
1350
1487
|
} catch (e) {
|
|
1351
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: ${
|
|
1488
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: ${describeError2(e)}` }, null, 2) }] };
|
|
1352
1489
|
}
|
|
1353
1490
|
if (!statusRes.ok) {
|
|
1354
1491
|
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: created.job_id, error: `Poll failed: HTTP ${statusRes.status}` }, null, 2) }] };
|
|
1355
1492
|
}
|
|
1356
1493
|
const statusData = await statusRes.json();
|
|
1494
|
+
lastStatusData = statusData;
|
|
1357
1495
|
if (statusData.status === "complete" || statusData.status === "completed" || statusData.status === "failed") {
|
|
1358
1496
|
const result = {
|
|
1359
1497
|
ok: statusData.status === "complete" || statusData.status === "completed",
|
|
@@ -1372,7 +1510,7 @@ function register(api) {
|
|
|
1372
1510
|
for (const output of result.outputs) {
|
|
1373
1511
|
if (output.name && /\.(png|jpg|jpeg)$/i.test(output.name) && output.url) {
|
|
1374
1512
|
try {
|
|
1375
|
-
const imgRes = await
|
|
1513
|
+
const imgRes = await fetchWithTimeout2(output.url, {}, PREVIEW_ARTIFACT_TIMEOUT_MS, "build preview artifact download");
|
|
1376
1514
|
if (imgRes.ok) {
|
|
1377
1515
|
const buf = await imgRes.arrayBuffer();
|
|
1378
1516
|
const base64 = Buffer.from(buf).toString("base64");
|
|
@@ -1389,7 +1527,12 @@ function register(api) {
|
|
|
1389
1527
|
}
|
|
1390
1528
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
|
1391
1529
|
}
|
|
1392
|
-
return { content: [{ type: "text", text: JSON.stringify(
|
|
1530
|
+
return { content: [{ type: "text", text: JSON.stringify(previewTimeoutToolResult(created.job_id, timeoutMs, lastStatusData, {
|
|
1531
|
+
build_duration_ms: lastStatusData?.build_duration_ms,
|
|
1532
|
+
...lastStatusData?.build_log ? { build_log: lastStatusData.build_log } : {},
|
|
1533
|
+
...lastStatusData?.container_log ? { container_log: lastStatusData.container_log } : {},
|
|
1534
|
+
...lastStatusData?.audit ? { audit: lastStatusData.audit } : {}
|
|
1535
|
+
}), null, 2) }] };
|
|
1393
1536
|
}
|
|
1394
1537
|
},
|
|
1395
1538
|
{ optional: true }
|
|
@@ -1397,7 +1540,7 @@ function register(api) {
|
|
|
1397
1540
|
async function riddleApiFetch(api2, method, path, body) {
|
|
1398
1541
|
const { apiKey, baseUrl } = getCfg(api2);
|
|
1399
1542
|
if (!apiKey) throw new Error("Missing Riddle API key. Set RIDDLE_API_KEY or configure in plugin settings.");
|
|
1400
|
-
|
|
1543
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
1401
1544
|
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
1402
1545
|
const res = await fetch(url, {
|
|
1403
1546
|
method,
|
|
@@ -1465,7 +1608,7 @@ function register(api) {
|
|
|
1465
1608
|
async execute(_id, params) {
|
|
1466
1609
|
const { apiKey, baseUrl } = getCfg(api);
|
|
1467
1610
|
if (!apiKey) return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
1468
|
-
|
|
1611
|
+
assertAllowedBaseUrl2(baseUrl);
|
|
1469
1612
|
const payload = { timeout_sec: params.timeout_sec || 60 };
|
|
1470
1613
|
if (params.stealth) payload.stealth = true;
|
|
1471
1614
|
if (params.custom_storage) payload.custom_storage = params.custom_storage;
|