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