agent-web-interface 4.0.0

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 (395) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +226 -0
  3. package/dist/src/browser/ensure-browser.d.ts +39 -0
  4. package/dist/src/browser/ensure-browser.d.ts.map +1 -0
  5. package/dist/src/browser/ensure-browser.js +65 -0
  6. package/dist/src/browser/ensure-browser.js.map +1 -0
  7. package/dist/src/browser/index.d.ts +8 -0
  8. package/dist/src/browser/index.d.ts.map +1 -0
  9. package/dist/src/browser/index.js +8 -0
  10. package/dist/src/browser/index.js.map +1 -0
  11. package/dist/src/browser/page-network-tracker.d.ts +96 -0
  12. package/dist/src/browser/page-network-tracker.d.ts.map +1 -0
  13. package/dist/src/browser/page-network-tracker.js +235 -0
  14. package/dist/src/browser/page-network-tracker.js.map +1 -0
  15. package/dist/src/browser/page-registry.d.ts +137 -0
  16. package/dist/src/browser/page-registry.d.ts.map +1 -0
  17. package/dist/src/browser/page-registry.js +194 -0
  18. package/dist/src/browser/page-registry.js.map +1 -0
  19. package/dist/src/browser/page-stabilization.d.ts +35 -0
  20. package/dist/src/browser/page-stabilization.d.ts.map +1 -0
  21. package/dist/src/browser/page-stabilization.js +42 -0
  22. package/dist/src/browser/page-stabilization.js.map +1 -0
  23. package/dist/src/browser/session-manager.d.ts +336 -0
  24. package/dist/src/browser/session-manager.d.ts.map +1 -0
  25. package/dist/src/browser/session-manager.js +964 -0
  26. package/dist/src/browser/session-manager.js.map +1 -0
  27. package/dist/src/cdp/cdp-client.interface.d.ts +193 -0
  28. package/dist/src/cdp/cdp-client.interface.d.ts.map +1 -0
  29. package/dist/src/cdp/cdp-client.interface.js +9 -0
  30. package/dist/src/cdp/cdp-client.interface.js.map +1 -0
  31. package/dist/src/cdp/index.d.ts +9 -0
  32. package/dist/src/cdp/index.d.ts.map +1 -0
  33. package/dist/src/cdp/index.js +8 -0
  34. package/dist/src/cdp/index.js.map +1 -0
  35. package/dist/src/cdp/puppeteer-cdp-client.d.ts +97 -0
  36. package/dist/src/cdp/puppeteer-cdp-client.d.ts.map +1 -0
  37. package/dist/src/cdp/puppeteer-cdp-client.js +273 -0
  38. package/dist/src/cdp/puppeteer-cdp-client.js.map +1 -0
  39. package/dist/src/cli/args.d.ts +35 -0
  40. package/dist/src/cli/args.d.ts.map +1 -0
  41. package/dist/src/cli/args.js +76 -0
  42. package/dist/src/cli/args.js.map +1 -0
  43. package/dist/src/delta/dom-stabilizer.d.ts +46 -0
  44. package/dist/src/delta/dom-stabilizer.d.ts.map +1 -0
  45. package/dist/src/delta/dom-stabilizer.js +121 -0
  46. package/dist/src/delta/dom-stabilizer.js.map +1 -0
  47. package/dist/src/delta/index.d.ts +8 -0
  48. package/dist/src/delta/index.d.ts.map +1 -0
  49. package/dist/src/delta/index.js +7 -0
  50. package/dist/src/delta/index.js.map +1 -0
  51. package/dist/src/factpack/action-selector.d.ts +36 -0
  52. package/dist/src/factpack/action-selector.d.ts.map +1 -0
  53. package/dist/src/factpack/action-selector.js +367 -0
  54. package/dist/src/factpack/action-selector.js.map +1 -0
  55. package/dist/src/factpack/dialog-detector.d.ts +19 -0
  56. package/dist/src/factpack/dialog-detector.d.ts.map +1 -0
  57. package/dist/src/factpack/dialog-detector.js +354 -0
  58. package/dist/src/factpack/dialog-detector.js.map +1 -0
  59. package/dist/src/factpack/form-detector.d.ts +28 -0
  60. package/dist/src/factpack/form-detector.d.ts.map +1 -0
  61. package/dist/src/factpack/form-detector.js +555 -0
  62. package/dist/src/factpack/form-detector.js.map +1 -0
  63. package/dist/src/factpack/index.d.ts +32 -0
  64. package/dist/src/factpack/index.d.ts.map +1 -0
  65. package/dist/src/factpack/index.js +73 -0
  66. package/dist/src/factpack/index.js.map +1 -0
  67. package/dist/src/factpack/page-classifier.d.ts +22 -0
  68. package/dist/src/factpack/page-classifier.d.ts.map +1 -0
  69. package/dist/src/factpack/page-classifier.js +526 -0
  70. package/dist/src/factpack/page-classifier.js.map +1 -0
  71. package/dist/src/factpack/types.d.ts +307 -0
  72. package/dist/src/factpack/types.d.ts.map +1 -0
  73. package/dist/src/factpack/types.js +12 -0
  74. package/dist/src/factpack/types.js.map +1 -0
  75. package/dist/src/form/dependency-tracker.d.ts +108 -0
  76. package/dist/src/form/dependency-tracker.d.ts.map +1 -0
  77. package/dist/src/form/dependency-tracker.js +298 -0
  78. package/dist/src/form/dependency-tracker.js.map +1 -0
  79. package/dist/src/form/field-extractor.d.ts +32 -0
  80. package/dist/src/form/field-extractor.d.ts.map +1 -0
  81. package/dist/src/form/field-extractor.js +544 -0
  82. package/dist/src/form/field-extractor.js.map +1 -0
  83. package/dist/src/form/form-detector.d.ts +103 -0
  84. package/dist/src/form/form-detector.d.ts.map +1 -0
  85. package/dist/src/form/form-detector.js +704 -0
  86. package/dist/src/form/form-detector.js.map +1 -0
  87. package/dist/src/form/form-state.d.ts +23 -0
  88. package/dist/src/form/form-state.d.ts.map +1 -0
  89. package/dist/src/form/form-state.js +39 -0
  90. package/dist/src/form/form-state.js.map +1 -0
  91. package/dist/src/form/index.d.ts +23 -0
  92. package/dist/src/form/index.d.ts.map +1 -0
  93. package/dist/src/form/index.js +27 -0
  94. package/dist/src/form/index.js.map +1 -0
  95. package/dist/src/form/runtime-value-reader.d.ts +72 -0
  96. package/dist/src/form/runtime-value-reader.d.ts.map +1 -0
  97. package/dist/src/form/runtime-value-reader.js +232 -0
  98. package/dist/src/form/runtime-value-reader.js.map +1 -0
  99. package/dist/src/form/types.d.ts +384 -0
  100. package/dist/src/form/types.d.ts.map +1 -0
  101. package/dist/src/form/types.js +17 -0
  102. package/dist/src/form/types.js.map +1 -0
  103. package/dist/src/index.d.ts +8 -0
  104. package/dist/src/index.d.ts.map +1 -0
  105. package/dist/src/index.js +212 -0
  106. package/dist/src/index.js.map +1 -0
  107. package/dist/src/lib/constants.d.ts +27 -0
  108. package/dist/src/lib/constants.d.ts.map +1 -0
  109. package/dist/src/lib/constants.js +63 -0
  110. package/dist/src/lib/constants.js.map +1 -0
  111. package/dist/src/lib/index.d.ts +12 -0
  112. package/dist/src/lib/index.d.ts.map +1 -0
  113. package/dist/src/lib/index.js +17 -0
  114. package/dist/src/lib/index.js.map +1 -0
  115. package/dist/src/lib/regions.d.ts +29 -0
  116. package/dist/src/lib/regions.d.ts.map +1 -0
  117. package/dist/src/lib/regions.js +93 -0
  118. package/dist/src/lib/regions.js.map +1 -0
  119. package/dist/src/lib/scoring.d.ts +47 -0
  120. package/dist/src/lib/scoring.d.ts.map +1 -0
  121. package/dist/src/lib/scoring.js +79 -0
  122. package/dist/src/lib/scoring.js.map +1 -0
  123. package/dist/src/lib/selectors.d.ts +42 -0
  124. package/dist/src/lib/selectors.d.ts.map +1 -0
  125. package/dist/src/lib/selectors.js +138 -0
  126. package/dist/src/lib/selectors.js.map +1 -0
  127. package/dist/src/lib/text-utils.d.ts +155 -0
  128. package/dist/src/lib/text-utils.d.ts.map +1 -0
  129. package/dist/src/lib/text-utils.js +391 -0
  130. package/dist/src/lib/text-utils.js.map +1 -0
  131. package/dist/src/observation/eid-linker.d.ts +104 -0
  132. package/dist/src/observation/eid-linker.d.ts.map +1 -0
  133. package/dist/src/observation/eid-linker.js +403 -0
  134. package/dist/src/observation/eid-linker.js.map +1 -0
  135. package/dist/src/observation/index.d.ts +12 -0
  136. package/dist/src/observation/index.d.ts.map +1 -0
  137. package/dist/src/observation/index.js +15 -0
  138. package/dist/src/observation/index.js.map +1 -0
  139. package/dist/src/observation/observation-accumulator.d.ts +58 -0
  140. package/dist/src/observation/observation-accumulator.d.ts.map +1 -0
  141. package/dist/src/observation/observation-accumulator.js +213 -0
  142. package/dist/src/observation/observation-accumulator.js.map +1 -0
  143. package/dist/src/observation/observation.types.d.ts +139 -0
  144. package/dist/src/observation/observation.types.d.ts.map +1 -0
  145. package/dist/src/observation/observation.types.js +59 -0
  146. package/dist/src/observation/observation.types.js.map +1 -0
  147. package/dist/src/observation/observer-script.d.ts +19 -0
  148. package/dist/src/observation/observer-script.d.ts.map +1 -0
  149. package/dist/src/observation/observer-script.js +569 -0
  150. package/dist/src/observation/observer-script.js.map +1 -0
  151. package/dist/src/query/index.d.ts +9 -0
  152. package/dist/src/query/index.d.ts.map +1 -0
  153. package/dist/src/query/index.js +10 -0
  154. package/dist/src/query/index.js.map +1 -0
  155. package/dist/src/query/query-engine.d.ts +111 -0
  156. package/dist/src/query/query-engine.d.ts.map +1 -0
  157. package/dist/src/query/query-engine.js +509 -0
  158. package/dist/src/query/query-engine.js.map +1 -0
  159. package/dist/src/query/types/index.d.ts +5 -0
  160. package/dist/src/query/types/index.d.ts.map +1 -0
  161. package/dist/src/query/types/index.js +5 -0
  162. package/dist/src/query/types/index.js.map +1 -0
  163. package/dist/src/query/types/query.types.d.ts +141 -0
  164. package/dist/src/query/types/query.types.d.ts.map +1 -0
  165. package/dist/src/query/types/query.types.js +19 -0
  166. package/dist/src/query/types/query.types.js.map +1 -0
  167. package/dist/src/renderer/budget-manager.d.ts +46 -0
  168. package/dist/src/renderer/budget-manager.d.ts.map +1 -0
  169. package/dist/src/renderer/budget-manager.js +133 -0
  170. package/dist/src/renderer/budget-manager.js.map +1 -0
  171. package/dist/src/renderer/constants.d.ts +38 -0
  172. package/dist/src/renderer/constants.d.ts.map +1 -0
  173. package/dist/src/renderer/constants.js +29 -0
  174. package/dist/src/renderer/constants.js.map +1 -0
  175. package/dist/src/renderer/index.d.ts +12 -0
  176. package/dist/src/renderer/index.d.ts.map +1 -0
  177. package/dist/src/renderer/index.js +16 -0
  178. package/dist/src/renderer/index.js.map +1 -0
  179. package/dist/src/renderer/section-renderers.d.ts +42 -0
  180. package/dist/src/renderer/section-renderers.d.ts.map +1 -0
  181. package/dist/src/renderer/section-renderers.js +252 -0
  182. package/dist/src/renderer/section-renderers.js.map +1 -0
  183. package/dist/src/renderer/token-counter.d.ts +45 -0
  184. package/dist/src/renderer/token-counter.d.ts.map +1 -0
  185. package/dist/src/renderer/token-counter.js +65 -0
  186. package/dist/src/renderer/token-counter.js.map +1 -0
  187. package/dist/src/renderer/types.d.ts +71 -0
  188. package/dist/src/renderer/types.d.ts.map +1 -0
  189. package/dist/src/renderer/types.js +7 -0
  190. package/dist/src/renderer/types.js.map +1 -0
  191. package/dist/src/renderer/xml-renderer.d.ts +42 -0
  192. package/dist/src/renderer/xml-renderer.d.ts.map +1 -0
  193. package/dist/src/renderer/xml-renderer.js +103 -0
  194. package/dist/src/renderer/xml-renderer.js.map +1 -0
  195. package/dist/src/server/index.d.ts +8 -0
  196. package/dist/src/server/index.d.ts.map +1 -0
  197. package/dist/src/server/index.js +8 -0
  198. package/dist/src/server/index.js.map +1 -0
  199. package/dist/src/server/mcp-server.d.ts +59 -0
  200. package/dist/src/server/mcp-server.d.ts.map +1 -0
  201. package/dist/src/server/mcp-server.js +140 -0
  202. package/dist/src/server/mcp-server.js.map +1 -0
  203. package/dist/src/server/server-config.d.ts +41 -0
  204. package/dist/src/server/server-config.d.ts.map +1 -0
  205. package/dist/src/server/server-config.js +80 -0
  206. package/dist/src/server/server-config.js.map +1 -0
  207. package/dist/src/server/session-store.d.ts +148 -0
  208. package/dist/src/server/session-store.d.ts.map +1 -0
  209. package/dist/src/server/session-store.js +224 -0
  210. package/dist/src/server/session-store.js.map +1 -0
  211. package/dist/src/shared/errors/browser-session.error.d.ts +102 -0
  212. package/dist/src/shared/errors/browser-session.error.d.ts.map +1 -0
  213. package/dist/src/shared/errors/browser-session.error.js +153 -0
  214. package/dist/src/shared/errors/browser-session.error.js.map +1 -0
  215. package/dist/src/shared/errors/index.d.ts +5 -0
  216. package/dist/src/shared/errors/index.d.ts.map +1 -0
  217. package/dist/src/shared/errors/index.js +5 -0
  218. package/dist/src/shared/errors/index.js.map +1 -0
  219. package/dist/src/shared/services/dom-transformer.service.d.ts +71 -0
  220. package/dist/src/shared/services/dom-transformer.service.d.ts.map +1 -0
  221. package/dist/src/shared/services/dom-transformer.service.js +190 -0
  222. package/dist/src/shared/services/dom-transformer.service.js.map +1 -0
  223. package/dist/src/shared/services/index.d.ts +7 -0
  224. package/dist/src/shared/services/index.d.ts.map +1 -0
  225. package/dist/src/shared/services/index.js +7 -0
  226. package/dist/src/shared/services/index.js.map +1 -0
  227. package/dist/src/shared/services/logging.service.d.ts +154 -0
  228. package/dist/src/shared/services/logging.service.d.ts.map +1 -0
  229. package/dist/src/shared/services/logging.service.js +267 -0
  230. package/dist/src/shared/services/logging.service.js.map +1 -0
  231. package/dist/src/shared/services/selector-builder.service.d.ts +53 -0
  232. package/dist/src/shared/services/selector-builder.service.d.ts.map +1 -0
  233. package/dist/src/shared/services/selector-builder.service.js +240 -0
  234. package/dist/src/shared/services/selector-builder.service.js.map +1 -0
  235. package/dist/src/shared/types/base.types.d.ts +45 -0
  236. package/dist/src/shared/types/base.types.d.ts.map +1 -0
  237. package/dist/src/shared/types/base.types.js +8 -0
  238. package/dist/src/shared/types/base.types.js.map +1 -0
  239. package/dist/src/shared/types/index.d.ts +5 -0
  240. package/dist/src/shared/types/index.d.ts.map +1 -0
  241. package/dist/src/shared/types/index.js +5 -0
  242. package/dist/src/shared/types/index.js.map +1 -0
  243. package/dist/src/snapshot/element-resolver.d.ts +102 -0
  244. package/dist/src/snapshot/element-resolver.d.ts.map +1 -0
  245. package/dist/src/snapshot/element-resolver.js +379 -0
  246. package/dist/src/snapshot/element-resolver.js.map +1 -0
  247. package/dist/src/snapshot/extractors/attribute-extractor.d.ts +40 -0
  248. package/dist/src/snapshot/extractors/attribute-extractor.d.ts.map +1 -0
  249. package/dist/src/snapshot/extractors/attribute-extractor.js +237 -0
  250. package/dist/src/snapshot/extractors/attribute-extractor.js.map +1 -0
  251. package/dist/src/snapshot/extractors/ax-extractor.d.ts +36 -0
  252. package/dist/src/snapshot/extractors/ax-extractor.d.ts.map +1 -0
  253. package/dist/src/snapshot/extractors/ax-extractor.js +144 -0
  254. package/dist/src/snapshot/extractors/ax-extractor.js.map +1 -0
  255. package/dist/src/snapshot/extractors/dom-extractor.d.ts +21 -0
  256. package/dist/src/snapshot/extractors/dom-extractor.d.ts.map +1 -0
  257. package/dist/src/snapshot/extractors/dom-extractor.js +137 -0
  258. package/dist/src/snapshot/extractors/dom-extractor.js.map +1 -0
  259. package/dist/src/snapshot/extractors/grouping-resolver.d.ts +39 -0
  260. package/dist/src/snapshot/extractors/grouping-resolver.d.ts.map +1 -0
  261. package/dist/src/snapshot/extractors/grouping-resolver.js +260 -0
  262. package/dist/src/snapshot/extractors/grouping-resolver.js.map +1 -0
  263. package/dist/src/snapshot/extractors/index.d.ts +19 -0
  264. package/dist/src/snapshot/extractors/index.d.ts.map +1 -0
  265. package/dist/src/snapshot/extractors/index.js +27 -0
  266. package/dist/src/snapshot/extractors/index.js.map +1 -0
  267. package/dist/src/snapshot/extractors/label-resolver.d.ts +44 -0
  268. package/dist/src/snapshot/extractors/label-resolver.d.ts.map +1 -0
  269. package/dist/src/snapshot/extractors/label-resolver.js +173 -0
  270. package/dist/src/snapshot/extractors/label-resolver.js.map +1 -0
  271. package/dist/src/snapshot/extractors/layout-extractor.d.ts +52 -0
  272. package/dist/src/snapshot/extractors/layout-extractor.d.ts.map +1 -0
  273. package/dist/src/snapshot/extractors/layout-extractor.js +382 -0
  274. package/dist/src/snapshot/extractors/layout-extractor.js.map +1 -0
  275. package/dist/src/snapshot/extractors/locator-builder.d.ts +27 -0
  276. package/dist/src/snapshot/extractors/locator-builder.d.ts.map +1 -0
  277. package/dist/src/snapshot/extractors/locator-builder.js +223 -0
  278. package/dist/src/snapshot/extractors/locator-builder.js.map +1 -0
  279. package/dist/src/snapshot/extractors/region-resolver.d.ts +31 -0
  280. package/dist/src/snapshot/extractors/region-resolver.d.ts.map +1 -0
  281. package/dist/src/snapshot/extractors/region-resolver.js +168 -0
  282. package/dist/src/snapshot/extractors/region-resolver.js.map +1 -0
  283. package/dist/src/snapshot/extractors/state-extractor.d.ts +30 -0
  284. package/dist/src/snapshot/extractors/state-extractor.d.ts.map +1 -0
  285. package/dist/src/snapshot/extractors/state-extractor.js +181 -0
  286. package/dist/src/snapshot/extractors/state-extractor.js.map +1 -0
  287. package/dist/src/snapshot/extractors/types.d.ts +213 -0
  288. package/dist/src/snapshot/extractors/types.d.ts.map +1 -0
  289. package/dist/src/snapshot/extractors/types.js +145 -0
  290. package/dist/src/snapshot/extractors/types.js.map +1 -0
  291. package/dist/src/snapshot/index.d.ts +14 -0
  292. package/dist/src/snapshot/index.d.ts.map +1 -0
  293. package/dist/src/snapshot/index.js +18 -0
  294. package/dist/src/snapshot/index.js.map +1 -0
  295. package/dist/src/snapshot/snapshot-compiler.d.ts +73 -0
  296. package/dist/src/snapshot/snapshot-compiler.d.ts.map +1 -0
  297. package/dist/src/snapshot/snapshot-compiler.js +763 -0
  298. package/dist/src/snapshot/snapshot-compiler.js.map +1 -0
  299. package/dist/src/snapshot/snapshot-health.d.ts +97 -0
  300. package/dist/src/snapshot/snapshot-health.d.ts.map +1 -0
  301. package/dist/src/snapshot/snapshot-health.js +214 -0
  302. package/dist/src/snapshot/snapshot-health.js.map +1 -0
  303. package/dist/src/snapshot/snapshot-store.d.ts +137 -0
  304. package/dist/src/snapshot/snapshot-store.d.ts.map +1 -0
  305. package/dist/src/snapshot/snapshot-store.js +202 -0
  306. package/dist/src/snapshot/snapshot-store.js.map +1 -0
  307. package/dist/src/snapshot/snapshot.types.d.ts +250 -0
  308. package/dist/src/snapshot/snapshot.types.d.ts.map +1 -0
  309. package/dist/src/snapshot/snapshot.types.js +54 -0
  310. package/dist/src/snapshot/snapshot.types.js.map +1 -0
  311. package/dist/src/state/actionables-filter.d.ts +47 -0
  312. package/dist/src/state/actionables-filter.d.ts.map +1 -0
  313. package/dist/src/state/actionables-filter.js +173 -0
  314. package/dist/src/state/actionables-filter.js.map +1 -0
  315. package/dist/src/state/atoms-extractor.d.ts +23 -0
  316. package/dist/src/state/atoms-extractor.d.ts.map +1 -0
  317. package/dist/src/state/atoms-extractor.js +160 -0
  318. package/dist/src/state/atoms-extractor.js.map +1 -0
  319. package/dist/src/state/constants.d.ts +125 -0
  320. package/dist/src/state/constants.d.ts.map +1 -0
  321. package/dist/src/state/constants.js +131 -0
  322. package/dist/src/state/constants.js.map +1 -0
  323. package/dist/src/state/diff-engine.d.ts +23 -0
  324. package/dist/src/state/diff-engine.d.ts.map +1 -0
  325. package/dist/src/state/diff-engine.js +475 -0
  326. package/dist/src/state/diff-engine.js.map +1 -0
  327. package/dist/src/state/element-identity.d.ts +75 -0
  328. package/dist/src/state/element-identity.d.ts.map +1 -0
  329. package/dist/src/state/element-identity.js +129 -0
  330. package/dist/src/state/element-identity.js.map +1 -0
  331. package/dist/src/state/element-ref.types.d.ts +135 -0
  332. package/dist/src/state/element-ref.types.d.ts.map +1 -0
  333. package/dist/src/state/element-ref.types.js +13 -0
  334. package/dist/src/state/element-ref.types.js.map +1 -0
  335. package/dist/src/state/element-registry.d.ts +118 -0
  336. package/dist/src/state/element-registry.d.ts.map +1 -0
  337. package/dist/src/state/element-registry.js +222 -0
  338. package/dist/src/state/element-registry.js.map +1 -0
  339. package/dist/src/state/health.types.d.ts +93 -0
  340. package/dist/src/state/health.types.d.ts.map +1 -0
  341. package/dist/src/state/health.types.js +56 -0
  342. package/dist/src/state/health.types.js.map +1 -0
  343. package/dist/src/state/layer-detector.d.ts +23 -0
  344. package/dist/src/state/layer-detector.d.ts.map +1 -0
  345. package/dist/src/state/layer-detector.js +368 -0
  346. package/dist/src/state/layer-detector.js.map +1 -0
  347. package/dist/src/state/locator-generator.d.ts +21 -0
  348. package/dist/src/state/locator-generator.d.ts.map +1 -0
  349. package/dist/src/state/locator-generator.js +137 -0
  350. package/dist/src/state/locator-generator.js.map +1 -0
  351. package/dist/src/state/state-manager.d.ts +104 -0
  352. package/dist/src/state/state-manager.d.ts.map +1 -0
  353. package/dist/src/state/state-manager.js +618 -0
  354. package/dist/src/state/state-manager.js.map +1 -0
  355. package/dist/src/state/state-renderer.d.ts +63 -0
  356. package/dist/src/state/state-renderer.d.ts.map +1 -0
  357. package/dist/src/state/state-renderer.js +340 -0
  358. package/dist/src/state/state-renderer.js.map +1 -0
  359. package/dist/src/state/types.d.ts +353 -0
  360. package/dist/src/state/types.d.ts.map +1 -0
  361. package/dist/src/state/types.js +8 -0
  362. package/dist/src/state/types.js.map +1 -0
  363. package/dist/src/tools/browser-tools.d.ts +140 -0
  364. package/dist/src/tools/browser-tools.d.ts.map +1 -0
  365. package/dist/src/tools/browser-tools.js +657 -0
  366. package/dist/src/tools/browser-tools.js.map +1 -0
  367. package/dist/src/tools/errors.d.ts +63 -0
  368. package/dist/src/tools/errors.d.ts.map +1 -0
  369. package/dist/src/tools/errors.js +86 -0
  370. package/dist/src/tools/errors.js.map +1 -0
  371. package/dist/src/tools/execute-action.d.ts +135 -0
  372. package/dist/src/tools/execute-action.d.ts.map +1 -0
  373. package/dist/src/tools/execute-action.js +579 -0
  374. package/dist/src/tools/execute-action.js.map +1 -0
  375. package/dist/src/tools/form-tools.d.ts +109 -0
  376. package/dist/src/tools/form-tools.d.ts.map +1 -0
  377. package/dist/src/tools/form-tools.js +434 -0
  378. package/dist/src/tools/form-tools.js.map +1 -0
  379. package/dist/src/tools/index.d.ts +11 -0
  380. package/dist/src/tools/index.d.ts.map +1 -0
  381. package/dist/src/tools/index.js +49 -0
  382. package/dist/src/tools/index.js.map +1 -0
  383. package/dist/src/tools/response-builder.d.ts +98 -0
  384. package/dist/src/tools/response-builder.d.ts.map +1 -0
  385. package/dist/src/tools/response-builder.js +219 -0
  386. package/dist/src/tools/response-builder.js.map +1 -0
  387. package/dist/src/tools/tool-schemas.d.ts +2482 -0
  388. package/dist/src/tools/tool-schemas.d.ts.map +1 -0
  389. package/dist/src/tools/tool-schemas.js +606 -0
  390. package/dist/src/tools/tool-schemas.js.map +1 -0
  391. package/dist/vitest.config.d.ts +3 -0
  392. package/dist/vitest.config.d.ts.map +1 -0
  393. package/dist/vitest.config.js +16 -0
  394. package/dist/vitest.config.js.map +1 -0
  395. package/package.json +76 -0
