@descryy/mcp 0.11.4 → 0.11.5

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 (441) hide show
  1. package/package.json +7 -7
  2. package/dist/action-handshake.d.ts +0 -38
  3. package/dist/action-handshake.d.ts.map +0 -1
  4. package/dist/action-handshake.js +0 -127
  5. package/dist/action-handshake.js.map +0 -1
  6. package/dist/bin/descry-mcp.d.ts +0 -7
  7. package/dist/bin/descry-mcp.d.ts.map +0 -1
  8. package/dist/bin/descry-mcp.js +0 -72
  9. package/dist/bin/descry-mcp.js.map +0 -1
  10. package/dist/browser/auth.d.ts +0 -84
  11. package/dist/browser/auth.d.ts.map +0 -1
  12. package/dist/browser/auth.js +0 -233
  13. package/dist/browser/auth.js.map +0 -1
  14. package/dist/browser/capability-probe.d.ts +0 -109
  15. package/dist/browser/capability-probe.d.ts.map +0 -1
  16. package/dist/browser/capability-probe.js +0 -201
  17. package/dist/browser/capability-probe.js.map +0 -1
  18. package/dist/browser/driver.d.ts +0 -591
  19. package/dist/browser/driver.d.ts.map +0 -1
  20. package/dist/browser/driver.js +0 -116
  21. package/dist/browser/driver.js.map +0 -1
  22. package/dist/browser/evidence.d.ts +0 -28
  23. package/dist/browser/evidence.d.ts.map +0 -1
  24. package/dist/browser/evidence.js +0 -82
  25. package/dist/browser/evidence.js.map +0 -1
  26. package/dist/browser/fake-driver.d.ts +0 -116
  27. package/dist/browser/fake-driver.d.ts.map +0 -1
  28. package/dist/browser/fake-driver.js +0 -416
  29. package/dist/browser/fake-driver.js.map +0 -1
  30. package/dist/browser/fault-attribution.d.ts +0 -178
  31. package/dist/browser/fault-attribution.d.ts.map +0 -1
  32. package/dist/browser/fault-attribution.js +0 -266
  33. package/dist/browser/fault-attribution.js.map +0 -1
  34. package/dist/browser/graph-write.d.ts +0 -45
  35. package/dist/browser/graph-write.d.ts.map +0 -1
  36. package/dist/browser/graph-write.js +0 -103
  37. package/dist/browser/graph-write.js.map +0 -1
  38. package/dist/browser/identity-graph-write.d.ts +0 -55
  39. package/dist/browser/identity-graph-write.d.ts.map +0 -1
  40. package/dist/browser/identity-graph-write.js +0 -45
  41. package/dist/browser/identity-graph-write.js.map +0 -1
  42. package/dist/browser/page-probe.d.ts +0 -81
  43. package/dist/browser/page-probe.d.ts.map +0 -1
  44. package/dist/browser/page-probe.js +0 -202
  45. package/dist/browser/page-probe.js.map +0 -1
  46. package/dist/browser/playwright-driver.d.ts +0 -114
  47. package/dist/browser/playwright-driver.d.ts.map +0 -1
  48. package/dist/browser/playwright-driver.js +0 -1518
  49. package/dist/browser/playwright-driver.js.map +0 -1
  50. package/dist/browser/provider.d.ts +0 -26
  51. package/dist/browser/provider.d.ts.map +0 -1
  52. package/dist/browser/provider.js +0 -14
  53. package/dist/browser/provider.js.map +0 -1
  54. package/dist/browser/reach-recording-session.d.ts +0 -44
  55. package/dist/browser/reach-recording-session.d.ts.map +0 -1
  56. package/dist/browser/reach-recording-session.js +0 -151
  57. package/dist/browser/reach-recording-session.js.map +0 -1
  58. package/dist/browser/reachability.d.ts +0 -168
  59. package/dist/browser/reachability.d.ts.map +0 -1
  60. package/dist/browser/reachability.js +0 -294
  61. package/dist/browser/reachability.js.map +0 -1
  62. package/dist/browser/registry.d.ts +0 -126
  63. package/dist/browser/registry.d.ts.map +0 -1
  64. package/dist/browser/registry.js +0 -177
  65. package/dist/browser/registry.js.map +0 -1
  66. package/dist/browser/scenario-provenance.d.ts +0 -23
  67. package/dist/browser/scenario-provenance.d.ts.map +0 -1
  68. package/dist/browser/scenario-provenance.js +0 -73
  69. package/dist/browser/scenario-provenance.js.map +0 -1
  70. package/dist/browser/scenario-resolve.d.ts +0 -28
  71. package/dist/browser/scenario-resolve.d.ts.map +0 -1
  72. package/dist/browser/scenario-resolve.js +0 -96
  73. package/dist/browser/scenario-resolve.js.map +0 -1
  74. package/dist/browser/scenario-runner.d.ts +0 -72
  75. package/dist/browser/scenario-runner.d.ts.map +0 -1
  76. package/dist/browser/scenario-runner.js +0 -353
  77. package/dist/browser/scenario-runner.js.map +0 -1
  78. package/dist/browser/stack-parser.d.ts +0 -19
  79. package/dist/browser/stack-parser.d.ts.map +0 -1
  80. package/dist/browser/stack-parser.js +0 -85
  81. package/dist/browser/stack-parser.js.map +0 -1
  82. package/dist/browser/tool-support.d.ts +0 -71
  83. package/dist/browser/tool-support.d.ts.map +0 -1
  84. package/dist/browser/tool-support.js +0 -216
  85. package/dist/browser/tool-support.js.map +0 -1
  86. package/dist/browser/url-scheme.d.ts +0 -14
  87. package/dist/browser/url-scheme.d.ts.map +0 -1
  88. package/dist/browser/url-scheme.js +0 -38
  89. package/dist/browser/url-scheme.js.map +0 -1
  90. package/dist/browser/wait-target.d.ts +0 -37
  91. package/dist/browser/wait-target.d.ts.map +0 -1
  92. package/dist/browser/wait-target.js +0 -54
  93. package/dist/browser/wait-target.js.map +0 -1
  94. package/dist/cancellation.d.ts +0 -20
  95. package/dist/cancellation.d.ts.map +0 -1
  96. package/dist/cancellation.js +0 -41
  97. package/dist/cancellation.js.map +0 -1
  98. package/dist/capped.d.ts +0 -39
  99. package/dist/capped.d.ts.map +0 -1
  100. package/dist/capped.js +0 -43
  101. package/dist/capped.js.map +0 -1
  102. package/dist/credential-store.d.ts +0 -22
  103. package/dist/credential-store.d.ts.map +0 -1
  104. package/dist/credential-store.js +0 -33
  105. package/dist/credential-store.js.map +0 -1
  106. package/dist/disclosure-ledger.d.ts +0 -14
  107. package/dist/disclosure-ledger.d.ts.map +0 -1
  108. package/dist/disclosure-ledger.js +0 -18
  109. package/dist/disclosure-ledger.js.map +0 -1
  110. package/dist/evidence/observed-route-evidence.d.ts +0 -25
  111. package/dist/evidence/observed-route-evidence.d.ts.map +0 -1
  112. package/dist/evidence/observed-route-evidence.js +0 -76
  113. package/dist/evidence/observed-route-evidence.js.map +0 -1
  114. package/dist/heap.d.ts +0 -61
  115. package/dist/heap.d.ts.map +0 -1
  116. package/dist/heap.js +0 -82
  117. package/dist/heap.js.map +0 -1
  118. package/dist/heartbeat.d.ts +0 -46
  119. package/dist/heartbeat.d.ts.map +0 -1
  120. package/dist/heartbeat.js +0 -66
  121. package/dist/heartbeat.js.map +0 -1
  122. package/dist/index.d.ts +0 -32
  123. package/dist/index.d.ts.map +0 -1
  124. package/dist/index.js +0 -19
  125. package/dist/index.js.map +0 -1
  126. package/dist/module-trust.d.ts +0 -37
  127. package/dist/module-trust.d.ts.map +0 -1
  128. package/dist/module-trust.js +0 -77
  129. package/dist/module-trust.js.map +0 -1
  130. package/dist/path-confinement.d.ts +0 -31
  131. package/dist/path-confinement.d.ts.map +0 -1
  132. package/dist/path-confinement.js +0 -44
  133. package/dist/path-confinement.js.map +0 -1
  134. package/dist/planner/predict-then-propose.d.ts +0 -134
  135. package/dist/planner/predict-then-propose.d.ts.map +0 -1
  136. package/dist/planner/predict-then-propose.js +0 -138
  137. package/dist/planner/predict-then-propose.js.map +0 -1
  138. package/dist/planner/propose-journey.d.ts +0 -75
  139. package/dist/planner/propose-journey.d.ts.map +0 -1
  140. package/dist/planner/propose-journey.js +0 -96
  141. package/dist/planner/propose-journey.js.map +0 -1
  142. package/dist/protocol.d.ts +0 -57
  143. package/dist/protocol.d.ts.map +0 -1
  144. package/dist/protocol.js +0 -90
  145. package/dist/protocol.js.map +0 -1
  146. package/dist/registry.d.ts +0 -103
  147. package/dist/registry.d.ts.map +0 -1
  148. package/dist/registry.js +0 -327
  149. package/dist/registry.js.map +0 -1
  150. package/dist/render.d.ts +0 -623
  151. package/dist/render.d.ts.map +0 -1
  152. package/dist/render.js +0 -968
  153. package/dist/render.js.map +0 -1
  154. package/dist/runtime-registry.d.ts +0 -22
  155. package/dist/runtime-registry.d.ts.map +0 -1
  156. package/dist/runtime-registry.js +0 -142
  157. package/dist/runtime-registry.js.map +0 -1
  158. package/dist/sandbox-defaults.d.ts +0 -41
  159. package/dist/sandbox-defaults.d.ts.map +0 -1
  160. package/dist/sandbox-defaults.js +0 -14
  161. package/dist/sandbox-defaults.js.map +0 -1
  162. package/dist/scenarios/browser-run-plan-projection.d.ts +0 -161
  163. package/dist/scenarios/browser-run-plan-projection.d.ts.map +0 -1
  164. package/dist/scenarios/browser-run-plan-projection.js +0 -264
  165. package/dist/scenarios/browser-run-plan-projection.js.map +0 -1
  166. package/dist/scenarios/credential-ref.d.ts +0 -102
  167. package/dist/scenarios/credential-ref.d.ts.map +0 -1
  168. package/dist/scenarios/credential-ref.js +0 -148
  169. package/dist/scenarios/credential-ref.js.map +0 -1
  170. package/dist/scenarios/index.d.ts +0 -7
  171. package/dist/scenarios/index.d.ts.map +0 -1
  172. package/dist/scenarios/index.js +0 -7
  173. package/dist/scenarios/index.js.map +0 -1
  174. package/dist/scenarios/parse.d.ts +0 -18
  175. package/dist/scenarios/parse.d.ts.map +0 -1
  176. package/dist/scenarios/parse.js +0 -212
  177. package/dist/scenarios/parse.js.map +0 -1
  178. package/dist/scenarios/scenario.d.ts +0 -93
  179. package/dist/scenarios/scenario.d.ts.map +0 -1
  180. package/dist/scenarios/scenario.js +0 -27
  181. package/dist/scenarios/scenario.js.map +0 -1
  182. package/dist/scenarios/secret-ref.d.ts +0 -86
  183. package/dist/scenarios/secret-ref.d.ts.map +0 -1
  184. package/dist/scenarios/secret-ref.js +0 -125
  185. package/dist/scenarios/secret-ref.js.map +0 -1
  186. package/dist/scenarios/storage.d.ts +0 -21
  187. package/dist/scenarios/storage.d.ts.map +0 -1
  188. package/dist/scenarios/storage.js +0 -103
  189. package/dist/scenarios/storage.js.map +0 -1
  190. package/dist/server.d.ts +0 -61
  191. package/dist/server.d.ts.map +0 -1
  192. package/dist/server.js +0 -651
  193. package/dist/server.js.map +0 -1
  194. package/dist/session.d.ts +0 -356
  195. package/dist/session.d.ts.map +0 -1
  196. package/dist/session.js +0 -779
  197. package/dist/session.js.map +0 -1
  198. package/dist/tools/alias-kit.d.ts +0 -29
  199. package/dist/tools/alias-kit.d.ts.map +0 -1
  200. package/dist/tools/alias-kit.js +0 -21
  201. package/dist/tools/alias-kit.js.map +0 -1
  202. package/dist/tools/aliases.d.ts +0 -42
  203. package/dist/tools/aliases.d.ts.map +0 -1
  204. package/dist/tools/aliases.js +0 -52
  205. package/dist/tools/aliases.js.map +0 -1
  206. package/dist/tools/analyze-workspace.d.ts +0 -52
  207. package/dist/tools/analyze-workspace.d.ts.map +0 -1
  208. package/dist/tools/analyze-workspace.js +0 -207
  209. package/dist/tools/analyze-workspace.js.map +0 -1
  210. package/dist/tools/analyze.d.ts +0 -163
  211. package/dist/tools/analyze.d.ts.map +0 -1
  212. package/dist/tools/analyze.js +0 -724
  213. package/dist/tools/analyze.js.map +0 -1
  214. package/dist/tools/browser-click.d.ts +0 -18
  215. package/dist/tools/browser-click.d.ts.map +0 -1
  216. package/dist/tools/browser-click.js +0 -110
  217. package/dist/tools/browser-click.js.map +0 -1
  218. package/dist/tools/browser-close-session.d.ts +0 -39
  219. package/dist/tools/browser-close-session.d.ts.map +0 -1
  220. package/dist/tools/browser-close-session.js +0 -120
  221. package/dist/tools/browser-close-session.js.map +0 -1
  222. package/dist/tools/browser-fill.d.ts +0 -18
  223. package/dist/tools/browser-fill.d.ts.map +0 -1
  224. package/dist/tools/browser-fill.js +0 -117
  225. package/dist/tools/browser-fill.js.map +0 -1
  226. package/dist/tools/browser-navigate.d.ts +0 -15
  227. package/dist/tools/browser-navigate.d.ts.map +0 -1
  228. package/dist/tools/browser-navigate.js +0 -104
  229. package/dist/tools/browser-navigate.js.map +0 -1
  230. package/dist/tools/browser-read.d.ts +0 -33
  231. package/dist/tools/browser-read.d.ts.map +0 -1
  232. package/dist/tools/browser-read.js +0 -113
  233. package/dist/tools/browser-read.js.map +0 -1
  234. package/dist/tools/browser-run-scenario.d.ts +0 -28
  235. package/dist/tools/browser-run-scenario.d.ts.map +0 -1
  236. package/dist/tools/browser-run-scenario.js +0 -225
  237. package/dist/tools/browser-run-scenario.js.map +0 -1
  238. package/dist/tools/browser-save-scenario.d.ts +0 -19
  239. package/dist/tools/browser-save-scenario.d.ts.map +0 -1
  240. package/dist/tools/browser-save-scenario.js +0 -447
  241. package/dist/tools/browser-save-scenario.js.map +0 -1
  242. package/dist/tools/browser-select.d.ts +0 -20
  243. package/dist/tools/browser-select.d.ts.map +0 -1
  244. package/dist/tools/browser-select.js +0 -120
  245. package/dist/tools/browser-select.js.map +0 -1
  246. package/dist/tools/browser-snapshot.d.ts +0 -46
  247. package/dist/tools/browser-snapshot.d.ts.map +0 -1
  248. package/dist/tools/browser-snapshot.js +0 -127
  249. package/dist/tools/browser-snapshot.js.map +0 -1
  250. package/dist/tools/browser-start-session.d.ts +0 -24
  251. package/dist/tools/browser-start-session.d.ts.map +0 -1
  252. package/dist/tools/browser-start-session.js +0 -640
  253. package/dist/tools/browser-start-session.js.map +0 -1
  254. package/dist/tools/browser-submit.d.ts +0 -18
  255. package/dist/tools/browser-submit.d.ts.map +0 -1
  256. package/dist/tools/browser-submit.js +0 -106
  257. package/dist/tools/browser-submit.js.map +0 -1
  258. package/dist/tools/browser-type.d.ts +0 -18
  259. package/dist/tools/browser-type.d.ts.map +0 -1
  260. package/dist/tools/browser-type.js +0 -115
  261. package/dist/tools/browser-type.js.map +0 -1
  262. package/dist/tools/browser-wait-for.d.ts +0 -45
  263. package/dist/tools/browser-wait-for.d.ts.map +0 -1
  264. package/dist/tools/browser-wait-for.js +0 -150
  265. package/dist/tools/browser-wait-for.js.map +0 -1
  266. package/dist/tools/browser.d.ts +0 -69
  267. package/dist/tools/browser.d.ts.map +0 -1
  268. package/dist/tools/browser.js +0 -386
  269. package/dist/tools/browser.js.map +0 -1
  270. package/dist/tools/chain.d.ts +0 -41
  271. package/dist/tools/chain.d.ts.map +0 -1
  272. package/dist/tools/chain.js +0 -105
  273. package/dist/tools/chain.js.map +0 -1
  274. package/dist/tools/change-scope.d.ts +0 -25
  275. package/dist/tools/change-scope.d.ts.map +0 -1
  276. package/dist/tools/change-scope.js +0 -110
  277. package/dist/tools/change-scope.js.map +0 -1
  278. package/dist/tools/code-context.d.ts +0 -83
  279. package/dist/tools/code-context.d.ts.map +0 -1
  280. package/dist/tools/code-context.js +0 -444
  281. package/dist/tools/code-context.js.map +0 -1
  282. package/dist/tools/contracts.d.ts +0 -74
  283. package/dist/tools/contracts.d.ts.map +0 -1
  284. package/dist/tools/contracts.js +0 -196
  285. package/dist/tools/contracts.js.map +0 -1
  286. package/dist/tools/cross-pr.d.ts +0 -178
  287. package/dist/tools/cross-pr.d.ts.map +0 -1
  288. package/dist/tools/cross-pr.js +0 -370
  289. package/dist/tools/cross-pr.js.map +0 -1
  290. package/dist/tools/disclosure.d.ts +0 -75
  291. package/dist/tools/disclosure.d.ts.map +0 -1
  292. package/dist/tools/disclosure.js +0 -307
  293. package/dist/tools/disclosure.js.map +0 -1
  294. package/dist/tools/draw-conclusion.d.ts +0 -55
  295. package/dist/tools/draw-conclusion.d.ts.map +0 -1
  296. package/dist/tools/draw-conclusion.js +0 -259
  297. package/dist/tools/draw-conclusion.js.map +0 -1
  298. package/dist/tools/git-context.d.ts +0 -39
  299. package/dist/tools/git-context.d.ts.map +0 -1
  300. package/dist/tools/git-context.js +0 -385
  301. package/dist/tools/git-context.js.map +0 -1
  302. package/dist/tools/git-diff.d.ts +0 -45
  303. package/dist/tools/git-diff.d.ts.map +0 -1
  304. package/dist/tools/git-diff.js +0 -338
  305. package/dist/tools/git-diff.js.map +0 -1
  306. package/dist/tools/git-history.d.ts +0 -16
  307. package/dist/tools/git-history.d.ts.map +0 -1
  308. package/dist/tools/git-history.js +0 -106
  309. package/dist/tools/git-history.js.map +0 -1
  310. package/dist/tools/history.d.ts +0 -34
  311. package/dist/tools/history.d.ts.map +0 -1
  312. package/dist/tools/history.js +0 -121
  313. package/dist/tools/history.js.map +0 -1
  314. package/dist/tools/impact.d.ts +0 -46
  315. package/dist/tools/impact.d.ts.map +0 -1
  316. package/dist/tools/impact.js +0 -225
  317. package/dist/tools/impact.js.map +0 -1
  318. package/dist/tools/index.d.ts +0 -59
  319. package/dist/tools/index.d.ts.map +0 -1
  320. package/dist/tools/index.js +0 -70
  321. package/dist/tools/index.js.map +0 -1
  322. package/dist/tools/kit.d.ts +0 -165
  323. package/dist/tools/kit.d.ts.map +0 -1
  324. package/dist/tools/kit.js +0 -153
  325. package/dist/tools/kit.js.map +0 -1
  326. package/dist/tools/link-workspace.d.ts +0 -24
  327. package/dist/tools/link-workspace.d.ts.map +0 -1
  328. package/dist/tools/link-workspace.js +0 -131
  329. package/dist/tools/link-workspace.js.map +0 -1
  330. package/dist/tools/lookup.d.ts +0 -19
  331. package/dist/tools/lookup.d.ts.map +0 -1
  332. package/dist/tools/lookup.js +0 -63
  333. package/dist/tools/lookup.js.map +0 -1
  334. package/dist/tools/mark-incident.d.ts +0 -28
  335. package/dist/tools/mark-incident.d.ts.map +0 -1
  336. package/dist/tools/mark-incident.js +0 -188
  337. package/dist/tools/mark-incident.js.map +0 -1
  338. package/dist/tools/merged-browser.d.ts +0 -11
  339. package/dist/tools/merged-browser.d.ts.map +0 -1
  340. package/dist/tools/merged-browser.js +0 -20
  341. package/dist/tools/merged-browser.js.map +0 -1
  342. package/dist/tools/merged-context.d.ts +0 -11
  343. package/dist/tools/merged-context.d.ts.map +0 -1
  344. package/dist/tools/merged-context.js +0 -21
  345. package/dist/tools/merged-context.js.map +0 -1
  346. package/dist/tools/merged-observe.d.ts +0 -13
  347. package/dist/tools/merged-observe.d.ts.map +0 -1
  348. package/dist/tools/merged-observe.js +0 -21
  349. package/dist/tools/merged-observe.js.map +0 -1
  350. package/dist/tools/merged-workspace.d.ts +0 -14
  351. package/dist/tools/merged-workspace.d.ts.map +0 -1
  352. package/dist/tools/merged-workspace.js +0 -21
  353. package/dist/tools/merged-workspace.js.map +0 -1
  354. package/dist/tools/observe-runtime.d.ts +0 -262
  355. package/dist/tools/observe-runtime.d.ts.map +0 -1
  356. package/dist/tools/observe-runtime.js +0 -1901
  357. package/dist/tools/observe-runtime.js.map +0 -1
  358. package/dist/tools/observe-tests.d.ts +0 -57
  359. package/dist/tools/observe-tests.d.ts.map +0 -1
  360. package/dist/tools/observe-tests.js +0 -368
  361. package/dist/tools/observe-tests.js.map +0 -1
  362. package/dist/tools/observe.d.ts +0 -28
  363. package/dist/tools/observe.d.ts.map +0 -1
  364. package/dist/tools/observe.js +0 -330
  365. package/dist/tools/observe.js.map +0 -1
  366. package/dist/tools/pr-analysis.d.ts +0 -189
  367. package/dist/tools/pr-analysis.d.ts.map +0 -1
  368. package/dist/tools/pr-analysis.js +0 -374
  369. package/dist/tools/pr-analysis.js.map +0 -1
  370. package/dist/tools/pre-push.d.ts +0 -168
  371. package/dist/tools/pre-push.d.ts.map +0 -1
  372. package/dist/tools/pre-push.js +0 -416
  373. package/dist/tools/pre-push.js.map +0 -1
  374. package/dist/tools/predict-reach.d.ts +0 -57
  375. package/dist/tools/predict-reach.d.ts.map +0 -1
  376. package/dist/tools/predict-reach.js +0 -159
  377. package/dist/tools/predict-reach.js.map +0 -1
  378. package/dist/tools/propagation.d.ts +0 -37
  379. package/dist/tools/propagation.d.ts.map +0 -1
  380. package/dist/tools/propagation.js +0 -162
  381. package/dist/tools/propagation.js.map +0 -1
  382. package/dist/tools/propose-journey.d.ts +0 -16
  383. package/dist/tools/propose-journey.d.ts.map +0 -1
  384. package/dist/tools/propose-journey.js +0 -121
  385. package/dist/tools/propose-journey.js.map +0 -1
  386. package/dist/tools/prove-reach.d.ts +0 -74
  387. package/dist/tools/prove-reach.d.ts.map +0 -1
  388. package/dist/tools/prove-reach.js +0 -286
  389. package/dist/tools/prove-reach.js.map +0 -1
  390. package/dist/tools/questions.d.ts +0 -30
  391. package/dist/tools/questions.d.ts.map +0 -1
  392. package/dist/tools/questions.js +0 -232
  393. package/dist/tools/questions.js.map +0 -1
  394. package/dist/tools/refusal-fetch.d.ts +0 -35
  395. package/dist/tools/refusal-fetch.d.ts.map +0 -1
  396. package/dist/tools/refusal-fetch.js +0 -123
  397. package/dist/tools/refusal-fetch.js.map +0 -1
  398. package/dist/tools/runtime-incident.d.ts +0 -33
  399. package/dist/tools/runtime-incident.d.ts.map +0 -1
  400. package/dist/tools/runtime-incident.js +0 -67
  401. package/dist/tools/runtime-incident.js.map +0 -1
  402. package/dist/tools/runtime-journey-drive.d.ts +0 -102
  403. package/dist/tools/runtime-journey-drive.d.ts.map +0 -1
  404. package/dist/tools/runtime-journey-drive.js +0 -247
  405. package/dist/tools/runtime-journey-drive.js.map +0 -1
  406. package/dist/tools/scope.d.ts +0 -45
  407. package/dist/tools/scope.d.ts.map +0 -1
  408. package/dist/tools/scope.js +0 -217
  409. package/dist/tools/scope.js.map +0 -1
  410. package/dist/tools/similar-incidents.d.ts +0 -43
  411. package/dist/tools/similar-incidents.d.ts.map +0 -1
  412. package/dist/tools/similar-incidents.js +0 -203
  413. package/dist/tools/similar-incidents.js.map +0 -1
  414. package/dist/tools/validate.d.ts +0 -133
  415. package/dist/tools/validate.d.ts.map +0 -1
  416. package/dist/tools/validate.js +0 -520
  417. package/dist/tools/validate.js.map +0 -1
  418. package/dist/tools/verb.d.ts +0 -46
  419. package/dist/tools/verb.d.ts.map +0 -1
  420. package/dist/tools/verb.js +0 -78
  421. package/dist/tools/verb.js.map +0 -1
  422. package/dist/tools/verification-status.d.ts +0 -38
  423. package/dist/tools/verification-status.d.ts.map +0 -1
  424. package/dist/tools/verification-status.js +0 -203
  425. package/dist/tools/verification-status.js.map +0 -1
  426. package/dist/tools/verify-claim.d.ts +0 -29
  427. package/dist/tools/verify-claim.d.ts.map +0 -1
  428. package/dist/tools/verify-claim.js +0 -220
  429. package/dist/tools/verify-claim.js.map +0 -1
  430. package/dist/tools/workspace.d.ts +0 -28
  431. package/dist/tools/workspace.d.ts.map +0 -1
  432. package/dist/tools/workspace.js +0 -101
  433. package/dist/tools/workspace.js.map +0 -1
  434. package/dist/transport.d.ts +0 -32
  435. package/dist/transport.d.ts.map +0 -1
  436. package/dist/transport.js +0 -85
  437. package/dist/transport.js.map +0 -1
  438. package/dist/workspace-index.d.ts +0 -164
  439. package/dist/workspace-index.d.ts.map +0 -1
  440. package/dist/workspace-index.js +0 -381
  441. package/dist/workspace-index.js.map +0 -1
