@browserbasehq/orca 3.5.0-vertex-test → 3.6.0-gemini-cua

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 (290) hide show
  1. package/README.md +7 -15
  2. package/dist/cjs/lib/utils.d.ts +8 -0
  3. package/dist/cjs/lib/utils.js +31 -0
  4. package/dist/cjs/lib/utils.js.map +1 -1
  5. package/dist/cjs/lib/v3/agent/AgentProvider.js +5 -3
  6. package/dist/cjs/lib/v3/agent/AgentProvider.js.map +1 -1
  7. package/dist/cjs/lib/v3/agent/AnthropicCUAClient.js +10 -8
  8. package/dist/cjs/lib/v3/agent/AnthropicCUAClient.js.map +1 -1
  9. package/dist/cjs/lib/v3/agent/GoogleCUAClient.js +124 -28
  10. package/dist/cjs/lib/v3/agent/GoogleCUAClient.js.map +1 -1
  11. package/dist/cjs/lib/v3/agent/utils/captureAriaTreeProbe.d.ts +35 -0
  12. package/dist/cjs/lib/v3/agent/utils/captureAriaTreeProbe.js +38 -0
  13. package/dist/cjs/lib/v3/agent/utils/captureAriaTreeProbe.js.map +1 -0
  14. package/dist/cjs/lib/v3/agent/utils/handleDoneToolCall.js +10 -1
  15. package/dist/cjs/lib/v3/agent/utils/handleDoneToolCall.js.map +1 -1
  16. package/dist/cjs/lib/v3/agent/utils/postStepProbeEvidence.d.ts +19 -0
  17. package/dist/cjs/lib/v3/agent/utils/postStepProbeEvidence.js +54 -0
  18. package/dist/cjs/lib/v3/agent/utils/postStepProbeEvidence.js.map +1 -0
  19. package/dist/cjs/lib/v3/agent/utils/toolOutputEvidence.d.ts +2 -0
  20. package/dist/cjs/lib/v3/agent/utils/toolOutputEvidence.js +62 -0
  21. package/dist/cjs/lib/v3/agent/utils/toolOutputEvidence.js.map +1 -0
  22. package/dist/cjs/lib/v3/agent/utils/wrapEvidenceCallback.d.ts +3 -0
  23. package/dist/cjs/lib/v3/agent/utils/wrapEvidenceCallback.js +25 -0
  24. package/dist/cjs/lib/v3/agent/utils/wrapEvidenceCallback.js.map +1 -0
  25. package/dist/cjs/lib/v3/api.d.ts +1 -0
  26. package/dist/cjs/lib/v3/api.js +37 -16
  27. package/dist/cjs/lib/v3/api.js.map +1 -1
  28. package/dist/cjs/lib/v3/cache/ActCache.js +1 -1
  29. package/dist/cjs/lib/v3/cache/ActCache.js.map +1 -1
  30. package/dist/cjs/lib/v3/cache/utils.d.ts +7 -0
  31. package/dist/cjs/lib/v3/cache/utils.js +19 -0
  32. package/dist/cjs/lib/v3/cache/utils.js.map +1 -1
  33. package/dist/cjs/lib/v3/dom/build/locatorScripts.generated.d.ts +24 -24
  34. package/dist/cjs/lib/v3/dom/build/locatorScripts.generated.js +24 -24
  35. package/dist/cjs/lib/v3/dom/build/locatorScripts.generated.js.map +1 -1
  36. package/dist/cjs/lib/v3/dom/locatorScripts/xpathResolver.js +79 -10
  37. package/dist/cjs/lib/v3/dom/locatorScripts/xpathResolver.js.map +1 -1
  38. package/dist/cjs/lib/v3/handlers/v3AgentHandler.d.ts +3 -1
  39. package/dist/cjs/lib/v3/handlers/v3AgentHandler.js +108 -16
  40. package/dist/cjs/lib/v3/handlers/v3AgentHandler.js.map +1 -1
  41. package/dist/cjs/lib/v3/handlers/v3CuaAgentHandler.d.ts +11 -0
  42. package/dist/cjs/lib/v3/handlers/v3CuaAgentHandler.js +119 -5
  43. package/dist/cjs/lib/v3/handlers/v3CuaAgentHandler.js.map +1 -1
  44. package/dist/cjs/lib/v3/index.d.ts +13 -1
  45. package/dist/cjs/lib/v3/index.js +19 -1
  46. package/dist/cjs/lib/v3/index.js.map +1 -1
  47. package/dist/cjs/lib/v3/launch/local.js +1 -0
  48. package/dist/cjs/lib/v3/launch/local.js.map +1 -1
  49. package/dist/cjs/lib/v3/llm/LLMProvider.d.ts +3 -0
  50. package/dist/cjs/lib/v3/llm/LLMProvider.js +45 -3
  51. package/dist/cjs/lib/v3/llm/LLMProvider.js.map +1 -1
  52. package/dist/cjs/lib/v3/llm/aisdk.js +4 -0
  53. package/dist/cjs/lib/v3/llm/aisdk.js.map +1 -1
  54. package/dist/cjs/lib/v3/llm/anthropicOptions.d.ts +57 -0
  55. package/dist/cjs/lib/v3/llm/anthropicOptions.js +115 -0
  56. package/dist/cjs/lib/v3/llm/anthropicOptions.js.map +1 -0
  57. package/dist/cjs/lib/v3/types/public/agent.d.ts +8 -2
  58. package/dist/cjs/lib/v3/types/public/agent.js +3 -0
  59. package/dist/cjs/lib/v3/types/public/agent.js.map +1 -1
  60. package/dist/cjs/lib/v3/types/public/agentEvidenceEvents.d.ts +85 -0
  61. package/dist/cjs/lib/v3/types/public/agentEvidenceEvents.js +15 -0
  62. package/dist/cjs/lib/v3/types/public/agentEvidenceEvents.js.map +1 -0
  63. package/dist/cjs/lib/v3/types/public/api.d.ts +925 -182
  64. package/dist/cjs/lib/v3/types/public/api.js +138 -20
  65. package/dist/cjs/lib/v3/types/public/api.js.map +1 -1
  66. package/dist/cjs/lib/v3/types/public/clipboard.d.ts +15 -0
  67. package/dist/cjs/lib/v3/types/public/clipboard.js +3 -0
  68. package/dist/cjs/lib/v3/types/public/clipboard.js.map +1 -0
  69. package/dist/cjs/lib/v3/types/public/index.d.ts +2 -0
  70. package/dist/cjs/lib/v3/types/public/index.js +2 -0
  71. package/dist/cjs/lib/v3/types/public/index.js.map +1 -1
  72. package/dist/cjs/lib/v3/types/public/model.d.ts +33 -8
  73. package/dist/cjs/lib/v3/types/public/model.js.map +1 -1
  74. package/dist/cjs/lib/v3/types/public/page.d.ts +29 -0
  75. package/dist/cjs/lib/v3/types/public/page.js.map +1 -1
  76. package/dist/cjs/lib/v3/types/public/sdkErrors.d.ts +3 -0
  77. package/dist/cjs/lib/v3/types/public/sdkErrors.js +8 -2
  78. package/dist/cjs/lib/v3/types/public/sdkErrors.js.map +1 -1
  79. package/dist/cjs/lib/v3/understudy/clipboard.d.ts +24 -0
  80. package/dist/cjs/lib/v3/understudy/clipboard.js +166 -0
  81. package/dist/cjs/lib/v3/understudy/clipboard.js.map +1 -0
  82. package/dist/cjs/lib/v3/understudy/context.d.ts +3 -0
  83. package/dist/cjs/lib/v3/understudy/context.js +15 -0
  84. package/dist/cjs/lib/v3/understudy/context.js.map +1 -1
  85. package/dist/cjs/lib/v3/understudy/page.d.ts +23 -1
  86. package/dist/cjs/lib/v3/understudy/page.js +283 -0
  87. package/dist/cjs/lib/v3/understudy/page.js.map +1 -1
  88. package/dist/cjs/lib/v3/v3.js +19 -7
  89. package/dist/cjs/lib/v3/v3.js.map +1 -1
  90. package/dist/cjs/lib/v3/verifier/errorTaxonomy.d.ts +35 -0
  91. package/dist/cjs/lib/v3/verifier/errorTaxonomy.js +291 -0
  92. package/dist/cjs/lib/v3/verifier/errorTaxonomy.js.map +1 -0
  93. package/dist/cjs/lib/v3/verifier/evidence.d.ts +44 -0
  94. package/dist/cjs/lib/v3/verifier/evidence.js +432 -0
  95. package/dist/cjs/lib/v3/verifier/evidence.js.map +1 -0
  96. package/dist/cjs/lib/v3/verifier/evidenceNormalization.d.ts +7 -0
  97. package/dist/cjs/lib/v3/verifier/evidenceNormalization.js +100 -0
  98. package/dist/cjs/lib/v3/verifier/evidenceNormalization.js.map +1 -0
  99. package/dist/cjs/lib/v3/verifier/index.d.ts +6 -0
  100. package/dist/cjs/lib/v3/verifier/index.js +16 -0
  101. package/dist/cjs/lib/v3/verifier/index.js.map +1 -0
  102. package/dist/cjs/lib/v3/verifier/prompts/batchedRelevance.d.ts +21 -0
  103. package/dist/cjs/lib/v3/verifier/prompts/batchedRelevance.js +71 -0
  104. package/dist/cjs/lib/v3/verifier/prompts/batchedRelevance.js.map +1 -0
  105. package/dist/cjs/lib/v3/verifier/prompts/firstPointOfFailure.d.ts +3 -0
  106. package/dist/cjs/lib/v3/verifier/prompts/firstPointOfFailure.js +166 -0
  107. package/dist/cjs/lib/v3/verifier/prompts/firstPointOfFailure.js.map +1 -0
  108. package/dist/cjs/lib/v3/verifier/prompts/fusedJudgment.d.ts +35 -0
  109. package/dist/cjs/lib/v3/verifier/prompts/fusedJudgment.js +158 -0
  110. package/dist/cjs/lib/v3/verifier/prompts/fusedJudgment.js.map +1 -0
  111. package/dist/cjs/lib/v3/verifier/prompts/fusedOutcome.d.ts +26 -0
  112. package/dist/cjs/lib/v3/verifier/prompts/fusedOutcome.js +137 -0
  113. package/dist/cjs/lib/v3/verifier/prompts/fusedOutcome.js.map +1 -0
  114. package/dist/cjs/lib/v3/verifier/prompts/index.d.ts +8 -0
  115. package/dist/cjs/lib/v3/verifier/prompts/index.js +22 -0
  116. package/dist/cjs/lib/v3/verifier/prompts/index.js.map +1 -0
  117. package/dist/cjs/lib/v3/verifier/prompts/perCriterionScore.d.ts +23 -0
  118. package/dist/cjs/lib/v3/verifier/prompts/perCriterionScore.js +77 -0
  119. package/dist/cjs/lib/v3/verifier/prompts/perCriterionScore.js.map +1 -0
  120. package/dist/cjs/lib/v3/verifier/prompts/render.d.ts +23 -0
  121. package/dist/cjs/lib/v3/verifier/prompts/render.js +45 -0
  122. package/dist/cjs/lib/v3/verifier/prompts/render.js.map +1 -0
  123. package/dist/cjs/lib/v3/verifier/prompts/rubricGeneration.d.ts +16 -0
  124. package/dist/cjs/lib/v3/verifier/prompts/rubricGeneration.js +321 -0
  125. package/dist/cjs/lib/v3/verifier/prompts/rubricGeneration.js.map +1 -0
  126. package/dist/cjs/lib/v3/verifier/prompts/taskValidity.d.ts +1 -0
  127. package/dist/cjs/lib/v3/verifier/prompts/taskValidity.js +116 -0
  128. package/dist/cjs/lib/v3/verifier/prompts/taskValidity.js.map +1 -0
  129. package/dist/cjs/lib/v3/verifier/rubricVerifier.d.ts +91 -0
  130. package/dist/cjs/lib/v3/verifier/rubricVerifier.js +1645 -0
  131. package/dist/cjs/lib/v3/verifier/rubricVerifier.js.map +1 -0
  132. package/dist/cjs/lib/v3/verifier/trajectory.d.ts +50 -0
  133. package/dist/cjs/lib/v3/verifier/trajectory.js +316 -0
  134. package/dist/cjs/lib/v3/verifier/trajectory.js.map +1 -0
  135. package/dist/cjs/lib/v3/verifier/types.d.ts +429 -0
  136. package/dist/cjs/lib/v3/verifier/types.js +3 -0
  137. package/dist/cjs/lib/v3/verifier/types.js.map +1 -0
  138. package/dist/cjs/lib/v3Evaluator.d.ts +14 -4
  139. package/dist/cjs/lib/v3Evaluator.js +189 -1
  140. package/dist/cjs/lib/v3Evaluator.js.map +1 -1
  141. package/dist/cjs/lib/v3LegacyEvaluator.js +5 -1
  142. package/dist/cjs/lib/v3LegacyEvaluator.js.map +1 -1
  143. package/dist/cjs/lib/version.d.ts +1 -1
  144. package/dist/cjs/lib/version.js +1 -1
  145. package/dist/cjs/lib/version.js.map +1 -1
  146. package/dist/esm/lib/utils.d.ts +8 -0
  147. package/dist/esm/lib/utils.js +28 -0
  148. package/dist/esm/lib/utils.js.map +1 -1
  149. package/dist/esm/lib/v3/agent/AgentProvider.js +5 -3
  150. package/dist/esm/lib/v3/agent/AgentProvider.js.map +1 -1
  151. package/dist/esm/lib/v3/agent/AnthropicCUAClient.js +10 -8
  152. package/dist/esm/lib/v3/agent/AnthropicCUAClient.js.map +1 -1
  153. package/dist/esm/lib/v3/agent/GoogleCUAClient.js +124 -28
  154. package/dist/esm/lib/v3/agent/GoogleCUAClient.js.map +1 -1
  155. package/dist/esm/lib/v3/agent/utils/captureAriaTreeProbe.d.ts +35 -0
  156. package/dist/esm/lib/v3/agent/utils/captureAriaTreeProbe.js +35 -0
  157. package/dist/esm/lib/v3/agent/utils/captureAriaTreeProbe.js.map +1 -0
  158. package/dist/esm/lib/v3/agent/utils/handleDoneToolCall.js +10 -1
  159. package/dist/esm/lib/v3/agent/utils/handleDoneToolCall.js.map +1 -1
  160. package/dist/esm/lib/v3/agent/utils/postStepProbeEvidence.d.ts +19 -0
  161. package/dist/esm/lib/v3/agent/utils/postStepProbeEvidence.js +50 -0
  162. package/dist/esm/lib/v3/agent/utils/postStepProbeEvidence.js.map +1 -0
  163. package/dist/esm/lib/v3/agent/utils/toolOutputEvidence.d.ts +2 -0
  164. package/dist/esm/lib/v3/agent/utils/toolOutputEvidence.js +59 -0
  165. package/dist/esm/lib/v3/agent/utils/toolOutputEvidence.js.map +1 -0
  166. package/dist/esm/lib/v3/agent/utils/wrapEvidenceCallback.d.ts +3 -0
  167. package/dist/esm/lib/v3/agent/utils/wrapEvidenceCallback.js +22 -0
  168. package/dist/esm/lib/v3/agent/utils/wrapEvidenceCallback.js.map +1 -0
  169. package/dist/esm/lib/v3/api.d.ts +1 -0
  170. package/dist/esm/lib/v3/api.js +38 -17
  171. package/dist/esm/lib/v3/api.js.map +1 -1
  172. package/dist/esm/lib/v3/cache/ActCache.js +2 -2
  173. package/dist/esm/lib/v3/cache/ActCache.js.map +1 -1
  174. package/dist/esm/lib/v3/cache/utils.d.ts +7 -0
  175. package/dist/esm/lib/v3/cache/utils.js +18 -0
  176. package/dist/esm/lib/v3/cache/utils.js.map +1 -1
  177. package/dist/esm/lib/v3/dom/build/locatorScripts.generated.d.ts +24 -24
  178. package/dist/esm/lib/v3/dom/build/locatorScripts.generated.js +24 -24
  179. package/dist/esm/lib/v3/dom/build/locatorScripts.generated.js.map +1 -1
  180. package/dist/esm/lib/v3/dom/locatorScripts/xpathResolver.js +79 -10
  181. package/dist/esm/lib/v3/dom/locatorScripts/xpathResolver.js.map +1 -1
  182. package/dist/esm/lib/v3/handlers/v3AgentHandler.d.ts +3 -1
  183. package/dist/esm/lib/v3/handlers/v3AgentHandler.js +108 -16
  184. package/dist/esm/lib/v3/handlers/v3AgentHandler.js.map +1 -1
  185. package/dist/esm/lib/v3/handlers/v3CuaAgentHandler.d.ts +11 -0
  186. package/dist/esm/lib/v3/handlers/v3CuaAgentHandler.js +119 -5
  187. package/dist/esm/lib/v3/handlers/v3CuaAgentHandler.js.map +1 -1
  188. package/dist/esm/lib/v3/index.d.ts +13 -1
  189. package/dist/esm/lib/v3/index.js +10 -0
  190. package/dist/esm/lib/v3/index.js.map +1 -1
  191. package/dist/esm/lib/v3/launch/local.js +1 -0
  192. package/dist/esm/lib/v3/launch/local.js.map +1 -1
  193. package/dist/esm/lib/v3/llm/LLMProvider.d.ts +3 -0
  194. package/dist/esm/lib/v3/llm/LLMProvider.js +44 -3
  195. package/dist/esm/lib/v3/llm/LLMProvider.js.map +1 -1
  196. package/dist/esm/lib/v3/llm/aisdk.js +4 -0
  197. package/dist/esm/lib/v3/llm/aisdk.js.map +1 -1
  198. package/dist/esm/lib/v3/llm/anthropicOptions.d.ts +57 -0
  199. package/dist/esm/lib/v3/llm/anthropicOptions.js +106 -0
  200. package/dist/esm/lib/v3/llm/anthropicOptions.js.map +1 -0
  201. package/dist/esm/lib/v3/types/public/agent.d.ts +8 -2
  202. package/dist/esm/lib/v3/types/public/agent.js +3 -0
  203. package/dist/esm/lib/v3/types/public/agent.js.map +1 -1
  204. package/dist/esm/lib/v3/types/public/agentEvidenceEvents.d.ts +85 -0
  205. package/dist/esm/lib/v3/types/public/agentEvidenceEvents.js +14 -0
  206. package/dist/esm/lib/v3/types/public/agentEvidenceEvents.js.map +1 -0
  207. package/dist/esm/lib/v3/types/public/api.d.ts +925 -182
  208. package/dist/esm/lib/v3/types/public/api.js +136 -18
  209. package/dist/esm/lib/v3/types/public/api.js.map +1 -1
  210. package/dist/esm/lib/v3/types/public/clipboard.d.ts +15 -0
  211. package/dist/esm/lib/v3/types/public/clipboard.js +2 -0
  212. package/dist/esm/lib/v3/types/public/clipboard.js.map +1 -0
  213. package/dist/esm/lib/v3/types/public/index.d.ts +2 -0
  214. package/dist/esm/lib/v3/types/public/index.js +2 -0
  215. package/dist/esm/lib/v3/types/public/index.js.map +1 -1
  216. package/dist/esm/lib/v3/types/public/model.d.ts +33 -8
  217. package/dist/esm/lib/v3/types/public/model.js.map +1 -1
  218. package/dist/esm/lib/v3/types/public/page.d.ts +29 -0
  219. package/dist/esm/lib/v3/types/public/page.js.map +1 -1
  220. package/dist/esm/lib/v3/types/public/sdkErrors.d.ts +3 -0
  221. package/dist/esm/lib/v3/types/public/sdkErrors.js +5 -0
  222. package/dist/esm/lib/v3/types/public/sdkErrors.js.map +1 -1
  223. package/dist/esm/lib/v3/understudy/clipboard.d.ts +24 -0
  224. package/dist/esm/lib/v3/understudy/clipboard.js +163 -0
  225. package/dist/esm/lib/v3/understudy/clipboard.js.map +1 -0
  226. package/dist/esm/lib/v3/understudy/context.d.ts +3 -0
  227. package/dist/esm/lib/v3/understudy/context.js +15 -0
  228. package/dist/esm/lib/v3/understudy/context.js.map +1 -1
  229. package/dist/esm/lib/v3/understudy/page.d.ts +23 -1
  230. package/dist/esm/lib/v3/understudy/page.js +284 -1
  231. package/dist/esm/lib/v3/understudy/page.js.map +1 -1
  232. package/dist/esm/lib/v3/v3.js +20 -8
  233. package/dist/esm/lib/v3/v3.js.map +1 -1
  234. package/dist/esm/lib/v3/verifier/errorTaxonomy.d.ts +35 -0
  235. package/dist/esm/lib/v3/verifier/errorTaxonomy.js +285 -0
  236. package/dist/esm/lib/v3/verifier/errorTaxonomy.js.map +1 -0
  237. package/dist/esm/lib/v3/verifier/evidence.d.ts +44 -0
  238. package/dist/esm/lib/v3/verifier/evidence.js +393 -0
  239. package/dist/esm/lib/v3/verifier/evidence.js.map +1 -0
  240. package/dist/esm/lib/v3/verifier/evidenceNormalization.d.ts +7 -0
  241. package/dist/esm/lib/v3/verifier/evidenceNormalization.js +93 -0
  242. package/dist/esm/lib/v3/verifier/evidenceNormalization.js.map +1 -0
  243. package/dist/esm/lib/v3/verifier/index.d.ts +6 -0
  244. package/dist/esm/lib/v3/verifier/index.js +3 -0
  245. package/dist/esm/lib/v3/verifier/index.js.map +1 -0
  246. package/dist/esm/lib/v3/verifier/prompts/batchedRelevance.d.ts +21 -0
  247. package/dist/esm/lib/v3/verifier/prompts/batchedRelevance.js +68 -0
  248. package/dist/esm/lib/v3/verifier/prompts/batchedRelevance.js.map +1 -0
  249. package/dist/esm/lib/v3/verifier/prompts/firstPointOfFailure.d.ts +3 -0
  250. package/dist/esm/lib/v3/verifier/prompts/firstPointOfFailure.js +162 -0
  251. package/dist/esm/lib/v3/verifier/prompts/firstPointOfFailure.js.map +1 -0
  252. package/dist/esm/lib/v3/verifier/prompts/fusedJudgment.d.ts +35 -0
  253. package/dist/esm/lib/v3/verifier/prompts/fusedJudgment.js +155 -0
  254. package/dist/esm/lib/v3/verifier/prompts/fusedJudgment.js.map +1 -0
  255. package/dist/esm/lib/v3/verifier/prompts/fusedOutcome.d.ts +26 -0
  256. package/dist/esm/lib/v3/verifier/prompts/fusedOutcome.js +134 -0
  257. package/dist/esm/lib/v3/verifier/prompts/fusedOutcome.js.map +1 -0
  258. package/dist/esm/lib/v3/verifier/prompts/index.d.ts +8 -0
  259. package/dist/esm/lib/v3/verifier/prompts/index.js +9 -0
  260. package/dist/esm/lib/v3/verifier/prompts/index.js.map +1 -0
  261. package/dist/esm/lib/v3/verifier/prompts/perCriterionScore.d.ts +23 -0
  262. package/dist/esm/lib/v3/verifier/prompts/perCriterionScore.js +74 -0
  263. package/dist/esm/lib/v3/verifier/prompts/perCriterionScore.js.map +1 -0
  264. package/dist/esm/lib/v3/verifier/prompts/render.d.ts +23 -0
  265. package/dist/esm/lib/v3/verifier/prompts/render.js +41 -0
  266. package/dist/esm/lib/v3/verifier/prompts/render.js.map +1 -0
  267. package/dist/esm/lib/v3/verifier/prompts/rubricGeneration.d.ts +16 -0
  268. package/dist/esm/lib/v3/verifier/prompts/rubricGeneration.js +318 -0
  269. package/dist/esm/lib/v3/verifier/prompts/rubricGeneration.js.map +1 -0
  270. package/dist/esm/lib/v3/verifier/prompts/taskValidity.d.ts +1 -0
  271. package/dist/esm/lib/v3/verifier/prompts/taskValidity.js +113 -0
  272. package/dist/esm/lib/v3/verifier/prompts/taskValidity.js.map +1 -0
  273. package/dist/esm/lib/v3/verifier/rubricVerifier.d.ts +91 -0
  274. package/dist/esm/lib/v3/verifier/rubricVerifier.js +1640 -0
  275. package/dist/esm/lib/v3/verifier/rubricVerifier.js.map +1 -0
  276. package/dist/esm/lib/v3/verifier/trajectory.d.ts +50 -0
  277. package/dist/esm/lib/v3/verifier/trajectory.js +273 -0
  278. package/dist/esm/lib/v3/verifier/trajectory.js.map +1 -0
  279. package/dist/esm/lib/v3/verifier/types.d.ts +429 -0
  280. package/dist/esm/lib/v3/verifier/types.js +2 -0
  281. package/dist/esm/lib/v3/verifier/types.js.map +1 -0
  282. package/dist/esm/lib/v3Evaluator.d.ts +14 -4
  283. package/dist/esm/lib/v3Evaluator.js +189 -1
  284. package/dist/esm/lib/v3Evaluator.js.map +1 -1
  285. package/dist/esm/lib/v3LegacyEvaluator.js +5 -1
  286. package/dist/esm/lib/v3LegacyEvaluator.js.map +1 -1
  287. package/dist/esm/lib/version.d.ts +1 -1
  288. package/dist/esm/lib/version.js +1 -1
  289. package/dist/esm/lib/version.js.map +1 -1
  290. package/package.json +18 -12
