@riddledc/riddle-proof 0.8.30 → 0.8.31

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.
Files changed (46) hide show
  1. package/dist/advanced/engine-harness.cjs +132 -10
  2. package/dist/advanced/engine-harness.js +2 -2
  3. package/dist/advanced/index.cjs +132 -10
  4. package/dist/advanced/index.d.cts +2 -2
  5. package/dist/advanced/index.d.ts +2 -2
  6. package/dist/advanced/index.js +4 -4
  7. package/dist/advanced/proof-run-core.cjs +3 -1
  8. package/dist/advanced/proof-run-core.d.cts +1 -1
  9. package/dist/advanced/proof-run-core.d.ts +1 -1
  10. package/dist/advanced/proof-run-core.js +1 -1
  11. package/dist/advanced/proof-run-engine.cjs +80 -1
  12. package/dist/advanced/proof-run-engine.d.cts +2 -2
  13. package/dist/advanced/proof-run-engine.d.ts +2 -2
  14. package/dist/advanced/proof-run-engine.js +2 -2
  15. package/dist/advanced/runner.js +2 -2
  16. package/dist/{chunk-3OTO7IDH.js → chunk-C2NHHBFV.js} +1 -1
  17. package/dist/{chunk-32RE64IO.js → chunk-IOI6QR3B.js} +78 -1
  18. package/dist/{chunk-XJA2GDVN.js → chunk-U73JPBZW.js} +1 -1
  19. package/dist/{chunk-K6HZUSHH.js → chunk-X7SQTCIQ.js} +3 -1
  20. package/dist/{chunk-UWO4YR7I.js → chunk-ZREWMTFA.js} +53 -10
  21. package/dist/cli/index.js +3 -3
  22. package/dist/cli.cjs +132 -10
  23. package/dist/cli.js +3 -3
  24. package/dist/engine-harness.cjs +132 -10
  25. package/dist/engine-harness.js +2 -2
  26. package/dist/index.cjs +132 -10
  27. package/dist/index.js +3 -3
  28. package/dist/{proof-run-core-C8FDUhle.d.cts → proof-run-core-B1GeqkR8.d.cts} +2 -0
  29. package/dist/{proof-run-core-C8FDUhle.d.ts → proof-run-core-B1GeqkR8.d.ts} +2 -0
  30. package/dist/proof-run-core.cjs +3 -1
  31. package/dist/proof-run-core.d.cts +1 -1
  32. package/dist/proof-run-core.d.ts +1 -1
  33. package/dist/proof-run-core.js +1 -1
  34. package/dist/{proof-run-engine-D80hVFMf.d.cts → proof-run-engine-4dM37pEx.d.cts} +1 -1
  35. package/dist/{proof-run-engine-By7oLsF-.d.ts → proof-run-engine-BqaeqAze.d.ts} +1 -1
  36. package/dist/proof-run-engine.cjs +80 -1
  37. package/dist/proof-run-engine.d.cts +2 -2
  38. package/dist/proof-run-engine.d.ts +2 -2
  39. package/dist/proof-run-engine.js +2 -2
  40. package/dist/runner.js +2 -2
  41. package/lib/workspace-core.mjs +62 -7
  42. package/package.json +2 -2
  43. package/runtime/lib/riddle_core_call.mjs +662 -40
  44. package/runtime/lib/util.py +117 -40
  45. package/runtime/lib/verify.py +4 -3
  46. package/runtime/tests/recon_verify_smoke.py +132 -0
@@ -1,10 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readFileSync } from "node:fs";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { spawn } from "node:child_process";
4
+ import { createRequire } from "node:module";
5
+ import { createServer } from "node:net";
3
6
  import { homedir } from "node:os";
4
7
  import { join } from "node:path";
5
8
  import { pathToFileURL } from "node:url";
6
9
 
7
10
  const TOOL_DEFAULT_INCLUDE = ["screenshot", "console", "result", "data", "urls", "dataset", "sitemap", "visual_diff"];
11
+ const DEFAULT_BASE_URL = "https://api.riddledc.com";
12
+ const DEFAULT_API_KEY_FILE = "/tmp/riddle-api-key";
13
+ const require = createRequire(import.meta.url);
8
14
 
