@riddledc/riddle-proof 0.7.23 → 0.7.25

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/README.md CHANGED
@@ -339,6 +339,16 @@ that performs the interaction. If proof is weak, keep the diagnostics history in
339
339
  the run state so the next agent can see the last route, tool status, artifact
340
340
  shape, and errors without rerunning blind.
341
341
 
342
+ ### Riddle Job Polling
343
+
344
+ `riddle-proof-loop riddle-poll <job-id> --wait` keeps stdout as JSON and writes
345
+ human progress lines to stderr while waiting. The JSON result includes
346
+ `poll.timed_out`, `poll.elapsed_ms`, `poll.queue_elapsed_ms`, and
347
+ `poll.running_without_submission` so delayed dispatch is distinguishable from a
348
+ terminal proof failure. If `--wait` exhausts its attempts before a terminal job
349
+ status, the command exits non-zero and the result explains the last observed
350
+ status and `submitted_at` state.
351
+
342
352
  ## OpenClaw Adapter Boundary
343
353
 
344
354
  `@riddledc/riddle-proof/openclaw` translates OpenClaw Riddle Proof tool params
@@ -196,21 +196,109 @@ async function runRiddleScript(config, input) {
196
196
  function isTerminalRiddleJobStatus(status) {
197
197
  return ["completed", "complete", "completed_error", "completed_timeout", "failed"].includes(String(status || ""));
198
198
  }
199
+ function stringField(record, key) {
200
+ const value = record?.[key];
201
+ return typeof value === "string" && value.trim() ? value.trim() : null;
202
+ }
203
+ function parseTimestampMs(value) {
204
+ if (!value) return null;
205
+ const parsed = Date.parse(value);
206
+ return Number.isFinite(parsed) ? parsed : null;
207
+ }
208
+ function buildPollSnapshot(jobId, job, input) {
209
+ const status = job?.status ? String(job.status) : null;
210
+ const terminal = isTerminalRiddleJobStatus(status);
211
+ const createdAt = stringField(job, "created_at");
212
+ const submittedAt = stringField(job, "submitted_at");
213
+ const completedAt = stringField(job, "completed_at");
214
+ const createdMs = parseTimestampMs(createdAt);
215
+ const submittedMs = parseTimestampMs(submittedAt);
216
+ let queueElapsedMs = null;
217
+ if (createdMs !== null && submittedMs !== null) {
218
+ queueElapsedMs = Math.max(0, submittedMs - createdMs);
219
+ } else if (createdMs !== null && !submittedAt && !terminal) {
220
+ queueElapsedMs = Math.max(0, input.observedAt - createdMs);
221
+ }
222
+ return {
223
+ job_id: jobId,
224
+ status,
225
+ terminal,
226
+ attempt: input.attempt,
227
+ attempts: input.attempts,
228
+ elapsed_ms: Math.max(0, input.observedAt - input.startedAt),
229
+ created_at: createdAt,
230
+ submitted_at: submittedAt,
231
+ completed_at: completedAt,
232
+ queue_elapsed_ms: queueElapsedMs,
233
+ running_without_submission: Boolean(status && !terminal && !submittedAt)
234
+ };
235
+ }
236
+ function pollMessage(snapshot, timedOut) {
237
+ if (!timedOut) return void 0;
238
+ const submitted = snapshot.submitted_at || "not submitted";
239
+ const queue = snapshot.queue_elapsed_ms !== null ? ` queue_elapsed_ms=${snapshot.queue_elapsed_ms}` : "";
240
+ return `Riddle job ${snapshot.job_id} did not reach a terminal status after ${snapshot.attempt} poll attempts; status=${snapshot.status || "unknown"} submitted_at=${submitted}.${queue}`;
241
+ }
199
242
  async function pollRiddleJob(config, jobId, options = {}) {
200
243
  if (!jobId?.trim()) throw new Error("jobId is required");
201
- const attempts = options.attempts || (options.wait ? 60 : 1);
202
- const intervalMs = options.intervalMs || 2e3;
244
+ const attempts = Math.max(1, Math.floor(options.attempts ?? (options.wait ? 300 : 1)));
245
+ const intervalMs = Math.max(0, Math.floor(options.intervalMs ?? 2e3));
246
+ const progressEveryMs = Math.max(0, Math.floor(options.progressEveryMs ?? 1e4));
247
+ const startedAt = Date.now();
203
248
  let job = null;
249
+ let lastSnapshot = null;
250
+ let lastProgressAt = 0;
251
+ let lastProgressKey = "";
204
252
  for (let index = 0; index < attempts; index += 1) {
205
253
  job = await riddleRequestJson(config, `/v1/jobs/${jobId}`);
206
- if (isTerminalRiddleJobStatus(job.status)) break;
254
+ const observedAt = Date.now();
255
+ lastSnapshot = buildPollSnapshot(jobId, job, {
256
+ attempt: index + 1,
257
+ attempts,
258
+ startedAt,
259
+ observedAt
260
+ });
261
+ const progressKey = [
262
+ lastSnapshot.status || "unknown",
263
+ lastSnapshot.terminal ? "terminal" : "nonterminal",
264
+ lastSnapshot.submitted_at ? "submitted" : "unsubmitted"
265
+ ].join(":");
266
+ if (options.onProgress) {
267
+ const shouldReport = index === 0 || lastSnapshot.terminal || progressKey !== lastProgressKey || observedAt - lastProgressAt >= progressEveryMs;
268
+ if (shouldReport) {
269
+ lastProgressAt = observedAt;
270
+ lastProgressKey = progressKey;
271
+ await options.onProgress(lastSnapshot);
272
+ }
273
+ }
274
+ if (lastSnapshot.terminal) break;
207
275
  if (index + 1 < attempts) {
208
276
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
209
277
  }
210
278
  }
211
279
  const status = job?.status ? String(job.status) : null;
280
+ const fallbackObservedAt = Date.now();
281
+ const snapshot = lastSnapshot || buildPollSnapshot(jobId, job, {
282
+ attempt: 0,
283
+ attempts,
284
+ startedAt,
285
+ observedAt: fallbackObservedAt
286
+ });
212
287
  if (!isTerminalRiddleJobStatus(status)) {
213
- return { ok: true, job_id: jobId, status, terminal: false, job };
288
+ const timedOut = Boolean(options.wait);
289
+ return {
290
+ ok: !timedOut,
291
+ job_id: jobId,
292
+ status,
293
+ terminal: false,
294
+ job,
295
+ poll: {
296
+ ...snapshot,
297
+ timed_out: timedOut,
298
+ interval_ms: intervalMs,
299
+ message: pollMessage(snapshot, timedOut)
300
+ }
301
+ };
214
302
  }
215
303
  const artifacts = await riddleRequestJson(config, `/v1/jobs/${jobId}/artifacts`);
216
304
  return {
@@ -219,7 +307,12 @@ async function pollRiddleJob(config, jobId, options = {}) {
219
307
  status,
220
308
  terminal: true,
221
309
  job,
222
- artifacts
310
+ artifacts,
311
+ poll: {
312
+ ...snapshot,
313
+ timed_out: false,
314
+ interval_ms: intervalMs
315
+ }
223
316
  };
224
317
  }
225
318
  function createRiddleApiClient(config = {}) {
@@ -1121,6 +1121,20 @@ async function captureViewport(viewport) {
1121
1121
  const scrollWidth = documentElement ? documentElement.scrollWidth : 0;
1122
1122
  const viewportWidth = clientWidth || window.innerWidth;
1123
1123
  const overflowOffenders = [];
1124
+ function isContainedByHorizontalScroller(element) {
1125
+ let current = element.parentElement;
1126
+ while (current && current !== body && current !== documentElement) {
1127
+ const style = window.getComputedStyle(current);
1128
+ const overflowX = style.overflowX || style.overflow || "";
1129
+ if ((overflowX === "auto" || overflowX === "scroll") && current.scrollWidth > current.clientWidth + 1) {
1130
+ const currentRect = current.getBoundingClientRect();
1131
+ const contained = currentRect.left >= -0.5 && currentRect.right <= viewportWidth + 0.5;
1132
+ if (contained) return true;
1133
+ }
1134
+ current = current.parentElement;
1135
+ }
1136
+ return false;
1137
+ }
1124
1138
  for (const element of Array.from(body ? body.querySelectorAll("*") : [])) {
1125
1139
  const rect = element.getBoundingClientRect();
1126
1140
  if (!rect || rect.width < 1 || rect.height < 1) continue;
@@ -1130,6 +1144,7 @@ async function captureViewport(viewport) {
1130
1144
  const rightOverflow = Math.max(0, rect.right - viewportWidth);
1131
1145
  const overflow = Math.max(leftOverflow, rightOverflow);
1132
1146
  if (overflow <= 0.5) continue;
1147
+ if (isContainedByHorizontalScroller(element)) continue;
1133
1148
  const tag = element.tagName ? element.tagName.toLowerCase() : "element";
1134
1149
  const id = element.id ? "#" + element.id : "";
1135
1150
  const className = typeof element.className === "string"
@@ -1161,6 +1176,7 @@ async function captureViewport(viewport) {
1161
1176
  pathname: location.pathname,
1162
1177
  title: document.title,
1163
1178
  body_text_length: text.length,
1179
+ body_text: text,
1164
1180
  body_text_sample: text.slice(0, 8000),
1165
1181
  scroll_width: scrollWidth,
1166
1182
  client_width: clientWidth,
@@ -1186,7 +1202,7 @@ async function captureViewport(viewport) {
1186
1202
  selectors[check.selector] = await selectorStats(check.selector);
1187
1203
  }
1188
1204
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
1189
- text_matches[textKey(check)] = textMatches(dom.body_text_sample || "", check);
1205
+ text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
1190
1206
  }
1191
1207
  }
1192
1208
  const screenshotLabel = profileSlug + "-" + viewport.name;
package/dist/cli.cjs CHANGED
@@ -6743,21 +6743,109 @@ async function runRiddleScript(config, input) {
6743
6743
  function isTerminalRiddleJobStatus(status) {
6744
6744
  return ["completed", "complete", "completed_error", "completed_timeout", "failed"].includes(String(status || ""));
6745
6745
  }
6746
+ function stringField(record, key) {
6747
+ const value = record?.[key];
6748
+ return typeof value === "string" && value.trim() ? value.trim() : null;
6749
+ }
6750
+ function parseTimestampMs(value) {
6751
+ if (!value) return null;
6752
+ const parsed = Date.parse(value);
6753
+ return Number.isFinite(parsed) ? parsed : null;
6754
+ }
6755
+ function buildPollSnapshot(jobId, job, input) {
6756
+ const status = job?.status ? String(job.status) : null;
6757
+ const terminal = isTerminalRiddleJobStatus(status);
6758
+ const createdAt = stringField(job, "created_at");
6759
+ const submittedAt = stringField(job, "submitted_at");
6760
+ const completedAt = stringField(job, "completed_at");
6761
+ const createdMs = parseTimestampMs(createdAt);
6762
+ const submittedMs = parseTimestampMs(submittedAt);
6763
+ let queueElapsedMs = null;
6764
+ if (createdMs !== null && submittedMs !== null) {
6765
+ queueElapsedMs = Math.max(0, submittedMs - createdMs);
6766
+ } else if (createdMs !== null && !submittedAt && !terminal) {
6767
+ queueElapsedMs = Math.max(0, input.observedAt - createdMs);
6768
+ }
6769
+ return {
6770
+ job_id: jobId,
6771
+ status,
6772
+ terminal,
6773
+ attempt: input.attempt,
6774
+ attempts: input.attempts,
6775
+ elapsed_ms: Math.max(0, input.observedAt - input.startedAt),
6776
+ created_at: createdAt,
6777
+ submitted_at: submittedAt,
6778
+ completed_at: completedAt,
6779
+ queue_elapsed_ms: queueElapsedMs,
6780
+ running_without_submission: Boolean(status && !terminal && !submittedAt)
6781
+ };
6782
+ }
6783
+ function pollMessage(snapshot, timedOut) {
6784
+ if (!timedOut) return void 0;
6785
+ const submitted = snapshot.submitted_at || "not submitted";
6786
+ const queue = snapshot.queue_elapsed_ms !== null ? ` queue_elapsed_ms=${snapshot.queue_elapsed_ms}` : "";
6787
+ return `Riddle job ${snapshot.job_id} did not reach a terminal status after ${snapshot.attempt} poll attempts; status=${snapshot.status || "unknown"} submitted_at=${submitted}.${queue}`;
6788
+ }
6746
6789
  async function pollRiddleJob(config, jobId, options = {}) {
6747
6790
  if (!jobId?.trim()) throw new Error("jobId is required");
6748
- const attempts = options.attempts || (options.wait ? 60 : 1);
6749
- const intervalMs = options.intervalMs || 2e3;
6791
+ const attempts = Math.max(1, Math.floor(options.attempts ?? (options.wait ? 300 : 1)));
6792
+ const intervalMs = Math.max(0, Math.floor(options.intervalMs ?? 2e3));
6793
+ const progressEveryMs = Math.max(0, Math.floor(options.progressEveryMs ?? 1e4));
6794
+ const startedAt = Date.now();
6750
6795
  let job = null;
6796
+ let lastSnapshot = null;
6797
+ let lastProgressAt = 0;
6798
+ let lastProgressKey = "";
6751
6799
  for (let index = 0; index < attempts; index += 1) {
6752
6800
  job = await riddleRequestJson(config, `/v1/jobs/${jobId}`);
6753
- if (isTerminalRiddleJobStatus(job.status)) break;
6801
+ const observedAt = Date.now();
6802
+ lastSnapshot = buildPollSnapshot(jobId, job, {
6803
+ attempt: index + 1,
6804
+ attempts,
6805
+ startedAt,
6806
+ observedAt
6807
+ });
6808
+ const progressKey = [
6809
+ lastSnapshot.status || "unknown",
6810
+ lastSnapshot.terminal ? "terminal" : "nonterminal",
6811
+ lastSnapshot.submitted_at ? "submitted" : "unsubmitted"
6812
+ ].join(":");
6813
+ if (options.onProgress) {
6814
+ const shouldReport = index === 0 || lastSnapshot.terminal || progressKey !== lastProgressKey || observedAt - lastProgressAt >= progressEveryMs;
6815
+ if (shouldReport) {
6816
+ lastProgressAt = observedAt;
6817
+ lastProgressKey = progressKey;
6818
+ await options.onProgress(lastSnapshot);
6819
+ }
6820
+ }
6821
+ if (lastSnapshot.terminal) break;
6754
6822
  if (index + 1 < attempts) {
6755
6823
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
6756
6824
  }
6757
6825
  }
6758
6826
  const status = job?.status ? String(job.status) : null;
6827
+ const fallbackObservedAt = Date.now();
6828
+ const snapshot = lastSnapshot || buildPollSnapshot(jobId, job, {
6829
+ attempt: 0,
6830
+ attempts,
6831
+ startedAt,
6832
+ observedAt: fallbackObservedAt
6833
+ });
6759
6834
  if (!isTerminalRiddleJobStatus(status)) {
6760
- return { ok: true, job_id: jobId, status, terminal: false, job };
6835
+ const timedOut = Boolean(options.wait);
6836
+ return {
6837
+ ok: !timedOut,
6838
+ job_id: jobId,
6839
+ status,
6840
+ terminal: false,
6841
+ job,
6842
+ poll: {
6843
+ ...snapshot,
6844
+ timed_out: timedOut,
6845
+ interval_ms: intervalMs,
6846
+ message: pollMessage(snapshot, timedOut)
6847
+ }
6848
+ };
6761
6849
  }
6762
6850
  const artifacts = await riddleRequestJson(config, `/v1/jobs/${jobId}/artifacts`);
6763
6851
  return {
@@ -6766,7 +6854,12 @@ async function pollRiddleJob(config, jobId, options = {}) {
6766
6854
  status,
6767
6855
  terminal: true,
6768
6856
  job,
6769
- artifacts
6857
+ artifacts,
6858
+ poll: {
6859
+ ...snapshot,
6860
+ timed_out: false,
6861
+ interval_ms: intervalMs
6862
+ }
6770
6863
  };
6771
6864
  }
6772
6865
  function createRiddleApiClient(config = {}) {
@@ -7885,6 +7978,20 @@ async function captureViewport(viewport) {
7885
7978
  const scrollWidth = documentElement ? documentElement.scrollWidth : 0;
7886
7979
  const viewportWidth = clientWidth || window.innerWidth;
7887
7980
  const overflowOffenders = [];
7981
+ function isContainedByHorizontalScroller(element) {
7982
+ let current = element.parentElement;
7983
+ while (current && current !== body && current !== documentElement) {
7984
+ const style = window.getComputedStyle(current);
7985
+ const overflowX = style.overflowX || style.overflow || "";
7986
+ if ((overflowX === "auto" || overflowX === "scroll") && current.scrollWidth > current.clientWidth + 1) {
7987
+ const currentRect = current.getBoundingClientRect();
7988
+ const contained = currentRect.left >= -0.5 && currentRect.right <= viewportWidth + 0.5;
7989
+ if (contained) return true;
7990
+ }
7991
+ current = current.parentElement;
7992
+ }
7993
+ return false;
7994
+ }
7888
7995
  for (const element of Array.from(body ? body.querySelectorAll("*") : [])) {
7889
7996
  const rect = element.getBoundingClientRect();
7890
7997
  if (!rect || rect.width < 1 || rect.height < 1) continue;
@@ -7894,6 +8001,7 @@ async function captureViewport(viewport) {
7894
8001
  const rightOverflow = Math.max(0, rect.right - viewportWidth);
7895
8002
  const overflow = Math.max(leftOverflow, rightOverflow);
7896
8003
  if (overflow <= 0.5) continue;
8004
+ if (isContainedByHorizontalScroller(element)) continue;
7897
8005
  const tag = element.tagName ? element.tagName.toLowerCase() : "element";
7898
8006
  const id = element.id ? "#" + element.id : "";
7899
8007
  const className = typeof element.className === "string"
@@ -7925,6 +8033,7 @@ async function captureViewport(viewport) {
7925
8033
  pathname: location.pathname,
7926
8034
  title: document.title,
7927
8035
  body_text_length: text.length,
8036
+ body_text: text,
7928
8037
  body_text_sample: text.slice(0, 8000),
7929
8038
  scroll_width: scrollWidth,
7930
8039
  client_width: clientWidth,
@@ -7950,7 +8059,7 @@ async function captureViewport(viewport) {
7950
8059
  selectors[check.selector] = await selectorStats(check.selector);
7951
8060
  }
7952
8061
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
7953
- text_matches[textKey(check)] = textMatches(dom.body_text_sample || "", check);
8062
+ text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
7954
8063
  }
7955
8064
  }
7956
8065
  const screenshotLabel = profileSlug + "-" + viewport.name;
@@ -8140,7 +8249,7 @@ function usage() {
8140
8249
  " riddle-proof-loop riddle-preview-deploy <build-dir> <label>",
8141
8250
  " riddle-proof-loop riddle-server-preview <directory> --script-file <file> [--path /route] [--wait-for-selector selector]",
8142
8251
  " riddle-proof-loop riddle-run-script --url <url> --script-file <file> [--viewport 1280x720]",
8143
- " riddle-proof-loop riddle-poll <job-id> [--wait]",
8252
+ " riddle-proof-loop riddle-poll <job-id> [--wait] [--attempts n] [--quiet]",
8144
8253
  " riddle-proof-loop doctor local [--codex-command <path>]",
8145
8254
  "",
8146
8255
  "The default CLI run mode is checkpoint-mode=yield unless --agent local is used.",
@@ -8174,6 +8283,26 @@ function optionString(options, key) {
8174
8283
  function readStdin() {
8175
8284
  return (0, import_node_fs6.readFileSync)(0, "utf-8");
8176
8285
  }
8286
+ function formatPollDuration(ms) {
8287
+ if (typeof ms !== "number" || !Number.isFinite(ms)) return "n/a";
8288
+ const seconds = Math.max(0, Math.round(ms / 1e3));
8289
+ const minutes = Math.floor(seconds / 60);
8290
+ const remainder = seconds % 60;
8291
+ return minutes > 0 ? `${minutes}m${String(remainder).padStart(2, "0")}s` : `${seconds}s`;
8292
+ }
8293
+ function riddlePollProgressLine(snapshot) {
8294
+ const submittedAt = snapshot.submitted_at || "not-submitted";
8295
+ const queuePart = snapshot.running_without_submission ? ` queued_for=${formatPollDuration(snapshot.queue_elapsed_ms)}` : snapshot.queue_elapsed_ms !== null ? ` queue=${formatPollDuration(snapshot.queue_elapsed_ms)}` : "";
8296
+ const terminalPart = snapshot.terminal ? " terminal=true" : "";
8297
+ return [
8298
+ "[riddle-poll]",
8299
+ snapshot.job_id,
8300
+ `status=${snapshot.status || "unknown"}`,
8301
+ `attempt=${snapshot.attempt}/${snapshot.attempts}`,
8302
+ `elapsed=${formatPollDuration(snapshot.elapsed_ms)}`,
8303
+ `submitted_at=${submittedAt}${queuePart}${terminalPart}`
8304
+ ].join(" ");
8305
+ }
8177
8306
  function readJsonValue(value, label) {
8178
8307
  if (!value) throw new Error(`${label} is required.`);
8179
8308
  const raw = value === "-" ? readStdin() : (0, import_node_fs6.existsSync)(value) ? (0, import_node_fs6.readFileSync)(value, "utf-8") : value;
@@ -8605,10 +8734,16 @@ async function main() {
8605
8734
  if (command === "riddle-poll") {
8606
8735
  const jobId = positional[1];
8607
8736
  if (!jobId) throw new Error("riddle-poll requires <job-id>.");
8737
+ const wait = options.wait === true;
8608
8738
  const result = await createRiddleApiClient(riddleClientConfig(options)).pollJob(jobId, {
8609
- wait: options.wait === true,
8739
+ wait,
8610
8740
  attempts: optionString(options, "attempts") ? Number(optionString(options, "attempts")) : void 0,
8611
- intervalMs: optionString(options, "intervalMs") ? Number(optionString(options, "intervalMs")) : void 0
8741
+ intervalMs: optionString(options, "intervalMs") ? Number(optionString(options, "intervalMs")) : void 0,
8742
+ progressEveryMs: optionString(options, "progressEveryMs") ? Number(optionString(options, "progressEveryMs")) : void 0,
8743
+ onProgress: wait && options.quiet !== true ? (snapshot) => {
8744
+ process.stderr.write(`${riddlePollProgressLine(snapshot)}
8745
+ `);
8746
+ } : void 0
8612
8747
  });
8613
8748
  process.stdout.write(`${JSON.stringify(result, null, 2)}
8614
8749
  `);
package/dist/cli.js CHANGED
@@ -10,11 +10,11 @@ import {
10
10
  profileStatusExitCode,
11
11
  resolveRiddleProofProfileTargetUrl,
12
12
  resolveRiddleProofProfileTimeoutSec
13
- } from "./chunk-XOM2OYAB.js";
13
+ } from "./chunk-SDSBS64X.js";
14
14
  import {
15
15
  createRiddleApiClient,
16
16
  parseRiddleViewport
17
- } from "./chunk-5KOHSE4V.js";
17
+ } from "./chunk-7PSWK57T.js";
18
18
  import {
19
19
  createDisabledRiddleProofAgentAdapter,
20
20
  readRiddleProofRunStatus,
@@ -48,7 +48,7 @@ function usage() {
48
48
  " riddle-proof-loop riddle-preview-deploy <build-dir> <label>",
49
49
  " riddle-proof-loop riddle-server-preview <directory> --script-file <file> [--path /route] [--wait-for-selector selector]",
50
50
  " riddle-proof-loop riddle-run-script --url <url> --script-file <file> [--viewport 1280x720]",
51
- " riddle-proof-loop riddle-poll <job-id> [--wait]",
51
+ " riddle-proof-loop riddle-poll <job-id> [--wait] [--attempts n] [--quiet]",
52
52
  " riddle-proof-loop doctor local [--codex-command <path>]",
53
53
  "",
54
54
  "The default CLI run mode is checkpoint-mode=yield unless --agent local is used.",
@@ -82,6 +82,26 @@ function optionString(options, key) {
82
82
  function readStdin() {
83
83
  return readFileSync(0, "utf-8");
84
84
  }
85
+ function formatPollDuration(ms) {
86
+ if (typeof ms !== "number" || !Number.isFinite(ms)) return "n/a";
87
+ const seconds = Math.max(0, Math.round(ms / 1e3));
88
+ const minutes = Math.floor(seconds / 60);
89
+ const remainder = seconds % 60;
90
+ return minutes > 0 ? `${minutes}m${String(remainder).padStart(2, "0")}s` : `${seconds}s`;
91
+ }
92
+ function riddlePollProgressLine(snapshot) {
93
+ const submittedAt = snapshot.submitted_at || "not-submitted";
94
+ const queuePart = snapshot.running_without_submission ? ` queued_for=${formatPollDuration(snapshot.queue_elapsed_ms)}` : snapshot.queue_elapsed_ms !== null ? ` queue=${formatPollDuration(snapshot.queue_elapsed_ms)}` : "";
95
+ const terminalPart = snapshot.terminal ? " terminal=true" : "";
96
+ return [
97
+ "[riddle-poll]",
98
+ snapshot.job_id,
99
+ `status=${snapshot.status || "unknown"}`,
100
+ `attempt=${snapshot.attempt}/${snapshot.attempts}`,
101
+ `elapsed=${formatPollDuration(snapshot.elapsed_ms)}`,
102
+ `submitted_at=${submittedAt}${queuePart}${terminalPart}`
103
+ ].join(" ");
104
+ }
85
105
  function readJsonValue(value, label) {
86
106
  if (!value) throw new Error(`${label} is required.`);
87
107
  const raw = value === "-" ? readStdin() : existsSync(value) ? readFileSync(value, "utf-8") : value;
@@ -513,10 +533,16 @@ async function main() {
513
533
  if (command === "riddle-poll") {
514
534
  const jobId = positional[1];
515
535
  if (!jobId) throw new Error("riddle-poll requires <job-id>.");
536
+ const wait = options.wait === true;
516
537
  const result = await createRiddleApiClient(riddleClientConfig(options)).pollJob(jobId, {
517
- wait: options.wait === true,
538
+ wait,
518
539
  attempts: optionString(options, "attempts") ? Number(optionString(options, "attempts")) : void 0,
519
- intervalMs: optionString(options, "intervalMs") ? Number(optionString(options, "intervalMs")) : void 0
540
+ intervalMs: optionString(options, "intervalMs") ? Number(optionString(options, "intervalMs")) : void 0,
541
+ progressEveryMs: optionString(options, "progressEveryMs") ? Number(optionString(options, "progressEveryMs")) : void 0,
542
+ onProgress: wait && options.quiet !== true ? (snapshot) => {
543
+ process.stderr.write(`${riddlePollProgressLine(snapshot)}
544
+ `);
545
+ } : void 0
520
546
  });
521
547
  process.stdout.write(`${JSON.stringify(result, null, 2)}
522
548
  `);
package/dist/index.cjs CHANGED
@@ -9835,6 +9835,20 @@ async function captureViewport(viewport) {
9835
9835
  const scrollWidth = documentElement ? documentElement.scrollWidth : 0;
9836
9836
  const viewportWidth = clientWidth || window.innerWidth;
9837
9837
  const overflowOffenders = [];
9838
+ function isContainedByHorizontalScroller(element) {
9839
+ let current = element.parentElement;
9840
+ while (current && current !== body && current !== documentElement) {
9841
+ const style = window.getComputedStyle(current);
9842
+ const overflowX = style.overflowX || style.overflow || "";
9843
+ if ((overflowX === "auto" || overflowX === "scroll") && current.scrollWidth > current.clientWidth + 1) {
9844
+ const currentRect = current.getBoundingClientRect();
9845
+ const contained = currentRect.left >= -0.5 && currentRect.right <= viewportWidth + 0.5;
9846
+ if (contained) return true;
9847
+ }
9848
+ current = current.parentElement;
9849
+ }
9850
+ return false;
9851
+ }
9838
9852
  for (const element of Array.from(body ? body.querySelectorAll("*") : [])) {
9839
9853
  const rect = element.getBoundingClientRect();
9840
9854
  if (!rect || rect.width < 1 || rect.height < 1) continue;
@@ -9844,6 +9858,7 @@ async function captureViewport(viewport) {
9844
9858
  const rightOverflow = Math.max(0, rect.right - viewportWidth);
9845
9859
  const overflow = Math.max(leftOverflow, rightOverflow);
9846
9860
  if (overflow <= 0.5) continue;
9861
+ if (isContainedByHorizontalScroller(element)) continue;
9847
9862
  const tag = element.tagName ? element.tagName.toLowerCase() : "element";
9848
9863
  const id = element.id ? "#" + element.id : "";
9849
9864
  const className = typeof element.className === "string"
@@ -9875,6 +9890,7 @@ async function captureViewport(viewport) {
9875
9890
  pathname: location.pathname,
9876
9891
  title: document.title,
9877
9892
  body_text_length: text.length,
9893
+ body_text: text,
9878
9894
  body_text_sample: text.slice(0, 8000),
9879
9895
  scroll_width: scrollWidth,
9880
9896
  client_width: clientWidth,
@@ -9900,7 +9916,7 @@ async function captureViewport(viewport) {
9900
9916
  selectors[check.selector] = await selectorStats(check.selector);
9901
9917
  }
9902
9918
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
9903
- text_matches[textKey(check)] = textMatches(dom.body_text_sample || "", check);
9919
+ text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
9904
9920
  }
9905
9921
  }
9906
9922
  const screenshotLabel = profileSlug + "-" + viewport.name;
@@ -10275,21 +10291,109 @@ async function runRiddleScript(config, input) {
10275
10291
  function isTerminalRiddleJobStatus(status) {
10276
10292
  return ["completed", "complete", "completed_error", "completed_timeout", "failed"].includes(String(status || ""));
10277
10293
  }
10294
+ function stringField(record, key) {
10295
+ const value = record?.[key];
10296
+ return typeof value === "string" && value.trim() ? value.trim() : null;
10297
+ }
10298
+ function parseTimestampMs(value) {
10299
+ if (!value) return null;
10300
+ const parsed = Date.parse(value);
10301
+ return Number.isFinite(parsed) ? parsed : null;
10302
+ }
10303
+ function buildPollSnapshot(jobId, job, input) {
10304
+ const status = job?.status ? String(job.status) : null;
10305
+ const terminal = isTerminalRiddleJobStatus(status);
10306
+ const createdAt = stringField(job, "created_at");
10307
+ const submittedAt = stringField(job, "submitted_at");
10308
+ const completedAt = stringField(job, "completed_at");
10309
+ const createdMs = parseTimestampMs(createdAt);
10310
+ const submittedMs = parseTimestampMs(submittedAt);
10311
+ let queueElapsedMs = null;
10312
+ if (createdMs !== null && submittedMs !== null) {
10313
+ queueElapsedMs = Math.max(0, submittedMs - createdMs);
10314
+ } else if (createdMs !== null && !submittedAt && !terminal) {
10315
+ queueElapsedMs = Math.max(0, input.observedAt - createdMs);
10316
+ }
10317
+ return {
10318
+ job_id: jobId,
10319
+ status,
10320
+ terminal,
10321
+ attempt: input.attempt,
10322
+ attempts: input.attempts,
10323
+ elapsed_ms: Math.max(0, input.observedAt - input.startedAt),
10324
+ created_at: createdAt,
10325
+ submitted_at: submittedAt,
10326
+ completed_at: completedAt,
10327
+ queue_elapsed_ms: queueElapsedMs,
10328
+ running_without_submission: Boolean(status && !terminal && !submittedAt)
10329
+ };
10330
+ }
10331
+ function pollMessage(snapshot, timedOut) {
10332
+ if (!timedOut) return void 0;
10333
+ const submitted = snapshot.submitted_at || "not submitted";
10334
+ const queue = snapshot.queue_elapsed_ms !== null ? ` queue_elapsed_ms=${snapshot.queue_elapsed_ms}` : "";
10335
+ return `Riddle job ${snapshot.job_id} did not reach a terminal status after ${snapshot.attempt} poll attempts; status=${snapshot.status || "unknown"} submitted_at=${submitted}.${queue}`;
10336
+ }
10278
10337
  async function pollRiddleJob(config, jobId, options = {}) {
10279
10338
  if (!jobId?.trim()) throw new Error("jobId is required");
10280
- const attempts = options.attempts || (options.wait ? 60 : 1);
10281
- const intervalMs = options.intervalMs || 2e3;
10339
+ const attempts = Math.max(1, Math.floor(options.attempts ?? (options.wait ? 300 : 1)));
10340
+ const intervalMs = Math.max(0, Math.floor(options.intervalMs ?? 2e3));
10341
+ const progressEveryMs = Math.max(0, Math.floor(options.progressEveryMs ?? 1e4));
10342
+ const startedAt = Date.now();
10282
10343
  let job = null;
10344
+ let lastSnapshot = null;
10345
+ let lastProgressAt = 0;
10346
+ let lastProgressKey = "";
10283
10347
  for (let index = 0; index < attempts; index += 1) {
10284
10348
  job = await riddleRequestJson(config, `/v1/jobs/${jobId}`);
10285
- if (isTerminalRiddleJobStatus(job.status)) break;
10349
+ const observedAt = Date.now();
10350
+ lastSnapshot = buildPollSnapshot(jobId, job, {
10351
+ attempt: index + 1,
10352
+ attempts,
10353
+ startedAt,
10354
+ observedAt
10355
+ });
10356
+ const progressKey = [
10357
+ lastSnapshot.status || "unknown",
10358
+ lastSnapshot.terminal ? "terminal" : "nonterminal",
10359
+ lastSnapshot.submitted_at ? "submitted" : "unsubmitted"
10360
+ ].join(":");
10361
+ if (options.onProgress) {
10362
+ const shouldReport = index === 0 || lastSnapshot.terminal || progressKey !== lastProgressKey || observedAt - lastProgressAt >= progressEveryMs;
10363
+ if (shouldReport) {
10364
+ lastProgressAt = observedAt;
10365
+ lastProgressKey = progressKey;
10366
+ await options.onProgress(lastSnapshot);
10367
+ }
10368
+ }
10369
+ if (lastSnapshot.terminal) break;
10286
10370
  if (index + 1 < attempts) {
10287
10371
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
10288
10372
  }
10289
10373
  }
10290
10374
  const status = job?.status ? String(job.status) : null;
10375
+ const fallbackObservedAt = Date.now();
10376
+ const snapshot = lastSnapshot || buildPollSnapshot(jobId, job, {
10377
+ attempt: 0,
10378
+ attempts,
10379
+ startedAt,
10380
+ observedAt: fallbackObservedAt
10381
+ });
10291
10382
  if (!isTerminalRiddleJobStatus(status)) {
10292
- return { ok: true, job_id: jobId, status, terminal: false, job };
10383
+ const timedOut = Boolean(options.wait);
10384
+ return {
10385
+ ok: !timedOut,
10386
+ job_id: jobId,
10387
+ status,
10388
+ terminal: false,
10389
+ job,
10390
+ poll: {
10391
+ ...snapshot,
10392
+ timed_out: timedOut,
10393
+ interval_ms: intervalMs,
10394
+ message: pollMessage(snapshot, timedOut)
10395
+ }
10396
+ };
10293
10397
  }
10294
10398
  const artifacts = await riddleRequestJson(config, `/v1/jobs/${jobId}/artifacts`);
10295
10399
  return {
@@ -10298,7 +10402,12 @@ async function pollRiddleJob(config, jobId, options = {}) {
10298
10402
  status,
10299
10403
  terminal: true,
10300
10404
  job,
10301
- artifacts
10405
+ artifacts,
10406
+ poll: {
10407
+ ...snapshot,
10408
+ timed_out: false,
10409
+ interval_ms: intervalMs
10410
+ }
10302
10411
  };
10303
10412
  }
10304
10413
  function createRiddleApiClient(config = {}) {
package/dist/index.d.cts CHANGED
@@ -11,4 +11,4 @@ export { BuildVisualProofSessionInput, RIDDLE_PROOF_VISUAL_SESSION_FINGERPRINT_V
11
11
  export { AssessPlayabilityOptions, RIDDLE_PROOF_PLAYABILITY_ASSESSMENT_VERSION, RIDDLE_PROOF_PLAYABILITY_VERSION, RiddleProofPlayabilityAssessment, RiddleProofPlayabilityEvidence, assessPlayabilityEvidence, extractPlayabilityEvidence, isRiddleProofPlayabilityMode } from './playability.cjs';
12
12
  export { AssessBasicGameplayOptions, AttachBasicGameplayArtifactOptions, BASIC_GAMEPLAY_ACTION_TYPES, BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES, BasicGameplayActionResult, BasicGameplayActionType, BasicGameplayArtifactResolution, BasicGameplayAssessmentSummary, BasicGameplayBoundsOffender, BasicGameplayCanvasState, BasicGameplayCatchRecord, BasicGameplayChangeSummary, BasicGameplayFailureCode, BasicGameplayFixReference, BasicGameplayMetric, BasicGameplayMobileEvidence, BasicGameplayProgressCheckType, BasicGameplayProgressionCheck, BasicGameplayProofArtifact, BasicGameplayResponsiveViewportEvidence, BasicGameplayRouteReference, BasicGameplaySnapshot, BasicGameplaySuiteFailure, BasicGameplayWarningCode, CreateBasicGameplayCatchSummaryInput, RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION, RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION, RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION, RiddleProofBasicGameplayAssessment, RiddleProofBasicGameplayCatchSummary, RiddleProofBasicGameplayEvidence, RiddleProofBasicGameplayRouteAssessment, RiddleProofBasicGameplayRouteEvidence, assessBasicGameplayEvidence, assessBasicGameplayProgressionCheck, assessBasicGameplayProgressionChecks, assessBasicGameplayRoute, attachBasicGameplayArtifactScreenshotHashes, augmentBasicGameplayAssessmentWithProgressionChecks, compactBasicGameplayText, createBasicGameplayCatchRecords, createBasicGameplayCatchSummary, extractBasicGameplayEvidence, resolveBasicGameplayProgressionCheckWithArtifactScreenshots, sanitizeBasicGameplayJsonString } from './basic-gameplay.cjs';
13
13
  export { NormalizeRiddleProofProfileOptions, RIDDLE_PROOF_PROFILE_CHECK_TYPES, RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION, RIDDLE_PROOF_PROFILE_RESULT_VERSION, RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES, RIDDLE_PROOF_PROFILE_STATUSES, RIDDLE_PROOF_PROFILE_VERSION, RiddleProofProfile, RiddleProofProfileArtifactRef, RiddleProofProfileBaselinePolicy, RiddleProofProfileBoundsOffender, RiddleProofProfileCheck, RiddleProofProfileCheckResult, RiddleProofProfileCheckType, RiddleProofProfileEvidence, RiddleProofProfileFailureAction, RiddleProofProfileResult, RiddleProofProfileRouteEvidence, RiddleProofProfileRunner, RiddleProofProfileSetupAction, RiddleProofProfileSetupActionType, RiddleProofProfileStatus, RiddleProofProfileTarget, RiddleProofProfileViewport, RiddleProofProfileViewportEvidence, assessRiddleProofProfileEvidence, buildRiddleProofProfileScript, collectRiddleProfileArtifactRefs, createRiddleProofProfileConfigurationError, createRiddleProofProfileEnvironmentBlockedResult, createRiddleProofProfileInsufficientResult, extractRiddleProofProfileResult, normalizeRiddleProofProfile, profileStatusExitCode, resolveRiddleProofProfileRouteUrl, resolveRiddleProofProfileTargetUrl, resolveRiddleProofProfileTimeoutSec, slugifyRiddleProofProfileName, summarizeRiddleProofProfileResult } from './profile.cjs';
14
- export { DEFAULT_RIDDLE_API_BASE_URL, DEFAULT_RIDDLE_API_KEY_FILE, RiddleApiError, RiddleClientConfig, RiddleFetch, RiddlePollJobResult, RiddlePreviewDeployResult, RiddleRunScriptInput, RiddleServerPreviewInput, RiddleServerPreviewResult, createRiddleApiClient, deployRiddleStaticPreview, isTerminalRiddleJobStatus, parseRiddleViewport, pollRiddleJob, resolveRiddleApiKey, riddleRequestJson, runRiddleScript, runRiddleServerPreview } from './riddle-client.cjs';
14
+ export { DEFAULT_RIDDLE_API_BASE_URL, DEFAULT_RIDDLE_API_KEY_FILE, RiddleApiError, RiddleClientConfig, RiddleFetch, RiddlePollJobOptions, RiddlePollJobResult, RiddlePollProgressSnapshot, RiddlePollSummary, RiddlePreviewDeployResult, RiddleRunScriptInput, RiddleServerPreviewInput, RiddleServerPreviewResult, createRiddleApiClient, deployRiddleStaticPreview, isTerminalRiddleJobStatus, parseRiddleViewport, pollRiddleJob, resolveRiddleApiKey, riddleRequestJson, runRiddleScript, runRiddleServerPreview } from './riddle-client.cjs';
package/dist/index.d.ts CHANGED
@@ -11,4 +11,4 @@ export { BuildVisualProofSessionInput, RIDDLE_PROOF_VISUAL_SESSION_FINGERPRINT_V
11
11
  export { AssessPlayabilityOptions, RIDDLE_PROOF_PLAYABILITY_ASSESSMENT_VERSION, RIDDLE_PROOF_PLAYABILITY_VERSION, RiddleProofPlayabilityAssessment, RiddleProofPlayabilityEvidence, assessPlayabilityEvidence, extractPlayabilityEvidence, isRiddleProofPlayabilityMode } from './playability.js';
12
12
  export { AssessBasicGameplayOptions, AttachBasicGameplayArtifactOptions, BASIC_GAMEPLAY_ACTION_TYPES, BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES, BasicGameplayActionResult, BasicGameplayActionType, BasicGameplayArtifactResolution, BasicGameplayAssessmentSummary, BasicGameplayBoundsOffender, BasicGameplayCanvasState, BasicGameplayCatchRecord, BasicGameplayChangeSummary, BasicGameplayFailureCode, BasicGameplayFixReference, BasicGameplayMetric, BasicGameplayMobileEvidence, BasicGameplayProgressCheckType, BasicGameplayProgressionCheck, BasicGameplayProofArtifact, BasicGameplayResponsiveViewportEvidence, BasicGameplayRouteReference, BasicGameplaySnapshot, BasicGameplaySuiteFailure, BasicGameplayWarningCode, CreateBasicGameplayCatchSummaryInput, RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION, RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION, RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION, RiddleProofBasicGameplayAssessment, RiddleProofBasicGameplayCatchSummary, RiddleProofBasicGameplayEvidence, RiddleProofBasicGameplayRouteAssessment, RiddleProofBasicGameplayRouteEvidence, assessBasicGameplayEvidence, assessBasicGameplayProgressionCheck, assessBasicGameplayProgressionChecks, assessBasicGameplayRoute, attachBasicGameplayArtifactScreenshotHashes, augmentBasicGameplayAssessmentWithProgressionChecks, compactBasicGameplayText, createBasicGameplayCatchRecords, createBasicGameplayCatchSummary, extractBasicGameplayEvidence, resolveBasicGameplayProgressionCheckWithArtifactScreenshots, sanitizeBasicGameplayJsonString } from './basic-gameplay.js';
13
13
  export { NormalizeRiddleProofProfileOptions, RIDDLE_PROOF_PROFILE_CHECK_TYPES, RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION, RIDDLE_PROOF_PROFILE_RESULT_VERSION, RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES, RIDDLE_PROOF_PROFILE_STATUSES, RIDDLE_PROOF_PROFILE_VERSION, RiddleProofProfile, RiddleProofProfileArtifactRef, RiddleProofProfileBaselinePolicy, RiddleProofProfileBoundsOffender, RiddleProofProfileCheck, RiddleProofProfileCheckResult, RiddleProofProfileCheckType, RiddleProofProfileEvidence, RiddleProofProfileFailureAction, RiddleProofProfileResult, RiddleProofProfileRouteEvidence, RiddleProofProfileRunner, RiddleProofProfileSetupAction, RiddleProofProfileSetupActionType, RiddleProofProfileStatus, RiddleProofProfileTarget, RiddleProofProfileViewport, RiddleProofProfileViewportEvidence, assessRiddleProofProfileEvidence, buildRiddleProofProfileScript, collectRiddleProfileArtifactRefs, createRiddleProofProfileConfigurationError, createRiddleProofProfileEnvironmentBlockedResult, createRiddleProofProfileInsufficientResult, extractRiddleProofProfileResult, normalizeRiddleProofProfile, profileStatusExitCode, resolveRiddleProofProfileRouteUrl, resolveRiddleProofProfileTargetUrl, resolveRiddleProofProfileTimeoutSec, slugifyRiddleProofProfileName, summarizeRiddleProofProfileResult } from './profile.js';
14
- export { DEFAULT_RIDDLE_API_BASE_URL, DEFAULT_RIDDLE_API_KEY_FILE, RiddleApiError, RiddleClientConfig, RiddleFetch, RiddlePollJobResult, RiddlePreviewDeployResult, RiddleRunScriptInput, RiddleServerPreviewInput, RiddleServerPreviewResult, createRiddleApiClient, deployRiddleStaticPreview, isTerminalRiddleJobStatus, parseRiddleViewport, pollRiddleJob, resolveRiddleApiKey, riddleRequestJson, runRiddleScript, runRiddleServerPreview } from './riddle-client.js';
14
+ export { DEFAULT_RIDDLE_API_BASE_URL, DEFAULT_RIDDLE_API_KEY_FILE, RiddleApiError, RiddleClientConfig, RiddleFetch, RiddlePollJobOptions, RiddlePollJobResult, RiddlePollProgressSnapshot, RiddlePollSummary, RiddlePreviewDeployResult, RiddleRunScriptInput, RiddleServerPreviewInput, RiddleServerPreviewResult, createRiddleApiClient, deployRiddleStaticPreview, isTerminalRiddleJobStatus, parseRiddleViewport, pollRiddleJob, resolveRiddleApiKey, riddleRequestJson, runRiddleScript, runRiddleServerPreview } from './riddle-client.js';
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ import {
58
58
  resolveRiddleProofProfileTimeoutSec,
59
59
  slugifyRiddleProofProfileName,
60
60
  summarizeRiddleProofProfileResult
61
- } from "./chunk-XOM2OYAB.js";
61
+ } from "./chunk-SDSBS64X.js";
62
62
  import {
63
63
  DEFAULT_RIDDLE_API_BASE_URL,
64
64
  DEFAULT_RIDDLE_API_KEY_FILE,
@@ -72,7 +72,7 @@ import {
72
72
  riddleRequestJson,
73
73
  runRiddleScript,
74
74
  runRiddleServerPreview
75
- } from "./chunk-5KOHSE4V.js";
75
+ } from "./chunk-7PSWK57T.js";
76
76
  import {
77
77
  DEFAULT_DIAGNOSTIC_ARRAY_LIMIT,
78
78
  DEFAULT_DIAGNOSTIC_HISTORY_LIMIT,
package/dist/profile.cjs CHANGED
@@ -1164,6 +1164,20 @@ async function captureViewport(viewport) {
1164
1164
  const scrollWidth = documentElement ? documentElement.scrollWidth : 0;
1165
1165
  const viewportWidth = clientWidth || window.innerWidth;
1166
1166
  const overflowOffenders = [];
1167
+ function isContainedByHorizontalScroller(element) {
1168
+ let current = element.parentElement;
1169
+ while (current && current !== body && current !== documentElement) {
1170
+ const style = window.getComputedStyle(current);
1171
+ const overflowX = style.overflowX || style.overflow || "";
1172
+ if ((overflowX === "auto" || overflowX === "scroll") && current.scrollWidth > current.clientWidth + 1) {
1173
+ const currentRect = current.getBoundingClientRect();
1174
+ const contained = currentRect.left >= -0.5 && currentRect.right <= viewportWidth + 0.5;
1175
+ if (contained) return true;
1176
+ }
1177
+ current = current.parentElement;
1178
+ }
1179
+ return false;
1180
+ }
1167
1181
  for (const element of Array.from(body ? body.querySelectorAll("*") : [])) {
1168
1182
  const rect = element.getBoundingClientRect();
1169
1183
  if (!rect || rect.width < 1 || rect.height < 1) continue;
@@ -1173,6 +1187,7 @@ async function captureViewport(viewport) {
1173
1187
  const rightOverflow = Math.max(0, rect.right - viewportWidth);
1174
1188
  const overflow = Math.max(leftOverflow, rightOverflow);
1175
1189
  if (overflow <= 0.5) continue;
1190
+ if (isContainedByHorizontalScroller(element)) continue;
1176
1191
  const tag = element.tagName ? element.tagName.toLowerCase() : "element";
1177
1192
  const id = element.id ? "#" + element.id : "";
1178
1193
  const className = typeof element.className === "string"
@@ -1204,6 +1219,7 @@ async function captureViewport(viewport) {
1204
1219
  pathname: location.pathname,
1205
1220
  title: document.title,
1206
1221
  body_text_length: text.length,
1222
+ body_text: text,
1207
1223
  body_text_sample: text.slice(0, 8000),
1208
1224
  scroll_width: scrollWidth,
1209
1225
  client_width: clientWidth,
@@ -1229,7 +1245,7 @@ async function captureViewport(viewport) {
1229
1245
  selectors[check.selector] = await selectorStats(check.selector);
1230
1246
  }
1231
1247
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
1232
- text_matches[textKey(check)] = textMatches(dom.body_text_sample || "", check);
1248
+ text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
1233
1249
  }
1234
1250
  }
1235
1251
  const screenshotLabel = profileSlug + "-" + viewport.name;
package/dist/profile.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  resolveRiddleProofProfileTimeoutSec,
20
20
  slugifyRiddleProofProfileName,
21
21
  summarizeRiddleProofProfileResult
22
- } from "./chunk-XOM2OYAB.js";
22
+ } from "./chunk-SDSBS64X.js";
23
23
  export {
24
24
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
25
25
  RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
@@ -241,21 +241,109 @@ async function runRiddleScript(config, input) {
241
241
  function isTerminalRiddleJobStatus(status) {
242
242
  return ["completed", "complete", "completed_error", "completed_timeout", "failed"].includes(String(status || ""));
243
243
  }
244
+ function stringField(record, key) {
245
+ const value = record?.[key];
246
+ return typeof value === "string" && value.trim() ? value.trim() : null;
247
+ }
248
+ function parseTimestampMs(value) {
249
+ if (!value) return null;
250
+ const parsed = Date.parse(value);
251
+ return Number.isFinite(parsed) ? parsed : null;
252
+ }
253
+ function buildPollSnapshot(jobId, job, input) {
254
+ const status = job?.status ? String(job.status) : null;
255
+ const terminal = isTerminalRiddleJobStatus(status);
256
+ const createdAt = stringField(job, "created_at");
257
+ const submittedAt = stringField(job, "submitted_at");
258
+ const completedAt = stringField(job, "completed_at");
259
+ const createdMs = parseTimestampMs(createdAt);
260
+ const submittedMs = parseTimestampMs(submittedAt);
261
+ let queueElapsedMs = null;
262
+ if (createdMs !== null && submittedMs !== null) {
263
+ queueElapsedMs = Math.max(0, submittedMs - createdMs);
264
+ } else if (createdMs !== null && !submittedAt && !terminal) {
265
+ queueElapsedMs = Math.max(0, input.observedAt - createdMs);
266
+ }
267
+ return {
268
+ job_id: jobId,
269
+ status,
270
+ terminal,
271
+ attempt: input.attempt,
272
+ attempts: input.attempts,
273
+ elapsed_ms: Math.max(0, input.observedAt - input.startedAt),
274
+ created_at: createdAt,
275
+ submitted_at: submittedAt,
276
+ completed_at: completedAt,
277
+ queue_elapsed_ms: queueElapsedMs,
278
+ running_without_submission: Boolean(status && !terminal && !submittedAt)
279
+ };
280
+ }
281
+ function pollMessage(snapshot, timedOut) {
282
+ if (!timedOut) return void 0;
283
+ const submitted = snapshot.submitted_at || "not submitted";
284
+ const queue = snapshot.queue_elapsed_ms !== null ? ` queue_elapsed_ms=${snapshot.queue_elapsed_ms}` : "";
285
+ return `Riddle job ${snapshot.job_id} did not reach a terminal status after ${snapshot.attempt} poll attempts; status=${snapshot.status || "unknown"} submitted_at=${submitted}.${queue}`;
286
+ }
244
287
  async function pollRiddleJob(config, jobId, options = {}) {
245
288
  if (!jobId?.trim()) throw new Error("jobId is required");
246
- const attempts = options.attempts || (options.wait ? 60 : 1);
247
- const intervalMs = options.intervalMs || 2e3;
289
+ const attempts = Math.max(1, Math.floor(options.attempts ?? (options.wait ? 300 : 1)));
290
+ const intervalMs = Math.max(0, Math.floor(options.intervalMs ?? 2e3));
291
+ const progressEveryMs = Math.max(0, Math.floor(options.progressEveryMs ?? 1e4));
292
+ const startedAt = Date.now();
248
293
  let job = null;
294
+ let lastSnapshot = null;
295
+ let lastProgressAt = 0;
296
+ let lastProgressKey = "";
249
297
  for (let index = 0; index < attempts; index += 1) {
250
298
  job = await riddleRequestJson(config, `/v1/jobs/${jobId}`);
251
- if (isTerminalRiddleJobStatus(job.status)) break;
299
+ const observedAt = Date.now();
300
+ lastSnapshot = buildPollSnapshot(jobId, job, {
301
+ attempt: index + 1,
302
+ attempts,
303
+ startedAt,
304
+ observedAt
305
+ });
306
+ const progressKey = [
307
+ lastSnapshot.status || "unknown",
308
+ lastSnapshot.terminal ? "terminal" : "nonterminal",
309
+ lastSnapshot.submitted_at ? "submitted" : "unsubmitted"
310
+ ].join(":");
311
+ if (options.onProgress) {
312
+ const shouldReport = index === 0 || lastSnapshot.terminal || progressKey !== lastProgressKey || observedAt - lastProgressAt >= progressEveryMs;
313
+ if (shouldReport) {
314
+ lastProgressAt = observedAt;
315
+ lastProgressKey = progressKey;
316
+ await options.onProgress(lastSnapshot);
317
+ }
318
+ }
319
+ if (lastSnapshot.terminal) break;
252
320
  if (index + 1 < attempts) {
253
321
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
254
322
  }
255
323
  }
256
324
  const status = job?.status ? String(job.status) : null;
325
+ const fallbackObservedAt = Date.now();
326
+ const snapshot = lastSnapshot || buildPollSnapshot(jobId, job, {
327
+ attempt: 0,
328
+ attempts,
329
+ startedAt,
330
+ observedAt: fallbackObservedAt
331
+ });
257
332
  if (!isTerminalRiddleJobStatus(status)) {
258
- return { ok: true, job_id: jobId, status, terminal: false, job };
333
+ const timedOut = Boolean(options.wait);
334
+ return {
335
+ ok: !timedOut,
336
+ job_id: jobId,
337
+ status,
338
+ terminal: false,
339
+ job,
340
+ poll: {
341
+ ...snapshot,
342
+ timed_out: timedOut,
343
+ interval_ms: intervalMs,
344
+ message: pollMessage(snapshot, timedOut)
345
+ }
346
+ };
259
347
  }
260
348
  const artifacts = await riddleRequestJson(config, `/v1/jobs/${jobId}/artifacts`);
261
349
  return {
@@ -264,7 +352,12 @@ async function pollRiddleJob(config, jobId, options = {}) {
264
352
  status,
265
353
  terminal: true,
266
354
  job,
267
- artifacts
355
+ artifacts,
356
+ poll: {
357
+ ...snapshot,
358
+ timed_out: false,
359
+ interval_ms: intervalMs
360
+ }
268
361
  };
269
362
  }
270
363
  function createRiddleApiClient(config = {}) {
@@ -7,6 +7,31 @@ interface RiddleClientConfig {
7
7
  apiBaseUrl?: string;
8
8
  fetchImpl?: RiddleFetch;
9
9
  }
10
+ interface RiddlePollProgressSnapshot {
11
+ job_id: string;
12
+ status: string | null;
13
+ terminal: boolean;
14
+ attempt: number;
15
+ attempts: number;
16
+ elapsed_ms: number;
17
+ created_at: string | null;
18
+ submitted_at: string | null;
19
+ completed_at: string | null;
20
+ queue_elapsed_ms: number | null;
21
+ running_without_submission: boolean;
22
+ }
23
+ interface RiddlePollSummary extends RiddlePollProgressSnapshot {
24
+ timed_out: boolean;
25
+ interval_ms: number;
26
+ message?: string;
27
+ }
28
+ interface RiddlePollJobOptions {
29
+ wait?: boolean;
30
+ attempts?: number;
31
+ intervalMs?: number;
32
+ progressEveryMs?: number;
33
+ onProgress?: (snapshot: RiddlePollProgressSnapshot) => void | Promise<void>;
34
+ }
10
35
  interface RiddlePreviewDeployResult {
11
36
  ok: true;
12
37
  id: string;
@@ -58,6 +83,7 @@ interface RiddlePollJobResult {
58
83
  terminal: boolean;
59
84
  job: Record<string, unknown> | null;
60
85
  artifacts?: Record<string, unknown> | null;
86
+ poll?: RiddlePollSummary;
61
87
  }
62
88
  interface RiddleServerPreviewResult {
63
89
  ok: boolean;
@@ -83,21 +109,13 @@ declare function parseRiddleViewport(value?: string): {
83
109
  declare function runRiddleServerPreview(config: RiddleClientConfig, input: RiddleServerPreviewInput): Promise<RiddleServerPreviewResult>;
84
110
  declare function runRiddleScript(config: RiddleClientConfig, input: RiddleRunScriptInput): Promise<Record<string, unknown>>;
85
111
  declare function isTerminalRiddleJobStatus(status: unknown): boolean;
86
- declare function pollRiddleJob(config: RiddleClientConfig, jobId: string, options?: {
87
- wait?: boolean;
88
- attempts?: number;
89
- intervalMs?: number;
90
- }): Promise<RiddlePollJobResult>;
112
+ declare function pollRiddleJob(config: RiddleClientConfig, jobId: string, options?: RiddlePollJobOptions): Promise<RiddlePollJobResult>;
91
113
  declare function createRiddleApiClient(config?: RiddleClientConfig): {
92
114
  requestJson: <T = unknown>(pathname: string, init?: RequestInit) => Promise<T>;
93
115
  deployStaticPreview: (directory: string, label: string) => Promise<RiddlePreviewDeployResult>;
94
116
  runScript: (input: RiddleRunScriptInput) => Promise<Record<string, unknown>>;
95
117
  runServerPreview: (input: RiddleServerPreviewInput) => Promise<RiddleServerPreviewResult>;
96
- pollJob: (jobId: string, options?: {
97
- wait?: boolean;
98
- attempts?: number;
99
- intervalMs?: number;
100
- }) => Promise<RiddlePollJobResult>;
118
+ pollJob: (jobId: string, options?: RiddlePollJobOptions) => Promise<RiddlePollJobResult>;
101
119
  };
102
120
 
103
- export { DEFAULT_RIDDLE_API_BASE_URL, DEFAULT_RIDDLE_API_KEY_FILE, RiddleApiError, type RiddleClientConfig, type RiddleFetch, type RiddlePollJobResult, type RiddlePreviewDeployResult, type RiddleRunScriptInput, type RiddleServerPreviewInput, type RiddleServerPreviewResult, createRiddleApiClient, deployRiddleStaticPreview, isTerminalRiddleJobStatus, parseRiddleViewport, pollRiddleJob, resolveRiddleApiKey, riddleRequestJson, runRiddleScript, runRiddleServerPreview };
121
+ export { DEFAULT_RIDDLE_API_BASE_URL, DEFAULT_RIDDLE_API_KEY_FILE, RiddleApiError, type RiddleClientConfig, type RiddleFetch, type RiddlePollJobOptions, type RiddlePollJobResult, type RiddlePollProgressSnapshot, type RiddlePollSummary, type RiddlePreviewDeployResult, type RiddleRunScriptInput, type RiddleServerPreviewInput, type RiddleServerPreviewResult, createRiddleApiClient, deployRiddleStaticPreview, isTerminalRiddleJobStatus, parseRiddleViewport, pollRiddleJob, resolveRiddleApiKey, riddleRequestJson, runRiddleScript, runRiddleServerPreview };
@@ -7,6 +7,31 @@ interface RiddleClientConfig {
7
7
  apiBaseUrl?: string;
8
8
  fetchImpl?: RiddleFetch;
9
9
  }
10
+ interface RiddlePollProgressSnapshot {
11
+ job_id: string;
12
+ status: string | null;
13
+ terminal: boolean;
14
+ attempt: number;
15
+ attempts: number;
16
+ elapsed_ms: number;
17
+ created_at: string | null;
18
+ submitted_at: string | null;
19
+ completed_at: string | null;
20
+ queue_elapsed_ms: number | null;
21
+ running_without_submission: boolean;
22
+ }
23
+ interface RiddlePollSummary extends RiddlePollProgressSnapshot {
24
+ timed_out: boolean;
25
+ interval_ms: number;
26
+ message?: string;
27
+ }
28
+ interface RiddlePollJobOptions {
29
+ wait?: boolean;
30
+ attempts?: number;
31
+ intervalMs?: number;
32
+ progressEveryMs?: number;
33
+ onProgress?: (snapshot: RiddlePollProgressSnapshot) => void | Promise<void>;
34
+ }
10
35
  interface RiddlePreviewDeployResult {
11
36
  ok: true;
12
37
  id: string;
@@ -58,6 +83,7 @@ interface RiddlePollJobResult {
58
83
  terminal: boolean;
59
84
  job: Record<string, unknown> | null;
60
85
  artifacts?: Record<string, unknown> | null;
86
+ poll?: RiddlePollSummary;
61
87
  }
62
88
  interface RiddleServerPreviewResult {
63
89
  ok: boolean;
@@ -83,21 +109,13 @@ declare function parseRiddleViewport(value?: string): {
83
109
  declare function runRiddleServerPreview(config: RiddleClientConfig, input: RiddleServerPreviewInput): Promise<RiddleServerPreviewResult>;
84
110
  declare function runRiddleScript(config: RiddleClientConfig, input: RiddleRunScriptInput): Promise<Record<string, unknown>>;
85
111
  declare function isTerminalRiddleJobStatus(status: unknown): boolean;
86
- declare function pollRiddleJob(config: RiddleClientConfig, jobId: string, options?: {
87
- wait?: boolean;
88
- attempts?: number;
89
- intervalMs?: number;
90
- }): Promise<RiddlePollJobResult>;
112
+ declare function pollRiddleJob(config: RiddleClientConfig, jobId: string, options?: RiddlePollJobOptions): Promise<RiddlePollJobResult>;
91
113
  declare function createRiddleApiClient(config?: RiddleClientConfig): {
92
114
  requestJson: <T = unknown>(pathname: string, init?: RequestInit) => Promise<T>;
93
115
  deployStaticPreview: (directory: string, label: string) => Promise<RiddlePreviewDeployResult>;
94
116
  runScript: (input: RiddleRunScriptInput) => Promise<Record<string, unknown>>;
95
117
  runServerPreview: (input: RiddleServerPreviewInput) => Promise<RiddleServerPreviewResult>;
96
- pollJob: (jobId: string, options?: {
97
- wait?: boolean;
98
- attempts?: number;
99
- intervalMs?: number;
100
- }) => Promise<RiddlePollJobResult>;
118
+ pollJob: (jobId: string, options?: RiddlePollJobOptions) => Promise<RiddlePollJobResult>;
101
119
  };
102
120
 
103
- export { DEFAULT_RIDDLE_API_BASE_URL, DEFAULT_RIDDLE_API_KEY_FILE, RiddleApiError, type RiddleClientConfig, type RiddleFetch, type RiddlePollJobResult, type RiddlePreviewDeployResult, type RiddleRunScriptInput, type RiddleServerPreviewInput, type RiddleServerPreviewResult, createRiddleApiClient, deployRiddleStaticPreview, isTerminalRiddleJobStatus, parseRiddleViewport, pollRiddleJob, resolveRiddleApiKey, riddleRequestJson, runRiddleScript, runRiddleServerPreview };
121
+ export { DEFAULT_RIDDLE_API_BASE_URL, DEFAULT_RIDDLE_API_KEY_FILE, RiddleApiError, type RiddleClientConfig, type RiddleFetch, type RiddlePollJobOptions, type RiddlePollJobResult, type RiddlePollProgressSnapshot, type RiddlePollSummary, type RiddlePreviewDeployResult, type RiddleRunScriptInput, type RiddleServerPreviewInput, type RiddleServerPreviewResult, createRiddleApiClient, deployRiddleStaticPreview, isTerminalRiddleJobStatus, parseRiddleViewport, pollRiddleJob, resolveRiddleApiKey, riddleRequestJson, runRiddleScript, runRiddleServerPreview };
@@ -11,7 +11,7 @@ import {
11
11
  riddleRequestJson,
12
12
  runRiddleScript,
13
13
  runRiddleServerPreview
14
- } from "./chunk-5KOHSE4V.js";
14
+ } from "./chunk-7PSWK57T.js";
15
15
  export {
16
16
  DEFAULT_RIDDLE_API_BASE_URL,
17
17
  DEFAULT_RIDDLE_API_KEY_FILE,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.7.23",
3
+ "version": "0.7.25",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",