@@ -0,0 +1,1640 @@
1
+ import { z } from "zod";
2
+ import { normalizeRubric } from "./trajectory.js";
3
+ import { FIRST_POINT_OF_FAILURE_PROMPT, FUSED_JUDGMENT_PROMPT, FUSED_OUTCOME_PROMPT, MM_BATCHED_RELEVANCE_PROMPT, MM_PER_CRITERION_SCORE_PROMPT, RUBRIC_GENERATION_PROMPT, TASK_VALIDITY_PROMPT, buildInitUrlContext, parseFailureStepNumbers, renderPrompt, } from "./prompts/index.js";
4
+ import { collectCanonicalEvidence, isImageEvidence, isTextEvidence, } from "./evidence.js";
5
+ import { getTaxonomyText } from "./errorTaxonomy.js";
6
+ const RubricItemSchema = z.object({
7
+ criterion: z.string(),
8
+ description: z.string(),
9
+ max_points: z.number(),
10
+ condition: z.string().optional(),
11
+ task_span: z.string().optional(),
12
+ justification: z.string().optional(),
13
+ earned_points: z.union([z.number(), z.string()]).optional(),
14
+ });
15
+ const RubricSchema = z.object({
16
+ items: z.array(RubricItemSchema),
17
+ });
18
+ const FindingSchema = z.object({
19
+ category: z
20
+ .enum([
21
+ "agent_tool_usage",
22
+ "agent_strategy",
23
+ "rubric_quality",
24
+ "trajectory_capture",
25
+ "task_specification",
26
+ "verifier_uncertainty",
27
+ "other",
28
+ ])
29
+ .catch("other"),
30
+ severity: z.enum(["info", "warning", "blocking"]).catch("info"),
31
+ description: z.string(),
32
+ suggestedAction: z.string().optional(),
33
+ relatedSteps: z.array(z.number()).optional(),
34
+ });
35
+ const FusedOutcomeSchema = z.object({
36
+ primary_intent: z.string(),
37
+ reasoning: z.string(),
38
+ output_success: z.boolean(),
39
+ findings: z.array(FindingSchema).optional().default([]),
40
+ });
41
+ const FusedPerCriterionSchema = z.object({
42
+ criterion_idx: z.coerce.number().int().min(0),
43
+ applicable_evidence: z.string().optional().default(""),
44
+ justification: z.string().optional().default(""),
45
+ earned_points: z.coerce.number(),
46
+ evidence_sufficient: z.boolean().optional().default(true),
47
+ condition_met: z.boolean().nullable().optional(),
48
+ });
49
+ const FusedFailurePointSchema = z.object({
50
+ step_index: z.coerce.number().int(),
51
+ error_code: z.string(),
52
+ error_category: z.string(),
53
+ description: z.string(),
54
+ });
55
+ const FusedTaskValiditySchema = z.object({
56
+ is_ambiguous: z.boolean(),
57
+ ambiguity_reason: z.string().optional().default(""),
58
+ is_invalid: z.boolean(),
59
+ invalid_reason: z.string().optional().default(""),
60
+ });
61
+ const FusedJudgmentResponseSchema = z.object({
62
+ outcome: FusedOutcomeSchema,
63
+ per_criterion: z.array(FusedPerCriterionSchema),
64
+ failure_point: FusedFailurePointSchema.optional(),
65
+ task_validity: FusedTaskValiditySchema.optional(),
66
+ });
67
+ /** Outcome-only response: no per_criterion field, just outcome + diagnostics. */
68
+ const FusedOutcomeResponseSchema = z.object({
69
+ outcome: FusedOutcomeSchema,
70
+ failure_point: FusedFailurePointSchema.optional(),
71
+ task_validity: FusedTaskValiditySchema.optional(),
72
+ });
73
+ const BatchedRelevanceItemSchema = z.object({
74
+ evidence_idx: z.coerce.number().int().min(0),
75
+ scores: z.array(z.object({
76
+ criterion_idx: z.coerce.number().int().min(0),
77
+ score: z.coerce.number().int().min(0).max(10),
78
+ })),
79
+ });
80
+ const BatchedRelevanceResponseSchema = z.object({
81
+ items: z.array(BatchedRelevanceItemSchema),
82
+ });
83
+ const PerCriterionScoreResponseSchema = z.object({
84
+ criterion_idx: z.coerce.number().int().min(0),
85
+ applicable_evidence: z.string().optional().default(""),
86
+ justification: z.string().optional().default(""),
87
+ earned_points: z.coerce.number(),
88
+ evidence_sufficient: z.boolean().optional().default(true),
89
+ condition_met: z.boolean().nullable().optional(),
90
+ });
91
+ const TaskValiditySchema = z.object({
92
+ reasoning_is_ambiguous: z.string(),
93
+ is_ambiguous: z.boolean(),
94
+ ambiguity_codes: z.array(z.string()).default([]),
95
+ reasoning_is_invalid: z.string(),
96
+ is_invalid: z.boolean(),
97
+ invalid_task_codes: z.array(z.string()).default([]),
98
+ });
99
+ const FailurePointSchema = z.object({
100
+ step_numbers: z.string(),
101
+ error_code: z.string(),
102
+ error_category: z.string(),
103
+ error_type: z.string(),
104
+ what_happened: z.string(),
105
+ agent_reasoning: z.string(),
106
+ evidence: z.string(),
107
+ impact: z.string(),
108
+ });
109
+ const FailureAnalysisSchema = z.object({
110
+ reasoning: z.string(),
111
+ has_failure: z.boolean(),
112
+ failure_points: z.array(FailurePointSchema).default([]),
113
+ });
114
+ const noopLogger = () => { };
115
+ const APPROX_CHARS_PER_TOKEN = 4;
116
+ const DEFAULT_ACTION_HISTORY_TOKEN_BUDGET = 2_000;
117
+ const DEFAULT_EVIDENCE_TOKEN_BUDGET = 3_000;
118
+ const DEFAULT_OUTCOME_EVIDENCE_TOKEN_BUDGET = 4_000;
119
+ const DEFAULT_OUTCOME_IMAGE_LIMIT = 3;
120
+ const DEFAULT_MAX_PARALLEL = 8;
121
+ const DEFAULT_TOP_K = 5;
122
+ const DEFAULT_RELEVANCE_BATCH_SIZE = 4;
123
+ const OUTCOME_EVIDENCE_MAX_STEPS = 14;
124
+ const OUTCOME_EVIDENCE_STEP_CHARS = 900;
125
+ /**
126
+ * How much of the final aria tree to include in the always-attached
127
+ * "Final trajectory state" block. The verifier needs to see the end-of-run
128
+ * page content reliably — the top-K/relevance selection can starve it out
129
+ * when the final probe doesn't textually match the task keywords. 20k chars
130
+ * (~5k tokens) is comfortably above typical page sizes while bounded.
131
+ */
132
+ const FINAL_STATE_ARIA_CHARS = 20_000;
133
+ const DEFAULT_APPROACH = "b";
134
+ const DEFAULT_OPTIONAL_STEPS_MODE = "folded";
135
+ const NO_TRUNC = Number.MAX_SAFE_INTEGER;
136
+ function readPositiveIntEnv(env, name, fallback) {
137
+ const raw = env[name];
138
+ if (!raw)
139
+ return fallback;
140
+ const parsed = Number.parseInt(raw, 10);
141
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
142
+ }
143
+ function readChars(env, name, fallback, disabled) {
144
+ if (disabled)
145
+ return NO_TRUNC;
146
+ return readPositiveIntEnv(env, name, fallback);
147
+ }
148
+ function readApproach(env) {
149
+ const raw = env.VERIFIER_APPROACH;
150
+ if (raw === "a" || raw === "b" || raw === "outcome-only")
151
+ return raw;
152
+ return DEFAULT_APPROACH;
153
+ }
154
+ function readOptionalsMode(env) {
155
+ const raw = env.VERIFIER_OPTIONAL_STEPS;
156
+ if (raw === "folded" || raw === "separate" || raw === "skip")
157
+ return raw;
158
+ return DEFAULT_OPTIONAL_STEPS_MODE;
159
+ }
160
+ /**
161
+ * Resolve every verifier knob from env (+ optional overrides) into a frozen
162
+ * VerifierConfig. Called once by RubricVerifier's constructor; per-call
163
+ * overrides flow through verify()'s optional override arg.
164
+ *
165
+ * The master switch VERIFIER_DISABLE_TRUNCATION=1 lifts every per-section
166
+ * limit to MAX_SAFE_INTEGER — useful on high-context models where
167
+ * evidence-bound truncation is the bottleneck, not the token budget.
168
+ */
169
+ export function resolveVerifierConfig(env = process.env, overrides = {}) {
170
+ const truncDisabled = overrides.truncation?.disabled ?? env.VERIFIER_DISABLE_TRUNCATION === "1";
171
+ return {
172
+ approach: overrides.approach ?? readApproach(env),
173
+ optionalSteps: overrides.optionalSteps ?? readOptionalsMode(env),
174
+ topK: overrides.topK ??
175
+ readPositiveIntEnv(env, "VERIFIER_TOP_K", DEFAULT_TOP_K),
176
+ relevanceBatchSize: overrides.relevanceBatchSize ??
177
+ readPositiveIntEnv(env, "VERIFIER_RELEVANCE_BATCH_SIZE", DEFAULT_RELEVANCE_BATCH_SIZE),
178
+ outcomeMaxImages: overrides.outcomeMaxImages ??
179
+ readPositiveIntEnv(env, "VERIFIER_OUTCOME_MAX_IMAGES", DEFAULT_OUTCOME_IMAGE_LIMIT),
180
+ maxParallel: overrides.maxParallel ??
181
+ readPositiveIntEnv(env, "VERIFIER_MAX_PARALLEL", DEFAULT_MAX_PARALLEL),
182
+ evidenceTokenBudget: overrides.evidenceTokenBudget ??
183
+ readPositiveIntEnv(env, "VERIFIER_EVIDENCE_TOKEN_BUDGET", DEFAULT_EVIDENCE_TOKEN_BUDGET),
184
+ outcomeEvidenceTokenBudget: overrides.outcomeEvidenceTokenBudget ??
185
+ readPositiveIntEnv(env, "VERIFIER_OUTCOME_EVIDENCE_TOKEN_BUDGET", DEFAULT_OUTCOME_EVIDENCE_TOKEN_BUDGET),
186
+ actionHistoryTokenBudget: overrides.actionHistoryTokenBudget ??
187
+ readPositiveIntEnv(env, "VERIFIER_ACTION_HISTORY_TOKEN_BUDGET", DEFAULT_ACTION_HISTORY_TOKEN_BUDGET),
188
+ truncation: {
189
+ disabled: truncDisabled,
190
+ evidenceTextPreview: overrides.truncation?.evidenceTextPreview ??
191
+ readChars(env, "VERIFIER_EVIDENCE_TEXT_PREVIEW_CHARS", 200, truncDisabled),
192
+ groupedEvidenceText: overrides.truncation?.groupedEvidenceText ??
193
+ readChars(env, "VERIFIER_GROUPED_EVIDENCE_TEXT_CHARS", 600, truncDisabled),
194
+ buildEvidenceText: overrides.truncation?.buildEvidenceText ??
195
+ readChars(env, "VERIFIER_BUILD_EVIDENCE_TEXT_CHARS", 160, truncDisabled),
196
+ buildEvidenceAria: overrides.truncation?.buildEvidenceAria ??
197
+ readChars(env, "VERIFIER_BUILD_EVIDENCE_ARIA_CHARS", 1200, truncDisabled),
198
+ actionHistoryReasoning: overrides.truncation?.actionHistoryReasoning ??
199
+ readChars(env, "VERIFIER_ACTION_HISTORY_REASONING_CHARS", 140, truncDisabled),
200
+ },
201
+ };
202
+ }
203
+ function mergeConfig(base, overrides) {
204
+ if (!overrides)
205
+ return base;
206
+ return {
207
+ ...base,
208
+ ...overrides,
209
+ truncation: { ...base.truncation, ...(overrides.truncation ?? {}) },
210
+ };
211
+ }
212
+ /** Top-K grouping per criterion. Pure compute. */
213
+ function groupTopKByCriterion(args) {
214
+ const { numCriteria, relevanceScores, topK } = args;
215
+ const grouped = new Map();
216
+ for (let cIdx = 0; cIdx < numCriteria; cIdx++) {
217
+ const scored = [];
218
+ for (const [eIdx, scoreMap] of relevanceScores.entries()) {
219
+ scored.push({ eIdx, score: scoreMap.get(cIdx) ?? 0 });
220
+ }
221
+ scored.sort((a, b) => {
222
+ if (b.score !== a.score)
223
+ return b.score - a.score;
224
+ return a.eIdx - b.eIdx; // ties → chronological order
225
+ });
226
+ const topKEvidence = scored.slice(0, topK);
227
+ // Relevance-floor filter: if any selected evidence scored ≥6,
228
+ // drop low-relevance entries that are >2 points below the weakest
229
+ // high-relevance entry.
230
+ const highScores = topKEvidence
231
+ .filter((s) => s.score >= 6)
232
+ .map((s) => s.score);
233
+ if (highScores.length === 0) {
234
+ grouped.set(cIdx, topKEvidence.map((s) => s.eIdx));
235
+ continue;
236
+ }
237
+ const minHigh = Math.min(...highScores);
238
+ const kept = topKEvidence.filter((s) => !(s.score < 5 && minHigh - s.score > 2));
239
+ grouped.set(cIdx, (kept.length > 0 ? kept : topKEvidence).map((s) => s.eIdx));
240
+ }
241
+ return grouped;
242
+ }
243
+ function mapFusedPerCriterionToScores(rubric, perCriterion) {
244
+ const byIdx = new Map();
245
+ for (const entry of perCriterion)
246
+ byIdx.set(entry.criterion_idx, entry);
247
+ return rubric.items.map((c, i) => {
248
+ const entry = byIdx.get(i);
249
+ if (!entry) {
250
+ return {
251
+ criterion: c.criterion,
252
+ maxPoints: c.maxPoints,
253
+ earnedPoints: null,
254
+ explanation: "Verifier did not return a score for this criterion.",
255
+ evidenceInsufficient: true,
256
+ };
257
+ }
258
+ const clamped = Math.max(0, Math.min(c.maxPoints, entry.earned_points));
259
+ return {
260
+ criterion: c.criterion,
261
+ maxPoints: c.maxPoints,
262
+ earnedPoints: clamped,
263
+ explanation: entry.justification,
264
+ evidenceInsufficient: entry.evidence_sufficient === false,
265
+ };
266
+ });
267
+ }
268
+ function evidencePreview(point, previewChars) {
269
+ if (isImageEvidence(point)) {
270
+ return `Screenshot at step ${point.originalStepIndex} (${point.bytes.length} bytes, ${point.mediaType})`;
271
+ }
272
+ const preview = point.content.slice(0, previewChars);
273
+ return `${textEvidenceLabel(point)} at step ${point.originalStepIndex} — "${preview.replace(/\s+/g, " ")}${point.content.length > previewChars ? "…" : ""}"`;
274
+ }
275
+ function textEvidenceLabel(point) {
276
+ switch (point.source) {
277
+ case "probe-aria":
278
+ return "ariaTree";
279
+ case "agent-text":
280
+ return "agent text";
281
+ case "agent-json":
282
+ return "agent JSON";
283
+ case "tool-output":
284
+ return "tool output";
285
+ }
286
+ }
287
+ function renderEvidenceManifest(points, previewChars) {
288
+ if (points.length === 0)
289
+ return "(no evidence captured)";
290
+ return points
291
+ .map((p) => `- evidence_idx=${p.canonicalIndex}: ${evidencePreview(p, previewChars)}`)
292
+ .join("\n");
293
+ }
294
+ function renderGroupedEvidenceForApproach(rubric, evidence, groupedTopK, textLimit) {
295
+ if (evidence.length === 0)
296
+ return "(no evidence captured)";
297
+ const byIdx = new Map();
298
+ for (const e of evidence)
299
+ byIdx.set(e.canonicalIndex, e);
300
+ const sections = [];
301
+ for (let cIdx = 0; cIdx < rubric.items.length; cIdx++) {
302
+ const c = rubric.items[cIdx];
303
+ const topK = groupedTopK.get(cIdx) ?? [];
304
+ if (topK.length === 0) {
305
+ sections.push(`### Criterion ${cIdx}: ${c.criterion}\n(no evidence scored highly enough — rely on action history)`);
306
+ continue;
307
+ }
308
+ const body = topK
309
+ .map((eIdx) => {
310
+ const p = byIdx.get(eIdx);
311
+ if (!p)
312
+ return null;
313
+ if (isImageEvidence(p)) {
314
+ return `- Evidence #${eIdx} — image @ step=${p.originalStepIndex}`;
315
+ }
316
+ const text = p.content.replace(/\s+/g, " ").slice(0, textLimit);
317
+ return `- Evidence #${eIdx} — ${textEvidenceLabel(p)} @ step=${p.originalStepIndex}: "${text}${p.content.length > textLimit ? "…" : ""}"`;
318
+ })
319
+ .filter((x) => x !== null)
320
+ .join("\n");
321
+ sections.push(`### Criterion ${cIdx}: ${c.criterion}\n${body}`);
322
+ }
323
+ return sections.join("\n\n");
324
+ }
325
+ export class RubricVerifier {
326
+ getClient;
327
+ getRubricGenClient;
328
+ logger;
329
+ baseConfig;
330
+ constructor(opts) {
331
+ this.getClient = opts.getClient;
332
+ this.getRubricGenClient = opts.getRubricGenClient ?? opts.getClient;
333
+ this.logger = opts.logger ?? noopLogger;
334
+ this.baseConfig = resolveVerifierConfig(process.env, opts.config);
335
+ }
336
+ /** Resolved verifier knobs the constructor saw, frozen at construction. */
337
+ get config() {
338
+ return this.baseConfig;
339
+ }
340
+ async verify(trajectory, overrides) {
341
+ const taskSpec = trajectory.task;
342
+ const config = mergeConfig(this.baseConfig, overrides);
343
+ const hasTrajectorySignal = trajectory.steps.length > 0 || Boolean(trajectory.finalAnswer?.trim());
344
+ if (!hasTrajectorySignal) {
345
+ return this.emptyTrajectoryResult(normalizeRubric(taskSpec.precomputedRubric));
346
+ }
347
+ const { approach, optionalSteps: optionalsMode } = config;
348
+ if (approach === "outcome-only") {
349
+ return this.verifyOutcomeOnly(trajectory, taskSpec, config);
350
+ }
351
+ let rubric = normalizeRubric(taskSpec.precomputedRubric);
352
+ const rubricSource = rubric ? "precomputed" : "generated";
353
+ if (!rubric) {
354
+ rubric = await this.generateRubric(taskSpec);
355
+ }
356
+ // Empty-evidence trajectories fall back gracefully — the chosen approach
357
+ // degrades to an action-history-only judgment downstream.
358
+ const { evidence, loaded } = await collectCanonicalEvidence(trajectory);
359
+ const relevanceScores = await this.scoreRelevanceBatched({
360
+ taskSpec,
361
+ rubric,
362
+ evidence,
363
+ config,
364
+ });
365
+ const groupedTopK = groupTopKByCriterion({
366
+ numCriteria: rubric.items.length,
367
+ relevanceScores,
368
+ topK: config.topK,
369
+ });
370
+ let perCriterion;
371
+ let fusedOutcome;
372
+ let foldedFailurePoint;
373
+ let foldedTaskValidity;
374
+ if (approach === "b") {
375
+ const fused = await this.fusedJudgment({
376
+ trajectory,
377
+ taskSpec,
378
+ rubric,
379
+ evidence,
380
+ groupedTopK,
381
+ foldFailure: optionalsMode === "folded",
382
+ foldValidity: optionalsMode === "folded",
383
+ config,
384
+ });
385
+ perCriterion = mapFusedPerCriterionToScores(rubric, fused.per_criterion);
386
+ fusedOutcome = fused.outcome;
387
+ foldedFailurePoint = fused.failure_point;
388
+ foldedTaskValidity = fused.task_validity;
389
+ }
390
+ else {
391
+ // Approach a: per-criterion analysis returns earned_points directly;
392
+ // no separate whole-rubric rescore.
393
+ perCriterion = await this.scorePerCriterion({
394
+ trajectory,
395
+ taskSpec,
396
+ rubric,
397
+ evidence,
398
+ groupedTopK,
399
+ config,
400
+ });
401
+ const outcome = await this.verifyOutcomeFused({
402
+ trajectory,
403
+ taskSpec,
404
+ rubric,
405
+ perCriterion,
406
+ evidence,
407
+ foldFailure: optionalsMode === "folded",
408
+ foldValidity: optionalsMode === "folded",
409
+ config,
410
+ });
411
+ fusedOutcome = outcome.outcome;
412
+ foldedFailurePoint = outcome.failure_point;
413
+ foldedTaskValidity = outcome.task_validity;
414
+ }
415
+ // ── Process score (deterministic from earned_points) ──────────────────
416
+ const totals = perCriterion.reduce((acc, c) => ({
417
+ earned: acc.earned + (c.earnedPoints ?? 0),
418
+ max: acc.max + c.maxPoints,
419
+ }), { earned: 0, max: 0 });
420
+ const processScore = totals.max > 0 ? totals.earned / totals.max : 0;
421
+ const evidenceInsufficient = perCriterion
422
+ .filter((c) => c.evidenceInsufficient)
423
+ .map((c) => c.criterion);
424
+ const findings = (fusedOutcome?.findings ?? []).map((f) => ({
425
+ ...f,
426
+ category: f.category ?? "other",
427
+ severity: f.severity ?? "info",
428
+ }));
429
+ // ── Optional steps: folded, separate, or skipped ──────────────────────
430
+ let firstPointOfFailure;
431
+ if (foldedFailurePoint && !fusedOutcome?.output_success) {
432
+ firstPointOfFailure = {
433
+ stepIndex: foldedFailurePoint.step_index,
434
+ errorCode: foldedFailurePoint.error_code,
435
+ category: foldedFailurePoint.error_category,
436
+ description: foldedFailurePoint.description,
437
+ };
438
+ }
439
+ else if (optionalsMode === "separate" &&
440
+ fusedOutcome &&
441
+ !fusedOutcome.output_success) {
442
+ firstPointOfFailure = await this.analyzeFailures({
443
+ trajectory,
444
+ taskSpec,
445
+ rubric,
446
+ perCriterion,
447
+ outcome: {
448
+ output_success: fusedOutcome.output_success,
449
+ primary_intent: fusedOutcome.primary_intent,
450
+ reasoning: fusedOutcome.reasoning,
451
+ findings: fusedOutcome.findings ?? [],
452
+ },
453
+ config,
454
+ }).catch(() => undefined);
455
+ }
456
+ let taskValidity;
457
+ if (foldedTaskValidity) {
458
+ taskValidity = {
459
+ isAmbiguous: foldedTaskValidity.is_ambiguous,
460
+ isInvalid: foldedTaskValidity.is_invalid,
461
+ ambiguityReason: foldedTaskValidity.is_ambiguous && foldedTaskValidity.ambiguity_reason
462
+ ? foldedTaskValidity.ambiguity_reason
463
+ : undefined,
464
+ invalidReason: foldedTaskValidity.is_invalid && foldedTaskValidity.invalid_reason
465
+ ? foldedTaskValidity.invalid_reason
466
+ : undefined,
467
+ };
468
+ }
469
+ else if (optionalsMode === "separate") {
470
+ taskValidity = await this.classifyTaskValidity(taskSpec).catch(() => ({
471
+ isAmbiguous: false,
472
+ isInvalid: false,
473
+ }));
474
+ }
475
+ else {
476
+ taskValidity = { isAmbiguous: false, isInvalid: false };
477
+ }
478
+ return {
479
+ outcomeSuccess: fusedOutcome?.output_success ?? false,
480
+ processScore,
481
+ perCriterion,
482
+ taskValidity,
483
+ evidenceInsufficient,
484
+ findings: findings.length > 0 ? findings : undefined,
485
+ firstPointOfFailure,
486
+ rawSteps: {
487
+ primaryIntent: fusedOutcome?.primary_intent,
488
+ reasoning: fusedOutcome?.reasoning,
489
+ rubricSource,
490
+ approach,
491
+ optionalsMode,
492
+ totalEarned: totals.earned,
493
+ totalMax: totals.max,
494
+ evidenceImages: evidence.filter(isImageEvidence).length,
495
+ evidenceTexts: evidence.filter(isTextEvidence).length,
496
+ evidenceOriginalScreenshots: loaded.originalCount,
497
+ },
498
+ };
499
+ }
500
+ emptyTrajectoryResult(rubric) {
501
+ const items = rubric?.items ?? [];
502
+ return {
503
+ outcomeSuccess: false,
504
+ explanation: "No trajectory steps or final answer were captured; skipped verifier LLM calls.",
505
+ processScore: 0,
506
+ perCriterion: items.map((c) => ({
507
+ criterion: c.criterion,
508
+ maxPoints: c.maxPoints,
509
+ earnedPoints: 0,
510
+ explanation: "No trajectory steps or final answer were captured; skipped verifier LLM calls.",
511
+ evidenceInsufficient: true,
512
+ })),
513
+ taskValidity: { isAmbiguous: false, isInvalid: false },
514
+ evidenceInsufficient: items.map((c) => c.criterion),
515
+ rawSteps: {
516
+ reason: "empty-trajectory",
517
+ rubricSource: rubric ? "precomputed" : "none",
518
+ },
519
+ };
520
+ }
521
+ async verifyOutcomeOnly(trajectory, taskSpec, config) {
522
+ const foldFailure = config.optionalSteps === "folded";
523
+ const foldValidity = config.optionalSteps === "folded";
524
+ const taxonomyBlock = foldFailure
525
+ ? `\n${getTaxonomyText(1, 6, 4)}\n${getTaxonomyText(7, 8, 4)}\n`
526
+ : "";
527
+ const prompt = renderPrompt(FUSED_OUTCOME_PROMPT, {
528
+ task_definition: taskSpec.instruction,
529
+ init_url_context: buildInitUrlContext(taskSpec.initUrl),
530
+ action_history: this.formatActionHistory(trajectory, config),
531
+ outcome_evidence_summary: this.buildOutcomeEvidenceSummary(trajectory, taskSpec, config),
532
+ final_state_block: buildFinalStateBlock(trajectory),
533
+ agent_predicted_output: trajectory.finalAnswer ?? "(no final answer recorded)",
534
+ rubric_summary: "(no rubric - outcome-only mode; judge success from the task, action history, final answer, and attached screenshots)",
535
+ taxonomy_block: taxonomyBlock,
536
+ fold_failure_analysis: foldFailure ? "true" : "false",
537
+ fold_task_validity: foldValidity ? "true" : "false",
538
+ current_date: currentDateForVerifier(),
539
+ });
540
+ const messageContent = [{ type: "text", text: prompt }];
541
+ // Final images first (always attached), then recent images deduped.
542
+ const seenImageKeys = new Set();
543
+ const attachImage = (img) => {
544
+ const key = `${img.bytes.length}:${img.bytes.subarray(0, 32).toString("base64")}`;
545
+ if (seenImageKeys.has(key))
546
+ return;
547
+ seenImageKeys.add(key);
548
+ messageContent.push({
549
+ type: "image_url",
550
+ image_url: {
551
+ url: `data:${img.mediaType};base64,${img.bytes.toString("base64")}`,
552
+ },
553
+ });
554
+ };
555
+ for (const img of selectFinalImages(trajectory))
556
+ attachImage(img);
557
+ for (const img of selectRecentImages(trajectory, config.outcomeMaxImages))
558
+ attachImage(img);
559
+ let fused;
560
+ try {
561
+ const client = this.getClient();
562
+ const response = await client.createChatCompletion({
563
+ logger: this.logger,
564
+ options: {
565
+ messages: [
566
+ {
567
+ role: "system",
568
+ content: "You are an expert evaluator of web-navigation agent trajectories. Output only valid JSON conforming to the schema in the user message.",
569
+ },
570
+ { role: "user", content: messageContent },
571
+ ],
572
+ response_model: {
573
+ name: "FusedOutcome",
574
+ schema: FusedOutcomeResponseSchema,
575
+ },
576
+ },
577
+ });
578
+ fused = response.data;
579
+ }
580
+ catch {
581
+ fused = {
582
+ outcome: {
583
+ primary_intent: taskSpec.instruction,
584
+ reasoning: "Outcome-only LLM call failed; defaulting to output_success=false.",
585
+ output_success: false,
586
+ findings: [
587
+ {
588
+ category: "verifier_uncertainty",
589
+ severity: "warning",
590
+ description: "The outcome-only verification call did not return a parseable response.",
591
+ },
592
+ ],
593
+ },
594
+ };
595
+ }
596
+ const outcomeSuccess = fused.outcome.output_success;
597
+ const findings = (fused.outcome.findings ?? []).map((f) => ({
598
+ ...f,
599
+ category: f.category ?? "other",
600
+ severity: f.severity ?? "info",
601
+ }));
602
+ let firstPointOfFailure;
603
+ if (fused.failure_point && !outcomeSuccess) {
604
+ firstPointOfFailure = {
605
+ stepIndex: fused.failure_point.step_index,
606
+ errorCode: fused.failure_point.error_code,
607
+ category: fused.failure_point.error_category,
608
+ description: fused.failure_point.description,
609
+ };
610
+ }
611
+ const taskValidity = fused.task_validity
612
+ ? {
613
+ isAmbiguous: fused.task_validity.is_ambiguous,
614
+ isInvalid: fused.task_validity.is_invalid,
615
+ ambiguityReason: fused.task_validity.is_ambiguous &&
616
+ fused.task_validity.ambiguity_reason
617
+ ? fused.task_validity.ambiguity_reason
618
+ : undefined,
619
+ invalidReason: fused.task_validity.is_invalid && fused.task_validity.invalid_reason
620
+ ? fused.task_validity.invalid_reason
621
+ : undefined,
622
+ }
623
+ : { isAmbiguous: false, isInvalid: false };
624
+ return {
625
+ outcomeSuccess,
626
+ explanation: fused.outcome.reasoning,
627
+ taskValidity,
628
+ findings: findings.length > 0 ? findings : undefined,
629
+ firstPointOfFailure,
630
+ rawSteps: {
631
+ primaryIntent: fused.outcome.primary_intent,
632
+ reasoning: fused.outcome.reasoning,
633
+ approach: "outcome-only",
634
+ optionalsMode: config.optionalSteps,
635
+ screenshotsAttached: seenImageKeys.size,
636
+ },
637
+ };
638
+ }
639
+ /**
640
+ * Score every (evidence, criterion) pair with one batched call per chunk,
641
+ * to avoid a per-(criterion, frame) fan-out. Failed batches contribute
642
+ * all-zeros scores so the downstream top-K still produces valid groups.
643
+ */
644
+ async scoreRelevanceBatched(args) {
645
+ const { taskSpec, rubric, evidence, config } = args;
646
+ const out = new Map();
647
+ if (evidence.length === 0)
648
+ return out;
649
+ const numCriteria = rubric.items.length;
650
+ const rubricCriteriaText = rubric.items
651
+ .map((c, i) => `\n${i}. **${c.criterion}**\n Description: ${c.description}\n`)
652
+ .join("");
653
+ const batchSize = Math.max(1, config.relevanceBatchSize);
654
+ const batches = [];
655
+ for (let i = 0; i < evidence.length; i += batchSize) {
656
+ batches.push(evidence.slice(i, i + batchSize));
657
+ }
658
+ const limit = pLimit(config.maxParallel);
659
+ const tasks = batches.map((batch) => limit(async () => {
660
+ const manifest = renderEvidenceManifest(batch, config.truncation.evidenceTextPreview);
661
+ const prompt = renderPrompt(MM_BATCHED_RELEVANCE_PROMPT, {
662
+ task_definition: taskSpec.instruction,
663
+ init_url_context: buildInitUrlContext(taskSpec.initUrl),
664
+ rubric_criteria: rubricCriteriaText,
665
+ evidence_manifest: manifest,
666
+ });
667
+ const messageContent = [{ type: "text", text: prompt }];
668
+ for (const ev of batch) {
669
+ if (isImageEvidence(ev)) {
670
+ messageContent.push({
671
+ type: "image_url",
672
+ image_url: {
673
+ url: `data:${ev.mediaType};base64,${ev.bytes.toString("base64")}`,
674
+ },
675
+ });
676
+ }
677
+ else {
678
+ messageContent.push({
679
+ type: "text",
680
+ text: `\n[evidence_idx=${ev.canonicalIndex} — ${textEvidenceLabel(ev)} at step ${ev.originalStepIndex}]\n${ev.content}\n`,
681
+ });
682
+ }
683
+ }
684
+ try {
685
+ const client = this.getClient();
686
+ const response = await client.createChatCompletion({
687
+ logger: this.logger,
688
+ options: {
689
+ messages: [
690
+ {
691
+ role: "system",
692
+ content: "You are scoring how relevant each evidence point in a batch is to each rubric criterion. Output only valid JSON conforming to the schema in the user message.",
693
+ },
694
+ { role: "user", content: messageContent },
695
+ ],
696
+ response_model: {
697
+ name: "BatchedRelevance",
698
+ schema: BatchedRelevanceResponseSchema,
699
+ },
700
+ },
701
+ });
702
+ const data = response.data;
703
+ for (const item of data.items) {
704
+ const scoreMap = new Map();
705
+ for (const s of item.scores) {
706
+ if (s.criterion_idx >= 0 && s.criterion_idx < numCriteria) {
707
+ scoreMap.set(s.criterion_idx, s.score);
708
+ }
709
+ }
710
+ for (let i = 0; i < numCriteria; i++) {
711
+ if (!scoreMap.has(i))
712
+ scoreMap.set(i, 0);
713
+ }
714
+ out.set(item.evidence_idx, scoreMap);
715
+ }
716
+ }
717
+ catch {
718
+ // Per-batch failure: zero out the whole batch so the pipeline
719
+ // continues — top-K won't select these evidence points.
720
+ for (const ev of batch) {
721
+ const scoreMap = new Map();
722
+ for (let i = 0; i < numCriteria; i++)
723
+ scoreMap.set(i, 0);
724
+ out.set(ev.canonicalIndex, scoreMap);
725
+ }
726
+ }
727
+ }));
728
+ await Promise.all(tasks);
729
+ // Pad any missing evidence indices with zeros (defensive against the
730
+ // model omitting batch entries).
731
+ for (const ev of evidence) {
732
+ if (!out.has(ev.canonicalIndex)) {
733
+ const scoreMap = new Map();
734
+ for (let i = 0; i < numCriteria; i++)
735
+ scoreMap.set(i, 0);
736
+ out.set(ev.canonicalIndex, scoreMap);
737
+ }
738
+ }
739
+ return out;
740
+ }
741
+ /**
742
+ * One call per rubric criterion. Each call sees the criterion's top-K
743
+ * evidence points (images + ariaTree snippets), the action history, and
744
+ * the final answer; the response includes `earned_points` directly so the
745
+ * process score is deterministic (Σ earned / Σ max).
746
+ */
747
+ async scorePerCriterion(args) {
748
+ const { trajectory, taskSpec, rubric, evidence, groupedTopK, config } = args;
749
+ if (rubric.items.length === 0)
750
+ return [];
751
+ const evidenceByIdx = new Map();
752
+ for (const e of evidence)
753
+ evidenceByIdx.set(e.canonicalIndex, e);
754
+ const actionHistory = this.formatActionHistory(trajectory, config);
755
+ const predictedOutput = trajectory.finalAnswer ?? "(no final answer recorded)";
756
+ const limit = pLimit(config.maxParallel);
757
+ const tasks = rubric.items.map((criterion, cIdx) => limit(async () => {
758
+ const topK = groupedTopK.get(cIdx) ?? [];
759
+ const evidencePoints = topK
760
+ .map((eIdx) => evidenceByIdx.get(eIdx))
761
+ .filter((e) => e !== undefined);
762
+ const manifest = evidencePoints.length === 0
763
+ ? "(no evidence scored highly enough for this criterion — rely on action history)"
764
+ : renderEvidenceManifest(evidencePoints, config.truncation.evidenceTextPreview);
765
+ const conditionLine = criterion.condition
766
+ ? `- Condition: ${criterion.condition}`
767
+ : "";
768
+ const prompt = renderPrompt(MM_PER_CRITERION_SCORE_PROMPT, {
769
+ task_definition: taskSpec.instruction,
770
+ init_url_context: buildInitUrlContext(taskSpec.initUrl),
771
+ action_history: actionHistory,
772
+ agent_predicted_output: predictedOutput,
773
+ criterion_idx: cIdx,
774
+ criterion_name: criterion.criterion,
775
+ criterion_description: criterion.description,
776
+ criterion_max_points: criterion.maxPoints,
777
+ criterion_condition: conditionLine,
778
+ evidence_manifest: manifest,
779
+ });
780
+ const messageContent = [{ type: "text", text: prompt }];
781
+ for (const ev of evidencePoints) {
782
+ if (isImageEvidence(ev)) {
783
+ messageContent.push({
784
+ type: "image_url",
785
+ image_url: {
786
+ url: `data:${ev.mediaType};base64,${ev.bytes.toString("base64")}`,
787
+ },
788
+ });
789
+ }
790
+ else {
791
+ messageContent.push({
792
+ type: "text",
793
+ text: `\n[evidence_idx=${ev.canonicalIndex} — ${textEvidenceLabel(ev)} at step ${ev.originalStepIndex}]\n${ev.content}\n`,
794
+ });
795
+ }
796
+ }
797
+ try {
798
+ const client = this.getClient();
799
+ const response = await client.createChatCompletion({
800
+ logger: this.logger,
801
+ options: {
802
+ messages: [
803
+ {
804
+ role: "system",
805
+ content: "You are scoring one rubric criterion against the most relevant evidence from an agent's trajectory. Output only valid JSON conforming to the schema in the user message.",
806
+ },
807
+ { role: "user", content: messageContent },
808
+ ],
809
+ response_model: {
810
+ name: "PerCriterionScore",
811
+ schema: PerCriterionScoreResponseSchema,
812
+ },
813
+ },
814
+ });
815
+ const data = response.data;
816
+ const clamped = Math.max(0, Math.min(criterion.maxPoints, data.earned_points));
817
+ return {
818
+ criterion: criterion.criterion,
819
+ maxPoints: criterion.maxPoints,
820
+ earnedPoints: clamped,
821
+ explanation: data.justification,
822
+ evidenceInsufficient: data.evidence_sufficient === false,
823
+ };
824
+ }
825
+ catch {
826
+ return {
827
+ criterion: criterion.criterion,
828
+ maxPoints: criterion.maxPoints,
829
+ earnedPoints: null,
830
+ explanation: "Per-criterion scoring call failed; falling back to evidence-insufficient.",
831
+ evidenceInsufficient: true,
832
+ };
833
+ }
834
+ }));
835
+ return Promise.all(tasks);
836
+ }
837
+ /**
838
+ * Single fused multimodal call returning the full EvaluationResult shape:
839
+ * rubric + per-criterion top-K evidence + action history + final answer.
840
+ * Optionally folds in first-point-of-failure and task-validity. Image
841
+ * evidence rides inline; ariaTree text is embedded in the prompt under
842
+ * each criterion's manifest section.
843
+ */
844
+ async fusedJudgment(args) {
845
+ const { trajectory, taskSpec, rubric, evidence, groupedTopK, foldFailure, foldValidity, config, } = args;
846
+ const evidenceByIdx = new Map();
847
+ for (const e of evidence)
848
+ evidenceByIdx.set(e.canonicalIndex, e);
849
+ const usedImageIndices = new Set();
850
+ for (const topK of groupedTopK.values()) {
851
+ for (const eIdx of topK) {
852
+ const p = evidenceByIdx.get(eIdx);
853
+ if (p && isImageEvidence(p))
854
+ usedImageIndices.add(eIdx);
855
+ }
856
+ }
857
+ const usedImages = [...usedImageIndices]
858
+ .sort((a, b) => a - b)
859
+ .map((eIdx) => evidenceByIdx.get(eIdx))
860
+ .filter((p) => !!p && isImageEvidence(p));
861
+ const rubricBlock = rubric.items
862
+ .map((c, i) => {
863
+ const cond = c.condition ? `\n Condition: ${c.condition}` : "";
864
+ return `Criterion ${i} — "${c.criterion}" (max ${c.maxPoints} pts):\n Description: ${c.description}${cond}`;
865
+ })
866
+ .join("\n\n");
867
+ const evidenceBlock = renderGroupedEvidenceForApproach(rubric, evidence, groupedTopK, config.truncation.groupedEvidenceText);
868
+ const taxonomyBlock = foldFailure
869
+ ? `\n${getTaxonomyText(1, 6, 4)}\n${getTaxonomyText(7, 8, 4)}\n`
870
+ : "";
871
+ const prompt = renderPrompt(FUSED_JUDGMENT_PROMPT, {
872
+ task_definition: taskSpec.instruction,
873
+ init_url_context: buildInitUrlContext(taskSpec.initUrl),
874
+ action_history: this.formatActionHistory(trajectory, config),
875
+ agent_predicted_output: trajectory.finalAnswer ?? "(no final answer recorded)",
876
+ rubric_block: rubricBlock,
877
+ evidence_block: evidenceBlock,
878
+ final_state_block: buildFinalStateBlock(trajectory),
879
+ taxonomy_block: taxonomyBlock,
880
+ fold_failure_analysis: foldFailure ? "true" : "false",
881
+ fold_task_validity: foldValidity ? "true" : "false",
882
+ current_date: currentDateForVerifier(),
883
+ });
884
+ const messageContent = [{ type: "text", text: prompt }];
885
+ // Always include the terminal visual evidence first — these bytes are the
886
+ // single most reliable signal of what the agent actually saw at the end
887
+ // of the run, and they're not subject to the per-criterion top-K cutoff.
888
+ // Dedupe by content so we don't pay tokens twice when the top-K already
889
+ // picked the same frame.
890
+ const finalImageBytes = new Set();
891
+ for (const img of selectFinalImages(trajectory)) {
892
+ const key = `${img.bytes.length}:${img.bytes.subarray(0, 32).toString("base64")}`;
893
+ finalImageBytes.add(key);
894
+ messageContent.push({
895
+ type: "image_url",
896
+ image_url: {
897
+ url: `data:${img.mediaType};base64,${img.bytes.toString("base64")}`,
898
+ },
899
+ });
900
+ }
901
+ for (const img of usedImages) {
902
+ const key = `${img.bytes.length}:${img.bytes.subarray(0, 32).toString("base64")}`;
903
+ if (finalImageBytes.has(key))
904
+ continue;
905
+ messageContent.push({
906
+ type: "image_url",
907
+ image_url: {
908
+ url: `data:${img.mediaType};base64,${img.bytes.toString("base64")}`,
909
+ },
910
+ });
911
+ }
912
+ try {
913
+ const client = this.getClient();
914
+ const response = await client.createChatCompletion({
915
+ logger: this.logger,
916
+ options: {
917
+ messages: [
918
+ {
919
+ role: "system",
920
+ content: "You are an expert evaluator of web-navigation agent trajectories. Output only valid JSON conforming to the schema in the user message.",
921
+ },
922
+ { role: "user", content: messageContent },
923
+ ],
924
+ response_model: {
925
+ name: "FusedJudgment",
926
+ schema: FusedJudgmentResponseSchema,
927
+ },
928
+ },
929
+ });
930
+ return response.data;
931
+ }
932
+ catch (e) {
933
+ // Hard failure of the fused call: synthesize a no-confidence result
934
+ // so the pipeline can still produce an EvaluationResult object.
935
+ void e;
936
+ return {
937
+ outcome: {
938
+ primary_intent: taskSpec.instruction,
939
+ reasoning: "Fused judgment LLM call failed; returning evidence-insufficient result.",
940
+ output_success: false,
941
+ findings: [
942
+ {
943
+ category: "verifier_uncertainty",
944
+ severity: "warning",
945
+ description: "The fused judgment call did not return a parseable response.",
946
+ },
947
+ ],
948
+ },
949
+ per_criterion: rubric.items.map((c, i) => ({
950
+ criterion_idx: i,
951
+ applicable_evidence: "",
952
+ justification: "Fused judgment call failed for this criterion.",
953
+ earned_points: 0,
954
+ evidence_sufficient: false,
955
+ })),
956
+ };
957
+ }
958
+ }
959
+ /**
960
+ * Consume the pre-scored rubric from scorePerCriterion and produce the
961
+ * outcome result. When foldFailure/foldValidity are set, the response also
962
+ * includes first-point-of-failure and task-validity, saving 1–2 extra
963
+ * LLM calls.
964
+ */
965
+ async verifyOutcomeFused(args) {
966
+ const { trajectory, taskSpec, rubric, perCriterion, foldFailure, foldValidity, config, } = args;
967
+ void args.evidence;
968
+ const taxonomyBlock = foldFailure
969
+ ? `\n${getTaxonomyText(1, 6, 4)}\n${getTaxonomyText(7, 8, 4)}\n`
970
+ : "";
971
+ const prompt = renderPrompt(FUSED_OUTCOME_PROMPT, {
972
+ task_definition: taskSpec.instruction,
973
+ init_url_context: buildInitUrlContext(taskSpec.initUrl),
974
+ action_history: this.formatActionHistory(trajectory, config),
975
+ outcome_evidence_summary: this.buildOutcomeEvidenceSummary(trajectory, taskSpec, config),
976
+ final_state_block: buildFinalStateBlock(trajectory),
977
+ agent_predicted_output: trajectory.finalAnswer ?? "(no final answer recorded)",
978
+ rubric_summary: this.formatScoredRubricSummary(rubric, perCriterion),
979
+ taxonomy_block: taxonomyBlock,
980
+ fold_failure_analysis: foldFailure ? "true" : "false",
981
+ fold_task_validity: foldValidity ? "true" : "false",
982
+ current_date: currentDateForVerifier(),
983
+ });
984
+ // Always attach the terminal screenshots so the outcome judge can see the
985
+ // final page state, regardless of the (formerly text-only) evidence
986
+ // summary heuristics.
987
+ const messageContent = [{ type: "text", text: prompt }];
988
+ for (const img of selectFinalImages(trajectory)) {
989
+ messageContent.push({
990
+ type: "image_url",
991
+ image_url: {
992
+ url: `data:${img.mediaType};base64,${img.bytes.toString("base64")}`,
993
+ },
994
+ });
995
+ }
996
+ try {
997
+ const client = this.getClient();
998
+ const response = await client.createChatCompletion({
999
+ logger: this.logger,
1000
+ options: {
1001
+ messages: [
1002
+ {
1003
+ role: "system",
1004
+ content: "You are an expert evaluator of web-navigation agent trajectories. Output only valid JSON conforming to the schema in the user message.",
1005
+ },
1006
+ { role: "user", content: messageContent },
1007
+ ],
1008
+ response_model: {
1009
+ name: "FusedOutcome",
1010
+ schema: FusedOutcomeResponseSchema,
1011
+ },
1012
+ },
1013
+ });
1014
+ return response.data;
1015
+ }
1016
+ catch {
1017
+ // Failure surfaces as a no-confidence result.
1018
+ return {
1019
+ outcome: {
1020
+ primary_intent: taskSpec.instruction,
1021
+ reasoning: "Outcome LLM call failed; defaulting to output_success=false.",
1022
+ output_success: false,
1023
+ findings: [
1024
+ {
1025
+ category: "verifier_uncertainty",
1026
+ severity: "warning",
1027
+ description: "The outcome verification call did not return a parseable response.",
1028
+ },
1029
+ ],
1030
+ },
1031
+ };
1032
+ }
1033
+ }
1034
+ /**
1035
+ * Flat per-step evidence summary — fallback for trajectories with no
1036
+ * probe screenshots, such as harness-adapter or stubbed trajectories.
1037
+ */
1038
+ buildEvidenceContext(trajectory, config, opts = {}) {
1039
+ if (trajectory.steps.length === 0) {
1040
+ return { text: "(no steps captured)", images: [] };
1041
+ }
1042
+ const textLimit = config.truncation.buildEvidenceText;
1043
+ const ariaLimit = config.truncation.buildEvidenceAria;
1044
+ const text = clampToTokenBudget(trajectory.steps
1045
+ .map((s, i) => {
1046
+ const url = s.probeEvidence.url ? `, url=${s.probeEvidence.url}` : "";
1047
+ const hasScreenshot = s.probeEvidence.screenshotPath || s.probeEvidence.screenshot
1048
+ ? "yes"
1049
+ : "no";
1050
+ const tier1 = s.agentEvidence.modalities
1051
+ .map((m) => {
1052
+ if (m.type === "text")
1053
+ return `text(${m.content.slice(0, textLimit)})`;
1054
+ if (m.type === "image")
1055
+ return `image(${m.bytes.length} bytes)`;
1056
+ return `json(${safeJsonSnippet(m.content, 180)})`;
1057
+ })
1058
+ .join(", ");
1059
+ const toolOutput = safeJsonSnippet(s.toolOutput.result, 220);
1060
+ // Include the post-step a11y dump when captured — textual ground
1061
+ // truth for criteria that can't be verified from the visual probe
1062
+ // alone (prices, names, list contents). Per-step cap keeps the
1063
+ // total budget bounded.
1064
+ const ariaSnippet = typeof s.probeEvidence.ariaTree === "string" &&
1065
+ s.probeEvidence.ariaTree.length > 0
1066
+ ? `\n aria_tree: ${s.probeEvidence.ariaTree.slice(0, ariaLimit)}${s.probeEvidence.ariaTree.length > ariaLimit
1067
+ ? "… [truncated]"
1068
+ : ""}`
1069
+ : "";
1070
+ return `Screenshot ${i + 1} — step=${i}, action=${s.actionName}${url}, probe_screenshot=${hasScreenshot}\n tier1: ${tier1 || "(none)"}\n tool_output: ${toolOutput}${ariaSnippet}`;
1071
+ })
1072
+ .join("\n\n"), config.evidenceTokenBudget);
1073
+ if (opts.includeImages === false)
1074
+ return { text, images: [] };
1075
+ return {
1076
+ text,
1077
+ images: selectRecentImages(trajectory, config.outcomeMaxImages),
1078
+ };
1079
+ }
1080
+ /**
1081
+ * Compact text evidence for the one-call outcome verifier.
1082
+ *
1083
+ * Outcome-only does not run the rubric relevance selector, but it still needs
1084
+ * enough saved-page text to avoid replacing trajectory facts with model
1085
+ * memory. Select a bounded set of lexically relevant and recent steps, then
1086
+ * include short excerpts around task/final-answer terms.
1087
+ */
1088
+ buildOutcomeEvidenceSummary(trajectory, taskSpec, config) {
1089
+ if (trajectory.steps.length === 0)
1090
+ return "(no steps captured)";
1091
+ const keywords = outcomeKeywords(`${taskSpec.instruction}\n${trajectory.finalAnswer ?? ""}`);
1092
+ const lastImportantIndex = Math.max(0, trajectory.steps.length - 5);
1093
+ const candidates = trajectory.steps.map((step, position) => {
1094
+ const url = step.probeEvidence.url ?? "";
1095
+ const ariaTree = step.probeEvidence.ariaTree ?? "";
1096
+ const toolOutput = safeJsonSnippet(step.toolOutput?.result, 600);
1097
+ const actionArgs = safeJsonSnippet(step.actionArgs, 400);
1098
+ const haystack = [
1099
+ step.actionName,
1100
+ step.reasoning ?? "",
1101
+ url,
1102
+ actionArgs,
1103
+ toolOutput,
1104
+ ariaTree,
1105
+ ]
1106
+ .join("\n")
1107
+ .toLowerCase();
1108
+ let score = position >= lastImportantIndex ? 3 : 0;
1109
+ if (url)
1110
+ score += 1;
1111
+ if (/extract|observe|aria|navigate|click|type|search/i.test(step.actionName)) {
1112
+ score += 1;
1113
+ }
1114
+ for (const keyword of keywords) {
1115
+ if (haystack.includes(keyword)) {
1116
+ score += keyword.length >= 8 ? 3 : 1;
1117
+ }
1118
+ }
1119
+ return { step, position, score };
1120
+ });
1121
+ const selected = new Set();
1122
+ for (const candidate of [...candidates]
1123
+ .sort((a, b) => b.score - a.score || a.position - b.position)
1124
+ .slice(0, OUTCOME_EVIDENCE_MAX_STEPS)) {
1125
+ selected.add(candidate.position);
1126
+ }
1127
+ for (let i = Math.max(0, trajectory.steps.length - 4); i < trajectory.steps.length; i++) {
1128
+ selected.add(i);
1129
+ }
1130
+ const sections = [...selected]
1131
+ .sort((a, b) => a - b)
1132
+ .map((position) => {
1133
+ const step = trajectory.steps[position];
1134
+ const url = step.probeEvidence.url
1135
+ ? ` url=${step.probeEvidence.url}`
1136
+ : "";
1137
+ const reasoning = step.reasoning
1138
+ ? `\n reasoning: ${step.reasoning.slice(0, 220)}`
1139
+ : "";
1140
+ const toolOutput = step.toolOutput?.result
1141
+ ? `\n tool_output: ${safeJsonSnippet(step.toolOutput.result, 320)}`
1142
+ : "";
1143
+ const ariaExcerpt = step.probeEvidence.ariaTree
1144
+ ? `\n page_excerpt: ${bestOutcomeExcerpt(step.probeEvidence.ariaTree, keywords, OUTCOME_EVIDENCE_STEP_CHARS)}`
1145
+ : "";
1146
+ return `Step ${position}: ${step.actionName}(${summarizeArgs(step.actionArgs)})${url}${reasoning}${toolOutput}${ariaExcerpt}`;
1147
+ });
1148
+ return clampToTokenBudget(sections.join("\n\n"), config.outcomeEvidenceTokenBudget);
1149
+ }
1150
+ /** Generate a rubric from the task description alone. */
1151
+ async generateRubric(taskSpec) {
1152
+ const prompt = renderPrompt(RUBRIC_GENERATION_PROMPT, {
1153
+ task_id: taskSpec.instruction,
1154
+ init_url_context: buildInitUrlContext(taskSpec.initUrl),
1155
+ });
1156
+ const maxAttempts = 3;
1157
+ let lastError;
1158
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
1159
+ try {
1160
+ const client = this.getRubricGenClient();
1161
+ const response = await client.createChatCompletion({
1162
+ logger: this.logger,
1163
+ options: {
1164
+ messages: [
1165
+ {
1166
+ role: "system",
1167
+ content: "You are an expert rubric author. Output only valid JSON conforming to the schema requested in the user message. Do not include explanatory prose.",
1168
+ },
1169
+ { role: "user", content: prompt },
1170
+ ],
1171
+ response_model: { name: "Rubric", schema: RubricSchema },
1172
+ },
1173
+ });
1174
+ const data = response.data;
1175
+ const normalized = normalizeRubric({
1176
+ items: filterByTaskSpan(data.items, taskSpec.instruction, this.logger),
1177
+ });
1178
+ if (!normalized) {
1179
+ throw new Error("Rubric generation returned no rubric");
1180
+ }
1181
+ return normalized;
1182
+ }
1183
+ catch (err) {
1184
+ lastError = err;
1185
+ if (attempt === maxAttempts - 1)
1186
+ break;
1187
+ await new Promise((resolve) => setTimeout(resolve, 500 * 2 ** attempt));
1188
+ }
1189
+ }
1190
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
1191
+ }
1192
+ /**
1193
+ * Identify all distinct failure points using taxonomy categories 1–6
1194
+ * (agent-controllable errors) and return the earliest one. Best-effort:
1195
+ * returns undefined on LLM failure / unparseable output / no failures
1196
+ * found, rather than blocking the rest of the pipeline.
1197
+ */
1198
+ async analyzeFailures(args) {
1199
+ const { trajectory, taskSpec, rubric, perCriterion, outcome, config } = args;
1200
+ const evidenceContext = this.buildEvidenceContext(trajectory, config, {
1201
+ includeImages: false,
1202
+ });
1203
+ const prompt = renderPrompt(FIRST_POINT_OF_FAILURE_PROMPT, {
1204
+ task_definition: taskSpec.instruction,
1205
+ init_url_context: buildInitUrlContext(taskSpec.initUrl),
1206
+ action_history: this.formatActionHistory(trajectory, config),
1207
+ predicted_output: trajectory.finalAnswer ?? "(no final answer recorded)",
1208
+ rubric_summary: this.formatScoredRubricSummary(rubric, perCriterion),
1209
+ evidence_summary: evidenceContext.text,
1210
+ outcome_verification: `output_success=${outcome.output_success}\nprimary_intent=${outcome.primary_intent}\nreasoning=${outcome.reasoning}`,
1211
+ });
1212
+ const client = this.getClient();
1213
+ const response = await client.createChatCompletion({
1214
+ logger: this.logger,
1215
+ options: {
1216
+ messages: [
1217
+ {
1218
+ role: "system",
1219
+ content: "You are an expert failure analyst for computer-use web agents. Output only valid JSON conforming to the schema in the user message.",
1220
+ },
1221
+ { role: "user", content: prompt },
1222
+ ],
1223
+ response_model: {
1224
+ name: "FailureAnalysis",
1225
+ schema: FailureAnalysisSchema,
1226
+ },
1227
+ },
1228
+ });
1229
+ const data = response.data;
1230
+ if (!data.has_failure || data.failure_points.length === 0)
1231
+ return undefined;
1232
+ // Find the failure with the earliest step number: lowest min-step across
1233
+ // all failure_points.
1234
+ let best = null;
1235
+ for (const fp of data.failure_points) {
1236
+ const steps = parseFailureStepNumbers(fp.step_numbers, {
1237
+ maxStep: Math.max(0, trajectory.steps.length),
1238
+ });
1239
+ if (steps.length === 0)
1240
+ continue;
1241
+ const minStep = steps[0];
1242
+ if (best === null || minStep < best.minStep) {
1243
+ best = { minStep, point: fp };
1244
+ }
1245
+ }
1246
+ if (best === null)
1247
+ return undefined;
1248
+ return {
1249
+ stepIndex: best.minStep,
1250
+ errorCode: best.point.error_code,
1251
+ category: best.point.error_category,
1252
+ description: `${best.point.error_type}: ${best.point.what_happened}`,
1253
+ };
1254
+ }
1255
+ /**
1256
+ * Classify the task across ambiguity (taxonomy category 7) and
1257
+ * validity/feasibility (category 8). Pure task-level analysis; no
1258
+ * trajectory context needed. Best-effort: returns undefined on LLM error.
1259
+ */
1260
+ async classifyTaskValidity(taskSpec) {
1261
+ const prompt = renderPrompt(TASK_VALIDITY_PROMPT, {
1262
+ task_definition: taskSpec.instruction,
1263
+ url: taskSpec.initUrl ?? "(none)",
1264
+ // For browser-driven tasks the app is always Edge/Chrome. The prompt
1265
+ // accepts a free-form apps field; keeping it accurate matters less than
1266
+ // anchoring the model with non-empty context.
1267
+ apps: "Edge",
1268
+ date: new Date().toISOString().slice(0, 10),
1269
+ });
1270
+ const client = this.getClient();
1271
+ const response = await client.createChatCompletion({
1272
+ logger: this.logger,
1273
+ options: {
1274
+ messages: [
1275
+ {
1276
+ role: "system",
1277
+ content: "You are an expert failure analyst for computer-use web agents. Output only valid JSON conforming to the schema in the user message.",
1278
+ },
1279
+ { role: "user", content: prompt },
1280
+ ],
1281
+ response_model: {
1282
+ name: "TaskValidity",
1283
+ schema: TaskValiditySchema,
1284
+ },
1285
+ },
1286
+ });
1287
+ const data = response.data;
1288
+ return {
1289
+ isAmbiguous: data.is_ambiguous,
1290
+ isInvalid: data.is_invalid,
1291
+ ambiguityReason: data.is_ambiguous && data.reasoning_is_ambiguous
1292
+ ? data.reasoning_is_ambiguous
1293
+ : undefined,
1294
+ invalidReason: data.is_invalid && data.reasoning_is_invalid
1295
+ ? data.reasoning_is_invalid
1296
+ : undefined,
1297
+ };
1298
+ }
1299
+ /**
1300
+ * Format the rubric with per-criterion rescored points + explanations.
1301
+ * The outcome verifier reads this as advisory context — it sees how a
1302
+ * separate scoring system viewed each criterion but forms its own result.
1303
+ */
1304
+ formatScoredRubricSummary(rubric, scores) {
1305
+ return rubric.items
1306
+ .map((c, i) => {
1307
+ const cond = c.condition ? ` [condition: ${c.condition}]` : "";
1308
+ const score = scores[i];
1309
+ const earned = score?.earnedPoints ?? "—";
1310
+ const explanation = score?.explanation ?? "";
1311
+ return `${i + 1}. ${c.criterion} (${earned}/${c.maxPoints} pts)${cond}\n Description: ${c.description}\n Score explanation: ${explanation}`;
1312
+ })
1313
+ .join("\n\n");
1314
+ }
1315
+ /**
1316
+ * Compact textual action history for embedding in prompts. One line per
1317
+ * step. Full per-step detail lives in trajectory.json on disk.
1318
+ */
1319
+ formatActionHistory(trajectory, config) {
1320
+ const reasoningLimit = config.truncation.actionHistoryReasoning;
1321
+ const history = trajectory.steps
1322
+ .map((s, i) => {
1323
+ const argSummary = summarizeArgs(s.actionArgs);
1324
+ const reasoning = (s.reasoning ?? "").slice(0, reasoningLimit);
1325
+ const url = s.probeEvidence.url ? ` @ ${s.probeEvidence.url}` : "";
1326
+ return `Step ${i}: ${s.actionName}(${argSummary})${url}${reasoning ? `\n reasoning: ${reasoning}` : ""}`;
1327
+ })
1328
+ .join("\n");
1329
+ return clampToTokenBudget(history, config.actionHistoryTokenBudget);
1330
+ }
1331
+ }
1332
+ /** FIFO concurrency limiter; avoids a new dep. */
1333
+ function pLimit(concurrency) {
1334
+ const n = Math.max(1, Math.floor(concurrency));
1335
+ let active = 0;
1336
+ const queue = [];
1337
+ const next = () => {
1338
+ if (active >= n)
1339
+ return;
1340
+ const job = queue.shift();
1341
+ if (job) {
1342
+ active++;
1343
+ job();
1344
+ }
1345
+ };
1346
+ return (fn) => new Promise((resolve, reject) => {
1347
+ queue.push(() => {
1348
+ fn()
1349
+ .then(resolve, reject)
1350
+ .finally(() => {
1351
+ active--;
1352
+ next();
1353
+ });
1354
+ });
1355
+ next();
1356
+ });
1357
+ }
1358
+ /**
1359
+ * Always-attach final visual evidence: the trajectory's terminal page
1360
+ * observation plus the last step's probe screenshot, deduped. Returned in
1361
+ * chronological order (last-step probe first if both are present, then
1362
+ * finalObservation as the closing frame).
1363
+ *
1364
+ * This bypasses the per-criterion top-K relevance ranking used by
1365
+ * {@link fusedJudgment} and the (previously) text-only outcome prompt, so the
1366
+ * judge LLM always sees what the page actually looked like at the end of the
1367
+ * run — not just whatever images happened to rank well against keyword
1368
+ * heuristics.
1369
+ */
1370
+ function selectFinalImages(trajectory) {
1371
+ const out = [];
1372
+ const seen = new Set();
1373
+ const push = (label, bytes) => {
1374
+ if (!bytes || bytes.length === 0)
1375
+ return;
1376
+ const key = `${bytes.length}:${bytes.subarray(0, 32).toString("base64")}`;
1377
+ if (seen.has(key))
1378
+ return;
1379
+ seen.add(key);
1380
+ out.push({ label, bytes, mediaType: "image/png" });
1381
+ };
1382
+ const lastStepIdx = trajectory.steps.length - 1;
1383
+ if (lastStepIdx >= 0) {
1384
+ push(`step ${lastStepIdx} probe screenshot (final action)`, trajectory.steps[lastStepIdx].probeEvidence.screenshot);
1385
+ }
1386
+ push("trajectory final observation", trajectory.finalObservation?.screenshot);
1387
+ return out;
1388
+ }
1389
+ /**
1390
+ * Always-attach final textual evidence: the terminal page content (URL +
1391
+ * ariaTree) at the end of the run. Prefers `trajectory.finalObservation`
1392
+ * (the explicit post-run probe) and falls back to the last step's
1393
+ * `probeEvidence`. Clamped to {@link FINAL_STATE_ARIA_CHARS}.
1394
+ *
1395
+ * Returns an empty string when no terminal observation is available — the
1396
+ * caller should treat the resulting prompt section as "(no final state
1397
+ * captured)" rather than omit the header entirely so the LLM doesn't think
1398
+ * the field is missing.
1399
+ */
1400
+ function buildFinalStateBlock(trajectory) {
1401
+ const last = trajectory.steps[trajectory.steps.length - 1];
1402
+ const sources = [];
1403
+ if (last?.probeEvidence) {
1404
+ sources.push({
1405
+ label: `Last step (Step ${trajectory.steps.length - 1}, post-action)`,
1406
+ url: last.probeEvidence.url,
1407
+ ariaTree: last.probeEvidence.ariaTree,
1408
+ });
1409
+ }
1410
+ if (trajectory.finalObservation) {
1411
+ sources.push({
1412
+ label: "Final observation (probed after trajectory ended)",
1413
+ url: trajectory.finalObservation.url,
1414
+ ariaTree: trajectory.finalObservation.ariaTree,
1415
+ });
1416
+ }
1417
+ if (sources.length === 0)
1418
+ return "(no final state captured)";
1419
+ return sources
1420
+ .map((s) => {
1421
+ const aria = s.ariaTree && s.ariaTree.length > FINAL_STATE_ARIA_CHARS
1422
+ ? `${s.ariaTree.slice(0, FINAL_STATE_ARIA_CHARS)}\n...[truncated ${s.ariaTree.length - FINAL_STATE_ARIA_CHARS} chars]`
1423
+ : (s.ariaTree ?? "(no aria tree)");
1424
+ const url = s.url ? `\n url: ${s.url}` : "";
1425
+ return `${s.label}:${url}\n aria_tree:\n${aria}`;
1426
+ })
1427
+ .join("\n\n");
1428
+ }
1429
+ function selectRecentImages(trajectory, limit) {
1430
+ if (limit <= 0)
1431
+ return [];
1432
+ const images = [];
1433
+ const seen = new Set();
1434
+ for (let i = trajectory.steps.length - 1; i >= 0; i--) {
1435
+ const step = trajectory.steps[i];
1436
+ const candidates = [];
1437
+ if (step.probeEvidence.screenshot) {
1438
+ candidates.push({
1439
+ label: `step ${i} probe screenshot`,
1440
+ bytes: step.probeEvidence.screenshot,
1441
+ mediaType: "image/png",
1442
+ });
1443
+ }
1444
+ for (const modality of step.agentEvidence.modalities) {
1445
+ if (modality.type === "image") {
1446
+ candidates.push({
1447
+ label: `step ${i} agent image`,
1448
+ bytes: modality.bytes,
1449
+ mediaType: modality.mediaType,
1450
+ });
1451
+ }
1452
+ }
1453
+ for (const candidate of candidates) {
1454
+ const key = `${candidate.mediaType}:${candidate.bytes.length}:${candidate.bytes.subarray(0, 32).toString("base64")}`;
1455
+ if (seen.has(key))
1456
+ continue;
1457
+ seen.add(key);
1458
+ images.push(candidate);
1459
+ if (images.length >= limit)
1460
+ return images.reverse();
1461
+ }
1462
+ }
1463
+ return images.reverse();
1464
+ }
1465
+ function currentDateForVerifier() {
1466
+ return new Date().toISOString().slice(0, 10);
1467
+ }
1468
+ const OUTCOME_KEYWORD_STOPWORDS = new Set([
1469
+ "about",
1470
+ "above",
1471
+ "access",
1472
+ "agent",
1473
+ "also",
1474
+ "answer",
1475
+ "available",
1476
+ "based",
1477
+ "been",
1478
+ "being",
1479
+ "browser",
1480
+ "class",
1481
+ "click",
1482
+ "correct",
1483
+ "current",
1484
+ "details",
1485
+ "final",
1486
+ "find",
1487
+ "found",
1488
+ "from",
1489
+ "have",
1490
+ "including",
1491
+ "into",
1492
+ "list",
1493
+ "located",
1494
+ "model",
1495
+ "more",
1496
+ "navigated",
1497
+ "page",
1498
+ "provided",
1499
+ "request",
1500
+ "requested",
1501
+ "results",
1502
+ "search",
1503
+ "show",
1504
+ "successfully",
1505
+ "task",
1506
+ "that",
1507
+ "the",
1508
+ "their",
1509
+ "then",
1510
+ "there",
1511
+ "this",
1512
+ "through",
1513
+ "user",
1514
+ "using",
1515
+ "which",
1516
+ "with",
1517
+ ]);
1518
+ function outcomeKeywords(text) {
1519
+ const counts = new Map();
1520
+ for (const match of text.toLowerCase().matchAll(/[a-z0-9][a-z0-9._/-]*/g)) {
1521
+ const word = match[0].replace(/^[-_./]+|[-_./]+$/g, "");
1522
+ if (!word)
1523
+ continue;
1524
+ if (OUTCOME_KEYWORD_STOPWORDS.has(word))
1525
+ continue;
1526
+ if (word.length < 4 && !/\d/.test(word))
1527
+ continue;
1528
+ counts.set(word, (counts.get(word) ?? 0) + 1);
1529
+ }
1530
+ return [...counts.entries()]
1531
+ .sort((a, b) => b[1] - a[1] || b[0].length - a[0].length)
1532
+ .slice(0, 36)
1533
+ .map(([word]) => word)
1534
+ .sort((a, b) => b.length - a.length || a.localeCompare(b));
1535
+ }
1536
+ function bestOutcomeExcerpt(text, keywords, maxChars) {
1537
+ const compact = text.replace(/\s+/g, " ").trim();
1538
+ if (compact.length <= maxChars)
1539
+ return compact;
1540
+ const lower = compact.toLowerCase();
1541
+ let bestIndex = -1;
1542
+ for (const keyword of keywords) {
1543
+ const idx = lower.indexOf(keyword);
1544
+ if (idx >= 0) {
1545
+ bestIndex = idx;
1546
+ break;
1547
+ }
1548
+ }
1549
+ if (bestIndex < 0) {
1550
+ return `${compact.slice(0, maxChars)}... [truncated]`;
1551
+ }
1552
+ const before = Math.floor(maxChars * 0.35);
1553
+ const start = Math.max(0, bestIndex - before);
1554
+ const end = Math.min(compact.length, start + maxChars);
1555
+ const prefix = start > 0 ? "... " : "";
1556
+ const suffix = end < compact.length ? " ... [truncated]" : "";
1557
+ return `${prefix}${compact.slice(start, end)}${suffix}`;
1558
+ }
1559
+ function clampToTokenBudget(text, tokenBudget) {
1560
+ const maxChars = Math.max(0, tokenBudget) * APPROX_CHARS_PER_TOKEN;
1561
+ if (maxChars === 0 || text.length <= maxChars)
1562
+ return text;
1563
+ const keepHead = Math.floor(maxChars * 0.35);
1564
+ const keepTail = Math.max(0, maxChars - keepHead - 120);
1565
+ return [
1566
+ text.slice(0, keepHead).trimEnd(),
1567
+ `\n...[truncated ${text.length - keepHead - keepTail} chars to fit verifier context budget]...\n`,
1568
+ text.slice(text.length - keepTail).trimStart(),
1569
+ ].join("");
1570
+ }
1571
+ function filterByTaskSpan(items, taskInstruction, logger) {
1572
+ const normalizedTask = normalizeForSpanMatch(taskInstruction);
1573
+ const kept = [];
1574
+ const dropped = [];
1575
+ for (const item of items) {
1576
+ const span = item.task_span?.trim();
1577
+ if (!span) {
1578
+ dropped.push({
1579
+ criterion: item.criterion,
1580
+ reason: "missing task_span",
1581
+ });
1582
+ continue;
1583
+ }
1584
+ if (span === "<critical-point>" ||
1585
+ normalizedTask.includes(normalizeForSpanMatch(span))) {
1586
+ kept.push(item);
1587
+ continue;
1588
+ }
1589
+ dropped.push({
1590
+ criterion: item.criterion,
1591
+ reason: `task_span ${JSON.stringify(span)} not found in task instruction`,
1592
+ });
1593
+ }
1594
+ if (dropped.length > 0) {
1595
+ logger({
1596
+ category: "v3-evaluator",
1597
+ message: "rubric: dropped hallucinated criteria via task_span filter",
1598
+ auxiliary: {
1599
+ droppedCount: { value: String(dropped.length), type: "integer" },
1600
+ dropped: {
1601
+ value: JSON.stringify(dropped),
1602
+ type: "object",
1603
+ },
1604
+ },
1605
+ });
1606
+ }
1607
+ return kept;
1608
+ }
1609
+ function normalizeForSpanMatch(value) {
1610
+ return value.toLowerCase().replace(/\s+/g, " ").trim();
1611
+ }
1612
+ function safeJsonSnippet(value, maxChars) {
1613
+ let raw;
1614
+ try {
1615
+ raw = typeof value === "string" ? value : JSON.stringify(value);
1616
+ }
1617
+ catch {
1618
+ raw = String(value);
1619
+ }
1620
+ if (raw === undefined)
1621
+ return "(undefined)";
1622
+ return raw.length > maxChars ? `${raw.slice(0, maxChars)}...` : raw;
1623
+ }
1624
+ function summarizeArgs(args) {
1625
+ const keys = Object.keys(args);
1626
+ if (keys.length === 0)
1627
+ return "";
1628
+ return keys
1629
+ .slice(0, 3)
1630
+ .map((k) => {
1631
+ const v = args[k];
1632
+ if (typeof v === "string")
1633
+ return `${k}: ${v.slice(0, 60)}`;
1634
+ if (typeof v === "number" || typeof v === "boolean")
1635
+ return `${k}: ${v}`;
1636
+ return `${k}: ${typeof v}`;
1637
+ })
1638
+ .join(", ");
1639
+ }
1640
+ //# sourceMappingURL=rubricVerifier.js.map