9
15
  function readJson(path) {
10
16
  try {
@@ -33,39 +39,648 @@ function readOpenClawPluginConfig() {
33
39
  function buildConfig() {
34
40
  const pluginCfg = readOpenClawPluginConfig();
35
41
  return {
36
- apiKey: process.env.RIDDLE_API_KEY || pluginCfg.apiKey,
37
- baseUrl: pluginCfg.baseUrl || "https://api.riddledc.com",
42
+ apiKey: process.env.RIDDLE_API_KEY || readApiKeyFile() || pluginCfg.apiKey,
43
+ baseUrl: process.env.RIDDLE_API_BASE_URL || pluginCfg.baseUrl || DEFAULT_BASE_URL,
38
44
  workspace: process.env.OPENCLAW_WORKSPACE || process.cwd(),
39
45
  };
40
46
  }
41
47
 
42
- async function importFileIfExists(path) {
43
- if (!path || !existsSync(path)) return null;
44
- return import(pathToFileURL(path).href);
48
+ function readApiKeyFile() {
49
+ const keyFile = process.env.RIDDLE_API_KEY_FILE || DEFAULT_API_KEY_FILE;
50
+ try {
51
+ const key = readFileSync(keyFile, "utf8").trim();
52
+ return key || "";
53
+ } catch {
54
+ return "";
55
+ }
56
+ }
57
+
58
+ function requireConfig(config) {
59
+ const apiKey = config.apiKey || process.env.RIDDLE_API_KEY || readApiKeyFile();
60
+ const baseUrl = config.baseUrl || DEFAULT_BASE_URL;
61
+ if (!apiKey) {
62
+ throw new Error("Missing Riddle API key. Set RIDDLE_API_KEY or RIDDLE_API_KEY_FILE before running Riddle Proof direct browser evidence.");
63
+ }
64
+ assertAllowedBaseUrl(baseUrl);
65
+ return { apiKey, baseUrl, workspace: config.workspace || process.cwd() };
66
+ }
67
+
68
+ function assertAllowedBaseUrl(baseUrl) {
69
+ const url = new URL(baseUrl);
70
+ if (url.protocol !== "https:") throw new Error("Riddle baseUrl must be https: " + baseUrl);
71
+ if (url.hostname !== "api.riddledc.com") {
72
+ throw new Error("Refusing to use non-official Riddle host: " + url.hostname);
73
+ }
74
+ }
75
+
76
+ function detectMode(payload) {
77
+ if (payload.url) return "url";
78
+ if (payload.urls) return "urls";
79
+ if (payload.steps) return "steps";
80
+ if (payload.script) return "script";
81
+ return undefined;
82
+ }
83
+
84
+ function abToBase64(ab) {
85
+ return Buffer.from(ab).toString("base64");
86
+ }
87
+
88
+ function sleep(ms) {
89
+ return new Promise((resolve) => setTimeout(resolve, ms));
90
+ }
91
+
92
+ function truthy(value) {
93
+ return String(value || "").toLowerCase().match(/^(1|true|yes|y|on)$/);
94
+ }
95
+
96
+ function localRiddleScriptAllowed() {
97
+ const runner = String(process.env.RIDDLE_PROOF_BROWSER_RUNNER || "").toLowerCase();
98
+ if (runner === "hosted" || runner === "riddle") return false;
99
+ if (String(process.env.RIDDLE_PROOF_DISABLE_LOCAL_RIDDLE_SCRIPT || "").toLowerCase().match(/^(1|true|yes)$/)) return false;
100
+ return true;
101
+ }
102
+
103
+ function localRiddleScriptPreferred() {
104
+ const runner = String(process.env.RIDDLE_PROOF_BROWSER_RUNNER || "").toLowerCase();
105
+ return runner === "local" || runner === "playwright" || runner === "local-playwright";
106
+ }
107
+
108
+ function sanitizeArtifactName(input, fallback = "artifact") {
109
+ return String(input || fallback)
110
+ .trim()
111
+ .replace(/^\.\.?(\/|\\)/g, "")
112
+ .replace(/[/\\]/g, "-")
113
+ .replace(/\s+/g, "-")
114
+ .replace(/[^a-zA-Z0-9._-]/g, "-")
115
+ .replace(/-+/g, "-")
116
+ .replace(/^-|-$/g, "") || fallback;
117
+ }
118
+
119
+ function localArtifactRoot() {
120
+ const root = process.env.RIDDLE_PROOF_LOCAL_ARTIFACT_DIR || join(process.cwd(), "artifacts", "riddle-proof", "local-browser");
121
+ const id = `${new Date().toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}`;
122
+ const dir = join(root, id);
123
+ mkdirSync(dir, { recursive: true });
124
+ mkdirSync(join(dir, "screenshots"), { recursive: true });
125
+ return dir;
126
+ }
127
+
128
+ async function loadLocalPlaywright() {
129
+ const packagePaths = [
130
+ process.cwd(),
131
+ join(process.cwd(), "packages", "riddle-proof-runner-playwright"),
132
+ join(process.cwd(), "..", "riddle-proof-runner-playwright"),
133
+ process.env.RIDDLE_PROOF_PLAYWRIGHT_PACKAGE_DIR || "",
134
+ ].filter(Boolean);
135
+
136
+ let resolved = "";
137
+ try {
138
+ resolved = require.resolve("playwright");
139
+ } catch {
140
+ for (const packagePath of packagePaths) {
141
+ try {
142
+ resolved = require.resolve("playwright", { paths: [packagePath] });
143
+ break;
144
+ } catch {
145
+ // Try next path.
146
+ }
147
+ }
148
+ }
149
+ if (!resolved) {
150
+ throw new Error("Playwright is required for local Riddle Proof browser evidence. Install playwright or set RIDDLE_PROOF_BROWSER_RUNNER=hosted with a Riddle API key.");
151
+ }
152
+ const mod = await import(pathToFileURL(resolved).href);
153
+ return mod.default || mod;
154
+ }
155
+
156
+ async function runLocalRiddleScript(payload, defaults = {}, blockedHostedReason = "") {
157
+ if (!payload.script || typeof payload.script !== "string") {
158
+ return { ok: false, error: blockedHostedReason || "Local Riddle Proof browser evidence requires a script payload." };
159
+ }
160
+
161
+ const outputs = [];
162
+ const screenshots = [];
163
+ const consoleMessages = [];
164
+ const resultArtifacts = {};
165
+ const outputDir = localArtifactRoot();
166
+ const playwright = await loadLocalPlaywright();
167
+ const browserName = payload.browser || "chromium";
168
+ const browserType = playwright[browserName] || playwright.chromium;
169
+ const browser = await browserType.launch({ headless: payload.headful !== true });
170
+ const context = await browser.newContext({
171
+ viewport: payload.viewport || undefined,
172
+ });
173
+ const page = await context.newPage();
174
+ const timeoutMs = Number(payload.timeout_sec ?? payload.timeoutSec ?? 60) * 1000;
175
+ page.setDefaultTimeout(Math.min(timeoutMs, 120_000));
176
+ page.setDefaultNavigationTimeout(Math.min(timeoutMs, 120_000));
177
+ page.on("console", (message) => {
178
+ consoleMessages.push({ type: message.type(), text: message.text() });
179
+ });
180
+ page.on("pageerror", (error) => {
181
+ consoleMessages.push({ type: "pageerror", text: error?.message || String(error) });
182
+ });
183
+
184
+ const saveScreenshot = async (label = "screenshot", options = {}) => {
185
+ const name = sanitizeArtifactName(label, "screenshot").replace(/\.(png|jpg|jpeg)$/i, "") + ".png";
186
+ const path = join(outputDir, "screenshots", name);
187
+ const buffer = await page.screenshot({ fullPage: options?.fullPage === true });
188
+ writeFileSync(path, buffer);
189
+ const item = {
190
+ name,
191
+ url: pathToFileURL(path).href,
192
+ path,
193
+ size: buffer.byteLength,
194
+ };
195
+ outputs.push({ name, url: item.url, path, size_bytes: buffer.byteLength });
196
+ screenshots.push(item);
197
+ return { name, url: item.url, path };
198
+ };
199
+
200
+ const saveJson = async (name = "artifact", value = {}) => {
201
+ const fileName = sanitizeArtifactName(name, "artifact").replace(/\.json$/i, "") + ".json";
202
+ const path = join(outputDir, fileName);
203
+ writeFileSync(path, JSON.stringify(value, null, 2));
204
+ const item = { name: fileName, url: pathToFileURL(path).href, path };
205
+ outputs.push(item);
206
+ resultArtifacts[fileName] = value;
207
+ return item;
208
+ };
209
+
210
+ const previousEvidence = globalThis.__riddleProofEvidence;
211
+ globalThis.__riddleProofEvidence = undefined;
212
+ let scriptResult;
213
+ let proofEvidence;
214
+ try {
215
+ const runnerFactory = Object.getPrototypeOf(async function () {}).constructor;
216
+ const runScript = new runnerFactory("page", "saveScreenshot", "saveJson", "url", payload.script);
217
+ scriptResult = await Promise.race([
218
+ runScript(page, saveScreenshot, saveJson, payload.url || ""),
219
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`local riddle_script timed out after ${timeoutMs}ms`)), timeoutMs + 1000)),
220
+ ]);
221
+ proofEvidence = globalThis.__riddleProofEvidence;
222
+ } catch (err) {
223
+ const failedProofEvidence = globalThis.__riddleProofEvidence;
224
+ return {
225
+ ok: false,
226
+ mode: "script",
227
+ runner: "local-playwright",
228
+ error: err instanceof Error ? err.message : String(err),
229
+ script_error: err instanceof Error ? err.stack || err.message : String(err),
230
+ outputs,
231
+ screenshots,
232
+ screenshot: screenshots[0],
233
+ console: consoleMessages,
234
+ result: failedProofEvidence ? { proofEvidence: failedProofEvidence } : undefined,
235
+ };
236
+ } finally {
237
+ try {
238
+ await context.close();
239
+ await browser.close();
240
+ } finally {
241
+ globalThis.__riddleProofEvidence = previousEvidence;
242
+ }
243
+ }
244
+
245
+ let result = typeof scriptResult === "object" && scriptResult !== null && !Array.isArray(scriptResult)
246
+ ? scriptResult
247
+ : scriptResult !== undefined
248
+ ? { value: scriptResult }
249
+ : {};
250
+ if (proofEvidence !== undefined && result.proofEvidence === undefined && result.proof_evidence === undefined) {
251
+ result = { ...result, proofEvidence };
252
+ }
253
+
254
+ if (!outputs.find((item) => item.name === "result.json")) {
255
+ await saveJson("result", result);
256
+ }
257
+
258
+ return {
259
+ ok: true,
260
+ mode: "script",
261
+ runner: "local-playwright",
262
+ local: true,
263
+ outputs,
264
+ screenshots,
265
+ screenshot: screenshots[0],
266
+ console: consoleMessages,
267
+ result,
268
+ resultArtifacts,
269
+ };
270
+ }
271
+
272
+ function localServerPreviewAllowed() {
273
+ const runner = String(process.env.RIDDLE_PROOF_BROWSER_RUNNER || "").toLowerCase();
274
+ if (runner === "hosted" || runner === "riddle") return false;
275
+ if (truthy(process.env.RIDDLE_PROOF_DISABLE_LOCAL_SERVER_PREVIEW)) return false;
276
+ return true;
277
+ }
278
+
279
+ async function isPortAvailable(port) {
280
+ return new Promise((resolve) => {
281
+ const server = createServer();
282
+ server.once("error", () => resolve(false));
283
+ server.once("listening", () => server.close(() => resolve(true)));
284
+ server.listen(port, "127.0.0.1");
285
+ });
45
286
  }
