@wenathlan/devthink 2.0.14 → 2.0.19

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 (444) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/{dist/bridge.d.ts → bridge.d.ts} +1 -1
  3. package/bridge.d.ts.map +1 -0
  4. package/bridge.js +1877 -0
  5. package/bun.d.ts.map +1 -0
  6. package/bun.js +52329 -0
  7. package/caps/background.json +712 -0
  8. package/caps/cli.json +360 -0
  9. package/caps/library.json +3611 -0
  10. package/caps/mcp.json +126 -0
  11. package/caps/pagebridge.json +377 -0
  12. package/caps/popup.json +51 -0
  13. package/caps/sidepanel.json +522 -0
  14. package/checksums.txt +142 -0
  15. package/cli.d.ts.map +1 -0
  16. package/{dist/cli.js → cli.js} +2 -2
  17. package/companion.d.ts.map +1 -0
  18. package/companion.js +389 -0
  19. package/crossbrowser.d.ts.map +1 -0
  20. package/crossbrowser.js +1191 -0
  21. package/dashdone.d.ts.map +1 -0
  22. package/dashdone.js +368 -0
  23. package/deno.d.ts.map +1 -0
  24. package/deno.js +52325 -0
  25. package/devthink.umd.js +52346 -0
  26. package/fixtures/bun.ts +7 -0
  27. package/fixtures/cjs.js +6 -0
  28. package/fixtures/deno.ts +6 -0
  29. package/fixtures/example-org-pagestate.json +22 -0
  30. package/fixtures/importers/automa-workflow.json +18 -0
  31. package/fixtures/importers/selenium-side.json +20 -0
  32. package/fixtures/importers/tabular-plan.csv +7 -0
  33. package/fixtures/importers/uivision-macro.json +10 -0
  34. package/fixtures/importers/v1-plan.json +13 -0
  35. package/fixtures/mcp-client.mjs +14 -0
  36. package/fixtures/pages/checkout.html +192 -0
  37. package/fixtures/pages/dashboard.html +110 -0
  38. package/fixtures/pages/feed.html +113 -0
  39. package/fixtures/pages/product-grid.html +150 -0
  40. package/fixtures/plans/form-inventory-plan.json +11 -0
  41. package/fixtures/plans/release-notes-plan.json +11 -0
  42. package/fixtures/recipes/agentscompete.json +11 -0
  43. package/fixtures/recipes/agentsforms.json +11 -0
  44. package/fixtures/recipes/agentsmonitor.json +11 -0
  45. package/fixtures/recipes/agentsreview.json +29 -0
  46. package/fixtures/recipes/agentswarm.json +11 -0
  47. package/fixtures/recipes/fillcalendar.json +12 -0
  48. package/fixtures/recipes/fillcheckout.json +13 -0
  49. package/fixtures/recipes/filldependentdropdowns.json +12 -0
  50. package/fixtures/recipes/fillfromcsv.json +13 -0
  51. package/fixtures/recipes/fillloginform.json +12 -0
  52. package/fixtures/recipes/fillmultistepwizard.json +13 -0
  53. package/fixtures/recipes/fillregistration.json +12 -0
  54. package/fixtures/recipes/fillsearchfilters.json +12 -0
  55. package/fixtures/recipes/filltypeahead.json +11 -0
  56. package/fixtures/recipes/monitoralerts.json +12 -0
  57. package/fixtures/recipes/monitoravailability.json +11 -0
  58. package/fixtures/recipes/monitornetworkquiet.json +12 -0
  59. package/fixtures/recipes/monitorpagechanges.json +11 -0
  60. package/fixtures/recipes/monitorpricechange.json +11 -0
  61. package/fixtures/recipes/monitorschedule.json +12 -0
  62. package/fixtures/recipes/scrapedynamicprices.json +13 -0
  63. package/fixtures/recipes/scrapeimagegallery.json +12 -0
  64. package/fixtures/recipes/scrapeinfinitefeed.json +13 -0
  65. package/fixtures/recipes/scrapenestedlists.json +12 -0
  66. package/fixtures/recipes/scrapeproductgrid.json +12 -0
  67. package/fixtures/recipes/scrapereviews.json +12 -0
  68. package/fixtures/recipes/scrapesearchresults.json +12 -0
  69. package/fixtures/recipes/scrapetablepagination.json +12 -0
  70. package/fixtures/recipes/testaccessibility.json +34 -0
  71. package/fixtures/recipes/testconsoleerrors.json +12 -0
  72. package/fixtures/recipes/testdeeplinks.json +12 -0
  73. package/fixtures/recipes/testformvalidation.json +13 -0
  74. package/fixtures/recipes/testlayoutshift.json +12 -0
  75. package/fixtures/recipes/testlinkcheck.json +12 -0
  76. package/fixtures/recipes/testloadtiming.json +12 -0
  77. package/fixtures/recipes/testvisualdiff.json +12 -0
  78. package/fixtures/workflows/changelog-digest-workflow.json +13 -0
  79. package/fixtures/workflows/form-survey-workflow.json +13 -0
  80. package/gallery.json +706 -0
  81. package/hardening.d.ts.map +1 -0
  82. package/hardening.js +188 -0
  83. package/headless.d.ts.map +1 -0
  84. package/headless.js +11522 -0
  85. package/http.d.ts.map +1 -0
  86. package/http.js +2165 -0
  87. package/index.cjs +54682 -0
  88. package/index.d.ts.map +1 -0
  89. package/index.js +52310 -0
  90. package/index.neutral.js +46343 -0
  91. package/{web/manifest.json → manifest.json} +1 -1
  92. package/mcp.d.ts.map +1 -0
  93. package/mcp.js +20515 -0
  94. package/memory.d.ts.map +1 -0
  95. package/{memory.ts → memory.js} +2310 -3834
  96. package/nativehost.template.json +10 -0
  97. package/node.cjs +54686 -0
  98. package/node.d.ts.map +1 -0
  99. package/pack.d.ts.map +1 -0
  100. package/pack.js +805 -0
  101. package/package.json +83 -176
  102. package/policy.d.ts.map +1 -0
  103. package/{policy.ts → policy.js} +4679 -5073
  104. package/progress.d.ts.map +1 -0
  105. package/progress.js +645 -0
  106. package/protocol.d.ts.map +1 -0
  107. package/protocol.js +8334 -0
  108. package/schemas/audit.schema.json +226 -0
  109. package/schemas/capability.schema.json +113 -0
  110. package/schemas/envelope.schema.json +162 -0
  111. package/schemas/memory.schema.json +51 -0
  112. package/schemas/observation.schema.json +113 -0
  113. package/schemas/plan.schema.json +208 -0
  114. package/schemas/progress.schema.json +126 -0
  115. package/schemas/proposal.schema.json +68 -0
  116. package/schemas/session.schema.json +82 -0
  117. package/schemas/tool.schema.json +94 -0
  118. package/server.d.ts.map +1 -0
  119. package/server.js +28877 -0
  120. package/umd-example.html +18 -0
  121. package/umd.d.ts.map +1 -0
  122. package/accounts.ts +0 -2087
  123. package/agent.ts +0 -2331
  124. package/antigravity.ts +0 -2139
  125. package/apifreeze.ts +0 -4146
  126. package/auth.ts +0 -642
  127. package/background.ts +0 -16837
  128. package/bridge.ts +0 -2080
  129. package/build.ts +0 -54
  130. package/bun.ts +0 -26
  131. package/capture.ts +0 -1614
  132. package/checksecrets.ts +0 -72
  133. package/cjs.ts +0 -9
  134. package/cli.ts +0 -3500
  135. package/commands.ts +0 -1101
  136. package/companion.ts +0 -450
  137. package/compatibility.ts +0 -37
  138. package/config.ts +0 -3137
  139. package/constants.ts +0 -943
  140. package/core.ts +0 -1473
  141. package/crossbrowser.ts +0 -1359
  142. package/dashdone.ts +0 -605
  143. package/data.ts +0 -943
  144. package/database.ts +0 -133
  145. package/debug.ts +0 -850
  146. package/deno.ts +0 -27
  147. package/devthink.ts +0 -707
  148. package/dist/accounts.d.ts +0 -409
  149. package/dist/agent.d.ts +0 -837
  150. package/dist/antigravity.d.ts +0 -380
  151. package/dist/apifreeze.d.ts +0 -113
  152. package/dist/auth.d.ts +0 -224
  153. package/dist/capture.d.ts +0 -595
  154. package/dist/cjs.d.ts +0 -6
  155. package/dist/commands.d.ts +0 -310
  156. package/dist/compatibility.d.ts +0 -17
  157. package/dist/config.d.ts +0 -1853
  158. package/dist/constants.d.ts +0 -637
  159. package/dist/core.d.ts +0 -361
  160. package/dist/data.d.ts +0 -330
  161. package/dist/database.d.ts +0 -24
  162. package/dist/debug.d.ts +0 -270
  163. package/dist/engine.d.ts +0 -74
  164. package/dist/environments.d.ts +0 -244
  165. package/dist/evidence.d.ts +0 -277
  166. package/dist/export.d.ts +0 -311
  167. package/dist/fingerprint.d.ts +0 -594
  168. package/dist/flow.d.ts +0 -300
  169. package/dist/gates.d.ts +0 -103
  170. package/dist/identity.d.ts +0 -51
  171. package/dist/ids.d.ts +0 -7
  172. package/dist/llm.d.ts +0 -652
  173. package/dist/models.d.ts +0 -1807
  174. package/dist/net.d.ts +0 -325
  175. package/dist/neutral.d.ts +0 -41
  176. package/dist/oauth.d.ts +0 -703
  177. package/dist/page.d.ts +0 -1166
  178. package/dist/perf.d.ts +0 -481
  179. package/dist/plan.d.ts +0 -134
  180. package/dist/project.d.ts +0 -395
  181. package/dist/providers.d.ts +0 -37
  182. package/dist/quota.d.ts +0 -453
  183. package/dist/recovery.d.ts +0 -621
  184. package/dist/request.d.ts +0 -1492
  185. package/dist/run.d.ts +0 -779
  186. package/dist/runtime.d.ts +0 -212
  187. package/dist/search.d.ts +0 -375
  188. package/dist/security.d.ts +0 -534
  189. package/dist/serve.d.ts +0 -136
  190. package/dist/session.d.ts +0 -296
  191. package/dist/storage.d.ts +0 -53
  192. package/dist/streaming.d.ts +0 -1119
  193. package/dist/swarm.d.ts +0 -328
  194. package/dist/tools.d.ts +0 -170
  195. package/dist/types.d.ts +0 -8357
  196. package/dist/utils.d.ts +0 -82
  197. package/dist/version.d.ts +0 -7
  198. package/dist/versionregistry.d.ts +0 -406
  199. package/dist/views.d.ts +0 -579
  200. package/dist/workbenchsession.d.ts +0 -69
  201. package/dist/workflow.d.ts +0 -649
  202. package/docs/readme.md +0 -92
  203. package/docs/schemas/antigravity.json +0 -192
  204. package/engine.ts +0 -1524
  205. package/environments.ts +0 -1005
  206. package/evidence.ts +0 -643
  207. package/export.ts +0 -889
  208. package/fingerprint.ts +0 -1458
  209. package/flow.ts +0 -1190
  210. package/gates.ts +0 -307
  211. package/gateway.ts +0 -34
  212. package/hardening.ts +0 -191
  213. package/headless.ts +0 -369
  214. package/http.ts +0 -2110
  215. package/identity.ts +0 -246
  216. package/ids.ts +0 -26
  217. package/index.ts +0 -369
  218. package/llm.ts +0 -2682
  219. package/mcp.ts +0 -1835
  220. package/models.ts +0 -3805
  221. package/modes.ts +0 -43
  222. package/net.ts +0 -1214
  223. package/neutral.ts +0 -362
  224. package/node.ts +0 -26
  225. package/oauth.ts +0 -2216
  226. package/offscreen.ts +0 -170
  227. package/pack.ts +0 -956
  228. package/page.ts +0 -7824
  229. package/pagebridge.ts +0 -1116
  230. package/perf.ts +0 -957
  231. package/plan.ts +0 -672
  232. package/plugin.ts +0 -76
  233. package/prisma.config.ts +0 -27
  234. package/progress.ts +0 -1308
  235. package/project.ts +0 -1291
  236. package/protocol.ts +0 -4187
  237. package/providers.ts +0 -246
  238. package/quota.ts +0 -1887
  239. package/recovery.ts +0 -2433
  240. package/request.ts +0 -4482
  241. package/run.ts +0 -2135
  242. package/runtime.ts +0 -526
  243. package/search.ts +0 -1760
  244. package/security.ts +0 -1673
  245. package/serve.ts +0 -419
  246. package/server.ts +0 -5538
  247. package/session.ts +0 -1162
  248. package/storage.ts +0 -182
  249. package/streaming.ts +0 -4101
  250. package/swarm.ts +0 -884
  251. package/sync.ts +0 -56
  252. package/tests/agentcert.test.ts +0 -128
  253. package/tests/agentfleet.test.ts +0 -214
  254. package/tests/agentreview.test.ts +0 -241
  255. package/tests/agentstream.test.ts +0 -335
  256. package/tests/agentwork.test.ts +0 -356
  257. package/tests/apifreeze.test.ts +0 -290
  258. package/tests/apimap.test.ts +0 -112
  259. package/tests/approvalgate.test.ts +0 -175
  260. package/tests/artifactmanifest.test.ts +0 -204
  261. package/tests/attentionfeed.test.ts +0 -163
  262. package/tests/backgroundruns.test.ts +0 -100
  263. package/tests/batchscheduling.test.ts +0 -131
  264. package/tests/blackboard.test.ts +0 -238
  265. package/tests/bridge.test.ts +0 -1088
  266. package/tests/browsercoverage.test.ts +0 -134
  267. package/tests/browserpolyfills.test.ts +0 -124
  268. package/tests/build.mjs +0 -1260
  269. package/tests/clientauth.test.ts +0 -223
  270. package/tests/clitools.test.ts +0 -578
  271. package/tests/confirmgates.test.ts +0 -190
  272. package/tests/containerpack.test.ts +0 -143
  273. package/tests/controlflow.test.ts +0 -2120
  274. package/tests/coordination.test.ts +0 -345
  275. package/tests/copyscreen.test.ts +0 -139
  276. package/tests/core.test.ts +0 -417
  277. package/tests/costcert.test.ts +0 -95
  278. package/tests/crossbrowser.test.ts +0 -115
  279. package/tests/cspaudit.test.ts +0 -79
  280. package/tests/dashdone.test.ts +0 -615
  281. package/tests/datagrid.test.ts +0 -137
  282. package/tests/doccheck.test.ts +0 -107
  283. package/tests/dombatch.test.ts +0 -109
  284. package/tests/emulation.test.ts +0 -282
  285. package/tests/environments.test.ts +0 -259
  286. package/tests/evidenceviews.test.ts +0 -88
  287. package/tests/exporttools.test.ts +0 -185
  288. package/tests/firefoxprep.test.ts +0 -134
  289. package/tests/flowlibrary.test.ts +0 -242
  290. package/tests/flowrun.test.ts +0 -303
  291. package/tests/forensics.test.ts +0 -439
  292. package/tests/gateway.test.ts +0 -939
  293. package/tests/headlesslib.test.ts +0 -171
  294. package/tests/httpstream.test.ts +0 -277
  295. package/tests/immutablelog.test.ts +0 -157
  296. package/tests/inboundguard.test.ts +0 -177
  297. package/tests/lazyload.test.ts +0 -72
  298. package/tests/librarymodes.test.ts +0 -623
  299. package/tests/llm.test.ts +0 -696
  300. package/tests/manifest.test.ts +0 -226
  301. package/tests/maskinputs.test.ts +0 -150
  302. package/tests/matrixverify.test.ts +0 -111
  303. package/tests/mavenpack.test.ts +0 -75
  304. package/tests/mcpserver.test.ts +0 -704
  305. package/tests/memory.test.ts +0 -4155
  306. package/tests/memorycare.test.ts +0 -230
  307. package/tests/migrateplan.test.ts +0 -349
  308. package/tests/minimization.test.ts +0 -414
  309. package/tests/modelroute.test.ts +0 -112
  310. package/tests/multiagent.test.ts +0 -440
  311. package/tests/nativebridge.test.ts +0 -807
  312. package/tests/navigation.test.ts +0 -284
  313. package/tests/nugetpack.test.ts +0 -83
  314. package/tests/orchestration.test.ts +0 -449
  315. package/tests/originpolicy.test.ts +0 -405
  316. package/tests/outputcompare.test.ts +0 -97
  317. package/tests/package.test.ts +0 -91
  318. package/tests/pagebridge.test.ts +0 -5909
  319. package/tests/pentest.test.ts +0 -75
  320. package/tests/perfrecords.test.ts +0 -257
  321. package/tests/permdiff.test.ts +0 -85
  322. package/tests/phishguard.test.ts +0 -128
  323. package/tests/pickerviews.test.ts +0 -155
  324. package/tests/pipelines.test.ts +0 -381
  325. package/tests/planlint.test.ts +0 -240
  326. package/tests/planreview.test.ts +0 -383
  327. package/tests/platformtargets.test.ts +0 -56
  328. package/tests/policy.test.ts +0 -8284
  329. package/tests/portability.test.ts +0 -96
  330. package/tests/progress.test.ts +0 -1036
  331. package/tests/promptlibrary.test.ts +0 -116
  332. package/tests/protocol.test.ts +0 -4793
  333. package/tests/publishingpipeline.test.ts +0 -314
  334. package/tests/quickactions.test.ts +0 -164
  335. package/tests/readiness.test.ts +0 -78
  336. package/tests/recipes.test.ts +0 -163
  337. package/tests/redactshots.test.ts +0 -216
  338. package/tests/registernvidiakeys.ts +0 -51
  339. package/tests/relayserve.test.ts +0 -304
  340. package/tests/releasenotes.test.ts +0 -276
  341. package/tests/resilience.test.ts +0 -467
  342. package/tests/resourceaware.test.ts +0 -187
  343. package/tests/runbudget.test.ts +0 -125
  344. package/tests/runhistory.test.ts +0 -299
  345. package/tests/runreplay.test.ts +0 -113
  346. package/tests/runresume.test.ts +0 -184
  347. package/tests/runstate.test.ts +0 -288
  348. package/tests/runtimeadapters.test.ts +0 -251
  349. package/tests/safariskeleton.test.ts +0 -115
  350. package/tests/sandboxframe.test.ts +0 -196
  351. package/tests/sbom.test.ts +0 -105
  352. package/tests/secretvault.test.ts +0 -188
  353. package/tests/selcache.test.ts +0 -111
  354. package/tests/sessioninterface.test.ts +0 -791
  355. package/tests/sessions.test.ts +0 -591
  356. package/tests/siteprefs.test.ts +0 -228
  357. package/tests/snapshotdelta.test.ts +0 -165
  358. package/tests/statusviews.test.ts +0 -237
  359. package/tests/stepmeter.test.ts +0 -200
  360. package/tests/streamparse.test.ts +0 -163
  361. package/tests/stylepruning.test.ts +0 -90
  362. package/tests/surfaces.test.ts +0 -282
  363. package/tests/sweep.test.ts +0 -78
  364. package/tests/syncbridge.test.ts +0 -164
  365. package/tests/taskqueue.test.ts +0 -206
  366. package/tests/toolcalls.test.ts +0 -336
  367. package/tests/toolcatalog.test.ts +0 -201
  368. package/tests/torture-agent.test.ts +0 -706
  369. package/tests/torture-auth.test.ts +0 -461
  370. package/tests/torture-commands.test.ts +0 -895
  371. package/tests/torture-data.test.ts +0 -923
  372. package/tests/torture-environments.test.ts +0 -285
  373. package/tests/torture-export.test.ts +0 -263
  374. package/tests/torture-http.test.ts +0 -1379
  375. package/tests/torture-llm.test.ts +0 -808
  376. package/tests/torture-memory.test.ts +0 -1084
  377. package/tests/torture-plan.test.ts +0 -829
  378. package/tests/torture-run.test.ts +0 -626
  379. package/tests/torture-session.test.ts +0 -928
  380. package/tests/torture-swarm.test.ts +0 -1245
  381. package/tests/torture-tools.test.ts +0 -964
  382. package/tests/torture-workflow.test.ts +0 -2354
  383. package/tests/tourviews.test.ts +0 -52
  384. package/tests/transparency.test.ts +0 -147
  385. package/tests/trigger.test.ts +0 -941
  386. package/tests/v1sunset.test.ts +0 -428
  387. package/tests/virtlist.test.ts +0 -94
  388. package/tests/vision.test.ts +0 -540
  389. package/tests/vsixpack.test.ts +0 -145
  390. package/tests/webapi.test.ts +0 -523
  391. package/tests/workflow.test.ts +0 -1785
  392. package/tests/workfloweditor.test.ts +0 -759
  393. package/tests/xpipack.test.ts +0 -128
  394. package/tools.ts +0 -734
  395. package/types.ts +0 -9908
  396. package/umd.ts +0 -9
  397. package/utils.ts +0 -249
  398. package/version.ts +0 -8
  399. package/versionregistry.ts +0 -1310
  400. package/views.ts +0 -2153
  401. package/vitest.config.ts +0 -12
  402. package/web/App.tsx +0 -78
  403. package/web/README.md +0 -47
  404. package/web/capacitor.config.ts +0 -46
  405. package/web/const.ts +0 -19
  406. package/web/control.shell.tsx +0 -66
  407. package/web/dashboardpage.ts +0 -554
  408. package/web/db.ts +0 -493
  409. package/web/design.html +0 -541
  410. package/web/gatewayview/Gateway.tsx +0 -345
  411. package/web/gatewayview/config.ts +0 -1042
  412. package/web/gatewayview/definition.ts +0 -174
  413. package/web/icons.ts +0 -15
  414. package/web/optionspage.ts +0 -691
  415. package/web/popup.ts +0 -1651
  416. package/web/schema.prisma +0 -176
  417. package/web/sidepanel.ts +0 -7941
  418. package/web/sitemanifest.json +0 -13
  419. package/web/terminal-ui.tsx +0 -622
  420. package/web/transparencypage.ts +0 -541
  421. package/web/vite.config.ts +0 -39
  422. package/web/workspace.ts +0 -23
  423. package/workbenchmemory.ts +0 -104
  424. package/workbenchsession.ts +0 -304
  425. package/workflow.ts +0 -4495
  426. /package/{dist/bun.d.ts → bun.d.ts} +0 -0
  427. /package/{dist/cli.d.ts → cli.d.ts} +0 -0
  428. /package/{dist/companion.d.ts → companion.d.ts} +0 -0
  429. /package/{dist/crossbrowser.d.ts → crossbrowser.d.ts} +0 -0
  430. /package/{dist/dashdone.d.ts → dashdone.d.ts} +0 -0
  431. /package/{dist/deno.d.ts → deno.d.ts} +0 -0
  432. /package/{dist/hardening.d.ts → hardening.d.ts} +0 -0
  433. /package/{dist/headless.d.ts → headless.d.ts} +0 -0
  434. /package/{dist/http.d.ts → http.d.ts} +0 -0
  435. /package/{dist/index.d.ts → index.d.ts} +0 -0
  436. /package/{dist/mcp.d.ts → mcp.d.ts} +0 -0
  437. /package/{dist/memory.d.ts → memory.d.ts} +0 -0
  438. /package/{dist/node.d.ts → node.d.ts} +0 -0
  439. /package/{dist/pack.d.ts → pack.d.ts} +0 -0
  440. /package/{dist/policy.d.ts → policy.d.ts} +0 -0
  441. /package/{dist/progress.d.ts → progress.d.ts} +0 -0
  442. /package/{dist/protocol.d.ts → protocol.d.ts} +0 -0
  443. /package/{dist/server.d.ts → server.d.ts} +0 -0
  444. /package/{dist/umd.d.ts → umd.d.ts} +0 -0