@@ -1,1518 +0,0 @@
1
- /**
2
- * The real `BrowserDriver`, over `@descryy/runtime-browser`. Import stays
3
- * dynamic — `loadRuntimeModule` is a test injection seam (fake module, no
4
- * browser needed). Adds only element refs over the runtime; stack-trace parsing is caller-supplied (rule 1: no language named here).
5
- */
6
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
7
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
8
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
9
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
10
- });
11
- }
12
- return path;
13
- };
14
- // RT-263: static, unlike the dynamic `@descryy/runtime-browser` load below -- this
15
- // package is an ordinary dependency of @descryy/mcp regardless of whether the browser
16
- // backend is installed. Currently blocked on a runtime release: @descryy/mcp pins
17
- // @descryy/runtime-controller at 0.3.5, which predates preflight.ts.
18
- import { runPreflight, portAnswersCheck } from "@descryy/runtime-controller";
19
- import { BrowserSessionCrashedError, NoSuchOptionError, NotOperableError, NotSelectableError, NotSubmittableError, UnknownElementRefError, } from "./driver.js";
20
- import { clampWaitTimeout, describeWaitTarget, isEmptyWaitTarget, matchesWaitTarget, WAIT_POLL_INTERVAL_MS, } from "./wait-target.js";
21
- import { PROBE_CONSTANTS, probeInPage } from "./page-probe.js";
22
- /** The module the driver composes. Named once, imported at call time. */
23
- const RUNTIME_BROWSER_MODULE = "@descryy/runtime-browser";
24
- const defaultRuntimeModuleLoader = () => import(__rewriteRelativeImportExtension(RUNTIME_BROWSER_MODULE));
25
- /** Same seam, for the optional identity-grounding module — imported dynamically and only
26
- * when `groundIdentity` is set, so a build or a session that never asks for it pays nothing. */
27
- const IDENTITY_GROUNDING_MODULE = "@descryy/runtime-identity-grounding";
28
- const defaultIdentityGroundingModuleLoader = () => import(__rewriteRelativeImportExtension(IDENTITY_GROUNDING_MODULE));
29
- /** Ceiling on waiting for network activity to *start* — no signal proves "never will". Kept at the old fixed 300ms so a no-op action observes the same as before. */
30
- const ACTIVITY_GRACE_MS = 300;
31
- /** How long after the last request event counts as settled; fires only once activity is seen, so it measures a gap, not a guess about more coming. */
32
- const NETWORK_QUIET_MS = 120;
33
- /** Ceiling on the whole wait; reaching it is recorded on the observation and disclosed, never swallowed. */
34
- const SETTLE_TIMEOUT_MS = 5_000;
35
- /** How often the settle loop re-checks. Small enough not to add latency of its own. */
36
- const POLL_INTERVAL_MS = 10;
37
- /** Elements an agent can act on — deliberately narrow; a full-page snapshot would blow the reply budget. `[role]` included for custom controls. */
38
- const INTERACTIVE = "a, button, input, select, textarea, [role], [onclick], summary";
39
- /** How many scroll-and-re-check rounds a reveal pass runs before it stops and says it stopped. */
40
- const REVEAL_MAX_PASSES = 12;
41
- /** How long after a scroll before re-checking. A lazily-rendered row needs a frame or two to exist. */
42
- const REVEAL_SETTLE_MS = 150;
43
- /** `RawElementIdentity` -> the port's own `ElementComponentIdentity` — the one place the two
44
- * vocabularies are joined, so a change to either shows up as one diff here. */
45
- function mapIdentity(raw) {
46
- switch (raw.outcome) {
47
- case "grounded":
48
- return {
49
- outcome: "grounded",
50
- component: raw.component.name,
51
- file: raw.sourceLocation.file,
52
- line: raw.sourceLocation.line,
53
- authoredSource: raw.authoredSource,
54
- reason: null,
55
- };
56
- case "component-only":
57
- return {
58
- outcome: "component-only",
59
- component: raw.component.name,
60
- file: null,
61
- line: null,
62
- authoredSource: false,
63
- reason: raw.reason,
64
- };
65
- case "refused":
66
- return {
67
- outcome: "refused",
68
- component: null,
69
- file: null,
70
- line: null,
71
- authoredSource: false,
72
- reason: raw.reason,
73
- };
74
- }
75
- }
76
- /** Folds the session's install-time identity warnings into a drained observation, once —
77
- * same drain-not-peek discipline `drainEvidence` and `close` already hold every other field
78
- * to, so a warning surfaces on the first read after it happened and never again. */
79
- function withIdentityWarnings(observation, warnings) {
80
- if (warnings.length === 0)
81
- return observation;
82
- const drained = [...warnings];
83
- warnings.length = 0;
84
- return { ...observation, evidenceWarnings: [...observation.evidenceWarnings, ...drained] };
85
- }
86
- /** Grounds one selector, never throwing — a probe failure is reported as a named refusal
87
- * (rule 2: no guess), the same as every other outcome the grounder itself can name. */
88
- async function groundOne(grounder, page, selector) {
89
- try {
90
- const raw = await grounder.ground(page, selector);
91
- return mapIdentity(raw);
92
- }
93
- catch (error) {
94
- return {
95
- outcome: "refused",
96
- component: null,
97
- file: null,
98
- line: null,
99
- authoredSource: false,
100
- reason: `the identity probe failed: ${error instanceof Error ? error.message : String(error)}`,
101
- };
102
- }
103
- }
104
- /** What one element looks like from inside the page. Serialized and run there, so it closes over
105
- * nothing in this module — every helper it needs is declared inside it.
106
- *
107
- * Exported for `browser-element-describe.test.ts`: role, name and renderedness are pure
108
- * functions of one element, and pinning their decision table should not need a browser. */
109
- export function describeElement(el) {
110
- const tag = el.tagName.toLowerCase();
111
- const explicit = el.getAttribute("role");
112
- const type = el.getAttribute("type")?.toLowerCase() ?? null;
113
- /** The HTML→ARIA role of this element. The old chain ended `: tag`, which reported
114
- * `input[type="number"]` as role `"input"` — a role no agent filters on and no verb
115
- * validates against, so a real control went silently missing from every role-filtered
116
- * read. The mapping below is HTML's own, and it names no framework (rule 1).
117
- *
118
- * It still ends at the tag for a tag this does not know, deliberately: `[role]` and
119
- * `[onclick]` put arbitrary elements into the query, and inventing a role for one of
120
- * them would be this same defect pointed the other way. */
121
- function derivedRole() {
122
- if (tag === "a")
123
- return "link";
124
- if (tag === "button")
125
- return "button";
126
- if (tag === "textarea")
127
- return "textbox";
128
- // `summary` is the control that opens a `<details>`; a click is what it takes.
129
- if (tag === "summary")
130
- return "button";
131
- if (tag === "select") {
132
- const multiple = el.getAttribute("multiple") !== null;
133
- const size = Number(el.getAttribute("size") ?? "1");
134
- return multiple || size > 1 ? "listbox" : "combobox";
135
- }
136
- if (tag !== "input")
137
- return tag;
138
- switch (type) {
139
- case "button":
140
- case "submit":
141
- case "reset":
142
- case "image":
143
- return "button";
144
- case "checkbox":
145
- return "checkbox";
146
- case "radio":
147
- return "radio";
148
- case "range":
149
- return "slider";
150
- case "number":
151
- return "spinbutton";
152
- case "search":
153
- return "searchbox";
154
- // `password` has no ARIA role of its own; `textbox` is both the useful answer and the
155
- // true one about how it is operated. The rest are HTML's textbox-mapped types.
156
- case null:
157
- case "text":
158
- case "email":
159
- case "tel":
160
- case "url":
161
- case "password":
162
- return "textbox";
163
- default:
164
- // `file`, `color`, `date`, `hidden`, and anything a later HTML adds. Reporting the
165
- // tag here is a disclosed gap; reporting `textbox` would be a wrong fact.
166
- return tag;
167
- }
168
- }
169
- const role = explicit ?? derivedRole();
170
- /** A pragmatic accessible-name approximation, not a full AccName implementation.
171
- *
172
- * One case is carved out rather than approximated: a **chooser's own contents are its
173
- * options**, so `innerText` on a `<select>` returns the list of things it can be set to,
174
- * and a period picker came back named `"This month\nLast month"`. That is not a rough
175
- * name, it is a false one — no person calls the control that, and the `select` verb
176
- * matches on name. A chooser therefore skips the text tiers entirely and is left unnamed
177
- * when nothing else names it. Unnamed is a gap; named after its options is a lie. */
178
- function derivedName() {
179
- const aria = el.getAttribute("aria-label");
180
- if (aria !== null && aria.trim() !== "")
181
- return aria;
182
- if (tag !== "select") {
183
- const inner = el.innerText?.trim();
184
- if (inner !== undefined && inner !== "")
185
- return inner;
186
- const text = el.textContent?.trim();
187
- if (text !== undefined && text !== null && text !== "")
188
- return text;
189
- }
190
- // A push-button input carries its label in `value`, where no text tier would find it.
191
- if (tag === "input" && (type === "button" || type === "submit" || type === "reset")) {
192
- const value = el.getAttribute("value");
193
- if (value !== null && value.trim() !== "")
194
- return value.trim();
195
- }
196
- return el.getAttribute("placeholder") ?? el.getAttribute("title") ?? el.getAttribute("name") ?? "";
197
- }
198
- const name = derivedName();
199
- /** Whether the page is laying this element out and painting it — true, false, or `null`
200
- * when nothing available could answer.
201
- *
202
- * **Deliberately not a viewport test.** An element below the fold is on the page and the
203
- * reveal pass depends on finding it; treating off-screen as hidden would trade this
204
- * precision defect for a recall one. What it does reject is an element the page renders
205
- * nowhere at all: `display:none`, the `hidden` attribute, a closed `<dialog>`,
206
- * `content-visibility: hidden`, `visibility: hidden`, and `<canvas>` fallback content.
207
- *
208
- * Opacity is **not** rejected: a zero-opacity element still occupies space and still
209
- * takes a click, and a fade-in caught mid-animation would vanish from the snapshot. */
210
- function renderedState() {
211
- if (typeof el.checkVisibility === "function") {
212
- try {
213
- return el.checkVisibility({ visibilityProperty: true });
214
- }
215
- catch {
216
- return null;
217
- }
218
- }
219
- if (typeof el.getClientRects !== "function")
220
- return null;
221
- try {
222
- if (el.getClientRects().length === 0)
223
- return false;
224
- }
225
- catch {
226
- return null;
227
- }
228
- const view = globalThis.getComputedStyle;
229
- if (typeof view !== "function")
230
- return null;
231
- try {
232
- const visibility = view.call(globalThis, el)?.visibility;
233
- if (visibility === undefined)
234
- return null;
235
- return visibility !== "hidden" && visibility !== "collapse";
236
- }
237
- catch {
238
- return null;
239
- }
240
- }
241
- // The node's own root: the document for a light-DOM element, the shadow root for one inside
242
- // an open shadow root. Only a shadow root has a `host`, which is what tells them apart, and
243
- // it is also the scope a uniqueness check has to run in — `ownerDocument.querySelectorAll`
244
- // matches nothing at all for a shadow child, so every such element used to come back with a
245
- // null selector for a reason that read like "no stable hook".
246
- const root = typeof el.getRootNode === "function" ? el.getRootNode() : el.ownerDocument;
247
- const inShadowRoot = root !== el.ownerDocument && root.host !== undefined && root.host !== null;
248
- // Computed in this same round trip so role/name/selector read off one live DOM state.
249
- // Rejects ids that look generated (hex run, React's `:r0:` useId
250
- // style, or blank) — "stable across reload" is what a generated id lacks.
251
- const GENERATED_ID = /[0-9a-f]{8}|:r[0-9a-z]+:|^\s*$/i;
252
- const FORM_CONTROLS = ["input", "select", "textarea", "button"];
253
- function escapeAttrValue(value) {
254
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
255
- }
256
- function isUnique(candidate) {
257
- try {
258
- return root.querySelectorAll(candidate).length === 1;
259
- }
260
- catch {
261
- // Invalid selector (rare) is treated as "no candidate", not thrown.
262
- return false;
263
- }
264
- }
265
- /** The element's own stable-hook selector, or null. Used both on the target itself and while climbing for a structural path's anchor. */
266
- function stableHookSelector(node) {
267
- const testId = node.getAttribute("data-testid");
268
- if (testId !== null && testId.trim() !== "")
269
- return `[data-testid="${escapeAttrValue(testId)}"]`;
270
- const testAttr = node.getAttribute("data-test");
271
- if (testAttr !== null && testAttr.trim() !== "")
272
- return `[data-test="${escapeAttrValue(testAttr)}"]`;
273
- const id = node.getAttribute("id");
274
- if (id !== null && !GENERATED_ID.test(id))
275
- return `[id="${escapeAttrValue(id)}"]`;
276
- return null;
277
- }
278
- function nthOfTypeSegment(node, parent) {
279
- const nodeTag = node.tagName.toLowerCase();
280
- const siblings = parent.querySelectorAll(`:scope > ${nodeTag}`);
281
- if (siblings.length <= 1)
282
- return nodeTag;
283
- for (let i = 0; i < siblings.length; i += 1) {
284
- if (siblings.item(i) === node)
285
- return `${nodeTag}:nth-of-type(${i + 1})`;
286
- }
287
- return nodeTag;
288
- }
289
- /** A bounded path from the nearest ancestor carrying a stable hook, capped at 6 segments. Null
290
- * when none is found in range — including when the climb reaches a shadow boundary, where
291
- * `parentElement` is null and there is genuinely no further ancestor inside this root. */
292
- function structuralPath(target) {
293
- const segments = [];
294
- let node = target;
295
- for (let depth = 0; depth < 6; depth += 1) {
296
- const parent = node.parentElement;
297
- if (parent === null)
298
- return null;
299
- segments.unshift(nthOfTypeSegment(node, parent));
300
- const hook = stableHookSelector(parent);
301
- if (hook !== null) {
302
- const candidate = `${hook} > ${segments.join(" > ")}`;
303
- return isUnique(candidate) ? candidate : null;
304
- }
305
- node = parent;
306
- }
307
- return null;
308
- }
309
- function computeSelector(target) {
310
- // Priority order: first unique candidate wins. Each tier is checked
311
- // independently — a shared data-testid isn't addressable either,
312
- // so the next tier gets a turn instead of stopping there.
313
- const testId = target.getAttribute("data-testid");
314
- if (testId !== null && testId.trim() !== "") {
315
- const candidate = `[data-testid="${escapeAttrValue(testId)}"]`;
316
- if (isUnique(candidate))
317
- return candidate;
318
- }
319
- const testAttr = target.getAttribute("data-test");
320
- if (testAttr !== null && testAttr.trim() !== "") {
321
- const candidate = `[data-test="${escapeAttrValue(testAttr)}"]`;
322
- if (isUnique(candidate))
323
- return candidate;
324
- }
325
- const id = target.getAttribute("id");
326
- if (id !== null && !GENERATED_ID.test(id)) {
327
- const candidate = `[id="${escapeAttrValue(id)}"]`;
328
- if (isUnique(candidate))
329
- return candidate;
330
- }
331
- const controlTag = target.tagName.toLowerCase();
332
- const controlName = target.getAttribute("name");
333
- if (controlName !== null && controlName.trim() !== "" && FORM_CONTROLS.includes(controlTag)) {
334
- const candidate = `${controlTag}[name="${escapeAttrValue(controlName)}"]`;
335
- if (isUnique(candidate))
336
- return candidate;
337
- }
338
- const explicitRole = target.getAttribute("role");
339
- const ariaLabel = target.getAttribute("aria-label");
340
- if (explicitRole !== null && ariaLabel !== null && ariaLabel.trim() !== "") {
341
- const candidate = `[role="${escapeAttrValue(explicitRole)}"][aria-label="${escapeAttrValue(ariaLabel)}"]`;
342
- if (isUnique(candidate))
343
- return candidate;
344
- }
345
- return structuralPath(target);
346
- }
347
- return { role, name: name.slice(0, 200), selector: computeSelector(el), inShadowRoot, rendered: renderedState() };
348
- }
349
- /** Said once, so the cap and the sentence describing it cannot drift apart. */
350
- const CONTAINER_SCAN_NOTE = "this page holds more elements than the reveal pass scans for scrollable containers, so a list " +
351
- "inside one past that point was not scrolled and anything only it holds is not above";
352
- /**
353
- * One reveal step, run inside the page: scrolls the document **and every scrollable container
354
- * in it** by just under their own height.
355
- *
356
- * The container half is the point. `window.scrollBy` does nothing to a list inside
357
- * `overflow: auto` — a shape common enough that a window-only reveal reports "nothing below
358
- * the fold" about a page whose entire content is below one. The step stays under a full
359
- * viewport so a row sitting on the seam is never scrolled past between two enumerations.
360
- *
361
- * Serialized and run in the page, so it closes over nothing in this module — the scan cap is
362
- * declared inside it for that reason and mirrored in `CONTAINER_SCAN_NOTE` above.
363
- */
364
- function revealStep() {
365
- const win = globalThis;
366
- const doc = win.document;
367
- const SCAN_CAP = 5000;
368
- let scrolled = false;
369
- if (win.scrollY + win.innerHeight < doc.documentElement.scrollHeight - 1) {
370
- win.scrollBy(0, Math.round(win.innerHeight * 0.9));
371
- scrolled = true;
372
- }
373
- const all = doc.querySelectorAll("*");
374
- const limit = all.length > SCAN_CAP ? SCAN_CAP : all.length;
375
- let containers = 0;
376
- for (let i = 0; i < limit; i += 1) {
377
- const node = all.item(i);
378
- if (node === null)
379
- continue;
380
- // Geometry first, style second: the overflow read is the expensive one, and an element
381
- // whose content fits inside it can never be a scroller whatever its overflow says.
382
- if (typeof node.scrollHeight !== "number" || typeof node.clientHeight !== "number")
383
- continue;
384
- if (node.clientHeight === 0 || node.scrollHeight <= node.clientHeight + 1)
385
- continue;
386
- const overflow = typeof win.getComputedStyle === "function" ? win.getComputedStyle(node)?.overflowY : undefined;
387
- if (overflow !== "auto" && overflow !== "scroll" && overflow !== "overlay")
388
- continue;
389
- containers += 1;
390
- if (node.scrollTop + node.clientHeight < node.scrollHeight - 1) {
391
- node.scrollBy(0, Math.round(node.clientHeight * 0.9));
392
- scrolled = true;
393
- }
394
- }
395
- return { y: win.scrollY, scrolled, containers, scanCapped: all.length > SCAN_CAP };
396
- }
397
- /** A frame's URL, or "" — a frame detaching mid-snapshot throws from `url()`. */
398
- function safeUrl(frame) {
399
- try {
400
- return typeof frame.url === "function" ? frame.url() : "";
401
- }
402
- catch {
403
- return "";
404
- }
405
- }
406
- /** Something a person can read back: the frame's name, else the iframe element's id or title.
407
- * Null when it carries none — the reply then falls back to the frame's URL. */
408
- async function frameLabel(frame) {
409
- try {
410
- const declared = typeof frame.name === "function" ? frame.name() : "";
411
- if (declared !== "")
412
- return declared;
413
- if (typeof frame.frameElement !== "function")
414
- return null;
415
- const element = await frame.frameElement();
416
- try {
417
- if (typeof element.getAttribute !== "function")
418
- return null;
419
- const id = await element.getAttribute("id");
420
- if (id !== null && id.trim() !== "")
421
- return id;
422
- const title = await element.getAttribute("title");
423
- if (title !== null && title.trim() !== "")
424
- return title;
425
- return null;
426
- }
427
- finally {
428
- await element.dispose().catch(() => { });
429
- }
430
- }
431
- catch {
432
- return null;
433
- }
434
- }
435
- /** Identity for reveal accounting. Elements are compared as a multiset, not by ref: a ref is
436
- * issued per snapshot and two enumerations of the same row never share one. */
437
- function countKeys(elements) {
438
- const counts = new Map();
439
- for (const element of elements) {
440
- const key = `${element.frame?.id ?? ""}\u0000${element.role}\u0000${element.name}\u0000${element.selector ?? ""}`;
441
- counts.set(key, (counts.get(key) ?? 0) + 1);
442
- }
443
- return counts;
444
- }
445
- /** How many of `a` have no counterpart in `b`. */
446
- function surplus(a, b) {
447
- let total = 0;
448
- for (const [key, count] of a)
449
- total += Math.max(0, count - (b.get(key) ?? 0));
450
- return total;
451
- }
452
- export class BrowserBackendMissingError extends Error {
453
- constructor(cause) {
454
- super(`Could not load ${RUNTIME_BROWSER_MODULE}: ${cause instanceof Error ? cause.message : String(cause)}. ` +
455
- "This build of @descryy/mcp does not have the browser backend installed.");
456
- this.name = "BrowserBackendMissingError";
457
- }
458
- }
459
- /**
460
- * RT-263: the real implementation behind `BrowserDriver.probeTargetReachable` /
461
- * `BrowserPageSession.probeTargetReachable` — single TCP connect, seconds, before
462
- * either a real Chromium launch (`browser_start_session`) or a real replay step
463
- * (`browser_run_scenario`) spends the slow path learning the same thing. Lives here,
464
- * not in either tool, so the fake driver used across `packages/mcp/test`'s
465
- * `createFakeBrowserDriver`-based suites never runs a real network probe against a
466
- * URL nothing is actually listening on — the port method is optional precisely so
467
- * those tests are unaffected by this existing.
468
- */
469
- async function probeTargetReachable(targetUrl) {
470
- let host;
471
- let port;
472
- try {
473
- const parsed = new URL(targetUrl);
474
- // Only a network target has a port to answer. `about:blank`, `data:` and
475
- // `file:` pages load without one, so there is nothing to probe.
476
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
477
- return null;
478
- host = parsed.hostname;
479
- port = parsed.port !== "" ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
480
- }
481
- catch {
482
- // Not this probe's problem to diagnose -- the real navigate/launch attempt
483
- // reports its own, more specific error for an unparseable target.
484
- return null;
485
- }
486
- const result = await runPreflight([portAnswersCheck(host, port, `${targetUrl} answers`)]);
487
- if (result.status !== "broken")
488
- return { reachable: true, message: "ok" };
489
- return { reachable: false, message: result.checks.find((check) => !check.ok)?.message ?? `Nothing answered at ${targetUrl}.` };
490
- }
491
- export async function createPlaywrightBrowserDriver(options = {}) {
492
- let runtime;
493
- try {
494
- runtime = (await (options.loadRuntimeModule ?? defaultRuntimeModuleLoader)());
495
- }
496
- catch (error) {
497
- throw new BrowserBackendMissingError(error);
498
- }
499
- return {
500
- probeTargetReachable,
501
- async launch(launchOptions) {
502
- const auth = runtimeAuthFor(launchOptions);
503
- const launch = await runtime.launchBrowserSessionOrRefuse({
504
- headless: launchOptions.headless ?? true,
505
- // B1. This used to be `{ headless }` and nothing else, while the port
506
- // above already carried `storageState` and a reply note already said
507
- // it had been seeded. The option reached the fake driver and stopped;
508
- // against a real browser nothing was ever applied and the note was
509
- // false. Building the seed here, from the target URL, is also what
510
- // makes the origin a fact the runtime is *told* rather than one it
511
- // has to work out.
512
- ...(auth === null ? {} : { auth }),
513
- });
514
- if (!launch.launched) {
515
- const { launched: _launched, ...refusal } = launch;
516
- return { launched: false, ...refusal };
517
- }
518
- const session = await openSession(runtime, launch.session, launchOptions, options);
519
- return { launched: true, session };
520
- },
521
- };
522
- }
523
- async function openSession(runtime, session, launchOptions, options) {
524
- const emitted = [];
525
- const actions = [];
526
- const unsettled = [];
527
- // Never cleared, unlike `actions` — the session's whole-lifetime provenance record for
528
- // `browser_save_scenario`, not per-drain evidence. Goes out of reach the moment this closure
529
- // does (session closed/reaped) — there is no other path back to it.
530
- const performed = [];
531
- // inFlight/lastEventAt track request activity: counted, not slept through.
532
- let inFlight = 0;
533
- let lastEventAt = 0;
534
- session.page.on("request", () => {
535
- inFlight += 1;
536
- lastEventAt = Date.now();
537
- });
538
- const settleOne = () => {
539
- inFlight = Math.max(0, inFlight - 1);
540
- lastEventAt = Date.now();
541
- };
542
- session.page.on("requestfinished", settleOne);
543
- session.page.on("requestfailed", settleOne);
544
- let refs = new Map();
545
- let generation = 0;
546
- let closed = false;
547
- let finalObservation = null;
548
- /** Set when a `saveStorageStateTo` write failed. Read by `storageStateSaveFailure()`. */
549
- let storageStateSaveError = null;
550
- /** Set when `groundIdentity` was asked for and installed successfully. Every snapshot
551
- * element with a selector is grounded through it; one with none is never attempted
552
- * (§B1's `identity: null` distinction — see `driver.ts`). */
553
- let grounder = null;
554
- const identityWarnings = [];
555
- const context = {
556
- executionId: `mcp-browser-${session.sessionId}`,
557
- configuration: { environmentTier: "tier-2-container", fidelityLevel: 2, timeoutMs: 30_000, services: {} },
558
- emit: (evidence) => {
559
- emitted.push(evidence);
560
- },
561
- ...(options.resolveSourceRoot === undefined ? {} : { resolveSourceRoot: options.resolveSourceRoot }),
562
- };
563
- const actionCollector = runtime.createBrowserActionCollector({
564
- session,
565
- ...(options.service === undefined ? {} : { service: options.service }),
566
- });
567
- const consoleCollector = runtime.createBrowserConsoleCollector({
568
- session,
569
- ...(options.service === undefined ? {} : { service: options.service }),
570
- ...(options.stackParser === undefined ? {} : { stackTraceParser: options.stackParser }),
571
- });
572
- const networkCollector = runtime.createBrowserNetworkCollector({
573
- session,
574
- ...(options.service === undefined ? {} : { service: options.service }),
575
- ...(options.stackParser === undefined ? {} : { stackTraceParser: options.stackParser }),
576
- });
577
- // Started before first navigation: Playwright's init-script contract means
578
- // a collector started later misses instrumentation, reporting an
579
- // honest-looking but empty stack.
580
- await actionCollector.start(context);
581
- await consoleCollector.start(context);
582
- await networkCollector.start(context);
583
- // Same contract as the collectors above, and for the same reason: the identity probe is
584
- // an init script too, so it must install before the first navigation or it reports
585
- // `probe-not-installed` for the document's whole lifetime. Off by default (B1) — a session
586
- // that never asks for it pays no page.evaluate and loads no module.
587
- if (options.groundIdentity === true) {
588
- try {
589
- const identityModule = (await (options.loadIdentityGroundingModule ?? defaultIdentityGroundingModuleLoader)());
590
- grounder = identityModule.createFrameworkAwareGrounder(options.resolveSourceRoot === undefined ? {} : { resolveSourceRoot: options.resolveSourceRoot });
591
- await grounder.install(session.page);
592
- }
593
- catch (error) {
594
- // Honest degradation (rule 7): identity grounding could not be installed, so every
595
- // element on this session reports `identity: null` — never a guess — and the reason
596
- // travels on the observation rather than being swallowed.
597
- grounder = null;
598
- identityWarnings.push(`identity grounding was requested (groundIdentity) but could not be installed: ` +
599
- `${error instanceof Error ? error.message : String(error)}. Every element on this session ` +
600
- "reports identity: null rather than a guess.");
601
- }
602
- }
603
- await actionCollector.navigate(launchOptions.targetUrl);
604
- function assertUsable() {
605
- if (closed || !session.isUsable())
606
- throw new BrowserSessionCrashedError(session.sessionId);
607
- }
608
- function resolve(ref) {
609
- const found = refs.get(ref);
610
- if (found === undefined)
611
- throw new UnknownElementRefError(ref);
612
- return found;
613
- }
614
- function recordAction(action, ref, mode) {
615
- actions.push({ action, ref, mode, url: session.page.url(), at: new Date().toISOString() });
616
- }
617
- function recordPerformed(action, target, url, text) {
618
- performed.push({ action, target, url, text, at: new Date().toISOString() });
619
- }
620
- /** Waits for the action's network effect (started within `ACTIVITY_GRACE_MS`, then quiet for `NETWORK_QUIET_MS`), capped at `SETTLE_TIMEOUT_MS` — reaching the cap is recorded, never swallowed. */
621
- async function settleAfterAction(action, ref) {
622
- const startedAt = Date.now();
623
- lastEventAt = startedAt;
624
- let sawActivity = inFlight > 0;
625
- for (;;) {
626
- const now = Date.now();
627
- const waitedMs = now - startedAt;
628
- if (inFlight > 0)
629
- sawActivity = true;
630
- if (waitedMs >= SETTLE_TIMEOUT_MS) {
631
- unsettled.push({ action, ref, waitedMs });
632
- return;
633
- }
634
- if (sawActivity) {
635
- if (inFlight === 0 && now - lastEventAt >= NETWORK_QUIET_MS)
636
- return;
637
- }
638
- else if (waitedMs >= ACTIVITY_GRACE_MS) {
639
- return;
640
- }
641
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
642
- }
643
- }
644
- async function dropRefs() {
645
- const handles = [...refs.values()].map((r) => r.handle);
646
- refs = new Map();
647
- await Promise.all(handles.map((h) => h.dispose().catch(() => { })));
648
- }
649
- /** Every document to read, main first. When the backend exposes no frame list the page
650
- * itself is the only one, and that limitation is reported as an unread frame rather than
651
- * passed off as "this page has no iframes". */
652
- function documentsToRead() {
653
- if (typeof session.page.frames !== "function")
654
- return { frames: [{ frame: session.page, ref: null }], frameListUnavailable: true };
655
- const all = session.page.frames();
656
- const main = typeof session.page.mainFrame === "function" ? session.page.mainFrame() : all[0];
657
- const out = [];
658
- let index = 0;
659
- for (const frame of all) {
660
- if (frame === main) {
661
- out.unshift({ frame, ref: null });
662
- continue;
663
- }
664
- index += 1;
665
- out.push({ frame, ref: { id: `frame-${index}`, url: safeUrl(frame), name: null } });
666
- }
667
- if (out.length === 0 || out[0]?.ref !== null)
668
- out.unshift({ frame: session.page, ref: null });
669
- return { frames: out, frameListUnavailable: false };
670
- }
671
- /** Reads the elements of one document, describing each in a single round trip. */
672
- async function enumerateFrame(frame, frameRef) {
673
- const handles = await frame.$$(INTERACTIVE);
674
- const elements = [];
675
- let ambiguousInDocument = 0;
676
- let hidden = 0;
677
- let untested = 0;
678
- for (const handle of handles) {
679
- const described = await handle.evaluate(describeElement).catch(() => null);
680
- if (described === null) {
681
- await handle.dispose().catch(() => { });
682
- continue;
683
- }
684
- // The CSS query matches what is in the document; this is what is on the page. A
685
- // `display:none` menu, a closed modal and an inactive tab panel all match the query,
686
- // and reporting them as usable controls is Descry stating something false about the
687
- // user's screen. Withheld and counted, never dropped in silence (rule 7).
688
- if (described.rendered === false) {
689
- hidden += 1;
690
- await handle.dispose().catch(() => { });
691
- continue;
692
- }
693
- // `null` is "nothing could answer". The element is kept — omitting on an unanswered
694
- // question would hide real controls behind a test that did not run — and the fact that
695
- // the question went unanswered travels with the snapshot.
696
- if (described.rendered === null)
697
- untested += 1;
698
- let selector = described.selector;
699
- // A hook unique inside its own shadow root is not necessarily unique in the document:
700
- // two instances of the same component each carry `[data-testid="submit"]`. Playwright's
701
- // own CSS resolution pierces, so asking it is the same question a replay would ask.
702
- if (selector !== null && described.inShadowRoot === true) {
703
- const matches = await frame.$$(selector).catch(() => null);
704
- if (matches !== null)
705
- await Promise.all(matches.map((m) => m.dispose().catch(() => { })));
706
- if (matches === null || matches.length !== 1) {
707
- selector = null;
708
- ambiguousInDocument += 1;
709
- }
710
- }
711
- elements.push({
712
- handle,
713
- role: described.role,
714
- name: described.name,
715
- // An element inside a frame has no saveable selector: the scenario format carries no
716
- // field to name the frame, so a stored selector would replay against the main document.
717
- selector: frameRef === null ? selector : null,
718
- inShadowRoot: described.inShadowRoot === true,
719
- frame: frameRef,
720
- });
721
- }
722
- return { elements, ambiguousInDocument, hidden, untested };
723
- }
724
- /** Counts custom elements that look like closed shadow hosts. Returns null when the probe could not run. */
725
- async function probeClosedHosts(frame) {
726
- if (typeof frame.evaluate !== "function")
727
- return null;
728
- return frame
729
- .evaluate(() => {
730
- const page = globalThis;
731
- const all = page.document.querySelectorAll("*");
732
- let suspected = 0;
733
- for (let i = 0; i < all.length; i += 1) {
734
- const element = all.item(i);
735
- if (element === null)
736
- continue;
737
- // A dash in the tag name is what the custom-element spec requires of every one of
738
- // them; it names no language or framework (rule 1), only the HTML naming rule.
739
- if (!element.tagName.toLowerCase().includes("-"))
740
- continue;
741
- if (element.shadowRoot !== null && element.shadowRoot !== undefined)
742
- continue;
743
- if ((element.childElementCount ?? 0) > 0)
744
- continue;
745
- suspected += 1;
746
- }
747
- return suspected;
748
- })
749
- .catch(() => null);
750
- }
751
- /** Reads every document, and reports each one it could not read with the reason. */
752
- async function enumerateAll() {
753
- const { frames: documents, frameListUnavailable } = documentsToRead();
754
- const elements = [];
755
- const reach = [];
756
- let ambiguousInDocument = 0;
757
- let suspectedClosedHosts = 0;
758
- let probed = false;
759
- let hidden = 0;
760
- let untested = 0;
761
- for (const { frame, ref } of documents) {
762
- let frameRef = ref;
763
- if (frameRef !== null)
764
- frameRef = { ...frameRef, name: await frameLabel(frame) };
765
- try {
766
- const read = await enumerateFrame(frame, frameRef);
767
- elements.push(...read.elements);
768
- ambiguousInDocument += read.ambiguousInDocument;
769
- hidden += read.hidden;
770
- untested += read.untested;
771
- reach.push({ frame: frameRef, enumerated: true, elements: read.elements.length, reason: null });
772
- }
773
- catch (error) {
774
- reach.push({
775
- frame: frameRef,
776
- enumerated: false,
777
- elements: 0,
778
- reason: `this frame could not be read: ${error instanceof Error ? error.message : String(error)}`,
779
- });
780
- continue;
781
- }
782
- const closed = await probeClosedHosts(frame);
783
- if (closed !== null) {
784
- probed = true;
785
- suspectedClosedHosts += closed;
786
- }
787
- }
788
- if (frameListUnavailable) {
789
- reach.push({
790
- frame: { id: "frames-unavailable", url: "", name: null },
791
- enumerated: false,
792
- elements: 0,
793
- reason: "this browser backend exposes no frame list, so any iframe on this page was not read. " +
794
- "Nothing here claims the page has none.",
795
- });
796
- }
797
- return {
798
- elements,
799
- frames: reach,
800
- shadow: {
801
- piercedElements: elements.filter((e) => e.inShadowRoot).length,
802
- ambiguousInDocument,
803
- suspectedClosedHosts,
804
- probed,
805
- },
806
- hidden: { withheld: hidden, untested },
807
- };
808
- }
809
- /** Scrolls the document **and every scrollable container in it** a viewport at a time until
810
- * nothing can move further, or the pass cap stops it. Containers are the half a window-only
811
- * scroll never reached: a list inside `overflow: auto` does not move when the page does. */
812
- async function scrollToReveal() {
813
- const page = session.page;
814
- if (typeof page.evaluate !== "function") {
815
- return {
816
- passes: 0,
817
- stoppedAtCap: false,
818
- finalScrollY: 0,
819
- containersScrolled: 0,
820
- reason: "this browser backend exposes no page evaluation, so nothing below the fold could be revealed",
821
- };
822
- }
823
- const evaluate = page.evaluate.bind(page);
824
- let passes = 0;
825
- let containers = 0;
826
- let lastY = 0;
827
- let scanCapped = false;
828
- for (;;) {
829
- const step = await evaluate(revealStep).catch(() => null);
830
- if (step === null) {
831
- return {
832
- passes,
833
- stoppedAtCap: false,
834
- finalScrollY: lastY,
835
- containersScrolled: containers,
836
- reason: "the page stopped answering mid-reveal",
837
- };
838
- }
839
- lastY = step.y;
840
- containers = Math.max(containers, step.containers);
841
- scanCapped = scanCapped || step.scanCapped;
842
- // Nothing left to move: the page and every container it holds are at their end, or
843
- // none of them can scroll at all. Either way there is nothing further to reveal.
844
- if (!step.scrolled) {
845
- return {
846
- passes,
847
- stoppedAtCap: false,
848
- finalScrollY: step.y,
849
- containersScrolled: containers,
850
- reason: scanCapped ? CONTAINER_SCAN_NOTE : null,
851
- };
852
- }
853
- passes += 1;
854
- if (passes >= REVEAL_MAX_PASSES) {
855
- return {
856
- passes,
857
- stoppedAtCap: true,
858
- finalScrollY: step.y,
859
- containersScrolled: containers,
860
- reason: scanCapped ? CONTAINER_SCAN_NOTE : null,
861
- };
862
- }
863
- await new Promise((done) => setTimeout(done, REVEAL_SETTLE_MS));
864
- }
865
- }
866
- /**
867
- * Refuses a control the page has put out of reach.
868
- *
869
- * `click` never calls this: Playwright's own actionability checks already
870
- * cover it, and duplicating them here would mean two rules that can
871
- * disagree. `select` and `submit` do, because neither of their underlying
872
- * operations hit-tests — see `NotOperableError`.
873
- *
874
- * A browser that cannot answer is treated as operable rather than refused.
875
- * Refusing on an unanswered question would turn a missing capability into a
876
- * false negative on every element, which is the same trade rule 2 rejects
877
- * pointed the other way.
878
- */
879
- async function assertOperable(ref, handle) {
880
- const state = await handle
881
- .evaluate((el) => ({
882
- // `inert` applies to a whole subtree and `HTMLElement.inert` reflects only the
883
- // attribute on the node it is read from, so this has to walk ancestors.
884
- inert: el.closest === undefined ? null : el.closest("[inert]") !== null,
885
- disabled: el.disabled === true,
886
- }))
887
- .catch(() => null);
888
- if (state === null)
889
- return;
890
- if (state.inert === true)
891
- throw new NotOperableError(ref, "inert");
892
- if (state.disabled)
893
- throw new NotOperableError(ref, "disabled");
894
- }
895
- // Named rather than returned inline: `waitFor` re-enumerates the page each poll and does it
896
- // by calling this same `snapshot()`, so the refs it hands back come from the one enumeration
897
- // path instead of a second copy that could drift from it.
898
- const api = {
899
- sessionId: session.sessionId,
900
- startedAt: session.startedAt,
901
- isUsable: () => !closed && session.isUsable(),
902
- currentUrl: () => session.page.url(),
903
- probeTargetReachable,
904
- async navigate(url) {
905
- assertUsable();
906
- await dropRefs();
907
- await actionCollector.navigate(url);
908
- recordAction("navigate", null, null);
909
- recordPerformed("navigate", null, url, null);
910
- },
911
- async snapshot(options) {
912
- assertUsable();
913
- await dropRefs();
914
- generation += 1;
915
- let read = await enumerateAll();
916
- let reveal = null;
917
- if (options?.reveal === true) {
918
- const before = countKeys(read.elements);
919
- const scrolled = await scrollToReveal();
920
- if (scrolled.passes > 0) {
921
- // Handles taken before the scroll may point at rows the list has since destroyed;
922
- // the page is re-read rather than merged, so every ref handed back is one that
923
- // exists now. What the scroll cost is reported, not hidden.
924
- await Promise.all(read.elements.map((e) => e.handle.dispose().catch(() => { })));
925
- read = await enumerateAll();
926
- }
927
- const after = countKeys(read.elements);
928
- reveal = {
929
- passes: scrolled.passes,
930
- containersScrolled: scrolled.containersScrolled,
931
- revealedElements: surplus(after, before),
932
- recycledElements: surplus(before, after),
933
- stoppedAtCap: scrolled.stoppedAtCap,
934
- finalScrollY: scrolled.finalScrollY,
935
- framesNotScrolled: read.frames
936
- .filter((f) => f.frame !== null)
937
- .map((f) => f.frame?.name ?? f.frame?.url ?? f.frame?.id ?? ""),
938
- reason: scrolled.reason,
939
- };
940
- }
941
- const elements = [];
942
- for (const [index, found] of read.elements.entries()) {
943
- const ref = `ref-${generation}-${index}`;
944
- const element = {
945
- ref,
946
- role: found.role,
947
- name: found.name,
948
- selector: found.selector,
949
- frame: found.frame,
950
- inShadowRoot: found.inShadowRoot,
951
- // Grounding resolves a *document* selector, so a framed or hookless element
952
- // (selector === null) is never attempted — `identity` stays null rather than
953
- // guessed at, same reasoning `driver.ts` documents on the field itself.
954
- identity: grounder === null || found.selector === null
955
- ? null
956
- : await groundOne(grounder, session.page, found.selector),
957
- };
958
- refs.set(ref, { element, handle: found.handle });
959
- elements.push(element);
960
- }
961
- return {
962
- url: session.page.url(),
963
- title: await session.page.title().catch(() => ""),
964
- elements,
965
- totalElements: elements.length,
966
- frames: read.frames,
967
- shadow: read.shadow,
968
- hidden: read.hidden,
969
- reveal,
970
- };
971
- },
972
- async click(ref) {
973
- assertUsable();
974
- const { element, handle } = resolve(ref);
975
- await handle.click();
976
- recordAction("click", ref, null);
977
- recordPerformed("click", { role: element.role, name: element.name, selector: element.selector }, null, null);
978
- // requestfinished fires after the page's handler resolves; draining immediately would miss the very request being watched for.
979
- await settleAfterAction("click", ref);
980
- },
981
- async type(ref, text) {
982
- assertUsable();
983
- const { element, handle } = resolve(ref);
984
- if (handle.pressSequentially !== undefined)
985
- await handle.pressSequentially(text);
986
- else if (handle.type !== undefined)
987
- await handle.type(text);
988
- else
989
- throw new Error("this Playwright build exposes no keystroke-level typing method");
990
- recordAction("type", ref, "append");
991
- recordPerformed("type", { role: element.role, name: element.name, selector: element.selector }, null, text);
992
- await settleAfterAction("type", ref);
993
- },
994
- async fill(ref, text) {
995
- assertUsable();
996
- const { element, handle } = resolve(ref);
997
- await handle.fill(text);
998
- recordAction("fill", ref, "replace");
999
- recordPerformed("fill", { role: element.role, name: element.name, selector: element.selector }, null, text);
1000
- await settleAfterAction("fill", ref);
1001
- },
1002
- async select(ref, value) {
1003
- assertUsable();
1004
- const { element, handle } = resolve(ref);
1005
- await assertOperable(ref, handle);
1006
- // Read what the element actually offers before touching it. Playwright's own
1007
- // selectOption would time out on a non-match and say nothing useful about why; the
1008
- // refusal a caller can act on has to carry the real option list, and that means one
1009
- // round trip to read it.
1010
- const offered = await handle.evaluate((el) => {
1011
- if (el.options === undefined)
1012
- return null;
1013
- const out = [];
1014
- for (let i = 0; i < el.options.length; i += 1) {
1015
- const option = el.options[i];
1016
- out.push({ value: option.value, label: option.label === "" ? option.text : option.label });
1017
- }
1018
- return out;
1019
- });
1020
- if (offered === null)
1021
- throw new NotSelectableError(ref, element.role);
1022
- // By stored value first, then by the label a person reads — both exact. A caller
1023
- // reading a snapshot sees labels and a caller reading the page's source sees values;
1024
- // neither should have to know which one this element uses.
1025
- const matched = offered.find((o) => o.value === value) ?? offered.find((o) => o.label.trim() === value.trim());
1026
- if (matched === undefined) {
1027
- throw new NoSuchOptionError(ref, value, offered.map((o) => (o.label.trim() === "" ? o.value : o.label)));
1028
- }
1029
- if (handle.selectOption === undefined)
1030
- throw new Error("this Playwright build exposes no option-selecting method");
1031
- await handle.selectOption(matched.value);
1032
- recordAction("select", ref, "replace");
1033
- recordPerformed("select", { role: element.role, name: element.name, selector: element.selector }, null, matched.value);
1034
- await settleAfterAction("select", ref);
1035
- },
1036
- async submit(ref) {
1037
- assertUsable();
1038
- const { element, handle } = resolve(ref);
1039
- await assertOperable(ref, handle);
1040
- // Submitted through the form's own submit control rather than by synthesising a bare
1041
- // submit event: that is what a person's click does, it names the submitter the page's
1042
- // handler reads, and it runs the form's validation. A form with no such control is
1043
- // submitted by script on some other control — refused, not guessed at.
1044
- const outcome = await handle.evaluate((el) => {
1045
- const form = el.closest === undefined ? null : el.closest("form");
1046
- if (form === null)
1047
- return "noForm";
1048
- const control = form.querySelector('button[type="submit"], input[type="submit"], button:not([type])');
1049
- if (control === null)
1050
- return "noSubmitControl";
1051
- form.requestSubmit(control);
1052
- return "submitted";
1053
- });
1054
- if (outcome !== "submitted")
1055
- throw new NotSubmittableError(ref, outcome);
1056
- recordAction("submit", ref, null);
1057
- recordPerformed("submit", { role: element.role, name: element.name, selector: element.selector }, null, null);
1058
- await settleAfterAction("submit", ref);
1059
- },
1060
- async waitFor(target, waitOptions = {}) {
1061
- assertUsable();
1062
- if (isEmptyWaitTarget(target)) {
1063
- throw new Error("browser_wait_for needs a target: give a role, a name, or both. Waiting for nothing in particular " +
1064
- `would return the first element on the page, which is not what any caller means. Got: ${describeWaitTarget(target)}.`);
1065
- }
1066
- const timeoutMs = clampWaitTimeout(waitOptions.timeoutMs);
1067
- const startedAt = Date.now();
1068
- for (;;) {
1069
- assertUsable();
1070
- // Re-enumerates each poll rather than watching one selector: the port has no
1071
- // selector to watch, and a fresh snapshot is what makes the matched element
1072
- // actionable at all. That cost is real, and is why the interval is not tighter.
1073
- const snapshot = await api.snapshot();
1074
- const matches = snapshot.elements.filter((e) => matchesWaitTarget(e, target));
1075
- const waitedMs = Date.now() - startedAt;
1076
- if (matches.length > 0) {
1077
- return { appeared: true, waitedMs, timeoutMs, matched: matches[0], matchCount: matches.length, snapshot };
1078
- }
1079
- if (waitedMs >= timeoutMs) {
1080
- return { appeared: false, waitedMs, timeoutMs, matched: null, matchCount: 0, snapshot };
1081
- }
1082
- await new Promise((done) => setTimeout(done, WAIT_POLL_INTERVAL_MS));
1083
- }
1084
- },
1085
- async injectStorageState(origin, state) {
1086
- assertUsable();
1087
- if (typeof session.injectStorageState !== "function") {
1088
- // Honest degradation (rule 7), same shape the groundIdentity install
1089
- // failure above uses: an older runtime-browser build predates this
1090
- // primitive, so nothing was injected and the reason travels on the
1091
- // result rather than a silent no-op or a thrown TypeError.
1092
- return {
1093
- cookiesInjected: 0,
1094
- localStorageInjected: 0,
1095
- localStorageSkipped: "this build of @descryy/runtime-browser does not export injectStorageState -- nothing was injected. " +
1096
- "Republish runtime-browser at a version that includes BrowserSession.injectStorageState.",
1097
- };
1098
- }
1099
- return session.injectStorageState({
1100
- origin,
1101
- ...(state.cookies === undefined ? {} : { cookies: state.cookies }),
1102
- ...(state.localStorage === undefined ? {} : { localStorage: state.localStorage }),
1103
- });
1104
- },
1105
- async drainEvidence() {
1106
- const observation = collect(emitted, actions, unsettled);
1107
- emitted.length = 0;
1108
- actions.length = 0;
1109
- unsettled.length = 0;
1110
- return withIdentityWarnings(observation, identityWarnings);
1111
- },
1112
- performedSteps() {
1113
- return [...performed];
1114
- },
1115
- storageStateSaveFailure: () => storageStateSaveError,
1116
- /**
1117
- * §4.2's probe. Reads the page and counts; decides nothing — `estimateReach`
1118
- * is the only thing that turns these counts into a claim, so a change of
1119
- * policy never touches a driver.
1120
- *
1121
- * The body lives in `page-probe.ts` rather than here: this file belongs to
1122
- * another lane, and a six-line delegation is mergeable where two hundred
1123
- * lines of DOM walking would not be.
1124
- */
1125
- async fingerprint() {
1126
- assertUsable();
1127
- const [root] = await session.page.$$("html");
1128
- if (root === undefined) {
1129
- // No document element at all. Refusing is right: a fingerprint of
1130
- // zeroes would read as a page with nothing on it.
1131
- throw new Error("the page exposed no document element to probe");
1132
- }
1133
- try {
1134
- return await root.evaluate(probeInPage, PROBE_CONSTANTS);
1135
- }
1136
- finally {
1137
- await root.dispose();
1138
- }
1139
- },
1140
- async close() {
1141
- if (finalObservation !== null)
1142
- return finalObservation;
1143
- closed = true;
1144
- // Stopped before observing: a stop flushes what each collector was
1145
- // still holding, which observing first would drop.
1146
- await actionCollector.stop().catch(() => { });
1147
- await consoleCollector.stop().catch(() => { });
1148
- await networkCollector.stop().catch(() => { });
1149
- const observation = withIdentityWarnings(collect(emitted, actions, unsettled), identityWarnings);
1150
- emitted.length = 0;
1151
- actions.length = 0;
1152
- unsettled.length = 0;
1153
- await dropRefs();
1154
- // Before `session.close()`, not after: a closed context holds no
1155
- // cookies to read, and a save that ran afterwards would write a file
1156
- // that looks like a session with nothing in it — which is worse than no
1157
- // file, because the next run would seed from it and silently drive the
1158
- // logged-out surface. A failure here is reported as a note by the
1159
- // caller rather than swallowed, but it never fails the close: evidence
1160
- // already collected must not be lost to a write that did not work.
1161
- if (launchOptions.saveStorageStateTo !== undefined && session.saveStorageState !== undefined) {
1162
- try {
1163
- await session.saveStorageState(launchOptions.saveStorageStateTo);
1164
- }
1165
- catch (error) {
1166
- storageStateSaveError = error instanceof Error ? error.message : String(error);
1167
- }
1168
- }
1169
- await session.close().catch(() => { });
1170
- finalObservation = observation;
1171
- return observation;
1172
- },
1173
- };
1174
- return api;
1175
- }
1176
- /** Turns collector evidence into the port's vocabulary. Joins request/response on `requestId`, keeping `status: null` (not guessed) when unanswered. Exported for `browser-request-outcome-mapping.test.ts`. */
1177
- export function collect(emitted, actions, unsettled) {
1178
- // `requestId` is a correlation header the app must itself propagate, not a
1179
- // transport id — an id-only join silently reported `status: null` for
1180
- // every request without one, i.e. most of them, hence the URL fallback
1181
- // below. `status` and `outcome` are read from the *same* terminal event and
1182
- // stored together: two independent last-write-wins maps let a later
1183
- // terminal event for a *different* request overwrite one field but not the
1184
- // other (e.g. an id-less "unanswered" HTTP_ERROR with no numeric status
1185
- // updates only `outcome`), so a third, genuinely-still-pending id-less
1186
- // request sharing that URL could read back `status: 200` from one sibling
1187
- // and `outcome: "unanswered"` from another — a self-contradictory pair
1188
- // that is a real, reported defect (P6-D5), not a hypothetical one.
1189
- //
1190
- // **B4: that pairing was necessary and was not sufficient.** Three routes to
1191
- // a false fact survived it, each verified here rather than reasoned about,
1192
- // and each fixed at the line it occurs on below: the pair itself was never
1193
- // checked for self-consistency across a boundary that versions separately;
1194
- // "paired" was a set-membership test on the URL rather than a record of what
1195
- // this request consumed, so an in-flight sibling read as failed; and the
1196
- // HTTP_ERROR `descry-runtime` emits *alongside* a >= 400 NETWORK_RESPONSE
1197
- // took a second queue slot, handing one exchange's 500 to two requests.
1198
- const byRequestId = new Map();
1199
- // C3. A **transport-level** identity, when the collector carries one: an id
1200
- // the browser's own request object owns, not a header an application chose
1201
- // to propagate. It outranks `requestId` because it cannot be absent for
1202
- // application reasons, and where it is present the whole out-of-order
1203
- // question below simply does not arise. `descry-runtime`'s network
1204
- // collector already holds this identity — its pending map is keyed by the
1205
- // Playwright `Request` object itself, "not a URL or an id, which could
1206
- // collide across concurrent requests to the same endpoint" — and does not
1207
- // yet emit it. Read here the same way `outcome` was read before its
1208
- // producer shipped: validated, optional, and absent degrades to the join
1209
- // below rather than to a guess. See
1210
- // `DEC-NEXT-transport-level-exchange-id-for-browser-requests`.
1211
- const byExchangeId = new Map();
1212
- // FIFO per URL, fed only by unidentified terminal events. A URL is not
1213
- // unique the way an id is — a page can issue the same URL more than once —
1214
- // so a shared cache must hand each terminal event's (status, outcome) pair
1215
- // to at most one request, in arrival order, rather than broadcasting the
1216
- // most recent one to every request sharing that URL. An id-bearing terminal
1217
- // event is deliberately excluded from this pool: it is already claimed
1218
- // evidence for one specific request, and folding it into the shared pool
1219
- // would just relocate the same contamination.
1220
- //
1221
- // **C3: arrival order is not an answer, so it is no longer used as one.**
1222
- // Two unidentified requests to one URL whose responses arrive out of order
1223
- // swap records, and nothing at this layer can tell an in-order pair from a
1224
- // swapped one. A FIFO is therefore right about half the time and silent
1225
- // when it is wrong — the exact shape rule 2 forbids, because the failure
1226
- // leaves no trace in the reply. Where the assignment is genuinely
1227
- // undetermined the queue is not consulted at all and every request in that
1228
- // bucket is reported `unattributed` with the terminal events disclosed by
1229
- // URL instead. Where it is determined — one unidentified request, or
1230
- // several whose records are indistinguishable from one another, so that
1231
- // every possible assignment yields the same answer — it still pairs, since
1232
- // refusing there would trade a silent wrong answer for a loud missing one
1233
- // that nothing required.
1234
- const queueByUrl = new Map();
1235
- const warnings = new Set();
1236
- // URLs that produced an id-less NETWORK_RESPONSE, so the duplicate-HTTP_ERROR
1237
- // exclusion below can require its sibling rather than assume it. A build that
1238
- // ever emitted a numeric-status HTTP_ERROR *alone* would otherwise have it
1239
- // silently dropped, turning a real 4xx into "pending".
1240
- const respondedUrls = new Set();
1241
- for (const evidence of emitted) {
1242
- if (evidence.eventType !== "NETWORK_RESPONSE" || evidence.requestId !== null)
1243
- continue;
1244
- if (exchangeIdOf(evidence.payload) !== null)
1245
- continue;
1246
- const url = evidence.payload["url"];
1247
- if (typeof url === "string")
1248
- respondedUrls.add(url);
1249
- }
1250
- for (const evidence of emitted) {
1251
- if (evidence.eventType !== "NETWORK_RESPONSE" && evidence.eventType !== "HTTP_ERROR")
1252
- continue;
1253
- const url = evidence.payload["url"];
1254
- const rawStatus = evidence.payload["status"];
1255
- const status = typeof rawStatus === "number" ? rawStatus : null;
1256
- // B4. The pair is validated, not just each field: this payload crosses a
1257
- // package boundary that versions separately, which is the same reason
1258
- // `requestOutcomeOf` validates instead of casting. A numeric status means
1259
- // a response arrived; "unanswered" means none ever did. Both cannot be
1260
- // true of one request, and forwarding the pair verbatim is what produced
1261
- // the `status: 200` + `outcome: "unanswered"` record the cal.com pass
1262
- // reported. Neither field can be trusted to correct the other, so the
1263
- // directly-observed status is kept and the outcome degrades to the same
1264
- // "not reported" state an older collector build produces — and it is
1265
- // named in the reply rather than dropped quietly (rule 7).
1266
- const outcome = reconcile(status, requestOutcomeOf(evidence.payload), url, warnings);
1267
- const record = { status, outcome };
1268
- const exchangeId = exchangeIdOf(evidence.payload);
1269
- if (exchangeId !== null) {
1270
- byExchangeId.set(exchangeId, record);
1271
- continue;
1272
- }
1273
- if (evidence.requestId !== null) {
1274
- byRequestId.set(evidence.requestId, record);
1275
- continue;
1276
- }
1277
- if (typeof url !== "string")
1278
- continue;
1279
- // B4. One exchange, one slot. `descry-runtime`'s collector emits
1280
- // NETWORK_RESPONSE *and* a payload-sharing HTTP_ERROR for a single
1281
- // response with status >= 400, and a numeric status is the only shape in
1282
- // which it ever emits HTTP_ERROR — every other HTTP_ERROR it produces
1283
- // (requestfailed, its own timeout, a websocket socketerror, a session
1284
- // closing mid-flight) carries `status: null` and has no NETWORK_RESPONSE
1285
- // beside it. Keyed by requestId that duplicate is harmless (same content,
1286
- // one map entry); in this queue it took a second slot, so one 500
1287
- // exchange handed a 500 to a second request that never got a response at
1288
- // all. Excluded here only — an id-keyed one still records, in case a
1289
- // build ever emits it without its sibling.
1290
- if (evidence.eventType === "HTTP_ERROR" && status !== null && respondedUrls.has(url))
1291
- continue;
1292
- const queue = queueByUrl.get(url);
1293
- if (queue === undefined)
1294
- queueByUrl.set(url, [record]);
1295
- else
1296
- queue.push(record);
1297
- }
1298
- /** The record this evidence owns outright, or undefined when it must draw from the shared
1299
- * URL queue. Exchange id first: it identifies the transport exchange, where `requestId`
1300
- * identifies whatever the application decided to label. */
1301
- const keyedRecordFor = (evidence) => {
1302
- const exchangeId = exchangeIdOf(evidence.payload);
1303
- if (exchangeId !== null) {
1304
- const byExchange = byExchangeId.get(exchangeId);
1305
- if (byExchange !== undefined)
1306
- return byExchange;
1307
- }
1308
- return evidence.requestId === null ? undefined : byRequestId.get(evidence.requestId);
1309
- };
1310
- // C3. How many requests to each URL have no record of their own — exactly the set that
1311
- // draws from the shared queue, and therefore exactly the set the queue can be wrong about.
1312
- // Counted after the maps are built, not from the presence of an id: a request carrying an
1313
- // id that never came back on a terminal event falls through to the queue like any other,
1314
- // and counting it as identified would leave the queue one short of the requests using it.
1315
- const unkeyedByUrl = new Map();
1316
- for (const evidence of emitted) {
1317
- if (evidence.eventType !== "NETWORK_REQUEST")
1318
- continue;
1319
- const url = evidence.payload["url"];
1320
- if (typeof url !== "string")
1321
- continue;
1322
- if (keyedRecordFor(evidence) !== undefined)
1323
- continue;
1324
- unkeyedByUrl.set(url, (unkeyedByUrl.get(url) ?? 0) + 1);
1325
- }
1326
- // C3. The URLs whose assignment is undetermined. Not "has duplicates" — having two requests
1327
- // to one URL is ordinary and usually harmless. It is undetermined only when two different
1328
- // assignments would produce two different replies.
1329
- const undeterminedUrls = new Set();
1330
- for (const [url, records] of queueByUrl) {
1331
- const unkeyed = unkeyedByUrl.get(url) ?? 0;
1332
- if (unkeyed <= 1)
1333
- continue;
1334
- const first = records[0];
1335
- if (first === undefined)
1336
- continue;
1337
- const alike = records.every((record) => record.status === first.status && record.outcome === first.outcome);
1338
- // Enough alike records to go round means every request gets the same one, so which is
1339
- // which never comes up. One short means at least one request is still in flight and
1340
- // nothing can say which — and "pending" on the wrong one is a false fact about a healthy
1341
- // request, which is the defect B4 fixed in its first costume.
1342
- if (alike && records.length >= unkeyed)
1343
- continue;
1344
- undeterminedUrls.add(url);
1345
- warnings.add(undeterminedJoinWarning(url, unkeyed, records));
1346
- }
1347
- const requests = [];
1348
- const consoleErrors = [];
1349
- for (const evidence of emitted) {
1350
- if (evidence.eventType === "NETWORK_REQUEST") {
1351
- const method = evidence.payload["method"];
1352
- const url = evidence.payload["url"];
1353
- if (typeof method !== "string" || typeof url !== "string")
1354
- continue;
1355
- // The keyed match wins when it exists; only a request with no record of its own draws
1356
- // from the shared URL queue, and it draws once. C3: on an undetermined URL the queue is
1357
- // not consulted at all — consuming from it is what produced the swap.
1358
- const keyed = keyedRecordFor(evidence);
1359
- const undetermined = keyed === undefined && undeterminedUrls.has(url);
1360
- const matched = keyed ?? (undetermined ? undefined : queueByUrl.get(url)?.shift());
1361
- // B4. Paired is what this request *consumed*, not what its URL was seen
1362
- // in. It used to be a set-membership test on the URL, so a second,
1363
- // genuinely in-flight request to an already-answered URL came out with
1364
- // no outcome at all — which `isFailedRequest`'s older-build fallback
1365
- // reads as a failure, because its status is null. A healthy in-flight
1366
- // request reported as a failed one. No record consumed means no terminal
1367
- // event was ever attributable to this request, and "pending" is exactly
1368
- // that fact. An unrecognised outcome on a record that *was* consumed
1369
- // still leaves the field undefined, which is the older-build case the
1370
- // fallback exists for.
1371
- // C3. `unattributed` before `pending`: a terminal event for this URL exists, so claiming
1372
- // the request is still in flight would be as wrong as handing it a sibling's status —
1373
- // and wrong in the direction that reads as healthy.
1374
- const outcome = undetermined
1375
- ? "unattributed"
1376
- : matched === undefined
1377
- ? "pending"
1378
- : matched.outcome;
1379
- requests.push({
1380
- method,
1381
- url,
1382
- status: undetermined ? null : (matched?.status ?? null),
1383
- ...(outcome === undefined ? {} : { outcome }),
1384
- stackText: renderStack(evidence.stackTrace),
1385
- stackTrace: evidence.stackTrace,
1386
- scriptUrlMapping: scriptUrlOutcomesOf(evidence.payload),
1387
- at: evidence.timestamp,
1388
- });
1389
- continue;
1390
- }
1391
- // Uncaught page error and console error both mean "something went wrong"; callers don't need to know which listener fired.
1392
- if (evidence.eventType === "EXCEPTION" || evidence.eventType === "CONSOLE_MESSAGE") {
1393
- const level = evidence.payload["level"] ?? evidence.payload["type"];
1394
- if (evidence.eventType === "CONSOLE_MESSAGE" && level !== "error")
1395
- continue;
1396
- const text = evidence.payload["text"] ?? evidence.payload["message"];
1397
- consoleErrors.push({
1398
- text: typeof text === "string" ? text : JSON.stringify(evidence.payload),
1399
- stackText: renderStack(evidence.stackTrace),
1400
- scriptUrlMapping: scriptUrlOutcomesOf(evidence.payload),
1401
- at: evidence.timestamp,
1402
- });
1403
- }
1404
- }
1405
- return { actions: [...actions], consoleErrors, requests, unsettled: [...unsettled], evidenceWarnings: [...warnings] };
1406
- }
1407
- /** B4's pair check. Returns the outcome that may be published alongside `status`, dropping it —
1408
- * and naming the drop in `warnings` — when the two cannot both be true. Deliberately one-sided:
1409
- * a status is a number the collector read off a real response object, an outcome is a label it
1410
- * derived, so when they disagree the derived half is the one that goes. `url` only names the
1411
- * request in the warning; a missing one degrades the text, never the check. */
1412
- function reconcile(status, outcome, url, warnings) {
1413
- const impossible = (outcome === "unanswered" && status !== null) || (outcome === "answered" && status === null);
1414
- if (!impossible)
1415
- return outcome;
1416
- const where = typeof url === "string" ? ` for ${url}` : "";
1417
- warnings.add(`Descry's browser collector reported a request${where} as both "${outcome}" and ` +
1418
- `status ${status === null ? "null" : String(status)}, which cannot both be true. The status is ` +
1419
- "what was read off the response, so it is kept and the outcome is dropped — this request is " +
1420
- "reported without one rather than with a contradiction. Usually a version mismatch between " +
1421
- "@descryy/mcp and @descryy/runtime-browser.");
1422
- return undefined;
1423
- }
1424
- /** Reads `outcome` off an untyped cross-package payload; validated not cast, since this build doesn't depend on `@descryy/runtime-browser`'s types — unrecognised values degrade to undefined, never a guessed literal. `"unattributed"` is deliberately not accepted here: it is this package's verdict about a join it could not make, and a collector is in no position to assert it. */
1425
- function requestOutcomeOf(payload) {
1426
- const raw = payload["outcome"];
1427
- if (raw === "answered" || raw === "unanswered" || raw === "pending")
1428
- return raw;
1429
- return undefined;
1430
- }
1431
- /** Reads the collector's transport-level exchange id, when it carries one. Validated not cast,
1432
- * and an empty string is treated as absent — an id that identifies nothing would put every
1433
- * request into one bucket, which is worse than having none. */
1434
- function exchangeIdOf(payload) {
1435
- const raw = payload["exchangeId"];
1436
- return typeof raw === "string" && raw !== "" ? raw : null;
1437
- }
1438
- /** C3's disclosure. Says what was observed and refuses to say whose it was — the terminal
1439
- * events are named in full so a real 500 is never lost, only left unassigned. */
1440
- function undeterminedJoinWarning(url, unkeyed, records) {
1441
- const describe = (record) => `${record.status === null ? "no status" : `status ${record.status}`}${record.outcome === undefined ? "" : ` (${record.outcome})`}`;
1442
- const shortfall = records.length < unkeyed
1443
- ? ` Fewer terminal events arrived than requests were issued, so at least one of them was still in flight, and which one is the same unanswerable question.`
1444
- : "";
1445
- return (`Descry saw ${unkeyed} requests to ${url} carrying no id of their own, and ${records.length} terminal ` +
1446
- `event(s) for that URL that do not all say the same thing: ${records.map(describe).join("; ")}.${shortfall} ` +
1447
- "Pairing them by arrival order would be right roughly half the time and silent when it was wrong, so none " +
1448
- "of these requests is reported with a status or an outcome. The events just listed did happen — Descry " +
1449
- "cannot say which request each one belongs to. A transport-level exchange id from the browser collector " +
1450
- "would remove this; the collector in this build does not emit one.");
1451
- }
1452
- /** Reads `scriptUrlMapping` off an untyped cross-package payload; validated not cast, so a malformed/absent field degrades to "no mapping reported" rather than a half-populated result. */
1453
- function scriptUrlOutcomesOf(payload) {
1454
- const raw = payload["scriptUrlMapping"];
1455
- if (!Array.isArray(raw))
1456
- return [];
1457
- const outcomes = [];
1458
- for (const entry of raw) {
1459
- if (typeof entry !== "object" || entry === null)
1460
- continue;
1461
- const record = entry;
1462
- const url = record["url"];
1463
- if (typeof url !== "string")
1464
- continue;
1465
- const mapped = record["mapped"];
1466
- const reason = record["reason"];
1467
- outcomes.push({
1468
- url,
1469
- mapped: typeof mapped === "string" ? mapped : null,
1470
- reason: typeof reason === "string" ? reason : null,
1471
- });
1472
- }
1473
- return outcomes;
1474
- }
1475
- /** Human-readable rendering of parsed stack frames, for the reply; the R4 write uses `stackTrace` itself, never this. Primary frame marked since it isn't always the innermost. */
1476
- function renderStack(stack) {
1477
- if (stack === null || stack.frames.length === 0)
1478
- return null;
1479
- return stack.frames
1480
- .slice(0, 5)
1481
- .map((frame, index) => {
1482
- const where = `${frame.location.file}:${frame.location.line}`;
1483
- const name = frame.location.functionName ?? "(anonymous)";
1484
- return `${index === stack.primaryFrameIndex ? "> " : " "}at ${name} (${where})`;
1485
- })
1486
- .join("\n");
1487
- }
1488
- /**
1489
- * The runtime's auth seed for this launch, or null when the caller named none.
1490
- *
1491
- * The origin comes from `targetUrl` and from nowhere else — it is the same
1492
- * value the registry records as the session's consented origin, so "the seed
1493
- * is scoped to what was consented" is a property of one derivation rather
1494
- * than of two that have to agree.
1495
- */
1496
- function runtimeAuthFor(launchOptions) {
1497
- const state = launchOptions.storageState;
1498
- const headers = launchOptions.extraHttpHeaders;
1499
- if (state === undefined && headers === undefined)
1500
- return null;
1501
- let origin;
1502
- try {
1503
- origin = new URL(launchOptions.targetUrl).origin;
1504
- }
1505
- catch {
1506
- // Unparseable targets are refused far above this — the tool cannot mint
1507
- // consent for a URL it could not read an origin from. Carrying the raw
1508
- // string keeps this total rather than throwing from inside a launch.
1509
- origin = launchOptions.targetUrl;
1510
- }
1511
- return {
1512
- origin,
1513
- ...(state?.cookies === undefined ? {} : { cookies: state.cookies }),
1514
- ...(state?.localStorage === undefined ? {} : { localStorage: state.localStorage }),
1515
- ...(headers === undefined ? {} : { extraHttpHeaders: headers }),
1516
- };
1517
- }
1518
- //# sourceMappingURL=playwright-driver.js.map