@riddledc/openclaw-riddledc 0.5.4 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHECKSUMS.txt +2 -2
- package/dist/index.cjs +220 -14
- package/dist/index.js +220 -14
- package/openclaw.plugin.json +2 -1
- package/package.json +1 -1
package/CHECKSUMS.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
041dfffc9dfaaa099cb6243c3512574254b91a80d19e64a68b078d3fc97bd4c8 dist/index.cjs
|
|
2
2
|
94ce04f0e2d84bf64dd68f0500dfdd2f951287a3deccec87f197261961927f6f dist/index.d.cts
|
|
3
3
|
94ce04f0e2d84bf64dd68f0500dfdd2f951287a3deccec87f197261961927f6f dist/index.d.ts
|
|
4
|
-
|
|
4
|
+
b4d87078938b29cd38097688ac435c4fe328a561963212b53c678a2525c8b86a dist/index.js
|
package/dist/index.cjs
CHANGED
|
@@ -99,7 +99,8 @@ async function applySafetySpec(result, opts) {
|
|
|
99
99
|
}
|
|
100
100
|
if (base64Data) {
|
|
101
101
|
const ref = await writeArtifactBinary(opts.workspace, "screenshots", `${jobId}.png`, base64Data);
|
|
102
|
-
result.screenshot
|
|
102
|
+
const cdnUrl = typeof result.screenshot === "object" ? result.screenshot.url : void 0;
|
|
103
|
+
result.screenshot = { saved: ref.path, sizeBytes: ref.sizeBytes, ...cdnUrl ? { url: cdnUrl } : {} };
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
if (Array.isArray(result.screenshots)) {
|
|
@@ -107,6 +108,7 @@ async function applySafetySpec(result, opts) {
|
|
|
107
108
|
for (let i = 0; i < result.screenshots.length; i++) {
|
|
108
109
|
const ss = result.screenshots[i];
|
|
109
110
|
let base64Data = null;
|
|
111
|
+
const cdnUrl = typeof ss === "object" ? ss.url : void 0;
|
|
110
112
|
if (typeof ss === "string") {
|
|
111
113
|
base64Data = ss;
|
|
112
114
|
} else if (typeof ss === "object" && ss.data) {
|
|
@@ -114,7 +116,7 @@ async function applySafetySpec(result, opts) {
|
|
|
114
116
|
}
|
|
115
117
|
if (base64Data) {
|
|
116
118
|
const ref = await writeArtifactBinary(opts.workspace, "screenshots", `${jobId}-${i}.png`, base64Data);
|
|
117
|
-
savedRefs.push({ saved: ref.path, sizeBytes: ref.sizeBytes });
|
|
119
|
+
savedRefs.push({ saved: ref.path, sizeBytes: ref.sizeBytes, ...cdnUrl ? { url: cdnUrl } : {} });
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
result.screenshots = savedRefs;
|
|
@@ -146,6 +148,95 @@ async function applySafetySpec(result, opts) {
|
|
|
146
148
|
}
|
|
147
149
|
}
|
|
148
150
|
}
|
|
151
|
+
async function pollJobStatus(baseUrl, apiKey, jobId, maxWaitMs) {
|
|
152
|
+
const start = Date.now();
|
|
153
|
+
const POLL_INTERVAL = 2e3;
|
|
154
|
+
while (Date.now() - start < maxWaitMs) {
|
|
155
|
+
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/jobs/${jobId}`, {
|
|
156
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
157
|
+
});
|
|
158
|
+
if (!res.ok) {
|
|
159
|
+
return { status: "poll_error", error: `HTTP ${res.status}` };
|
|
160
|
+
}
|
|
161
|
+
const data = await res.json();
|
|
162
|
+
if (data.status === "completed" || data.status === "completed_timeout" || data.status === "completed_error" || data.status === "failed") {
|
|
163
|
+
return data;
|
|
164
|
+
}
|
|
165
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
|
166
|
+
}
|
|
167
|
+
return { status: "poll_timeout", error: `Job ${jobId} did not complete within ${maxWaitMs}ms` };
|
|
168
|
+
}
|
|
169
|
+
async function fetchArtifactsAndBuild(baseUrl, apiKey, jobId, include) {
|
|
170
|
+
const res = await fetch(
|
|
171
|
+
`${baseUrl.replace(/\/$/, "")}/v1/jobs/${jobId}/artifacts?include=${include.join(",")}`,
|
|
172
|
+
{ headers: { Authorization: `Bearer ${apiKey}` } }
|
|
173
|
+
);
|
|
174
|
+
if (!res.ok) {
|
|
175
|
+
return { error: `Artifacts fetch failed: HTTP ${res.status}` };
|
|
176
|
+
}
|
|
177
|
+
const data = await res.json();
|
|
178
|
+
const artifacts = data.artifacts || [];
|
|
179
|
+
const result = {};
|
|
180
|
+
if (data.status) result._artifactsStatus = data.status;
|
|
181
|
+
if (data.timeout) result._timeout = data.timeout;
|
|
182
|
+
if (data.error) result._error = data.error;
|
|
183
|
+
const screenshots = artifacts.filter((a) => a.name && /\.(png|jpg|jpeg)$/i.test(a.name));
|
|
184
|
+
if (screenshots.length > 0) {
|
|
185
|
+
result.screenshots = [];
|
|
186
|
+
for (const ss of screenshots) {
|
|
187
|
+
if (!ss.url) continue;
|
|
188
|
+
try {
|
|
189
|
+
const imgRes = await fetch(ss.url);
|
|
190
|
+
if (imgRes.ok) {
|
|
191
|
+
const buf = await imgRes.arrayBuffer();
|
|
192
|
+
result.screenshots.push({
|
|
193
|
+
name: ss.name,
|
|
194
|
+
data: `data:image/png;base64,${Buffer.from(buf).toString("base64")}`,
|
|
195
|
+
size: buf.byteLength,
|
|
196
|
+
url: ss.url
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (result.screenshots.length > 0) {
|
|
203
|
+
result.screenshot = result.screenshots[0];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const consoleArtifact = artifacts.find((a) => a.name === "console.json");
|
|
207
|
+
if (consoleArtifact?.url) {
|
|
208
|
+
try {
|
|
209
|
+
const cRes = await fetch(consoleArtifact.url);
|
|
210
|
+
if (cRes.ok) {
|
|
211
|
+
result.console = await cRes.json();
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const resultArtifact = artifacts.find((a) => a.name === "result.json");
|
|
217
|
+
if (resultArtifact?.url) {
|
|
218
|
+
try {
|
|
219
|
+
const rRes = await fetch(resultArtifact.url);
|
|
220
|
+
if (rRes.ok) {
|
|
221
|
+
result.result = await rRes.json();
|
|
222
|
+
}
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (include.includes("har")) {
|
|
227
|
+
const harArtifact = artifacts.find((a) => a.name === "network.har");
|
|
228
|
+
if (harArtifact?.url) {
|
|
229
|
+
try {
|
|
230
|
+
const hRes = await fetch(harArtifact.url);
|
|
231
|
+
if (hRes.ok) {
|
|
232
|
+
result.har = await hRes.json();
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
149
240
|
async function runWithDefaults(api, payload, defaults) {
|
|
150
241
|
const { apiKey, baseUrl } = getCfg(api);
|
|
151
242
|
if (!apiKey) {
|
|
@@ -159,8 +250,12 @@ async function runWithDefaults(api, payload, defaults) {
|
|
|
159
250
|
const userInclude = payload.include ?? [];
|
|
160
251
|
const userRequestedHar = userInclude.includes("har");
|
|
161
252
|
const harInline = !!payload.harInline;
|
|
253
|
+
const returnAsync = !!defaults?.returnAsync;
|
|
162
254
|
const merged = { ...payload };
|
|
163
255
|
delete merged.harInline;
|
|
256
|
+
if (returnAsync) {
|
|
257
|
+
merged.sync = false;
|
|
258
|
+
}
|
|
164
259
|
const defaultInc = defaults?.include ?? ["screenshot", "console"];
|
|
165
260
|
merged.include = Array.from(/* @__PURE__ */ new Set([...userInclude, ...defaultInc]));
|
|
166
261
|
merged.inlineConsole = merged.inlineConsole ?? true;
|
|
@@ -171,6 +266,46 @@ async function runWithDefaults(api, payload, defaults) {
|
|
|
171
266
|
const out = { ok: true, mode };
|
|
172
267
|
const { contentType, body, headers, status } = await postRun(baseUrl, apiKey, merged);
|
|
173
268
|
out.rawContentType = contentType ?? void 0;
|
|
269
|
+
const workspace = getWorkspacePath(api);
|
|
270
|
+
if (status === 408) {
|
|
271
|
+
let jobIdFrom408;
|
|
272
|
+
try {
|
|
273
|
+
const json408 = JSON.parse(Buffer.from(body).toString("utf8"));
|
|
274
|
+
jobIdFrom408 = json408.job_id;
|
|
275
|
+
} catch {
|
|
276
|
+
}
|
|
277
|
+
if (!jobIdFrom408) {
|
|
278
|
+
out.ok = false;
|
|
279
|
+
out.error = "Sync poll timed out but no job_id in 408 response";
|
|
280
|
+
return out;
|
|
281
|
+
}
|
|
282
|
+
out.job_id = jobIdFrom408;
|
|
283
|
+
if (returnAsync) {
|
|
284
|
+
out.status = "submitted";
|
|
285
|
+
return out;
|
|
286
|
+
}
|
|
287
|
+
const scriptTimeoutMs = (payload.timeout_sec ?? 60) * 1e3;
|
|
288
|
+
const pollMaxMs = scriptTimeoutMs + 3e4;
|
|
289
|
+
const jobStatus = await pollJobStatus(baseUrl, apiKey, jobIdFrom408, pollMaxMs);
|
|
290
|
+
if (jobStatus.status === "poll_timeout" || jobStatus.status === "poll_error" || jobStatus.status === "failed") {
|
|
291
|
+
out.ok = false;
|
|
292
|
+
out.status = jobStatus.status;
|
|
293
|
+
out.error = jobStatus.error || `Job ${jobStatus.status}`;
|
|
294
|
+
return out;
|
|
295
|
+
}
|
|
296
|
+
const artifacts = await fetchArtifactsAndBuild(baseUrl, apiKey, jobIdFrom408, merged.include);
|
|
297
|
+
Object.assign(out, artifacts);
|
|
298
|
+
out.job_id = jobIdFrom408;
|
|
299
|
+
out.status = jobStatus.status;
|
|
300
|
+
out.duration_ms = jobStatus.duration_ms;
|
|
301
|
+
if (jobStatus.status !== "completed") {
|
|
302
|
+
out.ok = false;
|
|
303
|
+
if (jobStatus.timeout) out.timeout = jobStatus.timeout;
|
|
304
|
+
if (jobStatus.error) out.error = jobStatus.error;
|
|
305
|
+
}
|
|
306
|
+
await applySafetySpec(out, { workspace, harInline });
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
174
309
|
if (status >= 400) {
|
|
175
310
|
try {
|
|
176
311
|
const txt2 = Buffer.from(body).toString("utf8");
|
|
@@ -189,15 +324,42 @@ async function runWithDefaults(api, payload, defaults) {
|
|
|
189
324
|
const duration = headers.get("x-duration-ms");
|
|
190
325
|
out.duration_ms = duration ? Number(duration) : void 0;
|
|
191
326
|
out.sync = true;
|
|
192
|
-
|
|
193
|
-
await applySafetySpec(out, { workspace: workspace2, harInline });
|
|
327
|
+
await applySafetySpec(out, { workspace, harInline });
|
|
194
328
|
return out;
|
|
195
329
|
}
|
|
196
330
|
const txt = Buffer.from(body).toString("utf8");
|
|
197
331
|
const json = JSON.parse(txt);
|
|
198
332
|
Object.assign(out, json);
|
|
199
333
|
out.job_id = json.job_id ?? json.jobId ?? out.job_id;
|
|
200
|
-
|
|
334
|
+
if (status === 202 && out.job_id && json.status_url) {
|
|
335
|
+
if (returnAsync) {
|
|
336
|
+
out.status = "submitted";
|
|
337
|
+
return out;
|
|
338
|
+
}
|
|
339
|
+
const scriptTimeoutMs = (payload.timeout_sec ?? 60) * 1e3;
|
|
340
|
+
const pollMaxMs = scriptTimeoutMs + 3e4;
|
|
341
|
+
const jobStatus = await pollJobStatus(baseUrl, apiKey, out.job_id, pollMaxMs);
|
|
342
|
+
if (jobStatus.status === "poll_timeout" || jobStatus.status === "poll_error" || jobStatus.status === "failed") {
|
|
343
|
+
out.ok = false;
|
|
344
|
+
out.status = jobStatus.status;
|
|
345
|
+
out.error = jobStatus.error || `Job ${jobStatus.status}`;
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
const artifacts = await fetchArtifactsAndBuild(baseUrl, apiKey, out.job_id, merged.include);
|
|
349
|
+
Object.assign(out, artifacts);
|
|
350
|
+
out.status = jobStatus.status;
|
|
351
|
+
out.duration_ms = jobStatus.duration_ms;
|
|
352
|
+
if (jobStatus.status !== "completed") {
|
|
353
|
+
out.ok = false;
|
|
354
|
+
if (jobStatus.timeout) out.timeout = jobStatus.timeout;
|
|
355
|
+
if (jobStatus.error) out.error = jobStatus.error;
|
|
356
|
+
}
|
|
357
|
+
await applySafetySpec(out, { workspace, harInline });
|
|
358
|
+
return out;
|
|
359
|
+
}
|
|
360
|
+
if (json.status === "completed_timeout" || json.status === "completed_error") {
|
|
361
|
+
out.ok = false;
|
|
362
|
+
}
|
|
201
363
|
await applySafetySpec(out, { workspace, harInline });
|
|
202
364
|
return out;
|
|
203
365
|
}
|
|
@@ -205,19 +367,61 @@ function register(api) {
|
|
|
205
367
|
api.registerTool(
|
|
206
368
|
{
|
|
207
369
|
name: "riddle_run",
|
|
208
|
-
description: 'Run a Riddle job (pass-through payload) against https://api.riddledc.com/v1/run. Supports url/urls/steps/script. Returns screenshot + console by default; pass include:["har"] to opt in to HAR capture.',
|
|
370
|
+
description: 'Run a Riddle job (pass-through payload) against https://api.riddledc.com/v1/run. Supports url/urls/steps/script. Returns screenshot + console by default; pass include:["har"] to opt in to HAR capture. Set async:true to return immediately with job_id (use riddle_poll to check status later).',
|
|
209
371
|
parameters: import_typebox.Type.Object({
|
|
210
|
-
payload: import_typebox.Type.Record(import_typebox.Type.String(), import_typebox.Type.Any())
|
|
372
|
+
payload: import_typebox.Type.Record(import_typebox.Type.String(), import_typebox.Type.Any()),
|
|
373
|
+
async: import_typebox.Type.Optional(import_typebox.Type.Boolean({ description: "Return job_id immediately without waiting for completion. Use riddle_poll to check status." }))
|
|
211
374
|
}),
|
|
212
375
|
async execute(_id, params) {
|
|
213
376
|
const result = await runWithDefaults(api, params.payload, {
|
|
214
|
-
include: ["screenshot", "console", "result"]
|
|
377
|
+
include: ["screenshot", "console", "result"],
|
|
378
|
+
returnAsync: !!params.async
|
|
215
379
|
});
|
|
216
380
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
217
381
|
}
|
|
218
382
|
},
|
|
219
383
|
{ optional: true }
|
|
220
384
|
);
|
|
385
|
+
api.registerTool(
|
|
386
|
+
{
|
|
387
|
+
name: "riddle_poll",
|
|
388
|
+
description: "Poll the status of an async Riddle job. Use after submitting a job with async:true. Returns current status if still running, or full results (screenshot, console, etc.) if completed.",
|
|
389
|
+
parameters: import_typebox.Type.Object({
|
|
390
|
+
job_id: import_typebox.Type.String({ description: "Job ID returned by an async riddle_* call" }),
|
|
391
|
+
include: import_typebox.Type.Optional(import_typebox.Type.Array(import_typebox.Type.String(), { description: "Artifacts to fetch on completion (default: screenshot, console, result)" })),
|
|
392
|
+
harInline: import_typebox.Type.Optional(import_typebox.Type.Boolean())
|
|
393
|
+
}),
|
|
394
|
+
async execute(_id, params) {
|
|
395
|
+
if (!params.job_id || typeof params.job_id !== "string") throw new Error("job_id must be a string");
|
|
396
|
+
const { apiKey, baseUrl } = getCfg(api);
|
|
397
|
+
if (!apiKey) {
|
|
398
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
399
|
+
}
|
|
400
|
+
assertAllowedBaseUrl(baseUrl);
|
|
401
|
+
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/jobs/${params.job_id}`, {
|
|
402
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
403
|
+
});
|
|
404
|
+
if (!res.ok) {
|
|
405
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: params.job_id, error: `HTTP ${res.status}` }, null, 2) }] };
|
|
406
|
+
}
|
|
407
|
+
const data = await res.json();
|
|
408
|
+
if (data.status !== "completed" && data.status !== "completed_timeout" && data.status !== "completed_error" && data.status !== "failed") {
|
|
409
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: true, job_id: params.job_id, status: data.status, message: "Job still running. Call riddle_poll again later." }, null, 2) }] };
|
|
410
|
+
}
|
|
411
|
+
const include = params.include ?? ["screenshot", "console", "result"];
|
|
412
|
+
const artifacts = await fetchArtifactsAndBuild(baseUrl, apiKey, params.job_id, include);
|
|
413
|
+
const out = { ok: data.status === "completed", job_id: params.job_id, status: data.status, duration_ms: data.duration_ms, ...artifacts };
|
|
414
|
+
if (data.status !== "completed") {
|
|
415
|
+
if (data.timeout) out.timeout = data.timeout;
|
|
416
|
+
if (data.error) out.error = data.error;
|
|
417
|
+
}
|
|
418
|
+
const workspace = getWorkspacePath(api);
|
|
419
|
+
await applySafetySpec(out, { workspace, harInline: !!params.harInline });
|
|
420
|
+
return { content: [{ type: "text", text: JSON.stringify(out, null, 2) }] };
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
{ optional: true }
|
|
424
|
+
);
|
|
221
425
|
api.registerTool(
|
|
222
426
|
{
|
|
223
427
|
name: "riddle_screenshot",
|
|
@@ -299,7 +503,7 @@ function register(api) {
|
|
|
299
503
|
api.registerTool(
|
|
300
504
|
{
|
|
301
505
|
name: "riddle_steps",
|
|
302
|
-
description: `Riddle: run a workflow in steps mode (goto/click/fill/screenshot/scrape/map/crawl/etc.). Supports authenticated sessions via cookies/localStorage. Data extraction steps: { scrape: true }, { map: { max_pages?: N } }, { crawl: { max_pages?: N, format?: 'json'|'csv' } }. Returns screenshot + console by default; pass include:["har","data","urls","dataset","sitemap"] for additional artifacts.`,
|
|
506
|
+
description: `Riddle: run a workflow in steps mode (goto/click/fill/screenshot/scrape/map/crawl/etc.). Supports authenticated sessions via cookies/localStorage. Data extraction steps: { scrape: true }, { map: { max_pages?: N } }, { crawl: { max_pages?: N, format?: 'json'|'csv' } }. Returns screenshot + console by default; pass include:["har","data","urls","dataset","sitemap"] for additional artifacts. Set async:true to return immediately with job_id (use riddle_poll to check status later).`,
|
|
303
507
|
parameters: import_typebox.Type.Object({
|
|
304
508
|
steps: import_typebox.Type.Array(import_typebox.Type.Record(import_typebox.Type.String(), import_typebox.Type.Any())),
|
|
305
509
|
timeout_sec: import_typebox.Type.Optional(import_typebox.Type.Number()),
|
|
@@ -316,7 +520,8 @@ function register(api) {
|
|
|
316
520
|
options: import_typebox.Type.Optional(import_typebox.Type.Record(import_typebox.Type.String(), import_typebox.Type.Any())),
|
|
317
521
|
include: import_typebox.Type.Optional(import_typebox.Type.Array(import_typebox.Type.String())),
|
|
318
522
|
harInline: import_typebox.Type.Optional(import_typebox.Type.Boolean()),
|
|
319
|
-
sync: import_typebox.Type.Optional(import_typebox.Type.Boolean())
|
|
523
|
+
sync: import_typebox.Type.Optional(import_typebox.Type.Boolean()),
|
|
524
|
+
async: import_typebox.Type.Optional(import_typebox.Type.Boolean({ description: "Return job_id immediately without waiting for completion. Use riddle_poll to check status." }))
|
|
320
525
|
}),
|
|
321
526
|
async execute(_id, params) {
|
|
322
527
|
if (!Array.isArray(params.steps)) throw new Error("steps must be an array");
|
|
@@ -330,7 +535,7 @@ function register(api) {
|
|
|
330
535
|
if (Object.keys(opts).length > 0) payload.options = opts;
|
|
331
536
|
if (params.include) payload.include = params.include;
|
|
332
537
|
if (params.harInline) payload.harInline = params.harInline;
|
|
333
|
-
const result = await runWithDefaults(api, payload, { include: ["screenshot", "console", "result", "data", "urls", "dataset", "sitemap", "visual_diff"] });
|
|
538
|
+
const result = await runWithDefaults(api, payload, { include: ["screenshot", "console", "result", "data", "urls", "dataset", "sitemap", "visual_diff"], returnAsync: !!params.async });
|
|
334
539
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
335
540
|
}
|
|
336
541
|
},
|
|
@@ -339,7 +544,7 @@ function register(api) {
|
|
|
339
544
|
api.registerTool(
|
|
340
545
|
{
|
|
341
546
|
name: "riddle_script",
|
|
342
|
-
description: 'Riddle: run full Playwright code (script mode). Supports authenticated sessions via cookies/localStorage. In scripts, use `await injectLocalStorage()` after navigating to the origin to apply localStorage values. Available sandbox helpers: saveScreenshot(label), saveHtml(label), saveJson(name, data), scrape(opts?), map(opts?), crawl(opts?). Returns screenshot + console by default; pass include:["har","data","urls","dataset","sitemap"] for additional artifacts.',
|
|
547
|
+
description: 'Riddle: run full Playwright code (script mode). Supports authenticated sessions via cookies/localStorage. In scripts, use `await injectLocalStorage()` after navigating to the origin to apply localStorage values. Available sandbox helpers: saveScreenshot(label), saveHtml(label), saveJson(name, data), scrape(opts?), map(opts?), crawl(opts?). Returns screenshot + console by default; pass include:["har","data","urls","dataset","sitemap"] for additional artifacts. Set async:true to return immediately with job_id (use riddle_poll to check status later).',
|
|
343
548
|
parameters: import_typebox.Type.Object({
|
|
344
549
|
script: import_typebox.Type.String(),
|
|
345
550
|
timeout_sec: import_typebox.Type.Optional(import_typebox.Type.Number()),
|
|
@@ -356,7 +561,8 @@ function register(api) {
|
|
|
356
561
|
options: import_typebox.Type.Optional(import_typebox.Type.Record(import_typebox.Type.String(), import_typebox.Type.Any())),
|
|
357
562
|
include: import_typebox.Type.Optional(import_typebox.Type.Array(import_typebox.Type.String())),
|
|
358
563
|
harInline: import_typebox.Type.Optional(import_typebox.Type.Boolean()),
|
|
359
|
-
sync: import_typebox.Type.Optional(import_typebox.Type.Boolean())
|
|
564
|
+
sync: import_typebox.Type.Optional(import_typebox.Type.Boolean()),
|
|
565
|
+
async: import_typebox.Type.Optional(import_typebox.Type.Boolean({ description: "Return job_id immediately without waiting for completion. Use riddle_poll to check status." }))
|
|
360
566
|
}),
|
|
361
567
|
async execute(_id, params) {
|
|
362
568
|
if (!params.script || typeof params.script !== "string") throw new Error("script must be a string");
|
|
@@ -370,7 +576,7 @@ function register(api) {
|
|
|
370
576
|
if (Object.keys(opts).length > 0) payload.options = opts;
|
|
371
577
|
if (params.include) payload.include = params.include;
|
|
372
578
|
if (params.harInline) payload.harInline = params.harInline;
|
|
373
|
-
const result = await runWithDefaults(api, payload, { include: ["screenshot", "console", "result", "data", "urls", "dataset", "sitemap", "visual_diff"] });
|
|
579
|
+
const result = await runWithDefaults(api, payload, { include: ["screenshot", "console", "result", "data", "urls", "dataset", "sitemap", "visual_diff"], returnAsync: !!params.async });
|
|
374
580
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
375
581
|
}
|
|
376
582
|
},
|
package/dist/index.js
CHANGED
|
@@ -75,7 +75,8 @@ async function applySafetySpec(result, opts) {
|
|
|
75
75
|
}
|
|
76
76
|
if (base64Data) {
|
|
77
77
|
const ref = await writeArtifactBinary(opts.workspace, "screenshots", `${jobId}.png`, base64Data);
|
|
78
|
-
result.screenshot
|
|
78
|
+
const cdnUrl = typeof result.screenshot === "object" ? result.screenshot.url : void 0;
|
|
79
|
+
result.screenshot = { saved: ref.path, sizeBytes: ref.sizeBytes, ...cdnUrl ? { url: cdnUrl } : {} };
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
if (Array.isArray(result.screenshots)) {
|
|
@@ -83,6 +84,7 @@ async function applySafetySpec(result, opts) {
|
|
|
83
84
|
for (let i = 0; i < result.screenshots.length; i++) {
|
|
84
85
|
const ss = result.screenshots[i];
|
|
85
86
|
let base64Data = null;
|
|
87
|
+
const cdnUrl = typeof ss === "object" ? ss.url : void 0;
|
|
86
88
|
if (typeof ss === "string") {
|
|
87
89
|
base64Data = ss;
|
|
88
90
|
} else if (typeof ss === "object" && ss.data) {
|
|
@@ -90,7 +92,7 @@ async function applySafetySpec(result, opts) {
|
|
|
90
92
|
}
|
|
91
93
|
if (base64Data) {
|
|
92
94
|
const ref = await writeArtifactBinary(opts.workspace, "screenshots", `${jobId}-${i}.png`, base64Data);
|
|
93
|
-
savedRefs.push({ saved: ref.path, sizeBytes: ref.sizeBytes });
|
|
95
|
+
savedRefs.push({ saved: ref.path, sizeBytes: ref.sizeBytes, ...cdnUrl ? { url: cdnUrl } : {} });
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
result.screenshots = savedRefs;
|
|
@@ -122,6 +124,95 @@ async function applySafetySpec(result, opts) {
|
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
126
|
}
|
|
127
|
+
async function pollJobStatus(baseUrl, apiKey, jobId, maxWaitMs) {
|
|
128
|
+
const start = Date.now();
|
|
129
|
+
const POLL_INTERVAL = 2e3;
|
|
130
|
+
while (Date.now() - start < maxWaitMs) {
|
|
131
|
+
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/jobs/${jobId}`, {
|
|
132
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
133
|
+
});
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
return { status: "poll_error", error: `HTTP ${res.status}` };
|
|
136
|
+
}
|
|
137
|
+
const data = await res.json();
|
|
138
|
+
if (data.status === "completed" || data.status === "completed_timeout" || data.status === "completed_error" || data.status === "failed") {
|
|
139
|
+
return data;
|
|
140
|
+
}
|
|
141
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
|
142
|
+
}
|
|
143
|
+
return { status: "poll_timeout", error: `Job ${jobId} did not complete within ${maxWaitMs}ms` };
|
|
144
|
+
}
|
|
145
|
+
async function fetchArtifactsAndBuild(baseUrl, apiKey, jobId, include) {
|
|
146
|
+
const res = await fetch(
|
|
147
|
+
`${baseUrl.replace(/\/$/, "")}/v1/jobs/${jobId}/artifacts?include=${include.join(",")}`,
|
|
148
|
+
{ headers: { Authorization: `Bearer ${apiKey}` } }
|
|
149
|
+
);
|
|
150
|
+
if (!res.ok) {
|
|
151
|
+
return { error: `Artifacts fetch failed: HTTP ${res.status}` };
|
|
152
|
+
}
|
|
153
|
+
const data = await res.json();
|
|
154
|
+
const artifacts = data.artifacts || [];
|
|
155
|
+
const result = {};
|
|
156
|
+
if (data.status) result._artifactsStatus = data.status;
|
|
157
|
+
if (data.timeout) result._timeout = data.timeout;
|
|
158
|
+
if (data.error) result._error = data.error;
|
|
159
|
+
const screenshots = artifacts.filter((a) => a.name && /\.(png|jpg|jpeg)$/i.test(a.name));
|
|
160
|
+
if (screenshots.length > 0) {
|
|
161
|
+
result.screenshots = [];
|
|
162
|
+
for (const ss of screenshots) {
|
|
163
|
+
if (!ss.url) continue;
|
|
164
|
+
try {
|
|
165
|
+
const imgRes = await fetch(ss.url);
|
|
166
|
+
if (imgRes.ok) {
|
|
167
|
+
const buf = await imgRes.arrayBuffer();
|
|
168
|
+
result.screenshots.push({
|
|
169
|
+
name: ss.name,
|
|
170
|
+
data: `data:image/png;base64,${Buffer.from(buf).toString("base64")}`,
|
|
171
|
+
size: buf.byteLength,
|
|
172
|
+
url: ss.url
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (result.screenshots.length > 0) {
|
|
179
|
+
result.screenshot = result.screenshots[0];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const consoleArtifact = artifacts.find((a) => a.name === "console.json");
|
|
183
|
+
if (consoleArtifact?.url) {
|
|
184
|
+
try {
|
|
185
|
+
const cRes = await fetch(consoleArtifact.url);
|
|
186
|
+
if (cRes.ok) {
|
|
187
|
+
result.console = await cRes.json();
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const resultArtifact = artifacts.find((a) => a.name === "result.json");
|
|
193
|
+
if (resultArtifact?.url) {
|
|
194
|
+
try {
|
|
195
|
+
const rRes = await fetch(resultArtifact.url);
|
|
196
|
+
if (rRes.ok) {
|
|
197
|
+
result.result = await rRes.json();
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (include.includes("har")) {
|
|
203
|
+
const harArtifact = artifacts.find((a) => a.name === "network.har");
|
|
204
|
+
if (harArtifact?.url) {
|
|
205
|
+
try {
|
|
206
|
+
const hRes = await fetch(harArtifact.url);
|
|
207
|
+
if (hRes.ok) {
|
|
208
|
+
result.har = await hRes.json();
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
125
216
|
async function runWithDefaults(api, payload, defaults) {
|
|
126
217
|
const { apiKey, baseUrl } = getCfg(api);
|
|
127
218
|
if (!apiKey) {
|
|
@@ -135,8 +226,12 @@ async function runWithDefaults(api, payload, defaults) {
|
|
|
135
226
|
const userInclude = payload.include ?? [];
|
|
136
227
|
const userRequestedHar = userInclude.includes("har");
|
|
137
228
|
const harInline = !!payload.harInline;
|
|
229
|
+
const returnAsync = !!defaults?.returnAsync;
|
|
138
230
|
const merged = { ...payload };
|
|
139
231
|
delete merged.harInline;
|
|
232
|
+
if (returnAsync) {
|
|
233
|
+
merged.sync = false;
|
|
234
|
+
}
|
|
140
235
|
const defaultInc = defaults?.include ?? ["screenshot", "console"];
|
|
141
236
|
merged.include = Array.from(/* @__PURE__ */ new Set([...userInclude, ...defaultInc]));
|
|
142
237
|
merged.inlineConsole = merged.inlineConsole ?? true;
|
|
@@ -147,6 +242,46 @@ async function runWithDefaults(api, payload, defaults) {
|
|
|
147
242
|
const out = { ok: true, mode };
|
|
148
243
|
const { contentType, body, headers, status } = await postRun(baseUrl, apiKey, merged);
|
|
149
244
|
out.rawContentType = contentType ?? void 0;
|
|
245
|
+
const workspace = getWorkspacePath(api);
|
|
246
|
+
if (status === 408) {
|
|
247
|
+
let jobIdFrom408;
|
|
248
|
+
try {
|
|
249
|
+
const json408 = JSON.parse(Buffer.from(body).toString("utf8"));
|
|
250
|
+
jobIdFrom408 = json408.job_id;
|
|
251
|
+
} catch {
|
|
252
|
+
}
|
|
253
|
+
if (!jobIdFrom408) {
|
|
254
|
+
out.ok = false;
|
|
255
|
+
out.error = "Sync poll timed out but no job_id in 408 response";
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
out.job_id = jobIdFrom408;
|
|
259
|
+
if (returnAsync) {
|
|
260
|
+
out.status = "submitted";
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
const scriptTimeoutMs = (payload.timeout_sec ?? 60) * 1e3;
|
|
264
|
+
const pollMaxMs = scriptTimeoutMs + 3e4;
|
|
265
|
+
const jobStatus = await pollJobStatus(baseUrl, apiKey, jobIdFrom408, pollMaxMs);
|
|
266
|
+
if (jobStatus.status === "poll_timeout" || jobStatus.status === "poll_error" || jobStatus.status === "failed") {
|
|
267
|
+
out.ok = false;
|
|
268
|
+
out.status = jobStatus.status;
|
|
269
|
+
out.error = jobStatus.error || `Job ${jobStatus.status}`;
|
|
270
|
+
return out;
|
|
271
|
+
}
|
|
272
|
+
const artifacts = await fetchArtifactsAndBuild(baseUrl, apiKey, jobIdFrom408, merged.include);
|
|
273
|
+
Object.assign(out, artifacts);
|
|
274
|
+
out.job_id = jobIdFrom408;
|
|
275
|
+
out.status = jobStatus.status;
|
|
276
|
+
out.duration_ms = jobStatus.duration_ms;
|
|
277
|
+
if (jobStatus.status !== "completed") {
|
|
278
|
+
out.ok = false;
|
|
279
|
+
if (jobStatus.timeout) out.timeout = jobStatus.timeout;
|
|
280
|
+
if (jobStatus.error) out.error = jobStatus.error;
|
|
281
|
+
}
|
|
282
|
+
await applySafetySpec(out, { workspace, harInline });
|
|
283
|
+
return out;
|
|
284
|
+
}
|
|
150
285
|
if (status >= 400) {
|
|
151
286
|
try {
|
|
152
287
|
const txt2 = Buffer.from(body).toString("utf8");
|
|
@@ -165,15 +300,42 @@ async function runWithDefaults(api, payload, defaults) {
|
|
|
165
300
|
const duration = headers.get("x-duration-ms");
|
|
166
301
|
out.duration_ms = duration ? Number(duration) : void 0;
|
|
167
302
|
out.sync = true;
|
|
168
|
-
|
|
169
|
-
await applySafetySpec(out, { workspace: workspace2, harInline });
|
|
303
|
+
await applySafetySpec(out, { workspace, harInline });
|
|
170
304
|
return out;
|
|
171
305
|
}
|
|
172
306
|
const txt = Buffer.from(body).toString("utf8");
|
|
173
307
|
const json = JSON.parse(txt);
|
|
174
308
|
Object.assign(out, json);
|
|
175
309
|
out.job_id = json.job_id ?? json.jobId ?? out.job_id;
|
|
176
|
-
|
|
310
|
+
if (status === 202 && out.job_id && json.status_url) {
|
|
311
|
+
if (returnAsync) {
|
|
312
|
+
out.status = "submitted";
|
|
313
|
+
return out;
|
|
314
|
+
}
|
|
315
|
+
const scriptTimeoutMs = (payload.timeout_sec ?? 60) * 1e3;
|
|
316
|
+
const pollMaxMs = scriptTimeoutMs + 3e4;
|
|
317
|
+
const jobStatus = await pollJobStatus(baseUrl, apiKey, out.job_id, pollMaxMs);
|
|
318
|
+
if (jobStatus.status === "poll_timeout" || jobStatus.status === "poll_error" || jobStatus.status === "failed") {
|
|
319
|
+
out.ok = false;
|
|
320
|
+
out.status = jobStatus.status;
|
|
321
|
+
out.error = jobStatus.error || `Job ${jobStatus.status}`;
|
|
322
|
+
return out;
|
|
323
|
+
}
|
|
324
|
+
const artifacts = await fetchArtifactsAndBuild(baseUrl, apiKey, out.job_id, merged.include);
|
|
325
|
+
Object.assign(out, artifacts);
|
|
326
|
+
out.status = jobStatus.status;
|
|
327
|
+
out.duration_ms = jobStatus.duration_ms;
|
|
328
|
+
if (jobStatus.status !== "completed") {
|
|
329
|
+
out.ok = false;
|
|
330
|
+
if (jobStatus.timeout) out.timeout = jobStatus.timeout;
|
|
331
|
+
if (jobStatus.error) out.error = jobStatus.error;
|
|
332
|
+
}
|
|
333
|
+
await applySafetySpec(out, { workspace, harInline });
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
if (json.status === "completed_timeout" || json.status === "completed_error") {
|
|
337
|
+
out.ok = false;
|
|
338
|
+
}
|
|
177
339
|
await applySafetySpec(out, { workspace, harInline });
|
|
178
340
|
return out;
|
|
179
341
|
}
|
|
@@ -181,19 +343,61 @@ function register(api) {
|
|
|
181
343
|
api.registerTool(
|
|
182
344
|
{
|
|
183
345
|
name: "riddle_run",
|
|
184
|
-
description: 'Run a Riddle job (pass-through payload) against https://api.riddledc.com/v1/run. Supports url/urls/steps/script. Returns screenshot + console by default; pass include:["har"] to opt in to HAR capture.',
|
|
346
|
+
description: 'Run a Riddle job (pass-through payload) against https://api.riddledc.com/v1/run. Supports url/urls/steps/script. Returns screenshot + console by default; pass include:["har"] to opt in to HAR capture. Set async:true to return immediately with job_id (use riddle_poll to check status later).',
|
|
185
347
|
parameters: Type.Object({
|
|
186
|
-
payload: Type.Record(Type.String(), Type.Any())
|
|
348
|
+
payload: Type.Record(Type.String(), Type.Any()),
|
|
349
|
+
async: Type.Optional(Type.Boolean({ description: "Return job_id immediately without waiting for completion. Use riddle_poll to check status." }))
|
|
187
350
|
}),
|
|
188
351
|
async execute(_id, params) {
|
|
189
352
|
const result = await runWithDefaults(api, params.payload, {
|
|
190
|
-
include: ["screenshot", "console", "result"]
|
|
353
|
+
include: ["screenshot", "console", "result"],
|
|
354
|
+
returnAsync: !!params.async
|
|
191
355
|
});
|
|
192
356
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
193
357
|
}
|
|
194
358
|
},
|
|
195
359
|
{ optional: true }
|
|
196
360
|
);
|
|
361
|
+
api.registerTool(
|
|
362
|
+
{
|
|
363
|
+
name: "riddle_poll",
|
|
364
|
+
description: "Poll the status of an async Riddle job. Use after submitting a job with async:true. Returns current status if still running, or full results (screenshot, console, etc.) if completed.",
|
|
365
|
+
parameters: Type.Object({
|
|
366
|
+
job_id: Type.String({ description: "Job ID returned by an async riddle_* call" }),
|
|
367
|
+
include: Type.Optional(Type.Array(Type.String(), { description: "Artifacts to fetch on completion (default: screenshot, console, result)" })),
|
|
368
|
+
harInline: Type.Optional(Type.Boolean())
|
|
369
|
+
}),
|
|
370
|
+
async execute(_id, params) {
|
|
371
|
+
if (!params.job_id || typeof params.job_id !== "string") throw new Error("job_id must be a string");
|
|
372
|
+
const { apiKey, baseUrl } = getCfg(api);
|
|
373
|
+
if (!apiKey) {
|
|
374
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
375
|
+
}
|
|
376
|
+
assertAllowedBaseUrl(baseUrl);
|
|
377
|
+
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/jobs/${params.job_id}`, {
|
|
378
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
379
|
+
});
|
|
380
|
+
if (!res.ok) {
|
|
381
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, job_id: params.job_id, error: `HTTP ${res.status}` }, null, 2) }] };
|
|
382
|
+
}
|
|
383
|
+
const data = await res.json();
|
|
384
|
+
if (data.status !== "completed" && data.status !== "completed_timeout" && data.status !== "completed_error" && data.status !== "failed") {
|
|
385
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: true, job_id: params.job_id, status: data.status, message: "Job still running. Call riddle_poll again later." }, null, 2) }] };
|
|
386
|
+
}
|
|
387
|
+
const include = params.include ?? ["screenshot", "console", "result"];
|
|
388
|
+
const artifacts = await fetchArtifactsAndBuild(baseUrl, apiKey, params.job_id, include);
|
|
389
|
+
const out = { ok: data.status === "completed", job_id: params.job_id, status: data.status, duration_ms: data.duration_ms, ...artifacts };
|
|
390
|
+
if (data.status !== "completed") {
|
|
391
|
+
if (data.timeout) out.timeout = data.timeout;
|
|
392
|
+
if (data.error) out.error = data.error;
|
|
393
|
+
}
|
|
394
|
+
const workspace = getWorkspacePath(api);
|
|
395
|
+
await applySafetySpec(out, { workspace, harInline: !!params.harInline });
|
|
396
|
+
return { content: [{ type: "text", text: JSON.stringify(out, null, 2) }] };
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
{ optional: true }
|
|
400
|
+
);
|
|
197
401
|
api.registerTool(
|
|
198
402
|
{
|
|
199
403
|
name: "riddle_screenshot",
|
|
@@ -275,7 +479,7 @@ function register(api) {
|
|
|
275
479
|
api.registerTool(
|
|
276
480
|
{
|
|
277
481
|
name: "riddle_steps",
|
|
278
|
-
description: `Riddle: run a workflow in steps mode (goto/click/fill/screenshot/scrape/map/crawl/etc.). Supports authenticated sessions via cookies/localStorage. Data extraction steps: { scrape: true }, { map: { max_pages?: N } }, { crawl: { max_pages?: N, format?: 'json'|'csv' } }. Returns screenshot + console by default; pass include:["har","data","urls","dataset","sitemap"] for additional artifacts.`,
|
|
482
|
+
description: `Riddle: run a workflow in steps mode (goto/click/fill/screenshot/scrape/map/crawl/etc.). Supports authenticated sessions via cookies/localStorage. Data extraction steps: { scrape: true }, { map: { max_pages?: N } }, { crawl: { max_pages?: N, format?: 'json'|'csv' } }. Returns screenshot + console by default; pass include:["har","data","urls","dataset","sitemap"] for additional artifacts. Set async:true to return immediately with job_id (use riddle_poll to check status later).`,
|
|
279
483
|
parameters: Type.Object({
|
|
280
484
|
steps: Type.Array(Type.Record(Type.String(), Type.Any())),
|
|
281
485
|
timeout_sec: Type.Optional(Type.Number()),
|
|
@@ -292,7 +496,8 @@ function register(api) {
|
|
|
292
496
|
options: Type.Optional(Type.Record(Type.String(), Type.Any())),
|
|
293
497
|
include: Type.Optional(Type.Array(Type.String())),
|
|
294
498
|
harInline: Type.Optional(Type.Boolean()),
|
|
295
|
-
sync: Type.Optional(Type.Boolean())
|
|
499
|
+
sync: Type.Optional(Type.Boolean()),
|
|
500
|
+
async: Type.Optional(Type.Boolean({ description: "Return job_id immediately without waiting for completion. Use riddle_poll to check status." }))
|
|
296
501
|
}),
|
|
297
502
|
async execute(_id, params) {
|
|
298
503
|
if (!Array.isArray(params.steps)) throw new Error("steps must be an array");
|
|
@@ -306,7 +511,7 @@ function register(api) {
|
|
|
306
511
|
if (Object.keys(opts).length > 0) payload.options = opts;
|
|
307
512
|
if (params.include) payload.include = params.include;
|
|
308
513
|
if (params.harInline) payload.harInline = params.harInline;
|
|
309
|
-
const result = await runWithDefaults(api, payload, { include: ["screenshot", "console", "result", "data", "urls", "dataset", "sitemap", "visual_diff"] });
|
|
514
|
+
const result = await runWithDefaults(api, payload, { include: ["screenshot", "console", "result", "data", "urls", "dataset", "sitemap", "visual_diff"], returnAsync: !!params.async });
|
|
310
515
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
311
516
|
}
|
|
312
517
|
},
|
|
@@ -315,7 +520,7 @@ function register(api) {
|
|
|
315
520
|
api.registerTool(
|
|
316
521
|
{
|
|
317
522
|
name: "riddle_script",
|
|
318
|
-
description: 'Riddle: run full Playwright code (script mode). Supports authenticated sessions via cookies/localStorage. In scripts, use `await injectLocalStorage()` after navigating to the origin to apply localStorage values. Available sandbox helpers: saveScreenshot(label), saveHtml(label), saveJson(name, data), scrape(opts?), map(opts?), crawl(opts?). Returns screenshot + console by default; pass include:["har","data","urls","dataset","sitemap"] for additional artifacts.',
|
|
523
|
+
description: 'Riddle: run full Playwright code (script mode). Supports authenticated sessions via cookies/localStorage. In scripts, use `await injectLocalStorage()` after navigating to the origin to apply localStorage values. Available sandbox helpers: saveScreenshot(label), saveHtml(label), saveJson(name, data), scrape(opts?), map(opts?), crawl(opts?). Returns screenshot + console by default; pass include:["har","data","urls","dataset","sitemap"] for additional artifacts. Set async:true to return immediately with job_id (use riddle_poll to check status later).',
|
|
319
524
|
parameters: Type.Object({
|
|
320
525
|
script: Type.String(),
|
|
321
526
|
timeout_sec: Type.Optional(Type.Number()),
|
|
@@ -332,7 +537,8 @@ function register(api) {
|
|
|
332
537
|
options: Type.Optional(Type.Record(Type.String(), Type.Any())),
|
|
333
538
|
include: Type.Optional(Type.Array(Type.String())),
|
|
334
539
|
harInline: Type.Optional(Type.Boolean()),
|
|
335
|
-
sync: Type.Optional(Type.Boolean())
|
|
540
|
+
sync: Type.Optional(Type.Boolean()),
|
|
541
|
+
async: Type.Optional(Type.Boolean({ description: "Return job_id immediately without waiting for completion. Use riddle_poll to check status." }))
|
|
336
542
|
}),
|
|
337
543
|
async execute(_id, params) {
|
|
338
544
|
if (!params.script || typeof params.script !== "string") throw new Error("script must be a string");
|
|
@@ -346,7 +552,7 @@ function register(api) {
|
|
|
346
552
|
if (Object.keys(opts).length > 0) payload.options = opts;
|
|
347
553
|
if (params.include) payload.include = params.include;
|
|
348
554
|
if (params.harInline) payload.harInline = params.harInline;
|
|
349
|
-
const result = await runWithDefaults(api, payload, { include: ["screenshot", "console", "result", "data", "urls", "dataset", "sitemap", "visual_diff"] });
|
|
555
|
+
const result = await runWithDefaults(api, payload, { include: ["screenshot", "console", "result", "data", "urls", "dataset", "sitemap", "visual_diff"], returnAsync: !!params.async });
|
|
350
556
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
351
557
|
}
|
|
352
558
|
},
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "openclaw-riddledc",
|
|
3
3
|
"name": "Riddle",
|
|
4
4
|
"description": "Riddle (riddledc.com) hosted browser API tools for OpenClaw agents.",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.6",
|
|
6
6
|
"notes": "0.4.0: Added riddle_scrape, riddle_map, riddle_crawl convenience tools. Updated riddle_steps and riddle_script descriptions with data extraction capabilities.",
|
|
7
7
|
"type": "plugin",
|
|
8
8
|
"bundledSkills": [],
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"riddle_steps",
|
|
34
34
|
"riddle_script",
|
|
35
35
|
"riddle_run",
|
|
36
|
+
"riddle_poll",
|
|
36
37
|
"riddle_scrape",
|
|
37
38
|
"riddle_map",
|
|
38
39
|
"riddle_crawl",
|