46
287
 
47
- async function loadCore() {
288
+ async function availablePort(preferred) {
289
+ const start = numberValue(preferred, 3000);
290
+ for (let offset = 0; offset < 50; offset += 1) {
291
+ const port = start + offset;
292
+ if (await isPortAvailable(port)) return port;
293
+ }
294
+ throw new Error(`No available local preview port near ${start}`);
295
+ }
296
+
297
+ function appendLimited(lines, value, maxLength = 20_000) {
298
+ if (!value) return;
299
+ lines.push(String(value));
300
+ while (lines.join("").length > maxLength && lines.length > 1) lines.shift();
301
+ }
302
+
303
+ function localPreviewUrl(port, targetPath = "/") {
304
+ return new URL(targetPath || "/", `http://127.0.0.1:${port}/`).href;
305
+ }
306
+
307
+ async function waitForReadiness(url, timeoutMs, child, stdoutLines, stderrLines) {
308
+ const start = Date.now();
309
+ let lastError = "";
310
+ while (Date.now() - start < timeoutMs) {
311
+ if (child.exitCode !== null) {
312
+ const tail = [stdoutLines.join(""), stderrLines.join("")].filter(Boolean).join("\n").slice(-2000);
313
+ throw new Error(`local server preview command exited before readiness (${child.exitCode}). ${tail}`.trim());
314
+ }
315
+ try {
316
+ const response = await fetch(url, { redirect: "manual" });
317
+ if (response.status < 500) return;
318
+ lastError = `HTTP ${response.status}`;
319
+ } catch (err) {
320
+ lastError = err instanceof Error ? err.message : String(err);
321
+ }
322
+ await sleep(500);
323
+ }
324
+ throw new Error(`local server preview was not ready at ${url} within ${timeoutMs}ms${lastError ? `: ${lastError}` : ""}`);
325
+ }
326
+
327
+ function terminateChild(child) {
328
+ if (!child || child.exitCode !== null) return;
48
329
  try {
49
- return await import("@riddledc/openclaw-riddledc/core");
50
- } catch (primaryErr) {
51
- const candidates = [
52
- process.env.RIDDLE_OPENCLAW_CORE_PATH,
53
- "/root/.openclaw/extensions/openclaw-riddledc/dist/core.js",
54
- "/root/.openclaw/extensions/@riddledc/openclaw-riddledc/dist/core.js",
55
- "/root/.openclaw/extensions/node_modules/@riddledc/openclaw-riddledc/dist/core.js",
56
- "/usr/lib/node_modules/@riddledc/openclaw-riddledc/dist/core.js",
330
+ if (child.pid) process.kill(-child.pid, "SIGTERM");
331
+ } catch {
332
+ try {
333
+ child.kill("SIGTERM");
334
+ } catch {
335
+ // Best effort cleanup.
336
+ }
337
+ }
338
+ setTimeout(() => {
339
+ try {
340
+ if (child.exitCode === null && child.pid) process.kill(-child.pid, "SIGKILL");
341
+ } catch {
342
+ // Best effort cleanup.
343
+ }
344
+ }, 2000).unref?.();
345
+ }
346
+
347
+ async function runLocalServerPreview(rawArgs) {
348
+ if (!localServerPreviewAllowed()) {
349
+ return { ok: false, error: "Local Riddle Proof server preview is disabled." };
350
+ }
351
+
352
+ const args = normalizeServerArgs(rawArgs);
353
+ const directory = String(args.directory || "").trim();
354
+ const command = String(args.command || "").trim();
355
+ if (!directory || !existsSync(directory)) {
356
+ return { ok: false, error: `Local Riddle Proof server preview directory does not exist: ${directory || "(missing)"}` };
357
+ }
358
+ if (!command) {
359
+ return { ok: false, error: "Local Riddle Proof server preview requires a command." };
360
+ }
361
+
362
+ const port = await availablePort(args.port);
363
+ const previewUrl = `http://127.0.0.1:${port}/`;
364
+ const targetUrl = localPreviewUrl(port, args.path || "/");
365
+ const readinessUrl = localPreviewUrl(port, args.readiness_path || args.path || "/");
366
+ const readinessTimeoutMs = numberValue(args.readiness_timeout ?? args.readinessTimeout, 120) * 1000;
367
+ const stdoutLines = [];
368
+ const stderrLines = [];
369
+ const env = {
370
+ ...process.env,
371
+ ...(args.env || {}),
372
+ PORT: String(port),
373
+ HOSTNAME: String((args.env || {}).HOSTNAME || "127.0.0.1"),
374
+ };
375
+
376
+ const child = spawn(command, {
377
+ cwd: directory,
378
+ env,
379
+ shell: true,
380
+ detached: true,
381
+ stdio: ["ignore", "pipe", "pipe"],
382
+ });
383
+ child.stdout?.on("data", (chunk) => appendLimited(stdoutLines, chunk.toString()));
384
+ child.stderr?.on("data", (chunk) => appendLimited(stderrLines, chunk.toString()));
385
+
386
+ try {
387
+ await waitForReadiness(readinessUrl, readinessTimeoutMs, child, stdoutLines, stderrLines);
388
+ const waitUntil = String(args.wait_until || args.waitUntil || "domcontentloaded");
389
+ const captureTimeoutSec = numberValue(args.timeout_sec ?? args.timeout, 300);
390
+ const scriptParts = [
391
+ `await page.goto(${JSON.stringify(targetUrl)}, { waitUntil: ${JSON.stringify(waitUntil)}, timeout: ${JSON.stringify(captureTimeoutSec * 1000)} });`,
57
392
  ];
393
+ if (args.wait_for_selector || args.waitForSelector) {
394
+ scriptParts.push(`await page.waitForSelector(${JSON.stringify(args.wait_for_selector || args.waitForSelector)}, { timeout: 30000 });`);
395
+ }
396
+ if (args.script && String(args.script).trim()) {
397
+ scriptParts.push(String(args.script).trim());
398
+ } else {
399
+ scriptParts.push("await saveScreenshot('after-proof');");
400
+ }
401
+ const capture = await runLocalRiddleScript({
402
+ ...args,
403
+ url: targetUrl,
404
+ script: scriptParts.join("\n"),
405
+ timeout_sec: captureTimeoutSec,
406
+ });
407
+ return {
408
+ ...capture,
409
+ local: true,
410
+ runner: "local-server-preview",
411
+ preview_url: previewUrl,
412
+ target_url: targetUrl,
413
+ readiness_url: readinessUrl,
414
+ port,
415
+ stdout: stdoutLines.join("").slice(-8000),
416
+ stderr: stderrLines.join("").slice(-8000),
417
+ };
418
+ } catch (err) {
419
+ return {
420
+ ok: false,
421
+ mode: "server_preview",
422
+ runner: "local-server-preview",
423
+ error: err instanceof Error ? err.message : String(err),
424
+ preview_url: previewUrl,
425
+ target_url: targetUrl,
426
+ readiness_url: readinessUrl,
427
+ port,
428
+ stdout: stdoutLines.join("").slice(-8000),
429
+ stderr: stderrLines.join("").slice(-8000),
430
+ };
431
+ } finally {
432
+ terminateChild(child);
433
+ }
434
+ }
435
+
436
+ async function postRun(baseUrl, apiKey, payload) {
437
+ const response = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/run`, {
438
+ method: "POST",
439
+ headers: {
440
+ Authorization: `Bearer ${apiKey}`,
441
+ "Content-Type": "application/json",
442
+ },
443
+ body: JSON.stringify(payload),
444
+ });
445
+ return {
446
+ contentType: response.headers.get("content-type"),
447
+ body: await response.arrayBuffer(),
448
+ headers: response.headers,
449
+ status: response.status,
450
+ };
451
+ }
58
452
 
59
- for (const candidate of candidates) {
60
- const mod = await importFileIfExists(candidate);
61
- if (mod) return mod;
453
+ async function pollJobStatus(config, jobId, maxWaitMs) {
454
+ const start = Date.now();
455
+ const pollIntervalMs = 2000;
456
+
457
+ while (Date.now() - start < maxWaitMs) {
458
+ const response = await fetch(`${config.baseUrl.replace(/\/$/, "")}/v1/jobs/${jobId}`, {
459
+ headers: { Authorization: `Bearer ${config.apiKey}` },
460
+ });
461
+ if (!response.ok) {
462
+ return { status: "poll_error", error: "HTTP " + response.status };
463
+ }
464
+ const data = await response.json();
465
+ if (["completed", "completed_timeout", "completed_error", "failed"].includes(data.status)) {
466
+ return data;
62
467
  }
468
+ await sleep(pollIntervalMs);
469
+ }
63
470
 
64
- const err = primaryErr instanceof Error ? primaryErr.message : String(primaryErr);
65
- throw new Error(
66
- "Riddle core package not found. Install/upgrade @riddledc/openclaw-riddledc with the ./core export before running riddle-proof direct mode. Import error: " + err
67
- );
471
+ return { status: "poll_timeout", error: `Job ${jobId} did not complete within ${maxWaitMs}ms` };
472
+ }
473
+
474
+ async function fetchArtifactsAndBuild(config, jobId, include) {
475
+ const response = await fetch(
476
+ `${config.baseUrl.replace(/\/$/, "")}/v1/jobs/${jobId}/artifacts?include=${include.join(",")}`,
477
+ { headers: { Authorization: `Bearer ${config.apiKey}` } }
478
+ );
479
+ if (!response.ok) {
480
+ return { error: "Artifacts fetch failed: HTTP " + response.status };
481
+ }
482
+
483
+ const data = await response.json();
484
+ const artifacts = data.artifacts || [];
485
+ const result = { outputs: artifacts };
486
+ if (data.status) result._artifactsStatus = data.status;
487
+ if (data.timeout) result._timeout = data.timeout;
488
+ if (data.error) result._error = data.error;
489
+
490
+ const screenshots = artifacts.filter((artifact) => artifact.name && /\.(png|jpg|jpeg)$/i.test(artifact.name));
491
+ if (screenshots.length > 0) {
492
+ result.screenshots = [];
493
+ for (const screenshot of screenshots) {
494
+ if (!screenshot.url) continue;
495
+ try {
496
+ const imageResponse = await fetch(screenshot.url);
497
+ if (imageResponse.ok) {
498
+ const buffer = await imageResponse.arrayBuffer();
499
+ result.screenshots.push({
500
+ name: screenshot.name,
501
+ data: `data:image/png;base64,${Buffer.from(buffer).toString("base64")}`,
502
+ size: buffer.byteLength,
503
+ url: screenshot.url,
504
+ });
505
+ }
506
+ } catch {
507
+ // Screenshot artifacts are optional diagnostics.
508
+ }
509
+ }
510
+ if (result.screenshots.length > 0) {
511
+ result.screenshot = result.screenshots[0];
512
+ }
68
513
  }
514
+
515
+ const consoleArtifact = artifacts.find((artifact) => artifact.name === "console.json");
516
+ if (consoleArtifact?.url) {
517
+ try {
518
+ const consoleResponse = await fetch(consoleArtifact.url);
519
+ if (consoleResponse.ok) result.console = await consoleResponse.json();
520
+ } catch {
521
+ // Optional diagnostic.
522
+ }
523
+ }
524
+
525
+ const resultArtifact = artifacts.find((artifact) => artifact.name === "result.json");
526
+ if (resultArtifact?.url) {
527
+ try {
528
+ const resultResponse = await fetch(resultArtifact.url);
529
+ if (resultResponse.ok) result.result = await resultResponse.json();
530
+ } catch {
531
+ // Optional diagnostic.
532
+ }
533
+ }
534
+
535
+ const visualDiffArtifact = artifacts.find((artifact) => artifact.name === "visual-diff.json");
536
+ if (include.includes("visual_diff") && visualDiffArtifact?.url) {
537
+ try {
538
+ const visualDiffResponse = await fetch(visualDiffArtifact.url);
539
+ if (visualDiffResponse.ok) result.visual_diff = await visualDiffResponse.json();
540
+ } catch {
541
+ // Optional diagnostic.
542
+ }
543
+ }
544
+
545
+ const visualDiffImages = artifacts.filter((artifact) => /^visual-diff.*\.(png|jpg|jpeg)$/i.test(artifact.name || ""));
546
+ if (visualDiffImages.length > 0) {
547
+ result.visual_diff_images = visualDiffImages;
548
+ }
549
+
550
+ if (include.includes("har")) {
551
+ const harArtifact = artifacts.find((artifact) => artifact.name === "network.har");
552
+ if (harArtifact?.url) {
553
+ try {
554
+ const harResponse = await fetch(harArtifact.url);
555
+ if (harResponse.ok) result.har = await harResponse.json();
556
+ } catch {
557
+ // Optional diagnostic.
558
+ }
559
+ }
560
+ }
561
+
562
+ return result;
563
+ }
564
+
565
+ async function runWithDefaults(payload, defaults = {}) {
566
+ if (localRiddleScriptPreferred() && payload.script && localRiddleScriptAllowed()) {
567
+ try {
568
+ return await runLocalRiddleScript(payload, defaults);
569
+ } catch (err) {
570
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
571
+ }
572
+ }
573
+
574
+ let config;
575
+ try {
576
+ config = requireConfig(buildConfig());
577
+ } catch (err) {
578
+ if (payload.script && localRiddleScriptAllowed()) {
579
+ try {
580
+ return await runLocalRiddleScript(payload, defaults, err instanceof Error ? err.message : String(err));
581
+ } catch (localErr) {
582
+ return {
583
+ ok: false,
584
+ error: [
585
+ err instanceof Error ? err.message : String(err),
586
+ localErr instanceof Error ? localErr.message : String(localErr),
587
+ ].filter(Boolean).join(" Local fallback also failed: "),
588
+ };
589
+ }
590
+ }
591
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
592
+ }
593
+
594
+ const mode = detectMode(payload);
595
+ const userInclude = Array.isArray(payload.include) ? payload.include : [];
596
+ const returnAsync = !!defaults.returnAsync;
597
+ const merged = { ...payload };
598
+ if (returnAsync) merged.sync = false;
599
+ merged.include = Array.from(new Set([...userInclude, ...(defaults.include || ["screenshot", "console"])]));
600
+ merged.inlineConsole = merged.inlineConsole ?? true;
601
+ merged.inlineResult = merged.inlineResult ?? true;
602
+ if (userInclude.includes("har")) merged.inlineHar = true;
603
+
604
+ const out = { ok: true, mode };
605
+ const { contentType, body, headers, status } = await postRun(config.baseUrl, config.apiKey, merged);
606
+ out.rawContentType = contentType || undefined;
607
+
608
+ if (status === 408) {
609
+ let jobId = "";
610
+ try {
611
+ jobId = JSON.parse(Buffer.from(body).toString("utf8")).job_id || "";
612
+ } catch {
613
+ // Fall through.
614
+ }
615
+ if (!jobId) {
616
+ return { ...out, ok: false, error: "Sync poll timed out but no job_id in 408 response" };
617
+ }
618
+ out.job_id = jobId;
619
+ if (returnAsync) {
620
+ out.status = "submitted";
621
+ return out;
622
+ }
623
+ return finishPolledRun(config, out, jobId, merged);
624
+ }
625
+
626
+ if (status >= 400) {
627
+ const text = Buffer.from(body).toString("utf8");
628
+ try {
629
+ return { ...out, ok: false, error: JSON.parse(text) };
630
+ } catch {
631
+ return { ...out, ok: false, error: `HTTP ${status}: ${text.slice(0, 500)}` };
632
+ }
633
+ }
634
+
635
+ if (contentType && contentType.includes("image/png")) {
636
+ out.rawPngBase64 = abToBase64(body);
637
+ out.job_id = headers.get("x-job-id") || undefined;
638
+ const duration = headers.get("x-duration-ms");
639
+ out.duration_ms = duration ? Number(duration) : undefined;
640
+ out.sync = true;
641
+ return out;
642
+ }
643
+
644
+ const text = Buffer.from(body).toString("utf8");
645
+ const json = JSON.parse(text);
646
+ Object.assign(out, json);
647
+ out.job_id = json.job_id || json.jobId || out.job_id;
648
+
649
+ if (status === 202 && out.job_id && json.status_url) {
650
+ if (returnAsync) {
651
+ out.status = "submitted";
652
+ return out;
653
+ }
654
+ return finishPolledRun(config, out, out.job_id, merged);
655
+ }
656
+
657
+ if (json.status === "completed_timeout" || json.status === "completed_error") {
658
+ out.ok = false;
659
+ }
660
+ return out;
661
+ }
662
+
663
+ async function finishPolledRun(config, out, jobId, payload) {
664
+ const scriptTimeoutMs = ((payload.timeout_sec ?? payload.timeoutSec ?? 60) * 1000);
665
+ const jobStatus = await pollJobStatus(config, jobId, scriptTimeoutMs + 30_000);
666
+ if (["poll_timeout", "poll_error", "failed"].includes(jobStatus.status)) {
667
+ out.ok = false;
668
+ out.status = jobStatus.status;
669
+ out.error = jobStatus.error || `Job ${jobStatus.status}`;
670
+ return out;
671
+ }
672
+
673
+ const artifacts = await fetchArtifactsAndBuild(config, jobId, payload.include || []);
674
+ Object.assign(out, artifacts);
675
+ out.job_id = jobId;
676
+ out.status = jobStatus.status;
677
+ out.duration_ms = jobStatus.duration_ms;
678
+ if (jobStatus.status !== "completed") {
679
+ out.ok = false;
680
+ if (jobStatus.timeout) out.timeout = jobStatus.timeout;
681
+ if (jobStatus.error) out.error = jobStatus.error;
682
+ }
683
+ return out;
69
684
  }
70
685
 
71
686
  function numberValue(value, fallback) {
@@ -99,38 +714,35 @@ function normalizeJobId(args) {
99
714
  }
100
715
 
101
716
  async function run(tool, args) {
102
- const core = await loadCore();
103
- const config = buildConfig();
104
-
105
717
  if (tool === "riddle_preview") {
106
- return core.createStaticPreview(config, {
107
- directory: args.directory ?? args.dir,
108
- framework: args.framework,
109
- });
718
+ return {
719
+ ok: false,
720
+ error: "Direct Riddle Proof preview creation is not wired in this runtime bridge yet. Use remote/no-diff evidence or add generic preview support before enabling implementation previews.",
721
+ };
110
722
  }
111
723
 
112
724
  if (tool === "riddle_preview_delete") {
113
- return core.deleteStaticPreview(config, args.id);
725
+ return { ok: false, error: "Direct Riddle Proof preview deletion is not wired in this runtime bridge yet." };
114
726
  }
115
727
 
116
728
  if (tool === "riddle_server_preview") {
117
- return core.createServerPreview(config, normalizeServerArgs(args));
729
+ return runLocalServerPreview(args);
118
730
  }
119
731
 
120
732
  if (tool === "riddle_server_preview_status") {
121
- return core.getServerPreviewStatus(config, normalizeJobId(args));
733
+ return { ok: false, error: "Direct Riddle Proof server preview status is not wired in this runtime bridge yet." };
122
734
  }
123
735
 
124
736
  if (tool === "riddle_build_preview") {
125
- return core.createBuildPreview(config, normalizeBuildArgs(args));
737
+ return { ok: false, error: "Direct Riddle Proof build preview is not wired in this runtime bridge yet." };
126
738
  }
127
739
 
128
740
  if (tool === "riddle_build_preview_status") {
129
- return core.getBuildPreviewStatus(config, normalizeJobId(args));
741
+ return { ok: false, error: "Direct Riddle Proof build preview status is not wired in this runtime bridge yet." };
130
742
  }
131
743
 
132
744
  if (tool === "riddle_script") {
133
- return core.runWithDefaults(config, args, {
745
+ return runWithDefaults(args, {
134
746
  include: TOOL_DEFAULT_INCLUDE,
135
747
  returnAsync: !!args.async,
136
748
  });
@@ -145,7 +757,7 @@ async function run(tool, args) {
145
757
  ...visualDiffOptions
146
758
  } = args;
147
759
  const script = `return await visualDiff(${JSON.stringify(visualDiffOptions)});`;
148
- return core.runWithDefaults(config, {
760
+ return runWithDefaults({
149
761
  url: args.url_before,
150
762
  script,
151
763
  options: { ...(options ?? {}), returnResult: true },
@@ -159,7 +771,7 @@ async function run(tool, args) {
159
771
  }
160
772
 
161
773
  if (tool === "riddle_run") {
162
- return core.runWithDefaults(config, args.payload ?? args, {
774
+ return runWithDefaults(args.payload ?? args, {
163
775
  include: ["screenshot", "console", "result"],
164
776
  returnAsync: !!args.async,
165
777
  });
@@ -174,12 +786,22 @@ async function main() {
174
786
  if (!tool) throw new Error("Usage: riddle_core_call.mjs <tool> <json-args>");
175
787
  const args = JSON.parse(rawArgs);
176
788
  const result = await run(tool, args);
177
- console.log(JSON.stringify(result));
789
+ writeResult(result);
178
790
  }
179
791
 
180
792
  main().catch((err) => {
181
- console.log(JSON.stringify({
793
+ writeResult({
182
794
  ok: false,
183
795
  error: err instanceof Error ? err.message : String(err),
184
- }));
796
+ });
185
797
  });
798
+
799
+ function writeResult(result) {
800
+ const encoded = JSON.stringify(result);
801
+ if (process.env.RIDDLE_PROOF_DIRECT_RESULT_FILE) {
802
+ writeFileSync(process.env.RIDDLE_PROOF_DIRECT_RESULT_FILE, encoded);
803
+ process.exit(0);
804
+ return;
805
+ }
806
+ console.log(encoded);
807
+ }