package/pagebridge.ts DELETED
@@ -1,1116 +0,0 @@
1
- import type { observation, regionrect, resolvedtarget, toolstep } from "./types.js";
2
- import { maskformstate, maskobservation, masktypedvalues } from "./security.js";
3
- import { runpageaction, type stepresult } from "./page.js";
4
- import { runpageread } from "./page.js";
5
- import { runpagecontrol } from "./page.js";
6
- import { runinteractstep } from "./page.js";
7
- import { runpointerstep } from "./page.js";
8
- import { runpagenav } from "./page.js";
9
- import { harvestdialoglog, type observeddialog } from "./page.js";
10
- import { builda11ytree, buildpagetree, buildreader, runpageobservation } from "./page.js";
11
- import { collecttables, detectlistpatterns, collectsiblings, normalizetable, runpagedetection } from "./page.js";
12
- import { runpagewatch } from "./page.js";
13
- import { runcdpstep, rundebugwatch, flushconsole } from "./page.js";
14
- import { runprofilestep } from "./page.js";
15
- import { runemulationstep, revertemulationlayer } from "./page.js";
16
- import { runpageform } from "./page.js";
17
- import { runpagewizard } from "./page.js";
18
- import { runpagedata } from "./page.js";
19
- import { checksum } from "./data.js";
20
- import { clean, elementlabel as label, elementselector as selector, resolvestep } from "./page.js";
21
-
22
- /**
23
- * Page bridge for reviewed steps.
24
- * Correlated rules for the dispatch seam, semantic snapshots, target preview with resolution, dialog log reads and the original action set live in this file.
25
- */
26
-
27
- function stepoptions(step: toolstep): Record<string, unknown> {
28
- if (!step.options) return {};
29
- try {
30
- const parsed = JSON.parse(step.options);
31
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {};
32
- } catch {
33
- return {};
34
- }
35
- }
36
-
37
- /** Routes one reviewed step to the execution environment the policy names: dom actions that need page events keep this pagecontext bridge because page events only fire in the live page, evaluate steps run inside the isolated world the background injects through the scripting api, parse heavy read kinds may offload into the offscreen worker pool behind the granted capability and a step carrying untrusted markup renders inside the sandboxframe — this bridge itself only ever executes the pagecontext steps. */
38
- export function routesenvironment(step: toolstep): "pagecontext" | "elsewhere" {
39
- if (step.environment !== undefined && step.environment !== "pagecontext") return "elsewhere";
40
- if (step.kind === "evaluate") return "elsewhere";
41
- const options = stepoptions(step);
42
- if (typeof options.markup === "string" && options.markup.trim() !== "") return "elsewhere";
43
- return "pagecontext";
44
- }
45
-
46
- const previewid = "devthinktargetpreview";
47
-
48
- function clearpreview(): void {
49
- document.getElementById(previewid)?.remove();
50
- }
51
-
52
- /** Shows an ephemeral outline only; it neither mutates page data nor dispatches page events. */
53
- export function previewtarget(
54
- step: toolstep,
55
- expectedorigin: string,
56
- ): { ok: boolean; summary: string; resolvedtarget?: resolvedtarget; candidates?: string[] } {
57
- if (location.origin !== expectedorigin) return { ok: false, summary: "Page origin changed before preview." };
58
- clearpreview();
59
- const resolution = resolvestep(step, document);
60
- if (resolution.status === "ambiguous")
61
- return {
62
- ok: false,
63
- summary: `The reviewed ${resolution.mode} reference matched ${resolution.candidates.length} elements: ${resolution.candidates.join("; ")}.`,
64
- candidates: resolution.candidates,
65
- };
66
- if (resolution.status !== "resolved") return { ok: false, summary: "Reviewed target is no longer available." };
67
- const target = resolution.element;
68
- const rect = target.getBoundingClientRect();
69
- if (rect.width <= 0 || rect.height <= 0) return { ok: false, summary: "Reviewed target is not currently visible." };
70
- const overlay = document.createElement("div");
71
- overlay.id = previewid;
72
- overlay.setAttribute("aria-hidden", "true");
73
- Object.assign(overlay.style, {
74
- position: "fixed",
75
- left: `${Math.max(0, rect.left - 3)}px`,
76
- top: `${Math.max(0, rect.top - 3)}px`,
77
- width: `${rect.width + 6}px`,
78
- height: `${rect.height + 6}px`,
79
- border: "3px solid #2f80ed",
80
- borderRadius: "6px",
81
- boxShadow: "0 0 0 3px rgba(47,128,237,.28)",
82
- pointerEvents: "none",
83
- zIndex: "2147483647",
84
- boxSizing: "border-box",
85
- });
86
- document.documentElement.append(overlay);
87
- window.setTimeout(clearpreview, 5000);
88
- return {
89
- ok: true,
90
- summary: `Previewing ${label(target) || target.tagName.toLowerCase()} for five seconds.`,
91
- resolvedtarget: resolution.target,
92
- };
93
- }
94
-
95
- /** Captures the complete semantic page context for a user-approved active tab, including the a11y, reader, listpattern and tableshape observation sections. */
96
- export function capturesnapshot(): observation {
97
- const candidates = [
98
- ...document.querySelectorAll(
99
- "a[href], button, input, textarea, select, [role=button], [role=link], [role=combobox], [role=option], [role=checkbox], [role=radio], [role=switch], [role=tab], details, summary",
100
- ),
101
- ];
102
- const interactive = candidates
103
- .map((element) => ({
104
- selector: selector(element),
105
- role: element.getAttribute("role") || element.tagName.toLowerCase(),
106
- label: label(element),
107
- }))
108
- .filter((item) => item.label || item.role);
109
- const forms = [...document.querySelectorAll("input, textarea, select")].map((element) => ({
110
- label: label(element),
111
- type: element.getAttribute("type") || element.tagName.toLowerCase(),
112
- name: element.getAttribute("name") || "",
113
- ...(element instanceof HTMLSelectElement
114
- ? { options: [...element.options].map((option) => clean(option.textContent || option.value)) }
115
- : {}),
116
- }));
117
- const text = clean(document.body?.innerText || "");
118
- const tree = buildpagetree(document);
119
- const tables = collecttables(document).map((entry) => {
120
- const shape = normalizetable(entry.rows, entry.caption);
121
- return {
122
- selector: entry.selector,
123
- headers: shape.headers,
124
- columns: shape.columns,
125
- rows: shape.rows,
126
- caption: shape.caption,
127
- };
128
- });
129
- return {
130
- schemaversion: 3,
131
- url: location.href,
132
- title: clean(document.title),
133
- textpreview: text,
134
- textlength: document.body?.innerText.length ?? 0,
135
- forms,
136
- interactive,
137
- capturedat: Date.now(),
138
- mode: "passive",
139
- digest: pagedigest(),
140
- a11y: builda11ytree(tree),
141
- reader: buildreader(tree, clean(document.title)),
142
- listpattern: detectlistpatterns(collectsiblings(document)),
143
- tableshape: tables,
144
- };
145
- }
146
-
147
- /** Reads and clears the dialog log the main world handler recorded on the shared document. */
148
- export function readdialogs(): observeddialog[] {
149
- return harvestdialoglog(document);
150
- }
151
-
152
- /** Extracts full, read-only structured content for a reviewed extraction step. */
153
- function extractcontent(targetselector: string | undefined, root: Document): stepresult {
154
- if (!targetselector) {
155
- const links = [...root.querySelectorAll("a[href]")].map((element) => {
156
- const href = element instanceof HTMLAnchorElement ? (element.getAttribute("href") ?? "") : "";
157
- return { text: element.textContent?.trim() ?? "", href };
158
- });
159
- return { ok: true, summary: `Extracted ${links.length} link entries.`, details: { links } };
160
- }
161
- const target = root.querySelector(targetselector);
162
- if (!target) return { ok: false, summary: "Extraction target is no longer available." };
163
- const text = target.textContent ?? "";
164
- return { ok: true, summary: `Extracted ${text.length} characters of content.`, details: { text } };
165
- }
166
-
167
- /** Scrolls one reviewed target into view without reading or changing other page state. */
168
- function scrolltarget(target: HTMLElement): stepresult {
169
- target.scrollIntoView({ block: "center", inline: "nearest", behavior: "auto" });
170
- return { ok: true, summary: `Scrolled ${label(target) || target.tagName.toLowerCase()} into view.` };
171
- }
172
-
173
- /** Dispatches hover events on one reviewed target. */
174
- function hovertarget(target: HTMLElement): stepresult {
175
- for (const type of ["pointerover", "mouseover", "pointerenter"] as const) {
176
- target.dispatchEvent(
177
- new PointerEvent(type, { bubbles: type !== "pointerenter", cancelable: true, composed: true }),
178
- );
179
- }
180
- target.dispatchEvent(new MouseEvent("mouseenter", { bubbles: false, cancelable: true }));
181
- return { ok: true, summary: `Hover events delivered to ${label(target) || target.tagName.toLowerCase()}.` };
182
- }
183
-
184
- /** Selects one reviewed existing option; values outside the declared options are refused. */
185
- function selectoption(target: HTMLElement, value: string): stepresult {
186
- if (!(target instanceof HTMLSelectElement)) return { ok: false, summary: "Target is not a select element." };
187
- const option = [...target.options].find(
188
- (candidate) => candidate.value === value || candidate.textContent?.trim() === value,
189
- );
190
- if (!option) return { ok: false, summary: "Reviewed option is not part of the select element." };
191
- target.value = option.value;
192
- target.dispatchEvent(new Event("input", { bubbles: true }));
193
- target.dispatchEvent(new Event("change", { bubbles: true }));
194
- return { ok: true, summary: `Selected ${clean(option.textContent || option.value)}.` };
195
- }
196
-
197
- const readkinds: ReadonlySet<string> = new Set([
198
- "readattribute",
199
- "readstyle",
200
- "readgeometry",
201
- "readvalue",
202
- "readtext",
203
- "readhtml",
204
- "countelements",
205
- "readtable",
206
- "readlinks",
207
- "readimages",
208
- "readmeta",
209
- "readforms",
210
- "readstorage",
211
- "waitfor",
212
- "waittext",
213
- "highlight",
214
- "mapclicks",
215
- "verifyvisible",
216
- "verifyenabled",
217
- "resolvexpath",
218
- ]);
219
- const mutatingkinds: ReadonlySet<string> = new Set([
220
- "presskey",
221
- "clickdeep",
222
- "rightclick",
223
- "doubleclick",
224
- "drag",
225
- "drop",
226
- "upload",
227
- "clear",
228
- "check",
229
- "uncheck",
230
- "toggle",
231
- "submit",
232
- "setattribute",
233
- "removeattribute",
234
- "writestorage",
235
- "evaluate",
236
- "fullscreen",
237
- ]);
238
- const controlkinds: ReadonlySet<string> = new Set([
239
- "typetime",
240
- "appendtext",
241
- "setvalue",
242
- "typeedit",
243
- "keyhold",
244
- "keyrelease",
245
- "submitsearch",
246
- "selectmulti",
247
- "chooseradio",
248
- "setslider",
249
- "setdate",
250
- "setcolor",
251
- "expanddetails",
252
- ]);
253
- const interactkinds: ReadonlySet<string> = new Set([
254
- "clicktext",
255
- "clickaria",
256
- "clickname",
257
- "pierceshadow",
258
- "enterframe",
259
- ]);
260
- const pointerkinds: ReadonlySet<string> = new Set(["movepointer", "clickpoint", "shiftclick"]);
261
- const observationkinds: ReadonlySet<string> = new Set([
262
- "a11ytree",
263
- "readvisible",
264
- "readertree",
265
- "readoutline",
266
- "readselection",
267
- "readopengraph",
268
- "readlang",
269
- "detectlanguage",
270
- "listshadow",
271
- "listframes",
272
- ]);
273
- const detectionkinds: ReadonlySet<string> = new Set([
274
- "detectlists",
275
- "detecttables",
276
- "detectinfinitescroll",
277
- "detectvirtual",
278
- "detectlazy",
279
- "detectsticky",
280
- "detectscrolllock",
281
- "countpages",
282
- "classifypage",
283
- "fingerprintsection",
284
- "readscrollpos",
285
- ]);
286
- const watchstepkinds: ReadonlySet<string> = new Set([
287
- "watchmutate",
288
- "watchbanner",
289
- "watchfocus",
290
- "waitquiet",
291
- "readjson",
292
- "diffsnapshots",
293
- "deriveselector",
294
- ]);
295
- const debugstepkinds: ReadonlySet<string> = new Set(["watchconsole", "watcherrors", "watchtasks"]);
296
- const cdpstepkinds: ReadonlySet<string> = new Set([
297
- "attachcdp",
298
- "detachcdp",
299
- "cdpcmd",
300
- "watchcdp",
301
- "setbreakpoint",
302
- "stepcode",
303
- "watchexpr",
304
- "overridescript",
305
- ]);
306
- const profilestepkinds: ReadonlySet<string> = new Set([
307
- "measureflow",
308
- "heapshot",
309
- "trackmemory",
310
- "profilecpu",
311
- "watchshifts",
312
- "traceload",
313
- "capturesourcemaps",
314
- ]);
315
- const emulationstepkinds: ReadonlySet<string> = new Set([
316
- "emulatedevice",
317
- "emulatenetwork",
318
- "emulatelocate",
319
- "setuseragent",
320
- "overridepermission",
321
- "blackboxscripts",
322
- ]);
323
- const navstepkinds: ReadonlySet<string> = new Set([
324
- "waitload",
325
- "waiturl",
326
- "followlink",
327
- "spanav",
328
- "spawait",
329
- "rewritequery",
330
- "setfragment",
331
- "stopnav",
332
- "prefetch",
333
- "preconnect",
334
- "printpdf",
335
- ]);
336
- const formkinds: ReadonlySet<string> = new Set([
337
- "fillform",
338
- "filllabel",
339
- "fillplaceholder",
340
- "detectfields",
341
- "generatevalues",
342
- "readerrors",
343
- "skiphoneypot",
344
- "detectlogin",
345
- "detecttemplate",
346
- "handoffcaptcha",
347
- "asksubmit",
348
- "submitform",
349
- "consentpassword",
350
- "attachfile",
351
- ]);
352
- const wizardkinds: ReadonlySet<string> = new Set([
353
- "runwizard",
354
- "selectchain",
355
- "picktypeahead",
356
- "pickdate",
357
- "fillcard",
358
- "fillcode",
359
- ]);
360
- const datastepkinds: ReadonlySet<string> = new Set(["scrapetable", "paginateextract"]);
361
-
362
- /** Derives the deterministic page digest of one page state from its url, title and text length; checkpoints compare their digest against it before a resume continues. */
363
- export function digestof(input: { url: string; title: string; textlength: number }): string {
364
- return `page:${checksum(`${input.url}|${input.title}|${input.textlength}`)}`;
365
- }
366
-
367
- /** Reports the final url of the live page after its redirect chain so the urlhistory records the url the run landed on. */
368
- export function pagefinalurl(): string {
369
- return location.href;
370
- }
371
-
372
- /** Reads the digest of the live page so a checkpoint revalidates the page it captured. */
373
- export function pagedigest(): string {
374
- return digestof({
375
- url: location.href,
376
- title: clean(document.title),
377
- textlength: document.body?.innerText.length ?? 0,
378
- });
379
- }
380
-
381
- /** The idempotencykeys this page context already executed; a replay of the same key skips the duplicate. */
382
- const executedstepkeys = new Set<string>();
383
-
384
- /** Reads the idempotency verdict of one step: a key the page context already executed skips while an unknown or absent key runs. */
385
- export function idempotencyverdict(
386
- step: toolstep,
387
- executedkeys: ReadonlySet<string>,
388
- ): { skip: boolean; summary: string } {
389
- if (step.idempotencykey === undefined || step.idempotencykey.trim() === "")
390
- return { skip: false, summary: "The step carries no idempotencykey; it runs exactly as reviewed." };
391
- if (executedkeys.has(step.idempotencykey))
392
- return {
393
- skip: true,
394
- summary: `The idempotencykey ${step.idempotencykey} already executed in this page context; the replay skips the duplicate so the side effect never repeats.`,
395
- };
396
- return {
397
- skip: false,
398
- summary: `The idempotencykey ${step.idempotencykey} is unknown to this page context; the step runs exactly once.`,
399
- };
400
- }
401
-
402
- /** Clears the executed idempotencykeys of the page context; a fresh run starts with an empty key set. */
403
- export function resetidempotency(): void {
404
- executedstepkeys.clear();
405
- }
406
-
407
- /** Builds the checkpoint payload after one successfully executed sensitive step: the run, the step boundary, the completed steps and the digest of the page it captured. */
408
- export function checkpointafterstep(input: {
409
- step: toolstep;
410
- runid: string;
411
- completed: string[];
412
- }): { runid: string; stepid: string; completed: string[]; digest: string } | undefined {
413
- if (input.step.risk !== "sensitive") return undefined;
414
- return { runid: input.runid, stepid: input.step.id, completed: [...input.completed], digest: pagedigest() };
415
- }
416
-
417
- /** Performs one local action after the background policy gate and a fresh target resolution; the 1.1.70 family executes steps keyed by their idempotencykey so replays stay deduplicated. */
418
- export async function performstep(
419
- step: toolstep,
420
- expectedorigin: string,
421
- rootdocument: Document = document,
422
- ): Promise<stepresult> {
423
- if (location.origin !== expectedorigin) return { ok: false, summary: "Page origin changed before action." };
424
- const idempotency = idempotencyverdict(step, executedstepkeys);
425
- if (idempotency.skip)
426
- return { ok: true, summary: idempotency.summary, details: { replay: true, idempotencykey: step.idempotencykey } };
427
- const output = await performstepinner(step, expectedorigin, rootdocument);
428
- if (output.ok && step.idempotencykey !== undefined && step.idempotencykey.trim() !== "")
429
- executedstepkeys.add(step.idempotencykey);
430
- return output;
431
- }
432
-
433
- /** Runs the reviewed dispatch of one step inside the page after the idempotency gate cleared it. */
434
- async function performstepinner(step: toolstep, expectedorigin: string, rootdocument: Document): Promise<stepresult> {
435
- if (step.kind === "observe") return { ok: true, summary: "Observation completed." };
436
- if (step.kind === "wait") {
437
- const requested = step.value ? Number.parseInt(step.value, 10) : 250;
438
- const duration = Number.isFinite(requested) && requested > 0 ? requested : 0;
439
- return new Promise((resolve) =>
440
- window.setTimeout(
441
- () => resolve({ ok: true, summary: `Reviewed wait of ${duration} milliseconds completed.` }),
442
- duration,
443
- ),
444
- );
445
- }
446
- if (step.kind === "extract") return extractcontent(step.target, rootdocument);
447
- if (step.kind === "navigate") {
448
- if (!step.value || new URL(step.value).origin !== expectedorigin)
449
- return { ok: false, summary: "Navigation target is outside the approved origin." };
450
- location.assign(step.value);
451
- return { ok: true, summary: "Navigation request sent." };
452
- }
453
- if (step.kind === "reload") {
454
- location.reload();
455
- return { ok: true, summary: "Page reload requested." };
456
- }
457
- if (step.kind === "back") {
458
- history.back();
459
- return { ok: true, summary: "History back requested." };
460
- }
461
- if (step.kind === "forward") {
462
- history.forward();
463
- return { ok: true, summary: "History forward requested." };
464
- }
465
- if (navstepkinds.has(step.kind)) return await runpagenav(step, rootdocument);
466
- if (step.kind === "writeclipboard") {
467
- const text = step.value ?? "";
468
- await navigator.clipboard.writeText(text);
469
- return {
470
- ok: true,
471
- summary: `Wrote ${text.length} reviewed character${text.length === 1 ? "" : "s"} to the clipboard with payload hash ${checksum(text)}.`,
472
- details: { length: text.length, hash: checksum(text), destination: "clipboard" },
473
- };
474
- }
475
- if (step.kind === "scrollpage") {
476
- const options = stepoptions(step);
477
- window.scrollBy({
478
- left: typeof options.x === "number" ? options.x : 0,
479
- top: typeof options.y === "number" ? options.y : 600,
480
- behavior: "auto",
481
- });
482
- return { ok: true, summary: "Window scrolled by the reviewed amounts." };
483
- }
484
- if (step.kind === "scrollend") {
485
- window.scrollTo(0, document.documentElement.scrollHeight);
486
- return { ok: true, summary: "Window scrolled to the page end." };
487
- }
488
- if (step.kind === "scrolltop") {
489
- window.scrollTo(0, 0);
490
- return { ok: true, summary: "Window scrolled to the page top." };
491
- }
492
- const resolution = resolvestep(step, rootdocument);
493
- if (resolution.status === "ambiguous") {
494
- return {
495
- ok: false,
496
- summary: `The reviewed ${resolution.mode} reference matched ${resolution.candidates.length} elements: ${resolution.candidates.join("; ")}.`,
497
- details: { mode: resolution.mode, candidates: resolution.candidates },
498
- };
499
- }
500
- const element = resolution.status === "resolved" ? resolution.element : null;
501
- let result: stepresult | Promise<stepresult>;
502
- if (formkinds.has(step.kind)) result = runpageform(step, element, rootdocument);
503
- else if (wizardkinds.has(step.kind)) result = runpagewizard(step, element, rootdocument);
504
- else if (datastepkinds.has(step.kind)) result = runpagedata(step, element, rootdocument);
505
- else if (readkinds.has(step.kind)) result = runpageread(step, element, rootdocument);
506
- else if (controlkinds.has(step.kind)) result = runpagecontrol(step, element, rootdocument);
507
- else if (interactkinds.has(step.kind)) return await runinteractstep(step, expectedorigin, performstep);
508
- else if (pointerkinds.has(step.kind)) result = runpointerstep(step, resolution);
509
- else if (observationkinds.has(step.kind)) result = runpageobservation(step, element, rootdocument);
510
- else if (detectionkinds.has(step.kind)) result = runpagedetection(step, element, rootdocument);
511
- else if (watchstepkinds.has(step.kind)) result = runpagewatch(step, element, rootdocument);
512
- else if (debugstepkinds.has(step.kind)) return await rundebugwatch(step);
513
- else if (cdpstepkinds.has(step.kind)) return await runcdpstep(step);
514
- else if (profilestepkinds.has(step.kind)) return await runprofilestep(step);
515
- else if (emulationstepkinds.has(step.kind)) return await runemulationstep(step);
516
- else if (mutatingkinds.has(step.kind)) result = runpageaction(step, element);
517
- else {
518
- if (!element) return { ok: false, summary: "Action target is no longer available." };
519
- if (step.kind === "scrollby") {
520
- const options = stepoptions(step);
521
- element.scrollBy({
522
- left: typeof options.x === "number" ? options.x : 0,
523
- top: typeof options.y === "number" ? options.y : 600,
524
- behavior: "auto",
525
- });
526
- result = { ok: true, summary: "Container scrolled by the reviewed amounts." };
527
- } else if (step.kind === "focus") {
528
- element.focus();
529
- result = { ok: true, summary: "Target focused." };
530
- } else if (step.kind === "inspect")
531
- result = { ok: true, summary: `Target: ${label(element) || element.tagName.toLowerCase()}.` };
532
- else if (step.kind === "click") {
533
- element.click();
534
- result = { ok: true, summary: "Reviewed click completed." };
535
- } else if (step.kind === "scroll") result = scrolltarget(element);
536
- else if (step.kind === "hover") result = hovertarget(element);
537
- else if (step.kind === "select") result = selectoption(element, step.value ?? "");
538
- else if (step.kind === "type") {
539
- if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement))
540
- return { ok: false, summary: "Target cannot receive text." };
541
- if (typeof step.value !== "string") return { ok: false, summary: "Approved text is absent." };
542
- element.focus();
543
- element.value = step.value;
544
- element.dispatchEvent(new Event("input", { bubbles: true }));
545
- element.dispatchEvent(new Event("change", { bubbles: true }));
546
- result = { ok: true, summary: "Approved text entered." };
547
- } else return { ok: false, summary: "Unsupported action." };
548
- }
549
- const output = await result;
550
- if (resolution.status === "resolved") {
551
- return {
552
- ...output,
553
- details: { ...(output.details ?? {}), mode: resolution.target.mode, resolvedtarget: resolution.target },
554
- };
555
- }
556
- return output;
557
- }
558
-
559
- /** Measures the full scroll width and height before any stitching begins, beside the viewport geometry, the device pixel ratio and the current scroll position. */
560
- export function measurepage(): {
561
- scrollwidth: number;
562
- scrollheight: number;
563
- viewportwidth: number;
564
- viewportheight: number;
565
- pixelratio: number;
566
- scrollx: number;
567
- scrolly: number;
568
- } {
569
- const root = document.documentElement;
570
- return {
571
- scrollwidth: Math.max(root.scrollWidth, document.body?.scrollWidth ?? 0),
572
- scrollheight: Math.max(root.scrollHeight, document.body?.scrollHeight ?? 0),
573
- viewportwidth: window.innerWidth,
574
- viewportheight: window.innerHeight,
575
- pixelratio: window.devicePixelRatio || 1,
576
- scrollx: window.scrollX,
577
- scrolly: window.scrollY,
578
- };
579
- }
580
-
581
- /** Returns the pixel ratio scaled rect of one target element with the viewport crossing flag for the tiled fallback. */
582
- export function elementrect(selector: string): {
583
- ok: boolean;
584
- rect?: regionrect;
585
- pixelratio?: number;
586
- crossesviewport?: boolean;
587
- summary: string;
588
- } {
589
- const target = document.querySelector(selector);
590
- if (!target) return { ok: false, summary: "The reviewed capture element is no longer available." };
591
- const bounds = target.getBoundingClientRect();
592
- if (bounds.width <= 0 || bounds.height <= 0)
593
- return { ok: false, summary: "The reviewed capture element is not currently visible." };
594
- const viewport = { width: window.innerWidth, height: window.innerHeight };
595
- const rect: regionrect = {
596
- x: Math.round(bounds.left + window.scrollX),
597
- y: Math.round(bounds.top + window.scrollY),
598
- width: Math.round(bounds.width),
599
- height: Math.round(bounds.height),
600
- };
601
- const viewrect: regionrect = { x: bounds.left, y: bounds.top, width: bounds.width, height: bounds.height };
602
- return {
603
- ok: true,
604
- rect,
605
- pixelratio: window.devicePixelRatio || 1,
606
- crossesviewport:
607
- viewrect.x < 0 ||
608
- viewrect.y < 0 ||
609
- viewrect.x + viewrect.width > viewport.width ||
610
- viewrect.y + viewrect.height > viewport.height,
611
- summary: `Measured the capture element at ${rect.width} by ${rect.height} css pixels.`,
612
- };
613
- }
614
-
615
- /** Resolves the unique element references of one selector for the foreach executor of the workflow control engine: every matched element reports its stable selector without mutating the page. */
616
- export function queryelements(query: string): { ok: boolean; selectors: string[]; summary: string } {
617
- const matched = [...document.querySelectorAll(query)];
618
- if (matched.length === 0)
619
- return { ok: true, selectors: [], summary: `The reviewed selector ${query} matched no element.` };
620
- return {
621
- ok: true,
622
- selectors: matched.map((element) => selector(element)),
623
- summary: `Resolved ${matched.length} element reference${matched.length === 1 ? "" : "s"} of the reviewed selector ${query}.`,
624
- };
625
- }
626
-
627
- const scrollbarstyleid = "devthinkcapturehider";
628
-
629
- /** Prepares the page for capture: hides the capture scrollbars through a scoped style rule and returns the original scroll position. */
630
- export function preparecapture(): { scrollx: number; scrolly: number } {
631
- if (!document.getElementById(scrollbarstyleid)) {
632
- const style = document.createElement("style");
633
- style.id = scrollbarstyleid;
634
- style.setAttribute("aria-hidden", "true");
635
- style.textContent =
636
- "html::-webkit-scrollbar,body::-webkit-scrollbar{display:none!important}html{scrollbar-width:none!important}";
637
- document.documentElement.append(style);
638
- }
639
- return { scrollx: window.scrollX, scrolly: window.scrollY };
640
- }
641
-
642
- /** Scrolls the page to one reviewed tile offset and reports the settled position. */
643
- export function scrollcapture(x: number, y: number): { x: number; y: number } {
644
- window.scrollTo(x, y);
645
- return { x: window.scrollX, y: window.scrollY };
646
- }
647
-
648
- /** Restores the original scroll position after the last tile and removes the capture scrollbar hider. */
649
- export function restorecapture(state: { scrollx: number; scrolly: number }): void {
650
- document.getElementById(scrollbarstyleid)?.remove();
651
- window.scrollTo(state.scrollx, state.scrolly);
652
- }
653
-
654
- /** Scrolls one scrollable container to a reviewed step top and reports its geometry. */
655
- export function scrollcontainercapture(
656
- selector: string,
657
- top: number,
658
- ): { ok: boolean; top?: number; height?: number; viewportheight?: number; summary: string } {
659
- const container = document.querySelector(selector);
660
- if (!container) return { ok: false, summary: "The reviewed scrollable container is no longer available." };
661
- if (!(container instanceof HTMLElement))
662
- return { ok: false, summary: "The reviewed scrollable container cannot scroll." };
663
- container.scrollTo({ top, behavior: "auto" });
664
- return {
665
- ok: true,
666
- top: container.scrollTop,
667
- height: container.scrollHeight,
668
- viewportheight: container.clientHeight,
669
- summary: `Scrolled the container to ${container.scrollTop} of ${container.scrollHeight} pixels.`,
670
- };
671
- }
672
-
673
- /** Waits the reviewed settle time between capture tiles. */
674
- export function waitsettle(milliseconds: number): Promise<void> {
675
- return new Promise((resolve) => window.setTimeout(resolve, Math.max(0, milliseconds)));
676
- }
677
-
678
- /** Collects the visible text of one pdf report segment: every block element whose bounds intersect the reviewed vertical range contributes its text, so paginated reports split at reviewed break points. */
679
- export function pdfsegment(top: number, height: number): { ok: boolean; text: string; summary: string } {
680
- const blocks = [
681
- ...document.querySelectorAll("h1,h2,h3,h4,h5,h6,p,li,td,th,blockquote,pre,figcaption,section,article > div"),
682
- ];
683
- const lines: string[] = [];
684
- for (const block of blocks) {
685
- const bounds = block.getBoundingClientRect();
686
- const blocktop = bounds.top + window.scrollY;
687
- const blockbottom = blocktop + bounds.height;
688
- if (blockbottom <= top || blocktop >= top + height) continue;
689
- const text = clean(block.textContent ?? "");
690
- if (text) lines.push(text);
691
- }
692
- const text = lines.join("\n");
693
- return {
694
- ok: true,
695
- text,
696
- summary: `Collected ${text.length} characters of the report segment at ${Math.round(top)} to ${Math.round(top + height)} pixels.`,
697
- };
698
- }
699
-
700
- /** Resolves the scroll tops of the reviewed pdf break point selectors for paginated reports. */
701
- export function pdfbreaks(selectors: string[]): Array<{ selector: string; top: number }> {
702
- const resolved: Array<{ selector: string; top: number }> = [];
703
- for (const selector of selectors) {
704
- const target = document.querySelector(selector);
705
- if (!target) continue;
706
- resolved.push({ selector, top: Math.round(target.getBoundingClientRect().top + window.scrollY) });
707
- }
708
- return resolved;
709
- }
710
-
711
- /** Grabs one still frame of a video element at the reviewed timestamp: the video seeks, pauses and draws to a canvas that encodes as an image; cross origin videos without cors refuse the draw honestly. */
712
- export async function videoframe(
713
- selector: string,
714
- timestamp: number | undefined,
715
- poster: boolean,
716
- ): Promise<{ ok: boolean; dataurl?: string; width?: number; height?: number; summary: string }> {
717
- const target = document.querySelector(selector);
718
- if (!(target instanceof HTMLVideoElement))
719
- return { ok: false, summary: "The reviewed frame source is not a video element." };
720
- target.pause();
721
- if (
722
- typeof timestamp === "number" &&
723
- Number.isFinite(timestamp) &&
724
- timestamp >= 0 &&
725
- timestamp <= (target.duration || 0)
726
- ) {
727
- await new Promise<void>((resolve) => {
728
- const done = (): void => resolve();
729
- target.addEventListener("seeked", done, { once: true });
730
- target.currentTime = timestamp;
731
- window.setTimeout(done, 1500);
732
- });
733
- }
734
- const width = target.videoWidth || target.clientWidth || 1;
735
- const height = target.videoHeight || target.clientHeight || 1;
736
- try {
737
- const canvas = document.createElement("canvas");
738
- canvas.width = width;
739
- canvas.height = height;
740
- const context = canvas.getContext("2d");
741
- if (!context) return { ok: false, summary: "The frame grab could not create a canvas context." };
742
- context.drawImage(target, 0, 0, width, height);
743
- const dataurl = canvas.toDataURL("image/png");
744
- return {
745
- ok: dataurl.length > 100,
746
- dataurl,
747
- width,
748
- height,
749
- summary: `Grabbed the video frame at ${target.currentTime.toFixed(2)} seconds of ${width} by ${height} pixels${poster ? " as the poster frame" : ""}.`,
750
- };
751
- } catch {
752
- return {
753
- ok: false,
754
- summary: "The video frame draw was refused; cross origin videos need cors headers before frames can be read.",
755
- };
756
- }
757
- }
758
-
759
- /** Reads the playback state of one video element of the 1.1.77 family: the paused flag with the playback position and duration in milliseconds, so the frameocr pass refuses a playing video before any frame leaves the device; the read never pauses or seeks anything. */
760
- export function videostate(selector: string): {
761
- ok: boolean;
762
- paused: boolean;
763
- positionms: number;
764
- durationms: number;
765
- summary: string;
766
- } {
767
- const target = document.querySelector(selector);
768
- if (!(target instanceof HTMLVideoElement))
769
- return {
770
- ok: false,
771
- paused: false,
772
- positionms: 0,
773
- durationms: 0,
774
- summary: "The reviewed frame source is not a video element.",
775
- };
776
- return {
777
- ok: true,
778
- paused: target.paused,
779
- positionms: Math.round(target.currentTime * 1000),
780
- durationms: Number.isFinite(target.duration) ? Math.round(target.duration * 1000) : 0,
781
- summary: `The video ${selector} sits ${target.paused ? "paused" : "playing"} at ${(target.currentTime * 1000).toFixed(0)} milliseconds of ${(Number.isFinite(target.duration) ? target.duration * 1000 : 0).toFixed(0)} milliseconds.`,
782
- };
783
- }
784
-
785
- /** Reads the content of one canvas element: plain 2d canvases return their buffer directly and webgl canvases request the buffer through a preserved read; tainted canvases refuse honestly. */
786
- export function canvasdata(selector: string): {
787
- ok: boolean;
788
- context: "2d" | "webgl";
789
- dataurl?: string;
790
- width?: number;
791
- height?: number;
792
- preserved?: boolean;
793
- summary: string;
794
- } {
795
- const target = document.querySelector(selector);
796
- if (!(target instanceof HTMLCanvasElement))
797
- return { ok: false, context: "2d", summary: "The reviewed canvas element is no longer available." };
798
- const width = target.width || 1;
799
- const height = target.height || 1;
800
- const kind: "2d" | "webgl" = target.getContext("2d") ? "2d" : "webgl";
801
- try {
802
- let dataurl = "";
803
- let preserved = false;
804
- if (kind === "2d") {
805
- dataurl = target.toDataURL("image/png");
806
- } else {
807
- dataurl = target.toDataURL("image/png");
808
- if (dataurl.length <= 100) {
809
- const gl = (target.getContext("webgl") ??
810
- target.getContext("experimental-webgl")) as WebGLRenderingContext | null;
811
- if (gl) {
812
- const pixels = new Uint8Array(width * height * 4);
813
- gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
814
- const canvas = document.createElement("canvas");
815
- canvas.width = width;
816
- canvas.height = height;
817
- const context = canvas.getContext("2d");
818
- if (context) {
819
- const image = context.createImageData(width, height);
820
- for (let row = 0; row < height; row += 1) {
821
- const source = (height - 1 - row) * width * 4;
822
- const destination = row * width * 4;
823
- image.data.set(pixels.subarray(source, source + width * 4), destination);
824
- }
825
- context.putImageData(image, 0, 0);
826
- dataurl = canvas.toDataURL("image/png");
827
- preserved = true;
828
- }
829
- }
830
- }
831
- }
832
- return {
833
- ok: dataurl.length > 100,
834
- context: kind,
835
- dataurl,
836
- width,
837
- height,
838
- preserved,
839
- summary: `Read the ${kind} canvas buffer of ${width} by ${height} pixels${kind === "webgl" ? (preserved ? " through a preserved readPixels pass" : " through the preserved drawing buffer") : ""}.`,
840
- };
841
- } catch {
842
- return {
843
- ok: false,
844
- context: kind,
845
- summary: "The canvas read was refused; tainted canvas content needs cross origin resources with cors headers.",
846
- };
847
- }
848
- }
849
-
850
- /** Probes the media streams of the page: every video and audio element with a srcObject reports its track kinds, labels, settings and live states; peer connection statistics stay outside the isolated world bridge. */
851
- export function streamelements(selector?: string): Array<Record<string, unknown>> {
852
- const root = selector ? document.querySelector(selector) : document;
853
- if (!root) return [];
854
- const entries: Array<Record<string, unknown>> = [];
855
- for (const element of [...root.querySelectorAll("video, audio")]) {
856
- const media = element instanceof HTMLMediaElement ? element : null;
857
- if (!media) continue;
858
- const stream = media.srcObject;
859
- if (!(stream instanceof MediaStream)) continue;
860
- entries.push({
861
- kind: "webrtc",
862
- label: stream.id,
863
- live: stream.active,
864
- tracks: stream.getTracks().map((track) => ({
865
- kind: track.kind,
866
- label: track.label,
867
- state: track.readyState,
868
- ...(track.kind === "video"
869
- ? {
870
- width: track.getSettings().width,
871
- height: track.getSettings().height,
872
- framerate: track.getSettings().frameRate,
873
- }
874
- : {}),
875
- })),
876
- });
877
- }
878
- return entries;
879
- }
880
-
881
- /** Reads the embedded video and audio sources of the page with their formats, durations, dimensions, codecs and track lists. */
882
- export function mediaelements(): Array<Record<string, unknown>> {
883
- const entries: Array<Record<string, unknown>> = [];
884
- for (const element of [...document.querySelectorAll("video, audio")]) {
885
- if (!(element instanceof HTMLMediaElement)) continue;
886
- const source = element.querySelector("source");
887
- const url = element.currentSrc || element.src || (source instanceof HTMLSourceElement ? source.src : "") || "";
888
- if (!url) continue;
889
- const type = (source instanceof HTMLSourceElement ? source.type : "") || "";
890
- const codecs = type.includes("codecs=")
891
- ? type.slice(type.indexOf("codecs=") + "codecs=".length).replace(/["']/g, "")
892
- : "";
893
- entries.push({
894
- url,
895
- mime: type.split(";")[0] || "",
896
- duration: Number.isFinite(element.duration) ? element.duration : 0,
897
- width: element instanceof HTMLVideoElement ? element.videoWidth : 0,
898
- height: element instanceof HTMLVideoElement ? element.videoHeight : 0,
899
- codecs,
900
- tracks: [...element.textTracks].map((track) => track.label || track.kind).filter(Boolean),
901
- });
902
- }
903
- return entries;
904
- }
905
-
906
- /** Collects the page assets: declared favicons and apple touch icons with their sizes, manifest declared icons and logo candidates from meta images and header imagery. */
907
- export async function pageassets(): Promise<Array<Record<string, unknown>>> {
908
- const entries: Array<Record<string, unknown>> = [];
909
- for (const link of [...document.querySelectorAll("link[rel]")]) {
910
- const rel = (link.getAttribute("rel") ?? "").toLowerCase();
911
- const href = link.getAttribute("href");
912
- if (!href || !(rel.includes("icon") || rel.includes("apple-touch"))) continue;
913
- const resolved = new URL(href, location.href).toString();
914
- entries.push({ kind: "favicon", url: resolved, bytes: 0, sizes: link.getAttribute("sizes") ?? "any" });
915
- }
916
- const og = document.querySelector('meta[property="og:image"]');
917
- if (og instanceof HTMLMetaElement && og.content)
918
- entries.push({ kind: "logo", url: new URL(og.content, location.href).toString(), bytes: 0, sizes: "og" });
919
- for (const image of [...document.querySelectorAll("header img, nav img, img[alt*=logo i], img[src*=logo i]")]) {
920
- const url = image instanceof HTMLImageElement ? image.currentSrc || image.src : "";
921
- if (!url) continue;
922
- entries.push({
923
- kind: "logo",
924
- url: new URL(url, location.href).toString(),
925
- bytes: 0,
926
- sizes: image instanceof HTMLImageElement ? `${image.naturalWidth}x${image.naturalHeight}` : "",
927
- });
928
- }
929
- const manifestlink = document.querySelector('link[rel="manifest"]');
930
- if (manifestlink instanceof HTMLLinkElement && manifestlink.href) {
931
- try {
932
- const response = await fetch(manifestlink.href);
933
- const manifest = (await response.json()) as { icons?: Array<{ src?: string; sizes?: string }> };
934
- for (const icon of manifest.icons ?? []) {
935
- if (!icon.src) continue;
936
- entries.push({
937
- kind: "favicon",
938
- url: new URL(icon.src, location.href).toString(),
939
- bytes: 0,
940
- sizes: icon.sizes ?? "any",
941
- });
942
- }
943
- } catch {
944
- /* a refused manifest fetch leaves the declared icons unreported */
945
- }
946
- }
947
- return entries;
948
- }
949
-
950
- /** Detects every image of the page inside an optional selector scope: urls, alt text, natural dimensions, transfer sizes from the performance entries and mime types. */
951
- export function pageimages(selector?: string): Array<Record<string, unknown>> {
952
- const root = selector ? document.querySelector(selector) : document;
953
- if (!root) return [];
954
- const transfers = new Map<string, number>();
955
- for (const entry of performance.getEntriesByType("resource")) {
956
- const resource = entry as PerformanceResourceTiming;
957
- if (resource.transferSize > 0) transfers.set(resource.name, resource.transferSize);
958
- }
959
- const images: Array<Record<string, unknown>> = [];
960
- for (const element of [...root.querySelectorAll("img")]) {
961
- if (!(element instanceof HTMLImageElement)) continue;
962
- const url = element.currentSrc || element.src;
963
- if (!url) continue;
964
- const resolved = new URL(url, location.href).toString();
965
- const type = element.getAttribute("type") ?? "";
966
- images.push({
967
- url: resolved,
968
- alt: element.alt ?? "",
969
- width: element.naturalWidth || element.width,
970
- height: element.naturalHeight || element.height,
971
- bytes: transfers.get(resolved) ?? 0,
972
- mime: type || (element.src.startsWith("data:") ? element.src.slice(5, element.src.indexOf(";")) : "image/*"),
973
- });
974
- }
975
- return images;
976
- }
977
- /** Parses fetched markup through the page domparser and runs the reviewed html queries: attribute values, text and element counts per query. */
978
- export function parsehtmlmarkup(
979
- body: string,
980
- queries: Array<{ selector: string; attribute?: string; multi?: boolean }>,
981
- ): Array<{ selector: string; attribute?: string; multi: boolean; count: number; values: string[] }> {
982
- const parsed = new DOMParser().parseFromString(body, "text/html");
983
- return queries.map((query) => {
984
- const matches = [...parsed.querySelectorAll(query.selector)];
985
- const chosen = query.multi === true ? matches : matches.slice(0, 1);
986
- const values = chosen.map((element) =>
987
- query.attribute !== undefined ? (element.getAttribute(query.attribute) ?? "") : (element.textContent ?? ""),
988
- );
989
- return {
990
- selector: query.selector,
991
- ...(query.attribute !== undefined ? { attribute: query.attribute } : {}),
992
- multi: query.multi === true,
993
- count: matches.length,
994
- values,
995
- };
996
- });
997
- }
998
-
999
- /** Reads the request lifecycle facts the page timing buffers expose: every resource and navigation entry with its url, initiator, timing, transfer size, protocol and the response status a navigation entry reports; the buffers expose no header names, body bytes or subresource status codes. */
1000
- export function resourcerecords(): Array<Record<string, unknown>> {
1001
- const entries = [...performance.getEntriesByType("resource"), ...performance.getEntriesByType("navigation")];
1002
- return entries.map((entry) => {
1003
- const resource = entry as PerformanceResourceTiming & { responseStatus?: number };
1004
- return {
1005
- name: resource.name,
1006
- initiatorType: resource.initiatorType ?? "",
1007
- entryType: resource.entryType,
1008
- startTime: resource.startTime,
1009
- duration: resource.duration,
1010
- transferSize: resource.transferSize ?? 0,
1011
- nextHopProtocol: resource.nextHopProtocol ?? "",
1012
- ...(typeof resource.responseStatus === "number" ? { responseStatus: resource.responseStatus } : {}),
1013
- };
1014
- });
1015
- }
1016
-
1017
- /** Writes reviewed cookies for the granted origin of the page through the page document cookie jar: every record writes its name, value and path with an optional expiry; the write happens on the page the user granted, never through a browser cookies permission. */
1018
- export function writecookies(records: Array<{ name: string; value: string; path: string; expiresat?: number }>): {
1019
- written: number;
1020
- summary: string;
1021
- } {
1022
- let written = 0;
1023
- for (const record of records) {
1024
- const expiry = record.expiresat !== undefined ? `; expires=${new Date(record.expiresat).toUTCString()}` : "";
1025
- document.cookie = `${record.name}=${record.value}; path=${record.path}${expiry}; samesite=lax`;
1026
- written += 1;
1027
- }
1028
- return {
1029
- written,
1030
- summary: `Wrote ${written} reviewed cookie${written === 1 ? "" : "s"} through the page cookie jar of ${location.origin}.`,
1031
- };
1032
- }
1033
-
1034
- /** Reads the cookies of the granted origin through the page document cookie jar: document.cookie exposes the name and value pairs of the origin only, with no domain, path or expiry metadata. */
1035
- export function readcookies(): Array<{ name: string; value: string }> {
1036
- return document.cookie
1037
- .split(";")
1038
- .map((pair) => pair.trim())
1039
- .filter((pair) => pair.length > 0)
1040
- .map((pair) => {
1041
- const separator = pair.indexOf("=");
1042
- return separator === -1
1043
- ? { name: pair, value: "" }
1044
- : { name: pair.slice(0, separator), value: pair.slice(separator + 1) };
1045
- });
1046
- }
1047
-
1048
- /** Clears the cookies of the granted origin through the page document cookie jar: every matched name is expired on the root path; an absent name list clears every cookie the origin jar exposes. */
1049
- export function clearcookies(names?: string[]): { cleared: number; summary: string } {
1050
- const jar = readcookies();
1051
- const targets = names !== undefined && names.length > 0 ? jar.filter((cookie) => names.includes(cookie.name)) : jar;
1052
- for (const cookie of targets) document.cookie = `${cookie.name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
1053
- return {
1054
- cleared: targets.length,
1055
- summary: `Cleared ${targets.length} cookie${targets.length === 1 ? "" : "s"} through the page cookie jar of ${location.origin}.`,
1056
- };
1057
- }
1058
-
1059
- /** Masks the typed values of one step before they reach the log writer: a step whose target or kind names a masked field shape keeps its shape while the typed value and the matching option values carry the redaction marker, so typed secrets never land in any record. */
1060
- export function maskstepvalues(step: toolstep, shapes: string[]): toolstep {
1061
- const masked = masktypedvalues({ step, shapes });
1062
- return {
1063
- ...step,
1064
- ...(masked.value !== undefined ? { value: masked.value } : {}),
1065
- ...(masked.options !== undefined ? { options: masked.options } : {}),
1066
- };
1067
- }
1068
-
1069
- /** Masks the form state of one observation payload before logging: the observation schema keeps its field shapes while the values behind masked field shapes carry the redaction marker. */
1070
- export function maskobservationstate(
1071
- fields: Array<{ name: string; value: string }>,
1072
- shapes: string[],
1073
- ): Array<{ name: string; value: string }> {
1074
- return maskformstate(fields, shapes);
1075
- }
1076
-
1077
- /** Masks one full observation payload before it reaches the log writer: the field shapes stay while the option lists of masked field names carry the redaction marker. */
1078
- export function maskobservationsnapshot(shot: observation, shapes: string[]): observation {
1079
- return maskobservation(shot, shapes);
1080
- }
1081
-
1082
- Object.assign(globalThis, {
1083
- devthinkbridge: {
1084
- maskstepvalues,
1085
- maskobservationstate,
1086
- maskobservationsnapshot,
1087
- capturesnapshot,
1088
- previewtarget,
1089
- performstep,
1090
- readdialogs,
1091
- measurepage,
1092
- elementrect,
1093
- queryelements,
1094
- preparecapture,
1095
- scrollcapture,
1096
- restorecapture,
1097
- scrollcontainercapture,
1098
- waitsettle,
1099
- pdfsegment,
1100
- pdfbreaks,
1101
- videoframe,
1102
- videostate,
1103
- canvasdata,
1104
- streamelements,
1105
- mediaelements,
1106
- pageassets,
1107
- pageimages,
1108
- parsehtmlmarkup,
1109
- resourcerecords,
1110
- writecookies,
1111
- readcookies,
1112
- clearcookies,
1113
- revertemulationlayer,
1114
- flushconsole,
1115
- },
1116
- });