@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/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 assertAllowedBaseUrl(baseUrl) {
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 describeError(err) {
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 fetchWithTimeout(url, init, timeoutMs, label) {
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 isTransientFetchError(err) {
114
- const text = describeError(err).toLowerCase();
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 sleep(ms) {
343
+ function sleep2(ms) {
130
344
  return new Promise((resolve) => setTimeout(resolve, ms));
131
345
  }
132
- async function fetchWithRetry(url, init, timeoutMs, label, opts = {}) {
133
- const attempts = Math.max(1, opts.attempts ?? PREVIEW_RETRY_ATTEMPTS);
134
- const baseDelayMs = opts.baseDelayMs ?? PREVIEW_RETRY_BASE_DELAY_MS;
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 fetchWithTimeout(url, init, timeoutMs, label);
352
+ return await fetchWithTimeout2(url, init, timeoutMs, label);
139
353
  } catch (err) {
140
354
  lastErr = err;
141
- if (attempt >= attempts || !isTransientFetchError(err)) break;
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: ${describeError(err)}; retrying in ${delayMs}ms`);
145
- await sleep(delayMs);
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: ${describeError(lastErr)}`);
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, import_node_path.join)(workspace, "riddle", subdir);
155
- await (0, import_promises.mkdir)(dir, { recursive: true });
156
- const filePath = (0, import_node_path.join)(dir, filename);
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, import_promises.writeFile)(filePath, buf);
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, import_node_path.join)(workspace, "riddle", subdir);
163
- await (0, import_promises.mkdir)(dir, { recursive: true });
164
- const filePath = (0, import_node_path.join)(dir, filename);
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, import_promises.writeFile)(filePath, buf);
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 <= INLINE_CAP) {
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 > INLINE_CAP) {
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 > INLINE_CAP) {
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
- assertAllowedBaseUrl(baseUrl);
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
- assertAllowedBaseUrl(baseUrl);
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 { apiKey, baseUrl } = getCfg(api);
888
- if (!apiKey) {
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 { apiKey, baseUrl } = getCfg(api);
979
- if (!apiKey) {
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
- assertAllowedBaseUrl(baseUrl);
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, import_promises.stat)(dir);
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 fetchWithTimeout(`${endpoint}/v1/server-preview/env`, {
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
- }, PREVIEW_REQUEST_TIMEOUT_MS, "server preview env store");
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: ${describeError(e)}` }, null, 2) }] };
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 fetchWithRetry(`${endpoint}/v1/server-preview`, {
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
- }, PREVIEW_REQUEST_TIMEOUT_MS, "server preview create");
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: ${describeError(e)}` }, null, 2) }] };
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 execFile("tar", ["czf", tarball, ...excludeArgs, "-C", dir, "."], { timeout: 12e4 });
1104
- const tarData = await (0, import_promises.readFile)(tarball);
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 fetchWithRetry(created.upload_url, {
1226
+ uploadRes = await fetchWithRetry2(created.upload_url, {
1108
1227
  method: "PUT",
1109
1228
  headers: { "Content-Type": "application/gzip" },
1110
1229
  body: tarData
1111
- }, PREVIEW_UPLOAD_TIMEOUT_MS, "server preview upload");
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: ${describeError(e)}` }, null, 2) }] };
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, import_promises.rm)(tarball, { force: true });
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 fetchWithRetry(`${endpoint}/v1/server-preview/${created.job_id}/start`, {
1245
+ startRes = await fetchWithRetry2(`${endpoint}/v1/server-preview/${created.job_id}/start`, {
1127
1246
  method: "POST",
1128
1247
  headers: { Authorization: `Bearer ${apiKey}` }
1129
- }, PREVIEW_REQUEST_TIMEOUT_MS, "server preview start");
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: ${describeError(e)}` }, null, 2) }] };
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 fetchWithRetry(`${endpoint}/v1/server-preview/${created.job_id}`, {
1266
+ statusRes = await fetchWithRetry2(`${endpoint}/v1/server-preview/${created.job_id}`, {
1148
1267
  headers: { Authorization: `Bearer ${apiKey}` }
1149
- }, PREVIEW_REQUEST_TIMEOUT_MS, "server preview poll", { attempts: 2 });
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: ${describeError(e)}` }, null, 2) }] };
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 fetchWithTimeout(output.url, {}, PREVIEW_ARTIFACT_TIMEOUT_MS, "server preview artifact download");
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
- assertAllowedBaseUrl(baseUrl);
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, import_promises.stat)(dir);
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, import_promises.stat)(`${dir}/Dockerfile`);
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 fetchWithTimeout(`${endpoint}/v1/build-preview/env`, {
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
- }, PREVIEW_REQUEST_TIMEOUT_MS, "build preview env store");
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: ${describeError(e)}` }, null, 2) }] };
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 fetchWithRetry(`${endpoint}/v1/build-preview`, {
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
- }, PREVIEW_REQUEST_TIMEOUT_MS, "build preview create");
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: ${describeError(e)}` }, null, 2) }] };
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 execFile("tar", ["czf", tarball, ...excludeArgs, "-C", dir, "."], { timeout: 12e4 });
1304
- const tarData = await (0, import_promises.readFile)(tarball);
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 fetchWithRetry(created.upload_url, {
1426
+ uploadRes = await fetchWithRetry2(created.upload_url, {
1308
1427
  method: "PUT",
1309
1428
  headers: { "Content-Type": "application/gzip" },
1310
1429
  body: tarData
1311
- }, PREVIEW_UPLOAD_TIMEOUT_MS, "build preview upload");
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: ${describeError(e)}` }, null, 2) }] };
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, import_promises.rm)(tarball, { force: true });
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 fetchWithRetry(`${endpoint}/v1/build-preview/${created.job_id}/start`, {
1445
+ startRes = await fetchWithRetry2(`${endpoint}/v1/build-preview/${created.job_id}/start`, {
1327
1446
  method: "POST",
1328
1447
  headers: { Authorization: `Bearer ${apiKey}` }
1329
- }, PREVIEW_REQUEST_TIMEOUT_MS, "build preview start");
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: ${describeError(e)}` }, null, 2) }] };
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 fetchWithRetry(`${endpoint}/v1/build-preview/${created.job_id}`, {
1466
+ statusRes = await fetchWithRetry2(`${endpoint}/v1/build-preview/${created.job_id}`, {
1348
1467
  headers: { Authorization: `Bearer ${apiKey}` }
1349
- }, PREVIEW_REQUEST_TIMEOUT_MS, "build preview poll", { attempts: 2 });
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: ${describeError(e)}` }, null, 2) }] };
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 fetchWithTimeout(output.url, {}, PREVIEW_ARTIFACT_TIMEOUT_MS, "build preview artifact download");
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
- assertAllowedBaseUrl(baseUrl);
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
- assertAllowedBaseUrl(baseUrl);
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;