@@ -0,0 +1,704 @@
1
+ /**
2
+ * Form Detector
3
+ *
4
+ * Detects logical form boundaries in a BaseSnapshot, regardless of
5
+ * HTML <form> tag presence. Uses signal scoring to identify form
6
+ * regions with confidence scores.
7
+ *
8
+ * Detection approaches:
9
+ * 1. Semantic: <form> tags, role="form", role="search", <fieldset>
10
+ * 2. Structural: Input clusters, label-input pairs, submit buttons
11
+ * 3. Naming: Form keywords in labels, field name patterns
12
+ *
13
+ * @module form/form-detector
14
+ */
15
+ import { DEFAULT_FORM_DETECTION_CONFIG } from './types.js';
16
+ import { extractFields } from './field-extractor.js';
17
+ import { computeFormState } from './form-state.js';
18
+ import { createHash } from 'crypto';
19
+ /**
20
+ * Interactive input kinds
21
+ */
22
+ const INPUT_KINDS = new Set([
23
+ 'input',
24
+ 'textarea',
25
+ 'select',
26
+ 'combobox',
27
+ 'checkbox',
28
+ 'radio',
29
+ 'switch',
30
+ 'slider',
31
+ ]);
32
+ /**
33
+ * Button kinds that could be submit buttons
34
+ */
35
+ const BUTTON_KINDS = new Set(['button']);
36
+ /**
37
+ * Signal weights for form detection
38
+ */
39
+ const SIGNAL_WEIGHTS = {
40
+ form_tag: 0.5,
41
+ role_form: 0.45,
42
+ role_search: 0.4,
43
+ fieldset: 0.3,
44
+ input_cluster: 0.25,
45
+ label_input_pairs: 0.2,
46
+ submit_button: 0.3,
47
+ form_keywords: 0.15,
48
+ naming_pattern: 0.1,
49
+ };
50
+ /**
51
+ * Weighted keywords that indicate form intent.
52
+ * Higher weight = more explicit signal (e.g., "create account" is more explicitly signup than "email")
53
+ * Lower weight = ambiguous signal that appears in multiple form types
54
+ */
55
+ const INTENT_KEYWORDS = {
56
+ login: [
57
+ { keyword: 'log in', weight: 3 },
58
+ { keyword: 'login', weight: 3 },
59
+ { keyword: 'sign in', weight: 3 },
60
+ { keyword: 'signin', weight: 3 },
61
+ // These are ambiguous - they appear in both login and signup forms
62
+ { keyword: 'email', weight: 0.5 },
63
+ { keyword: 'password', weight: 0.5 },
64
+ { keyword: 'username', weight: 0.5 },
65
+ ],
66
+ signup: [
67
+ { keyword: 'sign up', weight: 3 },
68
+ { keyword: 'signup', weight: 3 },
69
+ { keyword: 'register', weight: 3 },
70
+ { keyword: 'create account', weight: 3 },
71
+ { keyword: 'join', weight: 2 },
72
+ ],
73
+ search: [
74
+ { keyword: 'search', weight: 3 },
75
+ { keyword: 'find', weight: 2 },
76
+ { keyword: 'lookup', weight: 2 },
77
+ { keyword: 'query', weight: 2 },
78
+ ],
79
+ checkout: [
80
+ { keyword: 'checkout', weight: 3 },
81
+ { keyword: 'payment', weight: 2 },
82
+ { keyword: 'order', weight: 2 },
83
+ { keyword: 'purchase', weight: 2 },
84
+ { keyword: 'buy now', weight: 3 },
85
+ ],
86
+ filter: [
87
+ { keyword: 'filter', weight: 3 },
88
+ { keyword: 'sort', weight: 2 },
89
+ { keyword: 'refine', weight: 2 },
90
+ { keyword: 'narrow', weight: 2 },
91
+ ],
92
+ settings: [
93
+ { keyword: 'settings', weight: 3 },
94
+ { keyword: 'preferences', weight: 2 },
95
+ { keyword: 'configuration', weight: 2 },
96
+ { keyword: 'options', weight: 1 },
97
+ ],
98
+ contact: [
99
+ { keyword: 'contact', weight: 3 },
100
+ { keyword: 'message', weight: 1 },
101
+ { keyword: 'feedback', weight: 2 },
102
+ { keyword: 'inquiry', weight: 2 },
103
+ ],
104
+ subscribe: [
105
+ { keyword: 'subscribe', weight: 3 },
106
+ { keyword: 'newsletter', weight: 3 },
107
+ { keyword: 'email updates', weight: 2 },
108
+ ],
109
+ shipping: [
110
+ { keyword: 'shipping', weight: 3 },
111
+ { keyword: 'delivery', weight: 2 },
112
+ { keyword: 'address', weight: 1 },
113
+ ],
114
+ payment: [
115
+ { keyword: 'payment', weight: 3 },
116
+ { keyword: 'credit card', weight: 3 },
117
+ { keyword: 'billing', weight: 2 },
118
+ { keyword: 'card number', weight: 3 },
119
+ ],
120
+ profile: [
121
+ { keyword: 'profile', weight: 3 },
122
+ { keyword: 'account', weight: 1 },
123
+ { keyword: 'personal info', weight: 2 },
124
+ ],
125
+ unknown: [],
126
+ };
127
+ /**
128
+ * Submit button keywords
129
+ */
130
+ const SUBMIT_KEYWORDS = [
131
+ 'submit',
132
+ 'send',
133
+ 'continue',
134
+ 'next',
135
+ 'save',
136
+ 'apply',
137
+ 'confirm',
138
+ 'add to',
139
+ 'sign in',
140
+ 'log in',
141
+ 'sign up',
142
+ 'register',
143
+ 'search',
144
+ 'buy',
145
+ 'checkout',
146
+ 'purchase',
147
+ 'subscribe',
148
+ ];
149
+ /**
150
+ * Form Detector class
151
+ */
152
+ export class FormDetector {
153
+ config;
154
+ constructor(config) {
155
+ this.config = { ...DEFAULT_FORM_DETECTION_CONFIG, ...config };
156
+ }
157
+ /**
158
+ * Detect all form regions in a snapshot.
159
+ *
160
+ * @param snapshot - BaseSnapshot to analyze
161
+ * @returns Array of detected FormRegions
162
+ */
163
+ detect(snapshot) {
164
+ const candidates = [];
165
+ // Phase 1: Detect explicit form elements (semantic signals)
166
+ const explicitForms = this.detectExplicitForms(snapshot);
167
+ candidates.push(...explicitForms);
168
+ // Track which fields are already claimed by explicit forms
169
+ const claimedFields = new Set();
170
+ for (const candidate of explicitForms) {
171
+ for (const eid of candidate.field_eids) {
172
+ claimedFields.add(eid);
173
+ }
174
+ }
175
+ // Phase 2: Detect implicit forms (formless input clusters)
176
+ if (this.config.detect_formless) {
177
+ const implicitForms = this.detectImplicitForms(snapshot, claimedFields);
178
+ candidates.push(...implicitForms);
179
+ }
180
+ // Phase 3: Filter by minimum confidence
181
+ const validCandidates = candidates.filter((c) => c.confidence >= this.config.min_confidence);
182
+ // Phase 4: Transform candidates to FormRegions
183
+ return validCandidates.map((candidate, index) => this.buildFormRegion(candidate, snapshot, index));
184
+ }
185
+ /**
186
+ * Detect explicit form elements (form tags, role=form, etc.)
187
+ */
188
+ detectExplicitForms(snapshot) {
189
+ const candidates = [];
190
+ const inputNodes = snapshot.nodes.filter((n) => INPUT_KINDS.has(n.kind));
191
+ // Find form structural nodes
192
+ const formNodes = snapshot.nodes.filter((n) => n.kind === 'form' || n.attributes?.role === 'form' || n.attributes?.role === 'search');
193
+ for (const formNode of formNodes) {
194
+ const signals = [];
195
+ // Determine signal type
196
+ if (formNode.kind === 'form') {
197
+ signals.push({
198
+ type: 'form_tag',
199
+ strength: 1.0,
200
+ evidence: `<form> element at ${formNode.node_id}`,
201
+ });
202
+ }
203
+ else if (formNode.attributes?.role === 'search') {
204
+ signals.push({
205
+ type: 'role_search',
206
+ strength: 1.0,
207
+ evidence: `role="search" at ${formNode.node_id}`,
208
+ });
209
+ }
210
+ else if (formNode.attributes?.role === 'form') {
211
+ signals.push({
212
+ type: 'role_form',
213
+ strength: 1.0,
214
+ evidence: `role="form" at ${formNode.node_id}`,
215
+ });
216
+ }
217
+ // Find fields within this form's region/group
218
+ const fieldEids = [];
219
+ for (const input of inputNodes) {
220
+ // Check if input is in the same group or under the same heading context
221
+ const isInForm = this.isNodeWithinForm(input, formNode, snapshot);
222
+ if (isInForm) {
223
+ fieldEids.push(input.node_id);
224
+ }
225
+ }
226
+ // Add input cluster signal if we found fields
227
+ if (fieldEids.length > 0) {
228
+ signals.push({
229
+ type: 'input_cluster',
230
+ strength: Math.min(1.0, fieldEids.length / 5),
231
+ evidence: `${fieldEids.length} input fields`,
232
+ });
233
+ }
234
+ // Check for submit button
235
+ const submitButton = this.findSubmitButton(snapshot, formNode, fieldEids);
236
+ if (submitButton) {
237
+ signals.push({
238
+ type: 'submit_button',
239
+ strength: 1.0,
240
+ evidence: `Submit button: "${submitButton.label}"`,
241
+ });
242
+ }
243
+ // Compute confidence
244
+ const confidence = this.computeConfidence(signals);
245
+ // Infer intent
246
+ const intent = this.inferIntent(snapshot, fieldEids, formNode);
247
+ candidates.push({
248
+ root_node_id: formNode.node_id,
249
+ root_backend_node_id: formNode.backend_node_id,
250
+ signals,
251
+ field_eids: fieldEids,
252
+ confidence,
253
+ intent,
254
+ bbox: formNode.layout?.bbox
255
+ ? {
256
+ x: formNode.layout.bbox.x,
257
+ y: formNode.layout.bbox.y,
258
+ width: formNode.layout.bbox.w,
259
+ height: formNode.layout.bbox.h,
260
+ }
261
+ : undefined,
262
+ });
263
+ }
264
+ return candidates;
265
+ }
266
+ /**
267
+ * Detect implicit forms (input clusters without form tag)
268
+ */
269
+ detectImplicitForms(snapshot, claimedFields) {
270
+ const candidates = [];
271
+ // Find unclaimed input nodes
272
+ const unclaimedInputs = snapshot.nodes.filter((n) => INPUT_KINDS.has(n.kind) && !claimedFields.has(n.node_id));
273
+ if (unclaimedInputs.length === 0) {
274
+ return candidates;
275
+ }
276
+ // Group inputs by proximity and structural context
277
+ const clusters = this.clusterInputs(unclaimedInputs, snapshot);
278
+ for (const cluster of clusters) {
279
+ if (cluster.length < 1)
280
+ continue;
281
+ const signals = [];
282
+ // Input cluster signal
283
+ signals.push({
284
+ type: 'input_cluster',
285
+ strength: Math.min(1.0, cluster.length / 3),
286
+ evidence: `${cluster.length} input fields clustered`,
287
+ });
288
+ // Check for label-input pairs
289
+ const labeledCount = cluster.filter((n) => n.label && n.label.trim().length > 0).length;
290
+ if (labeledCount > 0) {
291
+ signals.push({
292
+ type: 'label_input_pairs',
293
+ strength: labeledCount / cluster.length,
294
+ evidence: `${labeledCount}/${cluster.length} fields have labels`,
295
+ });
296
+ }
297
+ // Check for form keywords in labels
298
+ const allKeywords = Object.values(INTENT_KEYWORDS)
299
+ .flat()
300
+ .map((entry) => entry.keyword);
301
+ const hasFormKeywords = cluster.some((n) => this.hasIntentKeywords(n.label, allKeywords));
302
+ if (hasFormKeywords) {
303
+ signals.push({
304
+ type: 'form_keywords',
305
+ strength: 0.8,
306
+ evidence: 'Form-related keywords in labels',
307
+ });
308
+ }
309
+ // Check for submit button near cluster
310
+ const fieldEids = cluster.map((n) => n.node_id);
311
+ const submitButton = this.findSubmitButtonNearCluster(snapshot, cluster);
312
+ if (submitButton) {
313
+ signals.push({
314
+ type: 'submit_button',
315
+ strength: 0.9,
316
+ evidence: `Nearby submit button: "${submitButton.label}"`,
317
+ });
318
+ }
319
+ // Compute confidence
320
+ const confidence = this.computeConfidence(signals);
321
+ // Infer intent
322
+ const intent = this.inferIntent(snapshot, fieldEids, undefined);
323
+ // Compute bounding box from cluster
324
+ const bbox = this.computeClusterBbox(cluster);
325
+ candidates.push({
326
+ signals,
327
+ field_eids: fieldEids,
328
+ confidence,
329
+ intent,
330
+ bbox,
331
+ });
332
+ }
333
+ return candidates;
334
+ }
335
+ /**
336
+ * Cluster input nodes by proximity and structural context.
337
+ */
338
+ clusterInputs(inputs, _snapshot) {
339
+ if (inputs.length === 0)
340
+ return [];
341
+ if (inputs.length === 1)
342
+ return [[inputs[0]]];
343
+ // Group by region first
344
+ const byRegion = new Map();
345
+ for (const input of inputs) {
346
+ const key = input.where.region ?? 'unknown';
347
+ const group = byRegion.get(key) ?? [];
348
+ group.push(input);
349
+ byRegion.set(key, group);
350
+ }
351
+ const clusters = [];
352
+ // Within each region, cluster by proximity
353
+ for (const regionInputs of byRegion.values()) {
354
+ if (regionInputs.length === 1) {
355
+ clusters.push(regionInputs);
356
+ continue;
357
+ }
358
+ // Simple clustering by vertical proximity
359
+ const sorted = [...regionInputs].sort((a, b) => {
360
+ const yA = a.layout?.bbox?.y ?? 0;
361
+ const yB = b.layout?.bbox?.y ?? 0;
362
+ return yA - yB;
363
+ });
364
+ let currentCluster = [sorted[0]];
365
+ for (let i = 1; i < sorted.length; i++) {
366
+ const prev = sorted[i - 1];
367
+ const curr = sorted[i];
368
+ const prevY = (prev.layout?.bbox?.y ?? 0) + (prev.layout?.bbox?.h ?? 0);
369
+ const currY = curr.layout?.bbox?.y ?? 0;
370
+ const distance = currY - prevY;
371
+ if (distance <= this.config.cluster_distance) {
372
+ currentCluster.push(curr);
373
+ }
374
+ else {
375
+ if (currentCluster.length > 0) {
376
+ clusters.push(currentCluster);
377
+ }
378
+ currentCluster = [curr];
379
+ }
380
+ }
381
+ if (currentCluster.length > 0) {
382
+ clusters.push(currentCluster);
383
+ }
384
+ }
385
+ return clusters;
386
+ }
387
+ /**
388
+ * Check if a node is likely within a form's scope.
389
+ */
390
+ isNodeWithinForm(node, formNode, _snapshot) {
391
+ // Check region match
392
+ if (node.where.region !== formNode.where.region) {
393
+ return false;
394
+ }
395
+ // Check group_id if available
396
+ if (formNode.where.group_id && node.where.group_id) {
397
+ if (node.where.group_id === formNode.where.group_id) {
398
+ return true;
399
+ }
400
+ }
401
+ // Check heading context
402
+ if (formNode.where.heading_context && node.where.heading_context) {
403
+ if (node.where.heading_context === formNode.where.heading_context) {
404
+ return true;
405
+ }
406
+ }
407
+ // Check spatial proximity using bounding boxes
408
+ if (formNode.layout?.bbox && node.layout?.bbox) {
409
+ const formBbox = formNode.layout.bbox;
410
+ const nodeBbox = node.layout.bbox;
411
+ // Check if node is within or near form's bounding box
412
+ const isWithinX = nodeBbox.x >= formBbox.x - 50 && nodeBbox.x <= formBbox.x + formBbox.w + 50;
413
+ const isWithinY = nodeBbox.y >= formBbox.y - 50 && nodeBbox.y <= formBbox.y + formBbox.h + 50;
414
+ if (isWithinX && isWithinY) {
415
+ return true;
416
+ }
417
+ }
418
+ return false;
419
+ }
420
+ /**
421
+ * Find a submit button associated with a form.
422
+ */
423
+ findSubmitButton(snapshot, formNode, fieldEids) {
424
+ const buttons = snapshot.nodes.filter((n) => BUTTON_KINDS.has(n.kind));
425
+ for (const button of buttons) {
426
+ // Check if button is within form's scope
427
+ if (!this.isNodeWithinForm(button, formNode, snapshot)) {
428
+ continue;
429
+ }
430
+ // Check if button label suggests submission
431
+ if (this.isSubmitButton(button)) {
432
+ return button;
433
+ }
434
+ }
435
+ // Also check buttons near the fields
436
+ if (fieldEids.length > 0) {
437
+ const fieldNodes = fieldEids
438
+ .map((eid) => snapshot.nodes.find((n) => n.node_id === eid))
439
+ .filter((n) => n !== undefined);
440
+ return this.findSubmitButtonNearCluster(snapshot, fieldNodes);
441
+ }
442
+ return undefined;
443
+ }
444
+ /**
445
+ * Find a submit button near a cluster of inputs.
446
+ */
447
+ findSubmitButtonNearCluster(snapshot, cluster) {
448
+ if (cluster.length === 0)
449
+ return undefined;
450
+ const buttons = snapshot.nodes.filter((n) => BUTTON_KINDS.has(n.kind));
451
+ const clusterBbox = this.computeClusterBbox(cluster);
452
+ if (!clusterBbox)
453
+ return undefined;
454
+ // Find buttons near the cluster
455
+ const nearbyButtons = buttons.filter((button) => {
456
+ if (!button.layout?.bbox)
457
+ return false;
458
+ const btnBbox = button.layout.bbox;
459
+ // Check if button is below or to the right of the cluster
460
+ const isNearX = btnBbox.x >= clusterBbox.x - 100 && btnBbox.x <= clusterBbox.x + clusterBbox.width + 100;
461
+ const isNearY = btnBbox.y >= clusterBbox.y - 50 && btnBbox.y <= clusterBbox.y + clusterBbox.height + 150;
462
+ return isNearX && isNearY;
463
+ });
464
+ // Find the best submit button candidate
465
+ for (const button of nearbyButtons) {
466
+ if (this.isSubmitButton(button)) {
467
+ return button;
468
+ }
469
+ }
470
+ return undefined;
471
+ }
472
+ /**
473
+ * Check if a button looks like a submit button.
474
+ */
475
+ isSubmitButton(button) {
476
+ const label = button.label.toLowerCase();
477
+ // Check for submit keywords
478
+ for (const keyword of SUBMIT_KEYWORDS) {
479
+ if (label.includes(keyword)) {
480
+ return true;
481
+ }
482
+ }
483
+ // Check for type="submit" attribute
484
+ if (button.attributes?.input_type === 'submit') {
485
+ return true;
486
+ }
487
+ return false;
488
+ }
489
+ /**
490
+ * Compute confidence score from signals.
491
+ */
492
+ computeConfidence(signals) {
493
+ let score = 0;
494
+ for (const signal of signals) {
495
+ const weight = SIGNAL_WEIGHTS[signal.type] ?? 0;
496
+ score += weight * signal.strength;
497
+ }
498
+ // Normalize to 0-1
499
+ return Math.min(1.0, score);
500
+ }
501
+ /**
502
+ * Infer the intent of a form.
503
+ */
504
+ inferIntent(snapshot, fieldEids, formNode) {
505
+ // Collect all relevant text to analyze
506
+ const textToAnalyze = [];
507
+ // Add form node label if available
508
+ if (formNode?.label) {
509
+ textToAnalyze.push(formNode.label);
510
+ }
511
+ // Add form heading context
512
+ if (formNode?.where.heading_context) {
513
+ textToAnalyze.push(formNode.where.heading_context);
514
+ }
515
+ // Add field labels
516
+ for (const eid of fieldEids) {
517
+ const node = snapshot.nodes.find((n) => n.node_id === eid);
518
+ if (node?.label) {
519
+ textToAnalyze.push(node.label);
520
+ }
521
+ if (node?.attributes?.placeholder) {
522
+ textToAnalyze.push(node.attributes.placeholder);
523
+ }
524
+ }
525
+ const combinedText = textToAnalyze.join(' ').toLowerCase();
526
+ // Score each intent using weighted keywords
527
+ let bestIntent = 'unknown';
528
+ let bestScore = 0;
529
+ for (const [intent, keywordEntries] of Object.entries(INTENT_KEYWORDS)) {
530
+ if (intent === 'unknown')
531
+ continue;
532
+ let score = 0;
533
+ for (const entry of keywordEntries) {
534
+ if (combinedText.includes(entry.keyword)) {
535
+ score += entry.weight;
536
+ }
537
+ }
538
+ if (score > bestScore) {
539
+ bestScore = score;
540
+ bestIntent = intent;
541
+ }
542
+ }
543
+ return bestIntent;
544
+ }
545
+ /**
546
+ * Check if text contains any of the given keywords.
547
+ */
548
+ hasIntentKeywords(text, keywords) {
549
+ const lower = text.toLowerCase();
550
+ return keywords.some((k) => lower.includes(k));
551
+ }
552
+ /**
553
+ * Compute bounding box for a cluster of nodes.
554
+ */
555
+ computeClusterBbox(nodes) {
556
+ const bboxes = nodes
557
+ .map((n) => n.layout?.bbox)
558
+ .filter((b) => b !== undefined);
559
+ if (bboxes.length === 0)
560
+ return undefined;
561
+ let minX = Infinity;
562
+ let minY = Infinity;
563
+ let maxX = -Infinity;
564
+ let maxY = -Infinity;
565
+ for (const bbox of bboxes) {
566
+ minX = Math.min(minX, bbox.x);
567
+ minY = Math.min(minY, bbox.y);
568
+ maxX = Math.max(maxX, bbox.x + bbox.w);
569
+ maxY = Math.max(maxY, bbox.y + bbox.h);
570
+ }
571
+ return {
572
+ x: minX,
573
+ y: minY,
574
+ width: maxX - minX,
575
+ height: maxY - minY,
576
+ };
577
+ }
578
+ /**
579
+ * Build a FormRegion from a candidate.
580
+ */
581
+ buildFormRegion(candidate, snapshot, index) {
582
+ // Generate form ID
583
+ const formId = this.generateFormId(candidate, index);
584
+ // Extract fields
585
+ const fields = extractFields(snapshot, candidate.field_eids, this.config);
586
+ // Find action buttons
587
+ const actions = this.extractFormActions(snapshot, candidate);
588
+ // Compute form state
589
+ const state = computeFormState(fields);
590
+ // Determine form pattern
591
+ const pattern = this.inferPattern(fields, snapshot);
592
+ // Build detection info
593
+ const detection = {
594
+ method: candidate.root_node_id ? 'semantic' : 'structural',
595
+ confidence: candidate.confidence,
596
+ signals: candidate.signals,
597
+ };
598
+ return {
599
+ form_id: formId,
600
+ detection,
601
+ intent: candidate.intent,
602
+ pattern,
603
+ fields,
604
+ actions,
605
+ state,
606
+ bbox: candidate.bbox,
607
+ };
608
+ }
609
+ /**
610
+ * Generate a unique form ID.
611
+ */
612
+ generateFormId(candidate, index) {
613
+ const components = [
614
+ candidate.intent ?? 'form',
615
+ candidate.root_node_id ?? `cluster-${index}`,
616
+ String(candidate.field_eids.length),
617
+ ];
618
+ const hash = createHash('sha256').update(components.join('::')).digest('hex');
619
+ return `form-${hash.substring(0, 8)}`;
620
+ }
621
+ /**
622
+ * Extract form action buttons.
623
+ */
624
+ extractFormActions(snapshot, candidate) {
625
+ const actions = [];
626
+ const buttons = snapshot.nodes.filter((n) => BUTTON_KINDS.has(n.kind));
627
+ for (const button of buttons) {
628
+ // Skip disabled buttons for now but still include them
629
+ const isSubmit = this.isSubmitButton(button);
630
+ const isNearForm = candidate.bbox
631
+ ? this.isButtonNearBbox(button, candidate.bbox)
632
+ : candidate.field_eids.length === 0 ||
633
+ this.isButtonNearFields(button, snapshot, candidate.field_eids);
634
+ if (!isNearForm)
635
+ continue;
636
+ // Determine action type
637
+ let type = 'action';
638
+ const label = button.label.toLowerCase();
639
+ if (isSubmit) {
640
+ type = 'submit';
641
+ }
642
+ else if (label.includes('cancel') || label.includes('close')) {
643
+ type = 'cancel';
644
+ }
645
+ else if (label.includes('back') || label.includes('previous')) {
646
+ type = 'back';
647
+ }
648
+ else if (label.includes('next') || label.includes('continue')) {
649
+ type = 'next';
650
+ }
651
+ else if (label.includes('reset') || label.includes('clear')) {
652
+ type = 'reset';
653
+ }
654
+ actions.push({
655
+ eid: button.node_id,
656
+ backend_node_id: button.backend_node_id,
657
+ label: button.label,
658
+ type,
659
+ enabled: button.state?.enabled ?? true,
660
+ is_primary: isSubmit,
661
+ });
662
+ }
663
+ return actions;
664
+ }
665
+ /**
666
+ * Check if a button is near a bounding box.
667
+ */
668
+ isButtonNearBbox(button, bbox) {
669
+ if (!button.layout?.bbox)
670
+ return false;
671
+ const btnBbox = button.layout.bbox;
672
+ const isNearX = btnBbox.x >= bbox.x - 100 && btnBbox.x <= bbox.x + bbox.width + 100;
673
+ const isNearY = btnBbox.y >= bbox.y - 50 && btnBbox.y <= bbox.y + bbox.height + 150;
674
+ return isNearX && isNearY;
675
+ }
676
+ /**
677
+ * Check if a button is near a set of fields.
678
+ */
679
+ isButtonNearFields(button, snapshot, fieldEids) {
680
+ const fieldNodes = fieldEids
681
+ .map((eid) => snapshot.nodes.find((n) => n.node_id === eid))
682
+ .filter((n) => n !== undefined);
683
+ const clusterBbox = this.computeClusterBbox(fieldNodes);
684
+ if (!clusterBbox)
685
+ return false;
686
+ return this.isButtonNearBbox(button, clusterBbox);
687
+ }
688
+ /**
689
+ * Infer form pattern (single page, multi-step, etc.)
690
+ */
691
+ inferPattern(_fields, _snapshot) {
692
+ // For now, default to single_page
693
+ // Future: detect multi-step wizards, accordions, tabs
694
+ return 'single_page';
695
+ }
696
+ }
697
+ /**
698
+ * Convenience function for detecting forms in a snapshot.
699
+ */
700
+ export function detectForms(snapshot, config) {
701
+ const detector = new FormDetector(config);
702
+ return detector.detect(snapshot);
703
+ }
704
+ //# sourceMappingURL=form-detector.js.map